当前位置:   article > 正文

TCP/IP协议栈源代码分析

TCP/IP协议栈源代码分析

一、inet_init是如何被调用的?从start_kernel到inet_init调用路径

  • start_kernel 函数位于 init/main.c 文件中,是内核启动的入口函数。
  1. asmlinkage __visible void __init start_kernel(void)
  2. {
  3. // ...
  4. // 调用 rest_init 函数
  5. rest_init();
  6. // ...
  7. }
  • rest_init 函数位于 kernel/init/main.c 文件中,完成一些初始化工作,并通过 kernel_thread 函数创建一个新的内核线程。
  1. static void __init rest_init(void)
  2. {
  3. // ...
  4. // 创建一个新的内核线程,并指定其执行函数为 kernel_init
  5. kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
  6. // ...
  7. }
  • kernel_init 函数位于 init/main.c 文件中,是内核初始化的第一个阶段。
  1. static int __init kernel_init(void *unused)
  2. {
  3. // ...
  4. // 调用 do_basic_setup 函数
  5. do_basic_setup();
  6. // ...
  7. // 调用 do_initcalls 函数执行内核初始化的第二个阶段
  8. do_initcalls();
  9. // ...
  10. }
  • do_basic_setup 函数位于 init/main.c 文件中,完成一些基本的系统初始化工作,并调用 do_initcalls 函数执行内核初始化的第二个阶段。
  1. static void __init do_basic_setup(void)
  2. {
  3. // ...
  4. // 调用 do_initcalls 函数执行内核初始化的第二个阶段
  5. do_initcalls();
  6. // ...
  7. }
  • do_initcalls 函数位于 init/main.c 文件中,会调用 do_one_initcall 函数多次,以执行一系列的初始化函数。
  1. static void __init do_initcalls(void)
  2. {
  3. // ...
  4. // 调用 do_one_initcall 函数,执行一系列的初始化函数
  5. do_one_initcall(inet_init);
  6. // ...
  7. }
  • inet_init 函数位于 net/ipv4/af_inet.c 文件中,是网络子系统中 IPv4 协议族的初始化函数。它执行了一些与 IPv4 相关的初始化工作。
  1. static int __init inet_init(void)
  2. {
  3. // ...
  4. // 执行 IPv4 相关的初始化工作
  5. // ...
  6. return 0;
  7. }

因此,start_kernel -> rest_init -> kernel_init -> do_basic_setup -> do_initcalls -> do_one_initcall -> inet_initinet_init 函数从 start_kernelinet_init 的调用路径。

二、跟踪分析TCP/IP协议栈如何将自己与上层套接口与下层数据链路层关联起来的?

Linux 内核中,TCP/IP 协议栈通过网络套接口(socket)与上层应用程序和下层数据链路层进行关联。

  • 上层套接口与协议关联(以 TCP 为例):
  1. // 创建套接字
  2. int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  3. // 绑定套接字到本地地址和端口
  4. struct sockaddr_in addr;
  5. addr.sin_family = AF_INET;
  6. addr.sin_port = htons(1234);
  7. addr.sin_addr.s_addr = htonl(INADDR_ANY);
  8. bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
  9. // 监听连接请求
  10. listen(sockfd, SOMAXCONN);

socket 系统调用创建了一个 TCP 套接字,并返回相应的文件描述符。bind 系统调用将套接字绑定到本地地址和端口。之后,通过 listen 系统调用开始监听连接请求。

  • 数据传输和处理:
  1. // 接受连接请求
  2. struct sockaddr_in client_addr;
  3. socklen_t client_addrlen = sizeof(client_addr);
  4. int connfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addrlen);
  5. // 发送和接收数据
  6. char buffer[1024];
  7. ssize_t num_bytes = recv(connfd, buffer, sizeof(buffer), 0);
  8. send(connfd, buffer, num_bytes, 0);

accept 系统调用接受连接请求,并返回一个新的文件描述符 connfd,用于与客户端进行通信。通过 recvsend 系统调用,可以在套接字之间传输数据。

  • 下层数据链路层处理
  1. // 发送数据帧
  2. struct ethhdr eth_header;
  3. struct iphdr ip_header;
  4. struct tcphdr tcp_header;
  5. // 构建以太网头部、IP 头部和 TCP 头部
  6. struct sk_buff *skb = alloc_skb(total_len, GFP_KERNEL);
  7. // 分配 sk_buff 结构体
  8. // 将数据帧封装到 skb
  9. memcpy(skb_put(skb, sizeof(eth_header)), &eth_header, sizeof(eth_header));
  10. memcpy(skb_put(skb, sizeof(ip_header)), &ip_header, sizeof(ip_header));
  11. memcpy(skb_put(skb, sizeof(tcp_header)), &tcp_header, sizeof(tcp_header));
  12. // 发送 skb
  13. dev_queue_xmit(skb);

