Linux网络解读(5) - UDP数据接收

简介: UDP数据接收

UDP

UDP和IP的区别是多了端口的概念。

看一下端口绑定的过程:

int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
        struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
        struct sock *sk = sock->sk;
        struct inet_sock *inet = inet_sk(sk);
        unsigned short snum;
        int chk_addr_ret;
        int err;
        // 如果套接字有自己定义的bind函数则调用,只有raw类型的才有自定义的bind函数。
        if (sk->sk_prot->bind) {
                err = sk->sk_prot->bind(sk, uaddr, addr_len);
                goto out;
        }
        err = -EINVAL;
        if (addr_len < sizeof(struct sockaddr_in))
                goto out;
        chk_addr_ret = inet_addr_type(addr->sin_addr.s_addr);
        err = -EADDRNOTAVAIL;
        if (!sysctl_ip_nonlocal_bind &&
            !inet->freebind &&
            addr->sin_addr.s_addr != INADDR_ANY &&
            chk_addr_ret != RTN_LOCAL &&
            chk_addr_ret != RTN_MULTICAST &&
            chk_addr_ret != RTN_BROADCAST)
                goto out;
        snum = ntohs(addr->sin_port);
        err = -EACCES;
        if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE))
                goto out;
        lock_sock(sk);
        err = -EINVAL;
        if (sk->sk_state != TCP_CLOSE || inet->num)
                goto out_release_sock;
        inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr;
        if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
                inet->saddr = 0;  /* Use device */
        //调用具体协议的get_port方法,获取一个端口。
        if (sk->sk_prot->get_port(sk, snum)) {
                inet->saddr = inet->rcv_saddr = 0;
                err = -EADDRINUSE;
                goto out_release_sock;
        }
        if (inet->rcv_saddr)
                sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
        if (snum)
                sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
        inet->sport = htons(inet->num);
        inet->daddr = 0;
        inet->dport = 0;
        sk_dst_reset(sk);
        err = 0;
out_release_sock:
        release_sock(sk);
out:
        return err;
}

调用具体协议的get_port方法,获取一个端口:sk->sk_prot->get_port(sk, snum)

sk_prot是inetsw_array中的udp_prot,所以get_port是udp_v4_get_port。

注意:从用户态往内核态的协议选择走inetsw_array

static int udp_v4_get_port(struct sock *sk, unsigned short snum)
{
        struct hlist_node *node;
        struct sock *sk2;
        struct inet_sock *inet = inet_sk(sk);
        write_lock_bh(&udp_hash_lock);
        if (snum == 0) {
                int best_size_so_far, best, result, i;
                // 端口号的范围在sysctl_local_port_range
                // 每次调用都从udp_port_rover开始查找可用的端口
                if (udp_port_rover > sysctl_local_port_range[1] ||
                    udp_port_rover < sysctl_local_port_range[0])
                        udp_port_rover = sysctl_local_port_range[0];
                best_size_so_far = 32767;
                best = result = udp_port_rover;
                // 遍历udp_hash表
                // 在128个桶中寻找桶最小的,然后每隔128次试探一次。
                for (i = 0; i < UDP_HTABLE_SIZE; i++, result++) {
                        struct hlist_head *list;
                        int size;
                        list = &udp_hash[result & (UDP_HTABLE_SIZE - 1)];
                        if (hlist_empty(list)) {
                                if (result > sysctl_local_port_range[1])
                                        result = sysctl_local_port_range[0] +
                                                ((result - sysctl_local_port_range[0]) &
                                                 (UDP_HTABLE_SIZE - 1));
                                goto gotit;
                        }
                        size = 0;
                        // 计算当前桶的大小,是否是最小的桶,如果是更新best_size_so_far
                        sk_for_each(sk2, node, list)
                                if (++size >= best_size_so_far)
                                        goto next;
                        best_size_so_far = size;
                        best = result;
                next:;
                }
                result = best;
                // 最小的桶每隔128试探一次。
                for(i = 0; i < (1 << 16) / UDP_HTABLE_SIZE; i++, result += UDP_HTABLE_SIZE) {
                        if (result > sysctl_local_port_range[1])
                                result = sysctl_local_port_range[0]
                                        + ((result - sysctl_local_port_range[0]) &
                                           (UDP_HTABLE_SIZE - 1));
                        if (!udp_lport_inuse(result))
                                break;
                }
                if (i >= (1 << 16) / UDP_HTABLE_SIZE)
                        goto fail;
gotit:
                udp_port_rover = snum = result;
        } else {
                sk_for_each(sk2, node,
                            &udp_hash[snum & (UDP_HTABLE_SIZE - 1)]) {
                        struct inet_sock *inet2 = inet_sk(sk2);
                        if (inet2->num == snum &&
                            sk2 != sk &&
                            !ipv6_only_sock(sk2) &&
                            (!sk2->sk_bound_dev_if ||
                             !sk->sk_bound_dev_if ||
                             sk2->sk_bound_dev_if == sk->sk_bound_dev_if) &&
                            (!inet2->rcv_saddr ||
                             !inet->rcv_saddr ||
                             inet2->rcv_saddr == inet->rcv_saddr) &&
                            (!sk2->sk_reuse || !sk->sk_reuse))
                                goto fail;
                }
        }
        inet->num = snum;
        if (sk_unhashed(sk)) {
                struct hlist_head *h = &udp_hash[snum & (UDP_HTABLE_SIZE - 1)];
                sk_add_node(sk, h);
                sock_prot_inc_use(sk->sk_prot);
        }
        write_unlock_bh(&udp_hash_lock);
        return 0;
fail:
        write_unlock_bh(&udp_hash_lock);
        return 1;
}

