TCP 与 UDP 如何互通_tcp通道可以向udp通道发数据吗

今天再来个花式玩法。

TCP 连接的报文,结果却送到了 UDP socket,有趣…

既然以太帧既可以在铜线上传输,也可以在光纤上,甚至空气里传输,那么 SOCK_STREAM socket 也就可以在 UDP 上传输,反之,TCP 报文也可以被 SOCK_DGRAM 接收。

上周实现了 TCP 的不可靠传输:不可靠不重传的假 TCP。但还可以更花式,将 TCP 数据送到 SOCK_DGRAM socket 如何?很简单,协议转换一下而已。

先展示实验。

服务端不启动 iperf -s,反而启动 nc -u -l -p 12345.

客户端发起 iperf -c 192.168.1.248 -i 1 -p 12345 -t 5.

下面是服务端 nc -u -l -p 12345 的输出一角:
在这里插入图片描述
iperf -c 端的输出:
在这里插入图片描述
是不是很神奇?TCP 竟然可将报文送到 UDP。

实现并不难,关键是想到这种玩法。如此一来,socket 和传输协议真解耦:

  • 用 TCP 传输 socket(AF_INET, SOCK_DGRAM, 0)。
  • 用 UDP 传输 socket(AF_INET, SOCK_STREAM, 0)。
  • 一端为 socket(AF_INET, SOCK_STREAM, 0),另一端为 socket(AF_INET, SOCK_DGRAM, 0)。

由此引申出一种和传统封装型隧道不同的新隧道,协议转换型隧道。这类隧道解决了封装型隧道载荷率变低问题:vxlan 封装 TCP,wg 封装 vxlan,又是个 IPv6 环境,还能留给 payload 多少空间?

为保持原始 payload 的连接性(即五元组),协议转换型隧道需在隧道两端维护识别原始五元组的虚电路,比如当 tupleX 1.1.1.1:123 tcp 2.2.2.2:321 第一次通过隧道,隧道要建立一个虚电路,tupleX 便可脱去整个 inner TCP/IP 头,用 UDP 携带一个超精简仅携带 seq,ack,rwnd 等字段的小头重新封装通过隧道,在隧道对端由虚电路重组成 tupleX。

借 NAT64 的可行性,IPv4 也可用来做传输 IPv6 的隧道,IPv6 报文转成 IPv4 报文通过网络,从而提高载荷率。

MPLS 大致也是类似,但还是不同,没这么狠。

总之就是用一个相对短的协议封装 payload 通过可控的网络,这也是 overlay 的思路,只是着眼点不同:

  • 封装型 overlay 无状态,每包仅封装。
  • 转换型 overlay 有状态,事先建立虚电路,保存不变元数据。

为了不让协议头越封装越长,就要消耗点时间建立虚电路,这也是时间换空间。

回到最初,TCP 换 UDP 怎么做到的?代码如下:

// 又一个不可靠,不重传的实现,POC 只能单流玩,不能重入!
#include <linux/module.h>
#include <linux/netfilter_ipv4.h>
#include <net/tcp.h>

static int max_seq = 0;
static bool tcp_reply(struct tcphdr *tcph, const struct tcphdr *oth, uint16_t payload, bool *retrans)
{

    /* SYN > SYN-ACK */
    if (oth->syn && !oth->ack) {
        tcph->syn = true;
        tcph->ack = true;
        tcph->window = 65000;
        tcph->seq = htonl(prandom_u32() & ~oth->seq);
        tcph->ack_seq = htonl(ntohl(oth->seq) + oth->syn);
        max_seq = ntohl(oth->seq);
    }
        // 忽略重传数据
    if (ntohl(oth->seq) < max_seq) {
        *retrans = true;
        return false;
    }
    /* ACK > ACK */
        // 来了就 ACK,不重传
    if (oth->ack && (!(oth->fin || oth->syn))) {
        tcph->syn = false;
        tcph->ack = true;
        tcph->window = 65000;
        tcph->ack_seq = htonl(ntohl(oth->seq) + payload);
        tcph->seq = oth->ack_seq;
        max_seq = ntohl(oth->seq) + payload;
        return false;
    }

    /* FIN > RST */
    else if (oth->fin) {
        tcph->window  = 0;
        tcph->seq = oth->ack_seq;
        tcph->ack_seq = oth->ack_seq;
        tcph->fin = false;
        tcph->ack = false;
        tcph->rst = true;
    }
    return true;
}

