从Python DGRAM套接字获取目标IP和端口

问题描述 投票:0回答:3

我在

UDP
中有以下
DGRAM
/
socket
Python

sock.bind((UDP_IP, UDP_PORT))

while True:
    data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
    print "received message:", data

此代码在代理服务器中运行,因此目标 IP 和端口与套接字不匹配。我正在使用

tproxy
来拦截数据包。

如何获取目标IP和端口,而不是源IP和端口?

python sockets networking
3个回答
1
投票

您显然在 2019 年使用了 Python2,我仍然会回答您的问题,但使用 Python3,因为我认为这对最终来到这里的人会更有帮助。

当使用 Linux 内核的 TPROXY 功能时,为了获取传入数据报的目标地址,必须使用低级原语

socket.recvmsg()
。您可以在here找到其官方文档。

您还必须设置套接字选项

IP_RECVORIGDSTADDR
,根据
man 7 ip

启用 IP_ORIGDSTADDR 辅助消息 recvmsg(2),其中内核返回原始目标地址 正在接收的数据报。 辅助消息包含一个结构体 sockaddr_in。

sshuttle
工具为其创建了一个包装函数。我重写了它,使其更加通用并且易于任何人使用:

import socket
import struct
from typing import Tuple

Host = Tuple[str, int]

# They are not in the standard library yet !
IP_RECVORIGDSTADDR = 20
SOL_IPV6 = 41
IPV6_RECVORIGDSTADDR = 74

def recv_tproxy_udp(bind_sock, bufsize) -> Tuple[Host, Host, bytes]:
    max_ancillary_size = 28 # sizeof(struct sockaddr_in6)
    data, ancdata, flags, client = bind_sock.recvmsg(bufsize,
                                                     socket.CMSG_SPACE(max_ancillary_size))


    for cmsg_level, cmsg_type, cmsg_data in ancdata:

        # Handling IPv4
        if cmsg_level == socket.SOL_IP and cmsg_type == IP_RECVORIGDSTADDR:
            family, port = struct.unpack('=HH', cmsg_data[0:4])
            port = socket.htons(port)

            if family != socket.AF_INET:
                raise TypeError(f"Unsupported socket type '{family}'")

            ip = socket.inet_ntop(family, cmsg_data[4:8])
            destination = (ip, port)
            return client, destination, data

        # Handling IPv6
        elif cmsg_level == SOL_IPV6 and cmsg_type == IPV6_RECVORIGDSTADDR:
            family, port = struct.unpack('=HH', cmsg_data[0:4])
            port = socket.htons(port)

            if family != socket.AF_INET6:
                raise TypeError(f"Unsupported socket type '{family}'")

            ip = socket.inet_ntop(family, cmsg_data[8:24])
            destination = (ip, port)
            return client, destination, data

    raise ValueError("Unable to parse datagram")

不要忘记,为了处理 TPROXY,您还必须使用

IP_TRANSPARENT
设置
setsockopt()
选项,并且它要求您以 root 身份运行脚本:

server_sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
server_sock.setsockopt(socket.SOL_IP, socket.IP_TRANSPARENT, 1)
server_sock.setsockopt(socket.SOL_IP, IP_RECVORIGDSTADDR, 1)
server_sock.bind((bind_addr, bind_port))

0
投票

@ShellCode
的回答已经差不多完成了。但值得注意的是,如果我们正在侦听多播流量,则不需要设置 IP_TRANSPARENT,root 权限也是如此。下面是识别多播数据包的目标组地址的简单示例:

import socket
import struct
import time

MCAST_GRP = "240.0.0.0"
MCAST_PORT = 1234
IP_RECVORIGDSTADDR = 20

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_IP, IP_RECVORIGDSTADDR, 1)
# on this port, receives ALL multicast groups
sock.bind(('', MCAST_PORT))
mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)

sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

max_ancillary_size=28 #sizeof(struct sockaddr_in6)
bufsize = 10240

while True:
  data, ancdata, flags, client = sock.recvmsg(bufsize, socket.CMSG_SPACE(max_ancillary_size))
  for cmsg_level, cmsg_type, cmsg_data in ancdata:
    # Handling IPv4
    if cmsg_level == socket.SOL_IP and cmsg_type == IP_RECVORIGDSTADDR:
      family, port = struct.unpack('=HH', cmsg_data[0:4])
      port = socket.htons(port)

      if family != socket.AF_INET:
        raise TypeError(f"Unsupported socket type '{family}'")

      ip = socket.inet_ntop(family, cmsg_data[4:8])
      destination = (ip, port)
      print(ip)
  time.sleep(0.5)

-2
投票

来自文档

socket.getsockname()

返回套接字自己的地址。例如,这对于查找 IPv4/v6 套接字的端口号很有用。

所以你想打印出来

sock.getsockname()

© www.soinside.com 2019 - 2024. All rights reserved.