image.png

下面看一下UDP在内核中收数据过程。软中断最终调用到ip_local_deliver_finish,根据IP报文头部的协议字段skb->nh.iph->protocol在inet_protos找到对应的协议处理函数icmp, udp, tcp。

对于UDP来说

static struct net_protocol udp_protocol = {

.handler = udp_rcv,

.err_handler = udp_err,

.no_policy = 1,

};

int udp_rcv(struct sk_buff *skb)
{
          struct sock *sk;
          struct udphdr *uh;
        unsigned short ulen;
        struct rtable *rt = (struct rtable*)skb->dst;
        // 从skb获取源地址目的地址
        u32 saddr = skb->nh.iph->saddr;
        u32 daddr = skb->nh.iph->daddr;
        int len = skb->len;
        uh = skb->h.uh;
        ulen = ntohs(uh->len);
        if (ulen > len || ulen < sizeof(*uh))
                goto short_packet;
        if (pskb_trim(skb, ulen))
                goto short_packet;
        // 检查校验和        
        if (udp_checksum_init(skb, uh, ulen, saddr, daddr) < 0)
                goto csum_error;
        if(rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST))
                return udp_v4_mcast_deliver(skb, uh, saddr, daddr);
        // 根据skb中的源地址和端口,目的地址和端口,反向查找到这个数据应该发送到哪个sock上
        sk = udp_v4_lookup(saddr, uh->source, daddr, uh->dest, skb->dev->ifindex);
        if (sk != NULL) {
                // 把skb添加到sk->sk_receive_queue尾部
                // 并唤醒等待在sleep上的进程
                int ret = udp_queue_rcv_skb(sk, skb);
                sock_put(sk);
                if (ret > 0)
                        return -ret;
                return 0;
        }
        if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
                goto drop;
        /* No socket. Drop packet silently, if checksum is wrong */
        if (udp_checksum_complete(skb))
                goto csum_error;
        UDP_INC_STATS_BH(UDP_MIB_NOPORTS);
        icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
        // 忽略错误处理
        ...
        ...
        ...
}

下面看一下UDP如何把数据返回给用户。

