默默的一块石头
分类: 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数据包