在无套接字情况下,发送TCP包

目标

  1. 不使用套接字进行数据包传输。
  2. 在源端机器的内核态,组建一个TCP包并发送到目标机器。
  3. 在目标机器的内核态对自定义TCP包进行处理。

TCP/IP 参考模型

因特网协议栈中的层:

  1. 应用层
  2. 传输层
  3. 网络层
  4. 接口层

如下图中的Data Flow所示:


当我们发送一个TCP包的时候,我们应该从最上层的Application层准备需要传输的信息,然后依次填充TransportInternetLink 的协议头部。

发送端实现

发送端整体流程

243     /* 第一步 分配和设置sk_buff. */   
244     skb = alloc_set_skb();  
245     if (!skb)             
246         return -1;        
247                           
248     /* 第二步 拷贝TCP负载 */
249     err = copy_md5sum(skb);
250     if (err != 0) {       
251         kfree_skb(skb);   
252         return err;       
253     }
254                           
255     /* 第三步 组建TCP头部 */
256     err = build_tcphdr(skb);
257     if (err != 0) {       
258         kfree_skb(skb);   
259         return err;       
260     }
261    
262     /* 第四步 组建IP头部 */
263     err = build_iphdr(skb);
264     if (err != 0) {       
265         kfree_skb(skb);   
266         return err;       
267     }
268 
269     /* 第五步 计算TCP头和IP头中的checksum */  
270     iph = ip_hdr(skb);    
271     skb->ip_summed = CHECKSUM_NONE;   
272     skbcsum(skb, iph);
273 
274     /* 第六步 组建网络接口层的头部 */
275     err = build_ethhdr(skb); 
276     if (err != 0) {
277         return err;
278     }
279 
280     /* 第七步 发送SKB */
281     err = dev_queue_xmit(skb);
282 
283     return err;

分配SKB

内核中使用套接字缓冲区(struct sk_buff)表示协议栈中的数据包。

216 struct sk_buff * alloc_set_skb(void)
217 {
218     struct sk_buff *skb;
219
220     /* At least 32-bit aligned.  */
221     int size = ALIGN(sizeof(struct ethhdr), 4) + ALIGN(sizeof(struct iphdr), 4) + ALIGN(sizeof(struct tcphdr), 4) + ALIGN    (MD5LEN, 4);
222 
223     skb = alloc_skb(size, GFP_ATOMIC);
224     if (skb == NULL) {
225         printk(KERN_ERR"alloc skb failed.");
226         return NULL;
227     }
228 
229     /* Reserve space for headers and prepare control bits. */
230     skb_reserve(skb, size);
231 
232     return skb;
233 }

第221行调整size为了四字节边界对齐。第223行分配一个新的sk_buff实例。第230行skb_reserve通过移动skb的data和tail指针, 来调整skb的headroom。

拷贝传输信息

这里直接在内核态中将要传输信息拷贝到skb, 而非像套接字编程中将数据从用户态拷贝到内核态中。

205 int copy_md5sum(struct sk_buff *skb)
206 {
207     uint8_t md5_result[MD5LEN] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
208 
209     if (skb_tailroom(skb) < MD5LEN)
210         return -1;
211     else {
212         memcpy(skb_push(skb, MD5LEN), md5_result, MD5LEN);
213     }
214 
215     return 0;
216 }

这里我们需要将一个伪MD5值传递到对端。第209行用于判断内存空间是否可以容纳MD5值,第212行的skb_push用于向前移动skb的data指针,
此时data和tail之间的内存空间就可以存放传输信息。

组装TCP头部

TCP的头部定义如下:

184 int build_tcphdr(struct sk_buff *skb)
185 {
186     struct tcphdr *th;
187 
188     skb_push(skb, ALIGN(sizeof(struct tcphdr), 4));
189     skb_reset_transport_header(skb);
190 
191     /* Build TCP header and checksum it. */
192     th = tcp_hdr(skb);
193     th->source      = htons(SOURCE);
194     th->dest        = htons(DEST);
195     th->seq         = htonl(123);
196     th->ack_seq     = 0;
197     *(((__be16 *)th) + 6)   = htons(((sizeof(struct tcphdr) >> 2) << 12) | TCPCB_FLAG_FIN);
198     th->window = htons(560);
199     th->check = 0;
200     th->urg_ptr = 0;
201 
202     return 0;
203 }

第188行设置TCP头部所需要的内存空间,第189行用于设置skb的transport_header指针,这样做是为了方便后面的tcp_hdr函数使用。
第193和194行用于设置端口,第195和196行用于设置序号和确认号,第197行用于设置TCP头长度和FIN标志位,
第198行用于设置窗口值,第199行校验和先设置为0。

组装IP头部

IP的头部定义如下:

