1

我正在尝试编写一个透明代理,将任意 UDP 数据包转换为自定义协议并再次返回。我正在尝试使用透明代理来读取需要翻译的传入 UDP 数据包,并写入刚刚被反向翻译的传出 UDP 数据包。

我用于两种 UDP 套接字的套接字设置如下:

static int
setup_clear_sock(uint16_t proxy_port)
{
    struct sockaddr_in saddr;
    int sock;
    int val = 1;
    socklen_t ttllen = sizeof(std_ttl);

    sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock < 0)
    {
        perror("Failed to create clear proxy socket");
        return -1;
    }
    if (getsockopt(sock, IPPROTO_IP, IP_TTL, &std_ttl, &ttllen) < 0)
    {
        perror("Failed to read IP TTL option on clear proxy socket");
        return -1;
    }
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0)
    {
        perror("Failed to set reuse address option on clear socket");
        return -1;
    }
    if (setsockopt(sock, IPPROTO_IP, IP_TRANSPARENT, &val, sizeof(val)) < 0)
    {
        perror("Failed to set transparent proxy option on clear socket");
        return -1;
    }
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(proxy_port);
    saddr.sin_addr.s_addr = INADDR_ANY;
    if (bind(sock, (struct sockaddr *) &saddr, sizeof(saddr)) < 0)
    {
        perror("Failed to bind local address to clear proxy socket");
        return -1;
    }

    return sock;
}

我有两个不同但可能相关的问题。首先,当我从这个套接字读取传入的 UDP 数据包时,使用以下代码:

struct sock_double_addr_in
{
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port_a;
    struct in_addr sin_addr_a;
    sa_family_t sin_family_b;
    in_port_t sin_port_b;
    struct in_addr sin_addr_b;
    unsigned char sin_zero[sizeof(struct sockaddr) - __SOCKADDR_COMMON_SIZE - 8
                             - sizeof(struct in_addr) - sizeof(in_port_t)];
};


void
handle_clear_sock(void)
{
    ssize_t rcvlen;
    uint16_t nbo_udp_len, coded_len;
    struct sockaddr_in saddr;
    struct sock_double_addr_in sdaddr;
    bch_coding_context_t ctx;
    socklen_t addrlen = sizeof(sdaddr);

    rcvlen = recvfrom(sock_clear, &clear_buf, sizeof(clear_buf),
                      MSG_DONTWAIT | MSG_PROXY,
                      (struct sockaddr *) &sdaddr, &addrlen);
    if (rcvlen < 0)
    {
        perror("Failed to receive a packet from clear socket");
        return;
    }

    ....

我没有在 sdaddr 中看到目标地址。sin_family_b、sin_addr_b 和 sin_port_b 字段均为零。我已经在 gdb 中完成了结构的块内存转储,实际上字节从内核返回为零(在我的结构定义中,该字段的位置不错)。

通过硬编码一个固定的 IP 地址和端口来临时解决这个问题,我可以调试我的代理应用程序的其余部分,直到我可以发送一个刚刚被反向转换的传出 UDP 数据包。这段代码会发生这种情况:

    ....

    udp_len = ntohs(clear_buf.u16[2]);
    if (udp_len + 6 > decoded_len)
        fprintf(stderr, "Decoded fewer bytes (%u) than outputting in clear "
                        "(6 + %u)!\n", decoded_len, udp_len);

    sdaddr.sin_family = AF_INET;
    sdaddr.sin_port_a = clear_buf.u16[0];
    sdaddr.sin_addr_a.s_addr = coded_buf.u32[4];
    sdaddr.sin_family_b = AF_INET;
    sdaddr.sin_port_b = clear_buf.u16[1];
    sdaddr.sin_addr_b.s_addr = coded_buf.u32[3];
    if (sendto(sock_clear, &(clear_buf.u16[3]), udp_len, MSG_PROXY,
               (struct sockaddr *) &sdaddr, sizeof(sdaddr)) < 0)
        perror("Failed to send a packet on clear socket");
}

并且数据包永远不会出现。我检查了我构建的 sdaddr 结构的全部内容,所有字段看起来都不错。UDP 有效负载数据看起来不错。sendto() 系统调用没有返回错误——实际上,它返回零。而且该数据包永远不会出现在wireshark中。

那么我的透明代理是怎么回事?我怎样才能让它工作?(FWIW:开发主机是通用的 x86_64 ubuntu 14.04 LTS 盒子。)谢谢!

4

1 回答 1

1

好吧,所以我有一半的答案。

事实证明,如果我只使用 RAW IP 套接字,打开 IP_HDRINCL 选项,并在用户空间中构建带有完整 IP 标头的传出 UDP 数据包,内核将尊重非本地源地址并以这种方式发送数据包。

为此,我现在使用第三个套接字 sock_output,并且解码的 UDP 数据包正确输出。(有趣的附注:UDP 校验和字段必须为零,或者是正确的校验和值。其他任何事情都会导致内核静默丢弃数据包,并且您永远不会看到它出去。内核不会填写正确的如果您将其清零,则会为您提供校验和,但如果您指定它,它将验证它是否正确。不要以这种方式发送带有故意错误校验和的 UDP。)

所以问题的前半部分仍然存在:当我从 sock_clear 读取带有 MSG_PROXY 标志的数据包到 recvfrom() 时,为什么我在 sdaddr 的后半部分没有得到实际的目标地址?

于 2014-12-19T19:48:31.630 回答