在上述示例中,通过构建以太网头部、IP 头部和 TCP 头部,将数据帧封装到一个 sk_buff 结构体中。然后,使用 dev_queue_xmit 函数将 skb 发送到网络设备队列中,以便进一步处理和发送到物理链路。

三、TCP的三次握手源代码跟踪分析,跟踪找出设置和发送SYN/ACK的位置,以及状态转换的位置

  • 设置和发送 SYN/ACK 的位置:

在 TCP 的三次握手过程中,服务器端(被动打开方)负责设置和发送 SYN/ACK 报文。

  1. // 文件:net/ipv4/tcp_ipv4.c
  2. // 函数:tcp_v4_rcv
  3. // 位置:约在 3500 行附近
  4. // 处理接收到的 SYN 报文段
  5. static void tcp_v4_rcv(struct sk_buff *skb)
  6. {
  7. // ...
  8. // 检查报文段是否是 SYN 报文
  9. if (th->syn) {
  10. // ...
  11. // 创建一个新的连接请求
  12. struct tcp_sock *newtp = tcp_create_openreq_child(sk, skb);
  13. if (newtp) {
  14. // 设置新连接请求的状态和序列号等参数
  15. tcp_init_sock(newtp, req->rsk_listener);
  16. // 设置 SYN/ACK 报文的标志
  17. tcp_initialize_rcv_mss(newtp);
  18. tcp_synack_rtt_meas(newtp, req);
  19. // 构建 SYN/ACK 报文
  20. tcp_send_synack(newtp, req);
  21. // 发送 SYN/ACK 报文
  22. tcp_event_data_recv(newtp, skb);
  23. // ...
  24. }
  25. }
  26. // ...
  27. }

在上述代码中,tcp_v4_rcv 函数用于处理接收到的 TCP 报文。当接收到的报文段的 SYN 标志被设置时,表示这是一个连接请求的 SYN 报文。在这种情况下,内核会根据接收到的报文创建一个新的连接请求,并通过调用 tcp_send_synack 函数设置并发送 SYN/ACK 报文。

  • 状态转换的位置:

在 TCP 的三次握手过程中,涉及到多个状态的转换,其中包括 SYN_SENT、SYN_RECV、ESTABLISHED 等。

  1. // 文件:net/ipv4/tcp_ipv4.c
  2. // 函数:tcp_v4_syn_recv_sock
  3. // 位置:约在 3600 行附近
  4. // 创建 SYN_RECV 状态的套接字
  5. static struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
  6. struct request_sock *req,
  7. struct dst_entry *dst)
  8. {
  9. // ...
  10. // 创建 SYN_RECV 状态的套接字
  11. newsk = tcp_create_openreq_child(sk, skb);
  12. if (newsk) {
  13. // 设置套接字的状态为 SYN_RECV
  14. newtp = tcp_sk(newsk);
  15. newtp->rx_opt.tstamp_ok = sysctl_tcp_timestamps;
  16. newtp->advmss = tcp_mtu_to_mss(newtp, dst_mtu(dst));
  17. // ...
  18. // 触发状态转换
  19. inet_csk(sk)->icsk_accept_queue.rskq_defer_accept = 1;
  20. inet_csk(newsk)->icsk_ulp_data = req->ulp_info;
  21. // ...
  22. }
  23. // ...
  24. return newsk;
  25. }

在上述代码中,tcp_v4_syn_recv_sock函数用于创建 SYN_RECV 状态的套接字。在创建套接字后,通过设置套接字的状态和其他相关参数来触发状态转换。这里使用了 inet_csk(sk)->icsk_accept_queue.rskq_defer_accept 标志来延迟接受连接,从而将套接字状态从 SYN_SENT 转换为 SYN_RECV。

四、send在TCP/IP协议栈中的执行路径