static int udp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
                       size_t len, int noblock, int flags, int *addr_len)
{
        struct inet_sock *inet = inet_sk(sk);
          struct sockaddr_in *sin = (struct sockaddr_in *)msg->msg_name;
          struct sk_buff *skb;
          int copied, err;
        if (addr_len)
                *addr_len=sizeof(*sin);
        if (flags & MSG_ERRQUEUE)
                return ip_recv_error(sk, msg, len);
try_again:
        // 接收一个skb,如果是blocking的socket,而且此时没有数据包会进入wait_for_packet
        skb = skb_recv_datagram(sk, flags, noblock, &err);
        if (!skb)
                goto out;
        // 下面开始向用户态拷贝数据        
          copied = skb->len - sizeof(struct udphdr);
        if (copied > len) {
                copied = len;
                msg->msg_flags |= MSG_TRUNC;
        }
        if (skb->ip_summed==CHECKSUM_UNNECESSARY) {
                err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov,
                                              copied);
        } else if (msg->msg_flags&MSG_TRUNC) {
                if (__udp_checksum_complete(skb))
                        goto csum_copy_err;
                err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov,
                                              copied);
        } else {
                err = skb_copy_and_csum_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov);
                if (err == -EINVAL)
                        goto csum_copy_err;
        }
        if (err)
                goto out_free;
        sock_recv_timestamp(msg, sk, skb);
        if (sin)
        {
                sin->sin_family = AF_INET;
                sin->sin_port = skb->h.uh->source;
                sin->sin_addr.s_addr = skb->nh.iph->saddr;
                memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
          }
        if (inet->cmsg_flags)
                ip_cmsg_recv(msg, skb);
        err = copied;
        if (flags & MSG_TRUNC)
                err = skb->len - sizeof(struct udphdr);
        // 省略错误处理        
        ...
        ...
        ...
}
相关文章
|
17天前
|
安全 Linux 虚拟化
网络名称空间在Linux虚拟化技术中的位置
网络名称空间(Network Namespaces)是Linux内核特性之一,提供了隔离网络环境的能力,使得每个网络名称空间都拥有独立的网络设备、IP地址、路由表、端口号范围以及iptables规则等。这一特性在Linux虚拟化技术中占据了核心位置🌟,它不仅为构建轻量级虚拟化解决方案(如容器📦)提供了基础支持,也在传统的虚拟机技术中发挥作用,实现资源隔离和网络虚拟化。
网络名称空间在Linux虚拟化技术中的位置
|
17天前
|
网络协议 安全 Linux
Linux网络名称空间之独立网络资源管理
Linux网络名称空间是一种强大的虚拟化技术🛠️,它允许用户创建隔离的网络环境🌐,每个环境拥有独立的网络资源和配置。这项技术对于云计算☁️、容器化应用📦和网络安全🔒等领域至关重要。本文将详细介绍在Linux网络名称空间中可以拥有的独立网络资源,并指出应用开发人员在使用时应注意的重点。
|
17天前
|
安全 网络协议 Linux
Linux网络名称空间概述
Linux网络名称空间是操作系统级别的一种虚拟化技术🔄,它允许创建隔离的网络环境🌐,使得每个环境拥有自己独立的网络资源,如IP地址📍、路由表🗺️、防火墙规则🔥等。这种技术是Linux内核功能的一部分,为不同的用户空间进程提供了一种创建和使用独立网络协议栈的方式。本文旨在全方面、多维度解释Linux网络名称空间的概念、必要性和作用。
Linux网络名称空间概述
|
19天前
|
存储 缓存 Linux
Linux IO的奥秘:深入探索数据流动的魔法
Linux I/O(输入/输出)系统是其核心功能之一,负责处理数据在系统内部及与外界之间的流动。为了优化这一流程,Linux进行了一系列努力和抽象化,以提高效率、灵活性和易用性。🚀
Linux IO的奥秘:深入探索数据流动的魔法
|
25天前
|
Linux
Linux中centos桌面消失网络图标
Linux中centos桌面消失网络图标
13 0
|
1月前
|
算法 Shell Linux
【Shell 命令集合 备份压缩 】⭐Linux 压缩 恢复bzip2损坏数据 bzip2recover命令 使用指南
【Shell 命令集合 备份压缩 】⭐Linux 压缩 恢复bzip2损坏数据 bzip2recover命令 使用指南
33 0
|
1月前
|
Shell Linux C语言
【Shell 命令集合 网络通讯 】Linux 向指定用户或终端发送消息 write命令 使用指南
【Shell 命令集合 网络通讯 】Linux 向指定用户或终端发送消息 write命令 使用指南
34 0
|
1月前
|
安全 Unix Shell
【Shell 命令集合 网络通讯 】Linux 向所有当前登录的用户发送消息或通知 wall命令 使用指南
【Shell 命令集合 网络通讯 】Linux 向所有当前登录的用户发送消息或通知 wall命令 使用指南
29 0
|
15天前
|
存储 算法 Linux
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
39 6
|
5天前
|
机器学习/深度学习 缓存 监控
linux查看CPU、内存、网络、磁盘IO命令
`Linux`系统中,使用`top`命令查看CPU状态,要查看CPU详细信息,可利用`cat /proc/cpuinfo`相关命令。`free`命令用于查看内存使用情况。网络相关命令包括`ifconfig`(查看网卡状态)、`ifdown/ifup`(禁用/启用网卡)、`netstat`(列出网络连接,如`-tuln`组合)以及`nslookup`、`ping`、`telnet`、`traceroute`等。磁盘IO方面,`iostat`(如`-k -p ALL`)显示磁盘IO统计,`iotop`(如`-o -d 1`)则用于查看磁盘IO瓶颈。