• 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 
    #include 
    #include 
    
    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");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158

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

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

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

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

  • 相关阅读:
    数据库构建中的三范式设计(附SQL实例说明)
    莞中集训游记
    国家网络安全宣传周知识竞赛活动小程序界面分享
    机器学习---神经元模型
    Linux中的进程终止(详解)
    docker 笔记10:Docker轻量级可视化工具Portainer
    大数据flink篇之三-flink运行环境安装后续一yarn-session安装
    seata at模式死锁
    无人直播间带货还能做吗?
    基于javaweb+mysql的前后端分离网上商城项目设计和实现(java+ssm+springboot+vue+redis)
  • 原文地址:https://blog.csdn.net/dog250/article/details/126553782