send 函数用于将数据发送到 TCP/IP 协议栈中的 TCP 套接字

  • 用户层调用 send 函数:
  1. // 文件:net/socket.c
  2. // 函数:sock_sendmsg
  3. // 位置:约在 3500 行附近
  4. ssize_t sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
  5. {
  6. // ...
  7. // 调用协议族的 sendmsg 函数
  8. ssize_t ret = sock->ops->sendmsg(sock, msg, len);
  9. // ...
  10. return ret;
  11. }

sock_sendmsg 函数是用户层调用 send 函数的入口。它将调用协议族的 sendmsg 函数来处理数据的发送。

  • 调用 TCP 协议族的 sendmsg 函数:
  1. // 文件:net/ipv4/tcp.c
  2. // 函数:tcp_sendmsg
  3. // 位置:约在 8000 行附近
  4. ssize_t tcp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
  5. {
  6. // ...
  7. // 将用户层的数据复制到发送缓冲区
  8. ret = memcpy_from_msg(data, msg, size);
  9. // ...
  10. // 调用核心的发送函数
  11. ret = tcp_sendmsg_locked(sk, msg, size);
  12. // ...
  13. return ret;
  14. }

tcp_sendmsg 函数被调用来处理 TCP 协议族的数据发送。它首先将用户层的数据复制到发送缓冲区,然后调用核心的发送函数 tcp_sendmsg_locked 进行实际的发送操作。

  • 核心的发送函数 tcp_sendmsg_locked
  1. // 文件:net/ipv4/tcp_output.c
  2. // 函数:tcp_sendmsg_locked
  3. // 位置:约在 1200 行附近
  4. ssize_t tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
  5. {
  6. // ...
  7. // 创建发送的 sk_buff
  8. skb = sock_alloc_send_skb(sk, size, msg->msg_flags & MSG_DONTWAIT, &err);
  9. if (!skb) {
  10. // 处理内存不足的情况
  11. goto failure;
  12. }
  13. // ...
  14. // 将用户层的数据复制到 sk_buff
  15. ret = memcpy_from_msg(skb_put(skb, size), msg, size);
  16. // ...
  17. // 调用 TCP/IP 协议栈的数据发送函数
  18. ret = tcp_transmit_skb(sk, skb, msg->msg_flags);
  19. // ...
  20. return ret;
  21. }

tcp_sendmsg_locked 函数首先通过调用 sock_alloc_send_skb 函数创建一个发送的 sk_buff 结构体,然后将用户层的数据复制到 sk_buff 中。最后,它调用 TCP/IP 协议栈的数据发送函数 tcp_transmit_skb 来实际发送数据。

五、recv在TCP/IP协议栈中的执行路径

recv 函数用于从 TCP/IP 协议栈中的 TCP 套接字接收数据。

  • 用户层调用 recv 函数
  1. // 文件:net/socket.c
  2. // 函数:sock_recvmsg
  3. // 位置:约在 4000 行附近
  4. ssize_t sock_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
  5. int flags)
  6. {
  7. // ...
  8. // 调用协议族的 recvmsg 函数
  9. ssize_t ret = sock->ops->recvmsg(sock, msg, size, flags);
  10. // ...
  11. return ret;
  12. }

sock_recvmsg 函数是用户层调用 recv 函数的入口。它将调用协议族的 recvmsg 函数来处理数据的接收。

调用 TCP 协议族的 recvmsg 函数:

  1. // 文件:net/ipv4/tcp.c
  2. // 函数:tcp_recvmsg
  3. // 位置:约在 10000 行附近
  4. ssize_t tcp_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
  5. int flags)
  6. {
  7. // ...
  8. // 调用核心的接收函数
  9. ret = tcp_recvmsg_locked(sk, msg, size, flags);
  10. // ...
  11. return ret;
  12. }

tcp_recvmsg 函数被调用来处理 TCP 协议族的数据接收。它调用核心的接收函数 tcp_recvmsg_locked 进行实际的接收操作。

  • 核心的接收函数 tcp_recvmsg_locked
  1. / 文件:net/ipv4/tcp.c
  2. // 函数:tcp_recvmsg_locked
  3. // 位置:约在 9800 行附近
  4. ssize_t tcp_recvmsg_locked(struct sock *sk, struct msghdr *msg, size_t size,
  5. int flags)
  6. {
  7. // ...
  8. // 从套接字接收数据
  9. copied = tcp_read_sock(sk, msg, size, flags);
  10. // ...
  11. return copied;
  12. }

