Chinaunix首页 | 论坛 | 博客
  • 博客访问: 416948
  • 博文数量: 124
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 872
  • 用 户 组: 普通用户
  • 注册时间: 2018-03-29 14:38
个人简介

默默的一块石头

文章分类

全部博文(124)

文章存档

2022年(26)

2021年(10)

2020年(28)

2019年(60)

我的朋友

分类: LINUX

2019-11-18 17:16:01

发送TCP数据包
    与UDP一样,要从用户控件中创建的TCP套接字发送数据包,可使用多个系统调用,包括:send()、sendto()、sendmsg()和write()。这些系统调用最终都由方法tcp_sendmsg()(net/ipv4/tcp.c)来处理。它将来自用户空间的有效负载复制到内核,并将其作为TCP数据段进行发送。这个方法比方法udp_sendmsg()要复杂得多。

int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
 size_t size)
{
 struct iovec *iov;
 struct tcp_sock *tp = tcp_sk(sk);
 struct sk_buff *skb;
 int iovlen, flags, err, copied = 0;
 int mss_now = 0, size_goal, copied_syn = 0, offset = 0;
 bool sg;
 long timeo;
 ......
  这里不深究这个方法将来自用户空间的数据复制到SKB中的细节。创建SKB后,将调用方法tcp_push_one()来发送它。方法tcp_push_one()将调用方法tcp_write_xmit(),而tcp_write_xmit()又将调用方法tcp_transmit_skb().
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,
     gfp_t gfp_mask)
{
 icsk_af_ops(INTET连接套接字选项)是一个随地址簇而异的对象。对于IPv4 TCP,方法tcp_v4_init_sock()将其设置为一个名为ipv4_specific的inet_connection_sock_af_ops对象。queue_xmit回调函数被设置为通用方法ip_queue_xmit()。请参见net/ipv4/tcp_ipv4.c.
      ......
      err = icsk->icsk_af_ops->queue_xmit(skb, &inet->cork.fl);
      ......
}
(net/upv4/tcp_output.c)
------摘自《精通Linux内核网络》 11.4.7 发送TCP数据包

int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl)
{
.......
/* Make sure we can route this packet. */
 rt = (struct rtable *)__sk_dst_check(sk, 0);
 rtable对象为路由选择子系统查找结果。首先来讨论rtable实例为NULL,需要执行路由选择子系统查找的情形。如果设置了严格的路由选择选项标志,就将目标地址设置为IP选项中的第一个地址。
if (rt == NULL) {
 __be32 daddr;


 /* Use correct destination address if we have options. */
 daddr = inet->inet_daddr;
 if (inet_opt && inet_opt->opt.srr)
 daddr = inet_opt->opt.faddr;
 接下来。使用方法ip_route_output_ports()在路由选择子系统中执行查找。如果查找失败就将数据包丢弃,并返回错误-EHOSTUNREACH.
/* If this fails, retransmit mechanism of transport layer will
  * keep trying until route appears or the connection times
  * itself out.
  */
 rt = ip_route_output_ports(sock_net(sk), fl4, sk,
    daddr, inet->inet_saddr,
    inet->inet_dport,
    inet->inet_sport,
    sk->sk_protocol,
    RT_CONN_FLAGS(sk),
    sk->sk_bound_dev_if);
 if (IS_ERR(rt))
 goto no_route;
 sk_setup_caps(sk, &rt->dst);
 }
 skb_dst_set_noref(skb, &rt->dst);
......
  如果查找成功,但选项的is_strictroute标志和路由选择条目的rt_uses_gateway标志都被设置,就将数据包丢弃,并返回错误-EHOSTUNREACH.
packet_routed:
 if (inet_opt && inet_opt->opt.is_strictroute && rt->rt_uses_gateway)
  接下来,生成IPv4报头。你应该还记得,数据包来自第4层,skb->data指向的是传输层报头。方法skb_push()将指针skb->data向后移,移动量为IPv4报头的长度。如果使用了IP选项,还需加上IP选项列表的长度(optlen)。
/* OK, we know where to send it, allocate and build IP header. */
 skb_push(skb, sizeof(struct iphdr) + (inet_opt ? inet_opt->opt.optlen : 0));
 设置L3报头(skb->network_header),使其指向skb->data。
skb_reset_network_header(skb);
 iph = ip_hdr(skb);
 *((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
 if (ip_dont_fragment(sk, &rt->dst) && !skb->local_df)
 iph->frag_off = htons(IP_DF);
 else
 iph->frag_off = 0;
 iph->ttl      = ip_select_ttl(inet, &rt->dst);
 iph->protocol = sk->sk_protocol;
 ip_copy_addrs(iph, fl4);
将选项长度(optlen)除以4,并将结果与IPv4报头长度(iph->ihl)值相加,这是因为IPv4报头长度以4字节为单位。接下来,调用方法ip_options_build(),根据指定IP选项的内容在IPv4报头中创建选项。方法ip_options_build()的最后一个参数(is_frag)表明不会进行分段,这个方法在4.5节讨论过。
/* Transport layer set skb->h.foo itself. */

 if (inet_opt && inet_opt->opt.optlen) {
 iph->ihl += inet_opt->opt.optlen >> 2;
 ip_options_build(skb, &inet_opt->opt, inet->inet_daddr, rt, 0);
 }
  设置IPv4报头中的id:
ip_select_ident_more(skb, &rt->dst, sk,
      (skb_shinfo(skb)->gso_segs ?: 1) - 1);

 skb->priority = sk->sk_priority;
 skb->mark = sk->sk_mark;
发送数据包:
res = ip_local_out(skb);
}

------摘自《精通Linux内核网络》 4.6 发送IPv4数据包

阅读(2633) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~