161 int build_iphdr(struct sk_buff *skb)
162 {
163     struct iphdr *iph;
164     
165     skb_push(skb, sizeof(struct iphdr));
166     skb_reset_network_header(skb);
167     
168     iph = ip_hdr(skb);
169     *((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (RT_TOS(20) & 0xff));
170     iph->tot_len = htons(skb->len);
171     iph->frag_off = htons(IP_DF);
172     iph->ttl      = 64;
173     iph->protocol = IPPROTO_TCP;
174     iph->saddr    = in_aton(SOU_IP);
175     iph->daddr    = in_aton(DST_IP);
176     
177     return 0;
178 }

第165行设置IP头部所需要的内存空间,第169用于设置IP的版本、IHL和区分服务。第170行的tot_len为总长度。
第171行设置IP_DF表示没有IP分段。第171用于设置生存期,第172行用于设置协议,IP的头检验和暂时不设置。
第174和175行用于设置源地址和目标地址。

组建网络接口层的头部

在网络接口层主要是获得本机发包网卡的MAC地址和网关的MAC地址。

124 int build_ethhdr(struct sk_buff *skb)
125 {
126     struct ethhdr *eth;
127     struct net_device *dev;
128     int err;
129    
130     dev = dev_get_by_name(&init_net, SOU_DEVICE);
131     if (!dev) {
132         printk(KERN_ERR"get device failed.");
133         return -3;
134     }
135 
136     skb->dev = dev;
137     skb->pkt_type  = PACKET_OUTGOING;
138     skb->local_df  = 1;
139     skb->protocol = htons(ETH_P_IP);
140 
141     eth = (struct ethhdr *)skb_push(skb, ETH_HLEN);
142     skb_reset_mac_header(skb);
143 
144     memcpy(eth->h_source, dev->dev_addr, ETH_ALEN);
145    
146     gate_addr = in_aton(GATE_IP);
147     err = get_mac(eth->h_dest, gate_addr, RT_TOS(20), skb);
148     if (err != 1) {
149         kfree_skb(skb);
150         if (net_ratelimit())
151             printk(KERN_ERR"get device mac failed when send packets.err:%d", err);
152             return err;
153         }
154 
155     return 0;
156 }

第130行获得网卡SOU_DEVICE在内核中的指针,用于获得MAC地址。第147行用于获得网关的MAC地址。
第138行的local_df是指"don't fragment",设置为1代表不会被分段。

目的端实现

因为没有在目地端建立socket,我们在netfiter中对包进行处理,netfilter中hook点的位置如下:


60 struct nf_hook_ops nf_in_ops = {
61     .list       = { NULL, NULL},
62     .pf     = PF_INET,
63 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32)
64     .hook           = nf_in,
65     //.hooknum        = NF_INET_PRE_ROUTING,
66     .hooknum        = NF_INET_LOCAL_IN,
67 #else
68     .hook           = nf_in_p,
69     //.hooknum        = NF_IP_PRE_ROUTING,
70     .hooknum        = NF_IP_LOCAL_IN,
71 #endif
72     .priority       = NF_IP_PRI_FIRST,
73 };

hook函数如下:

10 static unsigned int nf_in(
11 #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)
12         const struct nf_hook_ops *ops,
13 #else
14         unsigned int hooknum,
15 #endif
16         struct sk_buff *skb,
17         const struct net_device *in,
18         const struct net_device *out,
19         int (*okfn)(struct sk_buff *))
20 {
21     unsigned short sport, dport;
22     struct iphdr *iph;
23     struct tcphdr *tcph;
24     char *tcp_payload = NULL;
25     size_t tcp_payload_len = 0;
26     int i;
27 
28     iph = ip_hdr(skb);
29     if (iph->protocol == IPPROTO_TCP) {
30         tcph = (struct tcphdr *)(skb->data + (iph->ihl << 2));
31 
32         sport = tcph->source;
33         dport = tcph->dest;
34         if (ntohs(sport) == 6880 && ntohs(dport) == 6880) {
35             printk(KERN_INFO "find a new packet");
36             tcp_payload = (char *)((unsigned char *)tcph + (tcph->doff << 2));
37             tcp_payload_len = ntohs(iph->tot_len) - (iph->ihl << 2) - (tcph->doff << 2);
38 
39             if (tcp_payload_len == 16)
40                 for (i = 0; i < tcp_payload_len; ++i)
41                     printk("%d:", tcp_payload[i]);
42 
43             return NF_DROP;
44         }
45     }
46     return NF_ACCEPT;
47 }

第29行到43行,我们对我们自己制造的TCP包进行处理。

结论

  1. 使用此种方法对于传输少量数据可行,但是由于丢包的问题,需要增加ACK和重传机制。
  2. 改为per-cpu模式话,可以用于局域网中测试网卡性能。

github地址

github地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,667评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,361评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,700评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,027评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,988评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,230评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,705评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,366评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,496评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,405评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,453评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,725评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,803评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,015评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,514评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,111评论 2 341

推荐阅读更多精彩内容

  • 简介 用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者...
    保川阅读 5,939评论 1 13
  • 个人认为,Goodboy1881先生的TCP /IP 协议详解学习博客系列博客是一部非常精彩的学习笔记,这虽然只是...
    贰零壹柒_fc10阅读 5,049评论 0 8
  • 18.1 引言 TCP是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。本章将...
    张芳涛阅读 3,329评论 0 13
  • 1.这篇文章不是本人原创的,只是个人为了对这部分知识做一个整理和系统的输出而编辑成的,在此郑重地向本文所引用文章的...
    SOMCENT阅读 13,032评论 6 174
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,836评论 6 13