tcp_recvmsg_locked 函数通过调用 tcp_read_sock 函数从套接字接收数据。

  • 调用 tcp_read_sock 函数:
  1. // 文件:net/ipv4/tcp.c
  2. // 函数:tcp_read_sock
  3. // 位置:约在 9100 行附近
  4. static int tcp_read_sock(struct sock *sk, struct msghdr *msg, int len,
  5. int nonblock)
  6. {
  7. // ...
  8. // 从接收缓冲区读取数据
  9. copied = skb_copy_datagram_msg(skb, 0, msg, len);
  10. // ...
  11. return copied;
  12. }

tcp_read_sock 函数通过调用 skb_copy_datagram_msg 函数从接收缓冲区读取数据,并将数据复制到 msg 结构体中。

六、路由表的结构和初始化过程

在 Linux 内核中,路由表用于确定将网络数据包从源地址传输到目标地址的路径。

  • 路由表的结构:
  1. // 文件:include/net/route.h
  2. // 位置:约在 200 行附近
  3. struct rtable {
  4. // ...
  5. struct net_device *dst.dev; // 目标设备
  6. __be32 rt_dst; // 目标 IP 地址
  7. __be32 rt_gateway; // 网关 IP 地址
  8. unsigned int rt_pmtu; // 最大传输单元
  9. // ...
  10. struct fib_nh_common nhc;
  11. // ...
  12. };

rtable 结构体表示路由表的一条路由项。其中包含了目标设备、目标 IP 地址、网关 IP 地址、最大传输单元等信息。

  • 路由表的初始化过程:

  1. // 文件:net/ipv4/route.c
  2. // 函数:ip_route_input_noref
  3. // 位置:约在 1000 行附近
  4. struct rtable *ip_route_input_noref(struct net *net, __be32 daddr,
  5. __be32 saddr, u8 tos, struct net_device *dev)
  6. {
  7. struct rtable *rt;
  8. // ...
  9. // 查找匹配的路由项
  10. rt = __ip_route_output_key(net, daddr, saddr, tos, dev);
  11. if (IS_ERR(rt)) {
  12. // 处理无法找到匹配路由项的情况
  13. return NULL;
  14. }
  15. // ...
  16. return rt;
  17. }

在上述代码中,ip_route_input_noref 函数用于在输入路径上查找匹配的路由项。它调用 __ip_route_output_key 函数来查找路由表中与目标 IP 地址最匹配的路由项。

  • 查找路由项的具体实现:
  1. // 文件:net/ipv4/route.c
  2. // 函数:__ip_route_output_key
  3. // 位置:约在 2000 行附近
  4. struct rtable *__ip_route_output_key(struct net *net, __be32 daddr,
  5. __be32 saddr, u8 tos, struct net_device *dev)
  6. {
  7. struct rtable *rt;
  8. // ...
  9. // 遍历路由缓存进行查找
  10. rt = ip_route_cache_lookup(net, daddr, saddr, tos, dev);
  11. if (rt) {
  12. // 找到匹配的路由项
  13. return rt;
  14. }
  15. // ...
  16. // 如果缓存中没有找到匹配项,则调用路由查找函数
  17. rt = ip_route_output_key_hash(net, daddr, saddr, tos, dev, 0, 0);
  18. // ...
  19. return rt;
  20. }

在上述代码中,__ip_route_output_key 函数首先尝试从路由缓存中查找匹配的路由项。如果路由缓存中没有找到匹配项,则调用 ip_route_output_key_hash 函数进行实际的路由查找。

七、通过目的IP查询路由表的到下一跳的IP地址的过程

通过目的 IP 查询路由表获取下一跳 IP 地址的过程,可以通过以下步骤完成:

  1. 获取目的 IP 地址。

  2. 遍历路由表的条目,逐个比较目的 IP 地址与路由表中的目标地址。

  3. 对于每个路由表条目,检查是否匹配目的 IP 地址。匹配通常涉及子网掩码和最长前缀匹配。

  4. 如果匹配成功,获取路由表条目中的下一跳 IP 地址。

  1. // 文件:net/ipv4/route.c
  2. // 函数:ip_route_output_key_hash
  3. // 位置:约在 2400 行附近
  4. struct rtable *ip_route_output_key_hash(struct net *net, __be32 daddr,
  5. __be32 saddr, u8 tos,
  6. struct net_device *dev,
  7. int oif, int hash)
  8. {
  9. // ...
  10. struct fib_result res;
  11. // 查询路由表
  12. fib_lookup(net, &fl, &res);
  13. // 检查是否匹配目的 IP 地址
  14. if (!res.fi) {
  15. // 没有匹配的路由项
  16. return NULL;
  17. }
  18. // 获取下一跳 IP 地址
  19. if (res.type == RTN_UNICAST) {
  20. // 单播路由
  21. struct fib_nh *nh = fib_info_nh(res.fi, res.table);
  22. next_hop = nh->nh_gw;
  23. } else {
  24. // 非单播路由,如多播、广播等
  25. // 处理方法根据需要进行调整
  26. // ...
  27. }
  28. // ...
  29. return rt;
  30. }

