作者:gfree.wind@gmail.com
继续分析udp_sendmsg,
- ipc.oif = sk->sk_bound_dev_if;
-
err = sock_tx_timestamp(msg, sk, &ipc.shtx);
-
if (err)
-
return err;
-
if (msg->msg_controllen) {
-
err = ip_cmsg_send(sock_net(sk), msg, &ipc);
-
if (err)
-
return err;
-
if (ipc.opt)
-
free = 1;
-
connected = 0;
-
}
-
if (!ipc.opt)
-
ipc.opt = inet->opt;
ipc.oif设置为socket bind的interface,设置发送数据包时间戳产生策略。msg->msg_controllen若不为0,那么就调用ip_cmsg_send——从函数名字上看,好像是要发送cmsg,然而实际上却没有任何数据发送。请看它的定义。
- int ip_cmsg_send(struct net *net, struct msghdr *msg, struct ipcm_cookie *ipc)
-
{
-
int err;
-
struct cmsghdr *cmsg;
-
-
for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
-
if (!CMSG_OK(msg, cmsg))
-
return -EINVAL;
-
if (cmsg->cmsg_level != SOL_IP)
-
continue;
-
switch (cmsg->cmsg_type) {
-
case IP_RETOPTS:
-
err = cmsg->cmsg_len - CMSG_ALIGN(sizeof(struct cmsghdr));
-
err = ip_options_get(net, &ipc->opt, CMSG_DATA(cmsg),
-
err 40 ? err : 40);
-
if (err)
-
return err;
-
break;
-
case IP_PKTINFO:
-
{
-
struct in_pktinfo *info;
-
if (cmsg->cmsg_len != CMSG_LEN(sizeof(struct in_pktinfo)))
-
return -EINVAL;
-
info = (struct in_pktinfo *)CMSG_DATA(cmsg);
-
ipc->oif = info->ipi_ifindex;
-
ipc->addr = info->ipi_spec_dst.s_addr;
-
break;
-
}
-
default:
-
return -EINVAL;
-
}
-
}
-
return 0;
-
}
从以上代码可以看出,cmsg只支持两种类型:1个是IP_RETOPTS——设置包的option,另外一个是IP_PKTINFO——具体定义,可以使用man 7 ip来查看。
- saddr = ipc.addr;
-
ipc.addr = faddr = daddr;
-
-
if (ipc.opt && ipc.opt->srr) {
-
if (!daddr)
-
return -EINVAL;
-
faddr = ipc.opt->faddr;
-
connected = 0;
-
}
-
tos = RT_TOS(inet->tos);
-
if (sock_flag(sk, SOCK_LOCALROUTE) ||
-
(msg->msg_flags & MSG_DONTROUTE) ||
-
(ipc.opt && ipc.opt->is_strictroute)) {
-
tos |= RTO_ONLINK;
-
connected = 0;
-
}
前面对于地址赋值的几行代码,我还没有看出什么用途来——哪个高手指教一下。后面是几种情况下,设置发送该包是不需要路由标志——表示到达目的地址是不需要下一跳的。
- if (ipv4_is_multicast(daddr)) {
-
if (!ipc.oif)
-
ipc.oif = inet->mc_index;
-
if (!saddr)
-
saddr = inet->mc_addr;
-
connected = 0;
-
}
如果目的地址是多播地址且没有bind interface,那么就使用本地多播interface,如果没有设置源地址,那么就使用本地多播地址。
- if (connected)
-
rt = (struct rtable *)sk_dst_check(sk, 0);
-
-
if (rt == NULL) {
-
struct flowi fl = { .oif = ipc.oif,
-
.mark = sk->sk_mark,
-
.nl_u = { .ip4_u =
-
{ .daddr = faddr,
-
.saddr = saddr,
-
.tos = tos } },
-
.proto = sk->sk_protocol,
-
.flags = inet_sk_flowi_flags(sk),
-
.uli_u = { .ports =
-
{ .sport = inet->inet_sport,
-
.dport = dport } } };
-
struct net *net = sock_net(sk);
-
-
security_sk_classify_flow(sk, &fl);
-
err = ip_route_output_flow(net, &rt, &fl, sk, 1);
-
if (err) {
-
if (err == -ENETUNREACH)
-
IP_INC_STATS_BH(net, IPSTATS_MIB_OUTNOROUTES);
-
goto out;
-
}
-
-
err = -EACCES;
-
if ((rt->rt_flags & RTCF_BROADCAST) &&
-
!sock_flag(sk, SOCK_BROADCAST))
-
goto out;
-
if (connected)
-
sk_dst_set(sk, dst_clone(&rt->dst));
-
}
如果是面向连接的socket,那么首先检查该socket之前使用的路由。如果没有有效的路由,那么就使用ip_route_output_flow来查找到一个路由。如果没有找到,就返回错误。如果是面向连接的socket,那么就将该路由保存到socket结构中。
- if (msg->msg_flags&MSG_CONFIRM)
-
goto do_confirm;
如果设置了MSG_CONFIRM标志,那么就跳转到do_confirm.MSG_CONFIRM的具体作用,请查看man 2 sendto。
- back_from_confirm:
-
-
saddr = rt->rt_src;
-
if (!ipc.addr)
-
daddr = ipc.addr = rt->rt_dst;
-
-
lock_sock(sk);
-
if (unlikely(up->pending)) {
-
/* The socket is already corked while preparing it. */
-
/* ... which is an evident application bug. --ANK */
-
release_sock(sk);
-
-
LIMIT_NETDEBUG(KERN_DEBUG "udp cork app bug 2\n");
-
err = -EINVAL;
-
goto out;
-
}
-
/*
-
* Now cork the socket to pend data.
-
*/
-
inet->cork.fl.fl4_dst = daddr;
-
inet->cork.fl.fl_ip_dport = dport;
-
inet->cork.fl.fl4_src = saddr;
-
inet->cork.fl.fl_ip_sport = inet->inet_sport;
-
up->pending = AF_INET;
使用路由的源地址作为包的源地址,检查这个socket是否还有pending的数据——如果有的话,根据注释就是出错了,而且这个不应该发生。正常情况下,设置inet->cork.fl,并置socket为pending状态。
- do_append_data:
-
up->len += ulen;
-
getfrag = is_udplite ? udplite_getfrag : ip_generic_getfrag;
-
err = ip_append_data(sk, getfrag, msg->msg_iov, ulen,
-
sizeof(struct udphdr), &ipc, &rt,
-
corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);
-
if (err)
-
udp_flush_pending_frames(sk);
-
else if (!corkreq)
-
err = udp_push_pending_frames(sk);
-
else if (unlikely(skb_queue_empty(&sk->sk_write_queue)))
-
up->pending = 0;
-
release_sock(sk);
使用ip_append_data将这次要发送的数据追加到该socket上——这个函数下次进行具体的分析。如果出错,就drop所有socket上的数据包。如果corkreq为0,那么就调用udp_push_pending_frames去发送数据。如果该socket的发送队列为空,那么就将socket的pending状态重置。最后释放socket锁——从这里可以看出,在使用socket发送数据时,当多线程一起发送时,虽然不能保证包的顺序,但是可以保证每个数据包不会与其他数据包混在一起。
- out:
-
ip_rt_put(rt);
-
if (free)
-
kfree(ipc.opt);
-
if (!err)
-
return len;
-
/*
-
* ENOBUFS = no kernel mem, SOCK_NOSPACE = no sndbuf space. Reporting
-
* ENOBUFS might not be good (it's not tunable per se), but otherwise
-
* we don't have a good statistic (IpOutDiscards but it can be too many
-
* things). We could add another new stat but at least for now that
-
* seems like overkill.
-
*/
-
if (err == -ENOBUFS || test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) {
-
UDP_INC_STATS_USER(sock_net(sk),
-
UDP_MIB_SNDBUFERRORS, is_udplite);
-
}
-
return err;
最后进行资源的释放。如果没有出错,就返回发送的数据长度,如果出错,就增加错误统计。