python - 接收来自特定源的UDP数据包

标签 python sockets networking dns

我正在尝试测量 DNS 服务器的响应。对小于 512 字节的典型 DNS 响应进行嗅探并不是什么大问题。我的问题是接收 3000+ 字节的大响应 - 在某些情况下为 5000+ 字节。我无法让套接字能够可靠地接收该数据。有没有办法使用 Python 套接字从特定源地址接收数据?

这是我到目前为止所拥有的:

import socket
import struct


def craft_dns(Qdns):

    iden = struct.pack('!H', randint(0, 65535))
    QR_thru_RD = chr(int('00000001', 2)) # '\x01'
    RA_thru_RCode = chr(int('00100000', 2)) # '\x00'
    Qcount = '\x00\x01' # question count is 1
    ANcount = '\x00\x00'
    NScount = '\x00\x00'
    ARcount = '\x00\x01' # additional resource count is 1
    pad = '\x00' #
    Rtype_ANY = '\x00\xff' # Request ANY record
    PROtype = '\x00\x01' # Protocol IN || '\x00\xff' # Protocol ANY
    DNSsec_do = chr(int('10000000', 2)) # flips DNSsec bit to enable
    edns0 = '\x00\x00\x29\x10\x00\x00\x00\x00\x00\x00\x00' # DNSsec disabled
    domain = Qdns.split('.')
    quest = ''

    for x in domain:
        quest += struct.pack('!B', len(x)) + x

    packet = (iden+QR_thru_RD+RA_thru_RCode+Qcount+ANcount+NScount+ARcount+
            quest+pad+Rtype_ANY+PROtype+edns0) # remove pad if asking <root>

    return packet

def craft_ip(target, resolv):

    ip_ver_len = int('01000101', 2) # IPvers: 4, 0100 | IP_hdr len: 5, 0101 = 69
    ipvers = 4
    ip_tos  = 0
    ip_len = 0 # socket will put in the right length
    iden = randint(0, 65535)
    ip_frag = 0 # off
    ttl = 255
    ip_proto = socket.IPPROTO_UDP # dns, brah
    chksm = 0 # socket will do the checksum
    s_addr = socket.inet_aton(target)
    d_addr = socket.inet_aton(resolv)

    ip_hdr = struct.pack('!BBHHHBBH4s4s', ip_ver_len, ip_tos, ip_len, iden, 
                    ip_frag, ttl, ip_proto, chksm, s_addr, d_addr)
    return ip_hdr

def craft_udp(sport, dest_port, packet): 

    #sport = randint(0, 65535) # not recommended to do a random port generation
    udp_len = 8 + len(packet) # calculate length of UDP frame in bytes.
    chksm = 0 # socket fills in
    udp_hdr = struct.pack('!HHHH', sport, dest_port, udp_len, chksm)

    return udp_hdr

def get_len(resolv, domain):

    target = "10.0.0.3"
    d_port = 53
    s_port = 5353

    ip_hdr = craft_ip(target, resolv)
    dns_payload = craft_dns(domain) # '\x00' for root
    udp_hdr = craft_udp(s_port, d_port, dns_payload)
    packet = ip_hdr + udp_hdr + dns_payload
    buf = bytearray("-" * 60000)

    recvSock = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.ntohs(0x0800))
    recvSock.settimeout(1)

    sendSock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
    sendSock.settimeout(1)

    sendSock.connect((resolv, d_port))
    sendSock.send(packet)

    msglen = 0
    while True:
        try:

            pkt = recvSock.recvfrom(65535)
            msglen += len(pkt[0])
            print repr(pkt[0])

        except socket.timeout as e:

            break

    sendSock.close()
    recvSock.close()    

    return msglen

result = get_len('75.75.75.75', 'isc.org')
print result

出于某种原因做

pkt = sendSock.recvfrom(65535)

什么也没收到。由于我使用的是 SOCK_RAW,上面的代码不太理想,但它可以工作 - 有点。如果套接字非常嘈杂(例如在 WLAN 上),我最终可能会收到远远超出 DNS 数据包的数据包,因为在接收多数据包 DNS 应答时,我无法知道何时停止接收数据包。对于安静的网络(例如实验室虚拟机),它可以工作。

在这种情况下有更好的方法使用接收套接字吗? 显然从代码来看,我对 Python 套接字的了解不是那么强。
我必须使用 SOCK_RAW 发送,因为我正在以原始格式构建数据包。如果我使用 SOCK_DGRAM,则自定义数据包在发送到 DNS 解析器时将会出现格式错误。

我能看到的唯一方法是使用原始套接字接收器(recvSock.recv或recvfrom)并解压每个数据包,查看源地址和目标地址是否与 get_len() 中提供的内容匹配,然后查看是否片段位被翻转。然后用len()记录每个数据包的字节长度。我宁愿不这样做。只是似乎有更好的方法。

最佳答案

好吧,我很愚蠢,没有查看接收套接字的协议(protocol)。当您尝试通过 IPPROTO_RAW 协议(protocol)接收数据包时,套接字会变得不稳定,因此我们确实需要两个套接字。通过更改为 IPPROTO_UDP 然后绑定(bind)它,套接字能够跟踪多个请求的完整 DNS 响应。我摆脱了 try/catch 和 while 循环,因为不再需要它,并且我可以使用此 block 拉动响应长度:

recvSock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_UDP)
recvSock.settimeout(.3)
recvSock.bind((target, s_port))

sendSock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
#sendSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sendSock.settimeout(.3)    
sendSock.bind((target, s_port))

sendSock.connect((resolv, d_port))
sendSock.send(packet)

pkt = recvSock.recvfrom(65535)
msglen = len(pkt[0])

现在该方法将返回从 DNS 查询接收到的确切字节。我会保留这个,以防其他人需要做类似的事情:)

关于python - 接收来自特定源的UDP数据包,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34377873/

相关文章:

python - 如何在 Python 2.7 中编写 unicode csv

java - Netty 多个客户端,断开一个影响所有

ios - 如何在 Swift 中访问 JSON 响应中的特定数据

java - 通过 TCP 套接字的 Avro 通信

node.js - 如何通过反向代理连接 socket.io

图形的 Javascript 库(在数学意义上)

linux - 将 netcat 与 -p 选项一起使用

Python导入错误: No module named cv

python - Pandas 列的 To_CSV 唯一值

Python 字典类, 'KeyError'