在上述代码中,ip_route_output_key_hash 函数调用了 fib_lookup 函数查询路由表,并根据查询结果判断是否匹配到目的 IP 地址。如果匹配成功,根据路由类型获取下一跳 IP 地址。

八、ARP缓存的数据结构及初始化过程,包括ARP缓存的初始化

ARP(Address Resolution Protocol)缓存用于存储IP地址与MAC地址之间的映射关系,以便在网络通信中进行地址解析。下面是一个简化的示例源代码,展示了ARP缓存的数据结构及初始化过程。

  • ARP缓存的数据结构:
  1. // 文件:include/net/arp.h
  2. // 位置:约在 100 行附近
  3. struct neighbour {
  4. // ...
  5. struct hh_cache hh;
  6. // ...
  7. u8 nud_state;
  8. // ...
  9. u8 ha[HARDWARE_ADDRESS_LEN]; // MAC地址
  10. // ...
  11. __be32 primary_key; // IP地址
  12. // ...
  13. struct timer_list timer;
  14. // ...
  15. };

在上述代码中,neighbour 结构体表示ARP缓存的一项。其中包含了MAC地址、IP地址等信息。

  • ARP缓存的初始化过程:
  1. // 文件:net/ipv4/arp.c
  2. // 函数:arp_init
  3. // 位置:约在 200 行附近
  4. void arp_init(void)
  5. {
  6. // ...
  7. // 创建ARP缓存
  8. arp_tbl = neigh_table_init(&arp_tbl_parm);
  9. if (!arp_tbl) {
  10. printk(KERN_INFO "ARP: Failed to allocate memory for ARP table\n");
  11. return;
  12. }
  13. // ...
  14. return;
  15. }

在上述代码中,arp_init 函数用于初始化ARP缓存。它调用 neigh_table_init 函数创建ARP缓存,并将参数传递给该函数进行初始化。

九、如何将IP地址解析出对应的MAC地址

在Linux内核中,将IP地址解析为对应的MAC地址主要通过ARP(Address Resolution Protocol)实现。大致的执行过程如下:

  • 用户空间调用套接字API发送一个IP数据包。

相关函数:sendto()connect()等。

相关文件:net/socket.cnet/ipv4/raw.c等。

  • 数据包到达网络层,内核检查目标IP地址是否在本地ARP缓存中,ARP缓存中存储了IP地址和对应的MAC地址。

相关数据结构:struct neighbourstruct neighbour_table等。

相关函数:neigh_lookup()等。

相关文件:net/core/neighbour.cinclude/net/neighbour.h等。

  • 如果目标IP地址不在本地ARP缓存中,内核创建并发送一个ARP请求消息。

相关函数:arp_create()arp_send()等。

相关文件:net/ipv4/arp.c等。

  • ARP请求消息封装到数据链路层帧中,通过网络接口发送出去。

相关函数:neigh_xmit()等。

相关文件:net/core/neighbour.cnet/ipv4/devinet.c等。

  • 目标主机收到ARP请求帧后,根据请求中的IP地址判断是否为自己的IP地址,并构建一个ARP响应消息发送回请求方。

相关函数:arp_rcv()等。

相关文件:net/ipv4/arp.c等。

  • 请求方收到ARP响应帧,提取MAC地址并将其存储到本地ARP缓存中。

相关函数:neigh_update()等。

相关文件:net/core/neighbour.cinclude/net/neighbour.h等。

  • 请求方将数据包发送到目标MAC地址。

相关函数:dev_queue_xmit()等。

相关文件:net/core/dev.c等。

十、跟踪TCP send过程中的路由查询和ARP解析的最底层实现

  • send过程中的路由查询 

这件事情主要由 ip_route_output_flow 函数完成,调用链 为:ip_route_output_flow->__ip_route_output_key->ip_route_output_key_hash->ip_route_output_key_hash_rcu