static unsigned int ipv4_pseudotcp_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
    struct iphdr *niph, ihdr, *iphu, *iph = ip_hdr(skb);
    struct tcphdr _otcph, *oth, thdr, *tcph;
    struct udphdr *udph;
    struct sk_buff *nskb;
    unsigned int delta = sizeof(struct tcphdr) - sizeof(struct udphdr);
    uint16_t tmp, payload;
    bool reply = false, retrans = false;

    if (iph->protocol != IPPROTO_TCP)
        goto out;
    if (skb->len < ip_hdrlen(skb) + sizeof(struct tcphdr))
        goto out;
    oth = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(_otcph), &_otcph);
    if (oth == NULL)
        goto out;
    if (nf_ip_checksum(skb, NF_INET_LOCAL_IN, ip_hdrlen(skb), IPPROTO_TCP))
        goto out;
    nskb = skb_copy_expand(skb, LL_MAX_HEADER, skb_tailroom(skb), GFP_ATOMIC);
    if (nskb == NULL)
        goto out;

    nf_reset_ct(nskb);
    skb_init_secmark(nskb);
    skb_shinfo(nskb)->gso_size = 0;
    skb_shinfo(nskb)->gso_segs = 0;
    skb_shinfo(nskb)->gso_type = 0;
    ihdr = *iph;
    tcph = (struct tcphdr *)(skb_network_header(nskb) + ip_hdrlen(nskb));
    thdr = *tcph;
    if (htons(tcph->dest) != 12345)
        goto out;

    niph = ip_hdr(nskb);
    niph->daddr = xchg(&niph->saddr, niph->daddr);
    tmp = tcph->source;
    tcph->source = tcph->dest;
    tcph->dest = tmp;
    payload = nskb->len - ip_hdrlen(nskb) - sizeof(struct tcphdr);
    tcph->doff = sizeof(struct tcphdr) / 4;
    skb_trim(nskb, ip_hdrlen(nskb) + sizeof(struct tcphdr));
    niph->tot_len = htons(nskb->len);
    tcph->urg_ptr = 0;
    ((u_int8_t *)tcph)[13] = 0;

    reply = tcp_reply(tcph, oth, payload, &retrans);
    if ((reply == false && payload == 0) || retrans)
        goto free_nskb;

    tcph->check = 0;
    tcph->check = tcp_v4_check(sizeof(struct tcphdr), niph->saddr, niph->daddr, csum_partial((char *)tcph, sizeof(struct tcphdr), 0));
    niph->frag_off = htons(IP_DF);
    niph->id = ~ihdr.id + 1;

    if (ip_route_me_harder(&init_net, nskb->sk, nskb, RTN_LOCAL))
        goto free_nskb;
    else
        niph = ip_hdr(nskb);
    nskb->ip_summed = CHECKSUM_NONE;
    niph->ttl = 64;
    niph->check = 0;
    niph->check = ip_fast_csum(skb_network_header(nskb), niph->ihl);
    nf_ct_attach(nskb, skb);

    NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_OUT, &init_net, nskb->sk, nskb, NULL, skb_dst(nskb)->dev, dst_output);
    if (reply == true || payload == 0)
        goto drop;

    ihdr.protocol = IPPROTO_UDP;
    ihdr.tot_len = htons(ntohs(ihdr.tot_len) - delta);
    iphu = (struct iphdr *)skb_pull(skb, delta);
    *iphu = ihdr;
    skb_reset_network_header(skb);
    skb_set_transport_header(skb,  iphu->ihl*4);
    ip_send_check(iphu);
    udph = (struct udphdr *)skb_transport_header(skb);
    udph->source = thdr.source;
    udph->dest = thdr.dest;
    udph->len = htons(ntohs(iphu->tot_len) - sizeof(struct iphdr));
    udph->check = 0;
out:
    return NF_ACCEPT;
free_nskb:
    kfree_skb(nskb);
drop:
    return NF_DROP;
}

static const struct nf_hook_ops ipv4_pseudotcp_ops[] = {
    {
        .hook = ipv4_pseudotcp_hook,
        .pf = NFPROTO_IPV4,
        .hooknum = NF_INET_LOCAL_IN,
        .priority = NF_IP_PRI_LAST,
    },
};

static int __init pseudotcp_init(void)
{
    return nf_register_net_hooks(&init_net, ipv4_pseudotcp_ops, ARRAY_SIZE(ipv4_pseudotcp_ops));
}

static void __exit pseudotcp_exit(void)
{
    nf_unregister_net_hooks(&init_net, ipv4_pseudotcp_ops, ARRAY_SIZE(ipv4_pseudotcp_ops));
}

module_init(pseudotcp_init);
module_exit(pseudotcp_exit);
MODULE_LICENSE("GPL");

这个算法依然不重传,完全是 UDP 的语义。既然接收端是 UDP,为什么大费周章用假 TCP 传输呢?
为了欺骗运营商呗。

还有一个意思,从此以后,TCP 发送端可以和 UDP 接收端对接,这对应用程序而言,意味着可分别改造即可完成适配,甚至故意这么玩,都可。

NAT64 可以将 IPv6 报文转换为 IPv4 报文,同样,TCP 报文也能转换成 UDP 报文。简单试试,有点意思。

浙江温州皮鞋湿,下雨进水不会胖。

原文链接: https://blog.csdn.net/dog250/article/details/126553782

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    TCP 与 UDP 如何互通_tcp通道可以向udp通道发数据吗

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/405405

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午9:10
下一篇 2023年4月26日 上午9:11

相关推荐