UDP 数据包转发从 sendmsg 获取 EINVAL

Bit*_*ank 2 linux dns networking udp embedded-linux

我正在编写我自己的 DNS 黑洞来阻止我家庭网络上的广告和恶意软件。我意识到这种类型的程序已经存在,但我想学习这个过程并编写自己的程序。从 Linux 文档中,似乎可以告诉 sendmsg() 使用不同的返回地址,以便可以转发 UDP 数据包,并且接收服务器会将响应发送给原始请求者而不是我的服务器。从稀疏文档中,我设置了绑定到端口 53 (DNS) 的套接字。我正在接收 DNS 请求,解释它们并在站点被列入黑名单时做出响应。对于“好”域名,我得到了 2 个不同的结果。在 MacOS 上,我发送了转发请求,但响应返回到我的程序而不是原始请求者。在 Linux(Armbian 4.13 内核)上,我从 sendmsg() 调用中获得 EINVAL,但没有发送任何内容。任何人都可以阐明我做错了什么吗?(为简洁起见,已删除所有错误检查)

附加信息...这个方案的一个问题是原始 DNS 请求绑定到 53 以外的套接字端口(显然)。如何告诉 sendmsg() 将响应返回到原始请求的正确端口号?

我的“代理”版本有效。我存储了原始请求的事务 ID 和请求端口号,并且能够成功返回响应

最新消息 - 我能够成功完成我想要的,但没有使用 sendmsg()。通过使用 RAW 套接字并“欺骗”IP 标头中的返回地址,我能够将数据包发送回不同的返回地址和端口号(使用 sendto)。nslookup 认为这是一个问题,但浏览器可以接受。我想我们可以称之为“案件结束”

struct sockaddr_in addr, addrfrom, fwaddr;
socklen_t addrLen = sizeof(struct sockaddr_in);
int rc, listen_sock;

listen_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
rc = 1;
setsockopt(listen_sock, IPPROTO_IP, IP_PKTINFO, &rc, sizeof(rc));
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(53); // port address of DNS
addr.sin_addr.s_addr = INADDR_ANY;
bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
memset(&fwaddr, 0, sizeof(fwaddr));
fwaddr.sin_family = AF_INET;
fwaddr.sin_port = htons(53);
fwaddr.sin_addr.s_addr = 0x08080808; // Google DNS server

<... Receive DNS request from client...>

if (bForward)
{
struct msghdr msg;
struct iovec iov[1];
struct {
   struct cmsghdr cm; /* this ensures alignment */
   struct in_pktinfo ipi;
} cmsg;
memset(&cmsg, 0, sizeof(cmsg));
memset(&msg, 0, sizeof(msg));
iov[0].iov_base = request_bufffer;
iov[0].iov_len = request_len;
msg.msg_flags = 0;
msg.msg_name = &fwaddr; // dest address of packet
msg.msg_namelen = addrLen;
msg.msg_iov = &iov[0];
msg.msg_iovlen = 1;

msg.msg_control = &cmsg;
msg.msg_controllen = sizeof(cmsg); //sizeof(struct in_pktinfo);
cmsg.cm.cmsg_len = sizeof(cmsg);
cmsg.cm.cmsg_level = IPPROTO_IP;
cmsg.cm.cmsg_type = IP_PKTINFO;

cmsg.ipi.ipi_ifindex = 0;
cmsg.ipi.ipi_spec_dst = addrfrom.sin_addr; // original source address
rc= sendmsg(listen_sock, &msg, 0);
} // bForward
Run Code Online (Sandbox Code Playgroud)

Pet*_*ain 5

问题

我在此代码中看到 2 个潜在问题。

  1. 您没有使用CMSG 宏来分配和操作您的辅助数据(即您的 cmsg 结构)。
  2. 即使这是固定的,您也试图欺骗发送数据包的源 IP 地址 - 即您试图声明它来自不属于此框的地址。我从未尝试过这样做,但如果您发现内核安全性阻止您这样做,我也不会感到惊讶。

那么该怎么办?

首先,我会使用类似SO answer 的代码修复您的消息结构。我怀疑您应该仔细查看in_pktinfo传入的偏移量和长度,cmsg_len因为这些可能会导致 EINVAL 错误。一种可能的策略可能是将代码切回到正常发送数据包的程度,以查看哪些数据结构被拒绝。

如果那不行,我想您可以考虑重新编写发送逻辑以使用原始套接字来绕过内核可能正在执行的任何 IP 验证。例如,这段代码看起来应该是您所需要的。

如果这还不够,我怀疑您需要在内核安全系统中进行路由。