ip_route_output_key_hash_rcu 是 Linux 内核中用于根据给定的散列键值进行路由查找和输出的函数。它是 IPv4 路由子系统的一部分,用于确定给定目标 IP 地址的下一跳。

  1. struct rtable *ip_route_output_key_hash_rcu(struct net *net, struct flowi4 *fl4, struct fib_result *res, const struct sk_buff *skb)
  2. {
  3. // ...
  4. err = fib_lookup(net, fl4, res, 0);
  5. // ...
  6. make_route:
  7. rth = __mkroute_output(res, fl4, orig_oif, dev_out, flags);
  8. // ...
  9. }

fib_lookup 是 Linux 内核中用于进行 FIB(Forwarding Information Base,转发信息库)查找的函数之一。它用于根据给定的目标地址查找匹配的路由表项。

  1. static inline int fib_lookup(struct net *net, const struct flowi4 *flp, struct fib_result *res, unsigned int flags)
  2. { struct fib_table *tb;
  3. // ...
  4. tb = fib_get_table(net, RT_TABLE_MAIN);
  5. if (tb)
  6. err = fib_table_lookup(tb, flp, res, flags | FIB_LOOKUP_NOREF);
  7. // ...
  8. }

  • ARP解析的最底层实现

__ipv4_neigh_lookup_noref 函数用于在本地 ARP 表中查找下一跳的 MAC 地址。该函数接收两个参数:dev 表示网络设备,dst 表示目标 IP 地址。它通过调用__neigh_lookup_noref 函数在 ARP 表中查找与目标 IP 地址匹配的邻居项(neighbour entry)。它接收邻居表 tbl、目标 IP 地址 pkey 和网络设备 dev 作为参数,返回一个指向邻居项结构体的指针。

若查找失败,则会调用 __neigh_create 函数。

  1. void create_neigh_entry(struct net_device *dev, __be32 dst_ip, unsigned char *dst_mac)
  2. {
  3. struct neighbour *neigh;
  4. // 创建邻居项
  5. neigh = __neigh_create(&arp_tbl, &dst_ip, dev, false);
  6. if (!neigh) {
  7. // 邻居项创建失败,处理错误情况
  8. return;
  9. }
  10. // 设置邻居项的 MAC 地址
  11. memcpy(neigh->ha, dst_mac, ETH_ALEN);
  12. // 将邻居项添加到邻居表
  13. neigh_add(neigh, false);
  14. }

它接收网络设备 dev、目标 IP 地址 dst_ip 和目标 MAC 地址 dst_mac 作为参数。函数首先调用 __neigh_create 函数创建一个新的邻居项,并指定邻居表 arp_tbl、目标 IP 地址和网络设备。然后,它将目标 MAC 地址拷贝到邻居项的 ha 字段中,并通过调用 neigh_add 函数将邻居项添加到邻居表中。

在 __neigh_create 之后,会调用 neigh_output 发送网络包。neigh_output调用neigh_resolve_output。neigh_resolve_output 函数是用于发送网络包的关键函数之一,它用于解析目标 IP 地址的邻居项,并将网络包发送到相应的邻居。

neigh_resolve_output 函数的主要作用是将网络包发送给指定的邻居。它通常会执行以下步骤:

  1. 验证邻居项的状态:检查邻居项的状态,例如是否是有效的、是否已解析等。如果邻居项状态不满足发送条件,可能需要进行邻居探测或其他操作来解析邻居项。

  2. 设置网络包的目标 MAC 地址:从邻居项中获取目标 MAC 地址,并将其设置为网络包的目标 MAC 地址,以便正确路由和传递给下一跳。

  3. 发送网络包:将网络包发送到网络设备的发送队列,以便进行实际的数据传输。发送过程可能涉及网络设备驱动程序的调用,通过底层的网络协议栈将数据封装成数据帧并发送出去。

  1. int neigh_resolve_output(struct neighbour *neigh, struct sk_buff *skb) {
  2. if (neigh->nud_state != NUD_VALID) {
  3. // 邻居项状态不满足发送条件,可能需要进行邻居探测或其他操作
  4. // ...
  5. }
  6. // 设置网络包的目标 MAC 地址
  7. skb->mac_dest = neigh->ha;
  8. // 发送网络包
  9. int ret = dev_queue_xmit(skb);
  10. return ret;
  11. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/102221
推荐阅读
相关标签
  

闽ICP备14008679号