源码分析
int udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
{
/*套接字的网络层表示转换成INET套接字的表示*/
struct inet_sock *inet = inet_sk(sk);
/*套接字的网络层表示转换成UDP套接字的表示*/
struct udp_sock *up = udp_sk(sk);
/*struct sockaddr_in * usin = ({ do { } while (0); (struct sockaddr_in *) msg->msg_name; })获取ip地址和端口*/
DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name);
struct flowi4 fl4_stack;
struct flowi4 *fl4;
int ulen = len;
struct ipcm_cookie ipc;
struct rtable *rt = NULL;
int free = 0;
int connected = 0;
__be32 daddr, faddr, saddr;
__be16 dport;
u8 tos;
/*获取pcflag标志确定该套接字是普通的UDP套接字还是UDP轻量级套接字*/
int err, is_udplite = IS_UDPLITE(sk);
/*如果 up->corkflag> 0,则将套接字标记为 UDP-Lite,后半段判断是否还需要发送更多消息*/
int corkreq = up->corkflag || msg->msg_flags&MSG_MORE;
int (*getfrag)(void *, char *, int, int, int, struct sk_buff *);
struct sk_buff *skb;
struct ip_options_data opt_copy;
/*UDP数据报最长为64KB*/
if (len > 0xFFFF)
return -EMSGSIZE;
/*
* Check the flags.
*/
/*UDP不支持发送带外数据,如果发送标志中设置了MSG_OOB,则返回*/
if (msg->msg_flags & MSG_OOB) /* Mirror BSD error message compatibility */
return -EOPNOTSUPP;
/*udp和udplite使用不同的getfrag*/
getfrag = is_udplite ? udplite_getfrag : ip_generic_getfrag;
fl4 = &inet->cork.fl.u.ip4;/*struct flowi4类型,*/
if (up->pending) {
/*
* There are pending frames.
* The socket lock must be held while it's corked.
*/
/*当前的sock有等待发送的数据,直接将数据追加*/
lock_sock(sk);
/*likely和unlikely对程序逻辑没影响,likely提示编译器括号内的内容为真的概率更大,unlikely相反*/
if (likely(up->pending)) {
if (unlikely(up->pending != AF_INET)) {
/* 和lock_sock成对出现,一个加锁,一个解锁*/
release_sock(sk);
return -EINVAL;
}
/*up->pending为AF_INET时候,直接跳转到数据发送,这里进行的了第一次数据发送后的数据发送*/
goto do_append_data;
}
release_sock(sk);
}
/*接下来的都是第一次发包时的操作,UDP数据报长度,包括UDP data + UDP header*/
ulen += sizeof(struct udphdr);
/*
* Get and verify the address.
*/
/*获取目的IP地址和端口:目的地址和端口有两个可能的来源:
1. 如果之前socket已经建立,那socket本身就存储了目标地址;
2. 地址通过msghdr传入,通常为调用sendto发送UDP数据*/
if (usin) {
if (msg->msg_namelen < sizeof(*usin))
return -EINVAL;
if (usin->sin_family != AF_INET) {
if (usin->sin_family != AF_UNSPEC)
return -EAFNOSUPPORT;
}
daddr = usin->sin_addr.s_addr;
dport = usin->sin_port;
/*目的端口不能为零*/
if (dport == 0)
return -EINVAL;
} else {
/*msg没有目的地址的情况:通常为先调用了connect,然后调用send发送UDP数据,
UDP套接字调用connetc之后,UDP传输控制块状态为TCP_ESTABLISHED*/
if (sk->sk_state != TCP_ESTABLISHED)/*即没有指明目的地址,又没有建立connect连接,则返错。*/
return -EDESTADDRREQ;
daddr = inet->inet_daddr;
dport = inet->inet_dport;
/* Open fast path for connected socket.
Route will not be used, if at least one option is set.
*/
/*为连接的套接字打开快速路径。如果至少设置了一个选项,则不会使用路由。*/
connected = 1;
}
/*获取存储在 socket 上的源地址、发送网络设备索引(device index)和时间戳选项*/
ipcm_init_sk(&ipc, inet);
/*保留了当套接字被解锁时创建 UDP 标头的信息。*/
ipc.gso_size = up->gso_size;
/*msg中控制信息处理*/
if (msg->msg_controllen) {
/*如果msg_controllen辅助缓冲区中有数据,则消息发送*/
err = udp_cmsg_send(sk, msg, &ipc.gso_size);
if (err > 0)
/*调用ip_cmsg_send处理控制信息,包括IP选项等...*/
err = ip_cmsg_send(sk, msg, &ipc,
sk->sk_family == AF_INET6);
if (unlikely(err < 0)) {
kfree(ipc.opt);
return err;
}
if (ipc.opt)
free = 1;
/*这里表示不进行路由*/
connected = 0;
}
/*如果发送的数据中没有IP选项控制信息,则从正在使用的socket中获取IP选项信息*/
if (!ipc.opt) {
struct ip_options_rcu *inet_opt;
rcu_read_lock();
inet_opt = rcu_dereference(inet->inet_opt);
if (inet_opt) {
memcpy(&opt_copy, inet_opt,
sizeof(*inet_opt) + inet_opt->opt.optlen);
ipc.opt = &opt_copy.opt;
}
rcu_read_unlock();
}
if (cgroup_bpf_enabled(BPF_CGROUP_UDP4_SENDMSG) && !connected) {
err = BPF_CGROUP_RUN_PROG_UDP4_SENDMSG_LOCK(sk,
(struct sockaddr *)usin, &ipc.addr);
if (err)
goto out_free;
if (usin) {
if (usin->sin_port == 0) {
/* BPF program set invalid port. Reject it. */
/*linux中不允许目的端口为0*/
err = -EINVAL;
goto out_free;
}
daddr = usin->sin_addr.s_addr;
dport = usin->sin_port;
}
}
saddr = ipc.addr;
ipc.addr = faddr = daddr;
if (ipc.opt && ipc.opt->opt.srr) {
if (!daddr) {
err = -EINVAL;
goto out_free;
}
faddr = ipc.opt->opt.faddr;
/*这里表示不进行路由,进行本地路由*/
connected = 0;
}
tos = get_rttos(&ipc, inet);
if (sock_flag(sk, SOCK_LOCALROUTE) ||
(msg->msg_flags & MSG_DONTROUTE) ||
(ipc.opt && ipc.opt->opt.is_strictroute)) {
tos |= RTO_ONLINK;
//SOCK_LOCALROUTE, /* route locally only, %SO_DONTROUTE setting */
//MSG_DONTROUTE,顾名思义,消息不路由
//ipc.opt && ipc.opt->opt.is_strictroute同时不为零
/*这里表示不进行路由,以上3种情况只进行本地路由*/
connected = 0;
}
//ip地址为224.*.*.*时
if (ipv4_is_multicast(daddr)) {
if (!ipc.oif || netif_index_is_l3_master(sock_net(sk), ipc.oif))
ipc.oif = inet->mc_index;
if (!saddr)
saddr = inet->mc_addr;
connected = 0;
} else if (!ipc.oif) {
ipc.oif = inet->uc_index;
} else if (ipv4_is_lbcast(daddr) && inet->uc_index) {/*ip地址如果为255.255.255.255*/
/* oif is set, packet is to local broadcast and
* uc_index is set. oif is most likely set
* by sk_bound_dev_if. If uc_index != oif check if the
* oif is an L3 master and uc_index is an L3 slave.
* If so, we want to allow the send using the uc_index.
*/
/*oif 设置,数据包到本地广播并设置 uc_index。 oif 很可能由 sk_bound_dev_if 设置。 如果 uc_index != oif 检查 oif 是否是 L3 master 并且 uc_index 是否是 L3 slave。 如果是这样,我们希望允许使用 uc_index 发送。*/
if (ipc.oif != inet->uc_index &&
ipc.oif == l3mdev_master_ifindex_by_index(sock_net(sk),
inet->uc_index)) {
ipc.oif = inet->uc_index;
}
}
if (connected)
/*目标路由检查*/
rt = (struct rtable *)sk_dst_check(sk, 0);
//如果没有路由,建立一次路由
if (!rt) {
struct net *net = sock_net(sk);
__u8 flow_flags = inet_sk_flowi_flags(sk);
fl4 = &fl4_stack;
flowi4_init_output(fl4, ipc.oif, ipc.sockc.mark, tos,
RT_SCOPE_UNIVERSE, sk->sk_protocol,
flow_flags,
faddr, saddr, dport, inet->inet_sport,
sk->sk_uid);
security_sk_classify_flow(sk, flowi4_to_flowi_common(fl4));
rt = ip_route_output_flow(net, fl4, sk);
if (IS_ERR(rt)) {
err = PTR_ERR(rt);
rt = NULL;
if (err == -ENETUNREACH)
IP_INC_STATS(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));
}
//msg_flags为确认路径有效性的话,跳转到do_confirm
if (msg->msg_flags&MSG_CONFIRM)
goto do_confirm;
back_from_confirm:
saddr = fl4->saddr;
if (!ipc.addr)
daddr = ipc.addr = fl4->daddr;
/* Lockless fast path for the non-corking case. */
if (!corkreq) {
struct inet_cork cork;
skb = ip_make_skb(sk, fl4, getfrag, msg, ulen,
sizeof(struct udphdr), &ipc, &rt,
&cork, msg->msg_flags);
err = PTR_ERR(skb);
if (!IS_ERR_OR_NULL(skb))
err = udp_send_skb(skb, fl4, &cork);
goto out;
}
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);
//这里表示程序错误导致阻塞,将不会发送数据
net_dbg_ratelimited("socket already corked
");
err = -EINVAL;
goto out;
}
/*
* Now cork the socket to pend data.
*/
fl4 = &inet->cork.fl.u.ip4;
fl4->daddr = daddr;
fl4->saddr = saddr;
fl4->fl4_dport = dport;
fl4->fl4_sport = inet->inet_sport;
up->pending = AF_INET;
do_append_data:
up->len += ulen;
/*udp组ip包*/
err = ip_append_data(sk, fl4, getfrag, msg, 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);
out:
ip_rt_put(rt);//等效release_sock(sk);
out_free:
if (free)
kfree(ipc.opt);//释放内存,内部将ipc.opt==NULL
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.
*/
/*ENOBUFS = 无内核内存,SOCK_NOSPACE = 无 sndbuf 空间。 报告 ENOBUFS 可能不好(它本身不可调),但除此之外我们没有很好的统计数据(IpOutDiscards,但它可能太多了)。 我们可以添加另一个新的统计数据,但至少现在这似乎有点矫枉过正。*/
if (err == -ENOBUFS || test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) {
UDP_INC_STATS(sock_net(sk),
UDP_MIB_SNDBUFERRORS, is_udplite);
}
return err;
do_confirm:
if (msg->msg_flags & MSG_PROBE)//MSG_PROBE表示不发送,仅探测路径
dst_confirm_neigh(&rt->dst, &fl4->daddr);
if (!(msg->msg_flags&MSG_PROBE) || len)
goto back_from_confirm;
err = 0;
goto out;
}
EXPORT_SYMBOL(udp_sendmsg);
总结
1.参数sk:套接字的网络层表示,msg:传递有效负荷,len:数据字节长度不包含udphdr
2.首先获取到目标地址和端口,原地址和端口,目的端口不能为0,目标地址为广播地址或者开头为224操作不同。
3.虽然是面向无连接的,但是需要起始点到目的地之间的链路,发送数据必须有一条路由缓存。如果目的地址改变,则重新建立一条路由并获取缓存。
4.数据包过大时分包,第一次发送数据和第二次发送数据的逻辑不同
5.发包中没有包含校验和的操作,只含有数据拼接和发送数据前的准备工作。