今天再来个花式玩法。

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;elseniph = 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 报文。简单试试,有点意思。

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

TCP 与 UDP 如何互通相关推荐

  1. 40 张图带你搞懂 TCP 和 UDP

    前言 拿下计网协议后,我就是公园里最靓的仔 TCP/IP 基础知识总结 计算机网络基础知识总结 那么下面就开始我们本篇文章,文章组织脉络如下 运输层位于应用层和网络层之间,是 OSI 分层体系中的第四 ...

  2. Node.js与网络:Node.js对TCP、UDP、Socket、HTTP等协议的实现和支持

    转自:https://itbilu.com/nodejs/core/VkcdcFq9.html OSI七层模型是不同计算机或通信系统间互联的标准体系和框架,在OSI中包括一系列标准和协议,如:TCP/ ...

  3. http协议相关内容,C/S与B/S,ip报文,TCP,UDP

    目录 HTTP技术架构 HTTP协议功能 工作原理 HTTP协议的主要组成部分 HTTP协议 Status C/S与B/S的比较: 服务器与Web服务器的差别 浏览器与客户端的差别 浏览器与Web服务 ...

  4. 网络协议梳理(三)(网关和路由器、动态路由算法、Bellman-Ford算法、Dijkstra算法、动态路由协议、TCP和UDP)

    MAC 头和 IP 头的细节 在任何一台机器上,当要访问另一个IP地址的时候,都会使用CIDR和子网掩码去判断目标IP地址和当前机器的IP地址是否属于同一网段. 如果是同一网段--如果ARP缓存中存有 ...

  5. TCP、UDP的区别

    TCP 和 UDP 的区别 TCP 是面向连接的,UDP 是面向无连接的 UDP程序结构较简单 TCP 是面向字节流的,UDP 是基于数据报的 TCP 保证数据正确性,UDP 可能丢包 TCP 保证数 ...

  6. TCP、UDP、Socket、HTTP面试题(总结最全面的面试题!!!)

    文章目录 先看一天面试的经验: 什么是网络编程 网络编程中两个主要的问题 网络协议是什么 为什么要对网络协议分层 计算机网络体系结构 1 TCP / UDP 1.1 什么是TCP/IP和UDP 1.2 ...

  7. 【面试】TCP、UDP、Socket、HTTP网络编程面试题

    文章目录 什么是网络编程 网络编程中两个主要的问题 网络协议是什么 为什么要对网络协议分层 计算机网络体系结构 1 TCP / UDP 1.1 什么是TCP/IP和UDP 1.2 TCP与UDP区别: ...

  8. 网络:TCP与UDP

    IP层之上就是传输层,而传输层比较重要的两个协议:TCP和UDP.对于不从事底层开发的人员来讲,或者对于应用开发的人来讲,最常用的就是这两个协议. TCP和UDP有哪些区别 TCP是面向连接的,UPD ...

  9. TCP、UDP网络编程面试题

    TCP.UDP.Socket.HTTP网络编程面试题 什么是网络编程 网络编程的本质是多台计算机之间的数据交换.数据传递本身没有多大的难度,不就是把一个设备中的数据发送给其他设备,然后接受另外一个设备 ...

最新文章

  1. 报告|机器人行业深度报告:机器人产业价值与回报相关度分析
  2. [转]Javascript的IE和Firefox(火狐)兼容性
  3. mysql 输出当前月所有日期与对应的星期
  4. php 鸟哥写过的c扩展,Yaconf —— 高性能的 PHP 配置管理扩展,鸟哥出品哦~
  5. TypeScript 枚举指南
  6. Hive 05_hive变量、动态分区
  7. WPF ChromiumWebBrowser 网页背景透明
  8. [轉]数据挖掘工具的选择
  9. html5--3.1 form元素
  10. 手机黑圆点怎么打_手机能「打快板」是怎么回事?浅谈手机的光学防抖
  11. Linux内存调试工具初探-MEMWATCH
  12. nginx实现网站url带参跳转 POST请求GET请求跳转
  13. 在我离开一段时间后锁定计算机,离开电脑一段时间怎么让win10自动锁屏
  14. (C语言) 用牛顿迭代法求方程2x^3 - 4x^2 + 3x - 6 = 0在1.5附近的根
  15. 极致浪漫: 你生日那天的宇宙是什么样子的?
  16. 计算机语言怎么学,教你如何学习计算机编程语言
  17. 打印机一直不停打乱码的解决方法
  18. 改408!985南京大学计算机系考研!
  19. QThread Qt
  20. NPOI创建DOCX常用操作【转】

热门文章

  1. NVIDIA GeForce Experience错误代码0x0003 0x0001
  2. 在Android中调用图片、视频、音频、录音、拍照
  3. 电脑磁盘通过Bitlocker上锁,为磁盘添加右键上锁菜单功能
  4. android云同步失败,有道云笔记同步失败如何解决 有道云笔记同步不了解决办法攻略大全...
  5. 横竖屏切换SurfaceView 大小的调整
  6. Vue 父子组件传值 之 子组件向父组件传值
  7. 按键助手 1.0 中文免费绿色版
  8. 让textarea 只读
  9. cmd 命令切换盘符
  10. 罗马利亚夫妻网恋结婚 给儿子取名“雅虎”