UDP发送数据包的函数是udp_sendmsg,完成从用户地址空间接受数据包然后赋值到内核空间。udp_sendmsg函数主输入参数有四个:
(1)、kiocb:为了提高对用户地址空间操作效率的数据结构体。
(2)、sk:打开的套接字数据结构,包含了套接字的所有设置信息和选项。
(3)、msg:存放管理用户地址空间的数据结构。
(4)、len:从用户空间接受的数据包长度。
int udp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
size_t len)
struct msghdr结构体:
struct msghdr {
void * msg_name; /* Socket name 目的地址选项 */
int msg_namelen; /* Length of name 目的地址长度*/
struct iovec * msg_iov; /* Data blocks 消息数组 */
__kernel_size_t msg_iovlen; /* Number of blocks */
void * msg_control; /* Per protocol magic (eg BSD file descriptor passing) 控制信息*/
__kernel_size_t msg_controllen; /* Length of cmsg list */
unsigned msg_flags; //接受数据的标志
};
1、正确性检查
首先是做数据包的正确检查,如果发送错误比如数据指针越内存了可能操作系统崩溃,所以必须做数据的正确性检查,首先检查数据包长度是否小于0xFFFF,因为udp数据包的长度表示位最大16位,然后检查套接字标志是否设置位非法的MSG_OOB(允许带外发送数据)
...
//检查数据包长度
if (len > 0xFFFF)
return -EMSGSIZE;
/*
* Check the flags.
*/
//套接字非法标志
if (msg->msg_flags & MSG_OOB) /* Mirror BSD error message compatibility */
return -EOPNOTSUPP;
...
2、处理早期悬挂的数据
先检查当前套接字是否有挂起等待发送的数据,如果有就跳转到do_append_data标签处,先处理挂起等待发送的数据包,
...
//是否有挂起等待发送的数据包
if (up->pending) {
/*
* There are pending frames.
* The socket lock must be held while it's corked.
*/
lock_sock(sk);
if (likely(up->pending)) {
//挂起的数据包是否是AF_INET协议族
if (unlikely(up->pending != AF_INET)) {
release_sock(sk);
return -EINVAL;
}
//先处理挂起的数据包复制到IP层
goto do_append_data;
}
release_sock(sk);
}
...
3、处理新的数据
如果没有挂起等待发送的数据包,udp_sendmsg就处理用户空间传来的数据。新数据处理主要有三个方面:目的IP地址检查、判断是否已经建立连接、控制信息处理。
(1)、目的IP检查
如果目的Ip地址msg->msg_name不为空,就要检查目的ip地址,目的ip地址由套接字名给出,而套接字名保存在msg->msg_name数据域,首先检查目的Ip地址的长度和协议族是否是AF_INET,如果不是就返回错误,检查通过后将目的ip和目的端口赋值给局部变量daddr、dport。
...
//检查目的IP
if (msg->msg_name) {
struct sockaddr_in * usin = (struct sockaddr_in *)msg->msg_name;
//目的地址长度检查
if (msg->msg_namelen < sizeof(*usin))
return -EINVAL;
//目的地址协议族检查
if (usin->sin_family != AF_INET) {
if (usin->sin_family != AF_UNSPEC)
return -EAFNOSUPPORT;
}
//目的ip目的端口赋值
daddr = usin->sin_addr.s_addr;
dport = usin->sin_port;
if (dport == 0)
return -EINVAL;
} else {
...
(2)、已经建立连接
msg->msg_name中的目的地址为空时就要检查是否已经建立连接状况,判断sk->sk_state是否等于TCP_ESTABLISHED,如果不是就返回错误无效的目的地址,如果sk->sk_state等于TCP_ESTABLISHED说明目的路由保存在路由高速缓寄存器中,这时即使应用层传进来来目的ip为空也可以发送数据包,从Inet中取出目的ip和目的端口赋值给局部变量daddr、dport。
...
//目的IP为空检查连接状态是否为TCP_ESTABLISHED
if (sk->sk_state != TCP_ESTABLISHED)
return -EDESTADDRREQ;
//将连接状态的路由信息中目的ip目的端口赋值
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;
...
(3)、控制信息处理
处理和目的IP后就要处理udp的控制信息,udp控制信息保存在msg->msg_control数据域中,如果msg->msg_controllen不为零说明有控制信息,控制信息通过函数ip_cmsg_send解析保存在局部变量struct ipcm_cookie ipc中。
struct ipcm_cookie{
__be32 addr; //输出网络设备地址
int oif //输出网络设备索引
struct ip_option *opt; //ip选项
}
ip_cmsg_send处理udp的控制信息主要有两种:
a、IP_RETOPTS:从ip协议头中获取ip选项保存到ipc->opt中
b、IP_PKTINFO:主要讲网络设备的索引和ip地址返回为ipc->addr、ipc->oif
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) {
//ip选项获取
case IP_RETOPTS:
err = cmsg->cmsg_len - CMSG_ALIGN(sizeof(struct cmsghdr));
//从ip协议头中获取Ip选项
err = ip_options_get(net, &ipc->opt, CMSG_DATA(cmsg),
err < 40 ? err : 40);
if (err)
return err;
break;
//获取输出设备网络索引和接口ip
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;
}
如果套接字中没有设置ip选项就从inet中获取ip选项数据域
...
//设置了套接字控制信息
if (msg->msg_controllen) {
//获取套接字控制信息保存在ipc结构体中
err = ip_cmsg_send(sock_net(sk), msg, &ipc);
if (err)
return err;
if (ipc.opt)
free = 1;
connected = 0;
}
//如果没有设置控制信息
//就从inet中获取ip选项
if (!ipc.opt)
ipc.opt = inet->opt;
//接口地址复制局部变量saddr
saddr = ipc.addr;
//目的地址复制ipc.addr
ipc.addr = faddr = daddr;
...
4、路由判断
向ip层发送数据,首先要判断路由,路由的情况有三种
4.1、不需要设置路由
不需要设置路由的主要有四种情况
a、数据包是本地局域网发送标志是sk->localroute。
b、输入信息选项msg->msg_flags设置了不需要路由标志。
c、ip选项设置了严格路由选项。
d、目的地址是组地址也不要设置路由
...
tos = RT_TOS(inet->tos);
//如果数据是本地局域网传送标志SOCK_LOCALROUTE
//或者不需要路由msg_flags标志MSG_DONTROUTE
//或者配置了严格路由,就不需要寻址路由tos设置为RTO+ONLINK
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;
}
...
4.2、路由已知
路由已知也就是connected标志位1,就从路由高速缓冲区寄存器中区路由保存到局部变量rt中
...
//路由已知,检查目的路由并赋值给路由高速缓冲区的局部变量rt
if (connected)
rt = (struct rtable *)sk_dst_check(sk, 0);
...
4.3、目的路由无效
检查到目的路由rt是NULL,就要调用ip_route_output_flow函数从路由表中搜索路由,要搜索路由首先要建立struct flowi结构体,主要更加源ip、目的ip、源端口、目的端口、输出网络设备获取路由。如果获取的路由是一个广播路由,但套接字没有设置SO_BROADCAST选项就返回错误。
...
//目的路由无效
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;
//目的路由是广播路由,但套接字没有设置SO_BROADCAST就返回错误
if ((rt->rt_flags & RTCF_BROADCAST) &&
!sock_flag(sk, SOCK_BROADCAST))
goto out;
if (connected)
//建立路由连接,保存路由信息到局部变量rt
sk_dst_set(sk, dst_clone(&rt->u.dst));
}
...
4.4、路由已知
到目的地址的有效路由已经建立,判断套接字选项是否设置了MSG_CONFIRM(套接字返回有效路由信息),如果设置了就要跳转到返回路由信息处理标签:do_confirm
...
//套接字设置了MSG_CONFIRM标志
//调转到返回路由信息处理标签
if (msg->msg_flags&MSG_CONFIRM)
goto do_confirm;
...
5、向IP层发送数据
向ip层发送数据主要分三个步骤
(1)、加锁,如果套接字已经阻塞就是否套接字并返回错误信息
...
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;
}
...
(2)、为套接字添加源ip、目的Ip、源端口、目的端口
...
//锁定成功,添加源IP、目的IP、等信息准备发送数据
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;
...
(3)、调用Ip_append_data把数据包复制到IP层缓冲区,getfrag复制把数据包从应用层复制到IP层缓冲区,如果corkreq没有设置MSG_MORE标志,那么立即调用udp_push_pending_frames把刚缓存的数据包发送出去。
...
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);
//缓存数据包失败删除把skb从sk_write_queue队列中释放
if (err)
udp_flush_pending_frames(sk);
//corkreq标志没有设置MSG_MORE立即调用udp_push_pending_frames发送
//发送刚缓存的数据包
else if (!corkreq)
err = udp_push_pending_frames(sk);
else if (unlikely(skb_queue_empty(&sk->sk_write_queue)))
up->pending = 0;
//释放sk
release_sock(sk);
...
6、ip_generic_getfrag函数
ip_generic_getfrag函数复制把数据从用户空间复制到内核空间,如果复制时不需要做校验和就调用memcpy_formiovecend,校验和留给硬件去做,如果需要做校验和就调用csum_partial_copy_fromviovecend计算部分校验和在复制到IP层。udp校验和分三个阶段:数据校验和、udp协议头校验和、ip协议头校验和。最终将数据从iov指针指向的用户地址空间复制到内核地址空间的局部缓冲区sk_buff中。
ip_generic_getfrag函数:
int
ip_generic_getfrag(void *from, char *to, int offset, int len, int odd, struct sk_buff *skb)
{
struct iovec *iov = from;
if (skb->ip_summed == CHECKSUM_PARTIAL) {
//复制时不需要计算校验和
if (memcpy_fromiovecend(to, iov, offset, len) < 0)
return -EFAULT;
} else {
__wsum csum = 0;
//计算校验和并复制数据
if (csum_partial_copy_fromiovecend(to, iov, offset, len, &csum) < 0)
return -EFAULT;
skb->csum = csum_block_add(skb->csum, csum, odd);
}
return 0;
}
struct iov结构体
struct iovec
{
void __user *iov_base; /* 应用层数据包存放在缓冲区的地址*/
__kernel_size_t iov_len; /* 缓冲区能接受的最大数据或者能写入的实际数据长度 */
};
memcpy_fromiovecend函数:
int memcpy_fromiovecend(unsigned char *kdata, const struct iovec *iov,
int offset, int len)
{
/* Skip over the finished iovecs */
//跳过已经复制完成的数据
while (offset >= iov->iov_len) {
offset -= iov->iov_len;
iov++;
}
while (len > 0) {
//取数据的起始地址
u8 __user *base = iov->iov_base + offset;
//iov->iov_len是struct iov结构中保存数据的最大长度、offset需要复制数据包的偏移量
//取len和iov->iov_len-offset的最小值
int copy = min_t(unsigned int, len, iov->iov_len - offset);
offset = 0;
//拷贝数据到内核空间
if (copy_from_user(kdata, base, copy))
return -EFAULT;
len -= copy;
kdata += copy;
iov++;
}
return 0;
}
udp_sendmsg函数代码:
int udp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
size_t len)
{
struct inet_sock *inet = inet_sk(sk);
struct udp_sock *up = udp_sk(sk);
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;
int err, is_udplite = IS_UDPLITE(sk);
int corkreq = up->corkflag || msg->msg_flags&MSG_MORE;
int (*getfrag)(void *, char *, int, int, int, struct sk_buff *);
//检查数据包长度
if (len > 0xFFFF)
return -EMSGSIZE;
/*
* Check the flags.
*/
//套接字非法标志
if (msg->msg_flags & MSG_OOB) /* Mirror BSD error message compatibility */
return -EOPNOTSUPP;
ipc.opt = NULL;
ipc.shtx.flags = 0;
//是否有挂起等待发送的数据包
if (up->pending) {
/*
* There are pending frames.
* The socket lock must be held while it's corked.
*/
lock_sock(sk);
if (likely(up->pending)) {
//挂起的数据包是否是AF_INET协议族
if (unlikely(up->pending != AF_INET)) {
release_sock(sk);
return -EINVAL;
}
//先处理挂起的数据包复制到IP层
goto do_append_data;
}
release_sock(sk);
}
ulen += sizeof(struct udphdr);
/*
* Get and verify the address.
*/
//检查目的IP
if (msg->msg_name) {
struct sockaddr_in * usin = (struct sockaddr_in *)msg->msg_name;
//目的地址长度检查
if (msg->msg_namelen < sizeof(*usin))
return -EINVAL;
//目的地址协议族检查
if (usin->sin_family != AF_INET) {
if (usin->sin_family != AF_UNSPEC)
return -EAFNOSUPPORT;
}
//目的ip目的端口赋值
daddr = usin->sin_addr.s_addr;
dport = usin->sin_port;
if (dport == 0)
return -EINVAL;
} else {
//目的IP为空检查连接状态是否为TCP_ESTABLISHED
if (sk->sk_state != TCP_ESTABLISHED)
return -EDESTADDRREQ;
//将连接状态的路由信息中目的ip目的端口赋值
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;
}
//输出网络设备地址
ipc.addr = inet->inet_saddr;
//输出网络设备索引号
ipc.oif = sk->sk_bound_dev_if;
err = sock_tx_timestamp(msg, sk, &ipc.shtx);
if (err)
return err;
//设置了套接字控制信息
if (msg->msg_controllen) {
//获取套接字控制信息保存在ipc结构体中
err = ip_cmsg_send(sock_net(sk), msg, &ipc);
if (err)
return err;
if (ipc.opt)
free = 1;
connected = 0;
}
//如果没有设置控制信息
//就从inet中获取ip选项
if (!ipc.opt)
ipc.opt = inet->opt;
//接口地址复制局部变量saddr
saddr = ipc.addr;
//目的地址复制ipc.addr
ipc.addr = faddr = daddr;
//如果设置了源路由
//那么下一站点的目的地址从源路由的IP地址列表中获取
if (ipc.opt && ipc.opt->srr) {
if (!daddr)
return -EINVAL;
faddr = ipc.opt->faddr;
connected = 0;
}
tos = RT_TOS(inet->tos);
//如果数据是本地局域网传送标志SOCK_LOCALROUTE
//或者不需要路由msg_flags标志MSG_DONTROUTE
//或者配置了严格路由,就不需要寻址路由tos设置为RTO+ONLINK
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;
}
//路由已知,检查目的路由并赋值给路由高速缓冲区的局部变量rt
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;
//目的路由是广播路由,但套接字没有设置SO_BROADCAST就返回错误
if ((rt->rt_flags & RTCF_BROADCAST) &&
!sock_flag(sk, SOCK_BROADCAST))
goto out;
if (connected)
//建立路由连接,保存路由信息到局部变量rt
sk_dst_set(sk, dst_clone(&rt->u.dst));
}
//套接字设置了MSG_CONFIRM标志
//调转到返回路由信息处理标签
if (msg->msg_flags&MSG_CONFIRM)
goto do_confirm;
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.
*/
//锁定成功,添加源IP、目的IP、等信息准备发送数据
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;
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);
//缓存数据包失败删除把skb从sk_write_queue队列中释放
if (err)
udp_flush_pending_frames(sk);
//corkreq标志没有设置MSG_MORE立即调用udp_push_pending_frames发送
//发送刚缓存的数据包
else if (!corkreq)
err = udp_push_pending_frames(sk);
else if (unlikely(skb_queue_empty(&sk->sk_write_queue)))
up->pending = 0;
//释放sk
release_sock(sk);
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;
do_confirm:
dst_confirm(&rt->u.dst);
if (!(msg->msg_flags&MSG_PROBE) || len)
goto back_from_confirm;
err = 0;
goto out;
}