-
inet_init是如何被调用的?从start_kernel到inet_init调用路径
-
在 Linux 内核启动过程中,inet_init 是在网络子系统初始化的一部分,负责初始化 TCP/IP 协议栈。下面是从 start_kernel 到 inet_init 的调用路径:
- start_kernel:
- start_kernel() 是 Linux 内核启动的入口函数,位于 init/main.c 文件中。
-
asmlinkage void __init start_kernel(void) { /* ... 其他初始化工作 ... */ kernel_init(); /* ... */ }
- kernel_init:
-
kernel_init() 在 start_kernel 中被调用,它位于 init/main.c 文件中,负责完成内核的初始化
-
static noinline void __init kernel_init(void) { /* ... 其他初始化工作 ... */ /* 启动用户空间初始化进程,即 init 进程 */ kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); /* 初始化网络子系统 */ inet_init(); /* ... */ }
- inet_init:
-
inet_init() 是 TCP/IP 协议栈的初始化函数,它位于 net/ipv4/inet_init.c 文件中。在 initcall_sequence 中被调用。
-
void __init inet_init(void) { /* ... TCP/IP 协议栈的初始化工作 ... */ }
-
跟踪分析TCP/IP协议栈如何将自己与上层套接口与下层数据链路层关联起来的?
-
上层套接口与应用程序关联
在 Linux 中,套接口与应用程序关联主要通过
struct socket
结构体来实现。套接口的创建和与应用程序的关联通常在网络套接口相关的系统调用中完成,例如socket
、bind
、listen
等。在
net/socket.c
文件中,有一系列的系统调用实现,其中涉及到套接口的创建、绑定等操作。 -
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) { struct socket *sock; int err; /* 创建套接口 */ sock = sock_create(family, type, protocol, &err); if (!sock) return err; /* 在当前进程中关联套接口 */ err = sock_map_fd(sock, 0, 1); if (err < 0) { fput(sock->file); return err; } return err; }
在这段代码中,
sock_create
函数创建了一个套接口对象,并通过sock_map_fd
将该套接口与当前进程关联。 -
下层数据链路层关联
下层数据链路层与 TCP/IP 协议栈的关联通常是通过网络设备结构体
struct net_device
来完成的。每个网络设备都有一个net_device
结构体,它包含了一系列函数指针,这些函数指针指向设备特定的实现,包括发送数据包、接收数据包等操作。在
net/core/dev.c
文件中,有与网络设备相关的初始化和注册函数,其中的register_netdev
函数用于注册网络设备。 -
int register_netdev(struct net_device *dev) { /* ... 设备初始化等操作 ... */ /* 注册网络设备 */ ret = register_netdevice(dev); /* ... 其他操作 ... */ return ret; }
在这个过程中,网络设备被注册到全局的设备列表中,以供协议栈使用。网络设备的初始化通常在特定的设备驱动中完成,例如 Ethernet 设备的初始化在
drivers/net/ethernet
目录中的相应驱动文件中。 -
TCP的三次握手源代码跟踪分析,跟踪找出设置和发送SYN/ACK的位置,以及状态转换的位置
-
TCP 的三次握手是建立连接时的过程,涉及到 SYN、SYN/ACK 和 ACK 三个状态。
-
SYN 发送 (Client -> Server):
- 客户端通过 connect() 发送 SYN 报文。
- tcp_v4_connect() 函数调用 tcp_connect()。
- tcp_connect() 函数设置连接状态为 TCP_SYN_SENT。
- 最终调用 tcp_transmit_skb() 发送 SYN。
-
SYN/ACK 接收与发送 (Server -> Client):
- 服务器接收到 SYN,触发连接的创建。
- tcp_v4_rcv() 函数检查接收到的报文,调用 tcp_rcv_synsent_state_process()。
- tcp_rcv_synsent_state_process() 函数处理接收到的 SYN,发送 SYN/ACK。
- 这会触发服务器端的状态转换,从 TCP_SYN_SENT 到 TCP_SYN_RECV。
- tcp_v4_send_synack函数会在建立连接时,设置并发送 SYN/ACK 包
-
int tcp_v4_send_synack(struct sock *sk, struct request_sock *req) { /* ... 其他设置 ... */ /* 设置 SYN/ACK 包并发送 */ tcp_v4_send_synack_locked(sk, req, (struct dst_entry *)NULL); /* ... 其他操作 ... */ return 0; }
具体的 SYN/ACK 的设置和发送是在 tcp_v4_send_synack 函数中完成的。
-
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) { /* ... 其他初始化 ... */ /* 发送 SYN 包 */ err = tcp_v4_send_synack(sk, req); /* ... 其他操作 ... */ return err; }
-
ACK 接收 (Client -> Server):
- 客户端接收到 SYN/ACK,发送 ACK。
- tcp_v4_rcv() 函数检查接收到的报文,调用 tcp_rcv_synrecv_state_process()。
- tcp_rcv_synrecv_state_process() 函数处理接收到的 SYN/ACK,发送 ACK。
- 这会触发客户端的状态转换,从 TCP_SYN_SENT 到 TCP_ESTABLISHED。
-
状态转换是通过修改 TCP 协议控制块(TCP socket)的状态来实现的。在三次握手的过程中,状态转换主要涉及到
TCP_SYN_SENT
到TCP_ESTABLISHED
的转换。 -
int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, struct tcphdr *th, unsigned len) { /* ... 其他操作 ... */ /* 进行状态转换 */ if (tcp_hdr(skb)->syn && after(TCP_SKB_CB(skb)->end_seq, TCP_SKB_CB(skb)->seq)) { /* SYN/ACK 收到,状态转换到 TCP_ESTABLISHED */ tcp_set_state(sk, TCP_ESTABLISHED); tcp_initialize_rcv_mss(sk); sk->sk_backlog_rcv = tcp_v4_do_rcv; tcp_hashed_ack_update_rtt(sk, tcp_time_stamp); return 1; } /* ... 其他操作 ... */ return 0; }
在这段代码中,如果收到了 SYN/ACK 包,就会将状态转换为
TCP_ESTABLISHED
,表示连接已经建立。
-
send在TCP/IP协议栈中的执行路径
-
在 Linux 内核中,send 函数用于将数据发送到已建立的 TCP 连接。
-
用户空间调用:
- 应用程序在用户空间调用 send 函数,通常使用系统调用的形式。
-
系统调用处理:
- 系统调用处理程序将用户空间传递的参数传递给内核。
-
Socket 层:
- 在 Socket 层,
sys_sendto
函数最终会调用sock_sendmsg
函数,该函数位于net/socket.c
文件中。在这个函数中,会调用到协议族相关的sendmsg
函数,对于 TCP 协议,是inet_sendmsg
。 -
ssize_t sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) { /* ... 其他操作 ... */ /* 调用协议族相关的 sendmsg 函数,对于 TCP,是 inet_sendmsg */ return sock->ops->sendmsg(sock, msg, size); } EXPORT_SYMBOL(sock_sendmsg);
- 在 Socket 层,
-
TCP 协议层:
- 在 TCP 协议层,
inet_sendmsg
函数最终会调用到tcp_sendmsg
函数,该函数定义在net/ipv4/tcp.c
文件中。 -
int inet_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) { /* ... 其他操作 ... */ /* 调用具体协议的 sendmsg 函数,对于 TCP,是 tcp_sendmsg */ return sk->sk_prot->sendmsg(sock, msg, size); } EXPORT_SYMBOL(inet_sendmsg);
-
TCP
sendmsg
: 在tcp_sendmsg
函数中,最终调用到tcp_sendmsg_locked
函数,实现了具体的发送逻辑。这个函数位于net/ipv4/tcp.c
文件中。
- 在 TCP 协议层,
-
recv在TCP/IP协议栈中的执行路径
-
用户空间调用:
- 应用程序在用户空间调用 recv 函数,例如 recvfrom 或 recvmsg。
-
系统调用处理:
- 用户空间的系统调用进入内核空间,调用
sys_recvfrom
或sys_recv
等系统调用函数。
- 用户空间的系统调用进入内核空间,调用
-
Socket 层:
- 在 Socket 层,
sys_recvfrom
函数最终会调用sock_recvmsg
函数,该函数位于net/socket.c
文件中。在这个函数中,会调用到协议族相关的recvmsg
函数,对于 TCP 协议,是inet_recvmsg
。 -
ssize_t sock_recvmsg(struct socket *sock, struct msghdr *msg, size_t size, int flags) { /* ... 其他操作 ... */ /* 调用协议族相关的 recvmsg 函数,对于 TCP,是 inet_recvmsg */ return sock->ops->recvmsg(sock, msg, size, flags); } EXPORT_SYMBOL(sock_recvmsg);
- 在 Socket 层,
-
TCP 协议层:
- 在 TCP 协议层,
inet_recvmsg
函数最终会调用到tcp_recvmsg
函数,该函数定义在net/ipv4/tcp.c
文件中。 -
int inet_recvmsg(struct socket *sock, struct msghdr *msg, size_t size, int flags) { /* ... 其他操作 ... */ /* 调用具体协议的 recvmsg 函数,对于 TCP,是 tcp_recvmsg */ return sk->sk_prot->recvmsg(sock, msg, size, flags); } EXPORT_SYMBOL(inet_recvmsg);
-
在
tcp_recvmsg
函数中,最终调用到tcp_recvmsg_locked
函数,实现了具体的接收逻辑。这个函数位于net/ipv4/tcp.c
文件中。
- 在 TCP 协议层,
-
路由表的结构和初始化过程
-
Linux 内核中的路由表结构主要由
struct rtable
表示,而初始化过程则涉及到整个网络子系统的初始化,特别是inet_init
函数。 - 路由表结构:
-
struct rtable
的定义位于include/net/ip.h
文件中。简要地说,这个结构体用于表示一个路由表项。 -
struct rtable { struct dst_entry dst; __be32 rt_src; __be32 rt_dst; __be32 rt_gateway; unsigned int rt_flags; struct rtable __rcu *u; };
-
dst
字段是目的地的通用结构。 -
rt_src
是源地址。 -
rt_dst
是目的地址。 -
rt_gateway
是网关地址。 -
rt_flags
是标志位,表示该路由表项的一些属性。
-
- 初始化过程:
- 路由表的初始化过程涉及到整个网络子系统的初始化,其中一个关键函数是
inet_init
。 -
static int __net_init inet_init_net(struct net *net) { int ret; /* ... 其他初始化 ... */ /* 初始化路由缓存 */ ret = ip_rt_init_net(net); if (ret < 0) goto out; /* ... 其他初始化 ... */ return 0; out: /* ... 处理初始化失败 ... */ return ret; } static int __net_init inet_init(struct net *net) { int ret; /* ... 其他初始化 ... */ /* 初始化 IPv4 子系统 */ ret = inet_init_net(net); if (ret < 0) goto out; /* ... 其他初始化 ... */ return 0; out: /* ... 处理初始化失败 ... */ return ret; } static __net_init int inet_net_init(struct net *net) { int err; /* ... 其他初始化 ... */ /* 初始化 IPv4 */ err = inet_init(net); if (err) goto out; /* ... 其他初始化 ... */ out: /* ... 处理初始化失败 ... */ return err; }
-
在这些初始化过程中,
ip_rt_init_net
函数用于初始化路由缓存。这个函数的定义位于net/ipv4/route.c
文件中。 -
int __net_init ip_rt_init_net(struct net *net) { int err; err = fib4_rules_init(net); if (err < 0) goto cleanup_rules; err = fib4_init_notifier(net); if (err) goto cleanup_notifier; err = fib4_rules_seq_init_net(net); if (err < 0) goto cleanup_rules_seq; return 0; cleanup_rules_seq: fib4_cleanup_rules_seq_net(net); cleanup_notifier: fib4_cleanup_notifier_net(net); cleanup_rules: fib4_cleanup_rules_net(net); return err; }
-
ip_rt_init_net
中会初始化 IPv4 路由规则,注册 notifier,以及初始化相关的数据结构。
- 路由表的初始化过程涉及到整个网络子系统的初始化,其中一个关键函数是
-
通过目的IP查询路由表的到下一跳的IP地址的过程
-
Linux 内核中通过目的 IP 查询路由表的过程主要涉及到
ip_route_input_noref
函数。 -
struct dst_entry *ip_route_input_noref(struct sk_buff *skb, __be32 daddr, __be32 saddr, u8 tos, struct net_device *dev) { struct flowi4 fl4 = { .daddr = daddr, .saddr = saddr, .tos = tos, .proto = skb->protocol, .oif = dev ? dev->ifindex : 0, }; struct dst_entry *dst; skb_dst_set(skb, NULL); /* 这里会调用 ip_route_output_key,根据传入的参数查找路由表 */ dst = ip_route_output_key(&init_net, &fl4); if (IS_ERR(dst)) { /* ... 处理错误 ... */ return dst; } /* ... 其他处理 ... */ return dst; } EXPORT_SYMBOL(ip_route_input_noref);
-
在这个函数中,
ip_route_output_key
函数会根据传入的参数构造一个路由查找的关键信息(struct flowi4
结构体),然后查找路由表,返回对应的路由表项(struct dst_entry
结构体)。 -
struct dst_entry *ip_route_output_key(struct net *net, struct flowi4 *fl4) { /* ... 其他处理 ... */ /* 调用 fib_lookup,进行路由查找 */ res = fib_lookup(net, fl4, &res); if (res < 0) goto e_nexthop; /* ... 其他处理 ... */ return dst; e_nexthop: /* ... 处理路由查找失败 ... */ return ERR_PTR(res); } EXPORT_SYMBOL(ip_route_output_key);
-
在
fib_lookup
函数中,会根据传入的struct flowi4
结构体进行路由查找,找到对应的下一跳信息。在查找成功后,会得到一个指向路由表项的指针(struct dst_entry
结构体)。 -
ARP缓存的数据结构及初始化过程,包括ARP缓存的初始化
-
在 Linux 内核中,ARP(Address Resolution Protocol)缓存使用 neigh_table 结构体来表示。
ARP 缓存数据结构:
- ARP 缓存的数据结构主要涉及到
neigh_table
、neigh_parms
和neigh_entry
:-
neigh_table
结构体表示 ARP 缓存表: -
struct neigh_table { /* ... 其他成员 ... */ struct neigh_parms parms; /* Parameters to use with that NDA */ struct neigh_parms *proxy_redir; struct neigh_parms *parms_devdown; /* ... 其他成员 ... */ struct neigh_table_ops *ops; /* ... 其他成员 ... */ atomic_t entries; /* ... 其他成员 ... */ struct neigh_hash_table *nht; /* ... 其他成员 ... */ };
-
neigh_parms
结构体表示 ARP 缓存表的参数: -
struct neigh_parms { /* ... 其他成员 ... */ atomic_t refcnt; /* Number of references to this neigh_parms */ /* ... 其他成员 ... */ rwlock_t lock; /* Lock for the neigh_parms */ /* ... 其他成员 ... */ };
-
neigh_entry
结构体表示 ARP 缓存表中的一个条目: -
struct neigh_entry { /* ... 其他成员 ... */ struct rcu_head rcu; /* ... 其他成员 ... */ struct neigh_table *tbl; /* ... 其他成员 ... */ struct neigh_parms *parms; /* ... 其他成员 ... */ __u8 ha[32]; /* 保存硬件地址的数组 */ /* ... 其他成员 ... */ };
-
-
ARP 缓存初始化过程:
-
static void arp_tbl_setup(struct net *net, struct neigh_table *tbl, struct neigh_parms *parms, struct neigh_parms *template, struct neigh_parms *nd_tbl_parms) { /* ... 其他初始化 ... */ tbl->parms = *parms; tbl->parms_devdown = nd_tbl_parms; tbl->proxy_redir = template; /* ... 其他初始化 ... */ rcu_assign_pointer(net->nd_tbl, tbl); } EXPORT_SYMBOL(arp_tbl_setup); struct neigh_table *neigh_table_init(struct net *net, const struct neigh_table *tmpl) { struct neigh_table *tbl; struct neigh_parms *parms, *devdown, *template; /* ... 参数初始化 ... */ tbl = kmemdup(tmpl, sizeof(struct neigh_table), GFP_KERNEL); if (!tbl) goto out; /* ... 参数初始化 ... */ tbl->nht = neigh_hash_alloc(tbl->parms.gc_args.hash_rnd, tbl->parms.gc_args.hash_mask, GFP_KERNEL); if (!tbl->nht) goto free_table; /* ... 初始化其他数据结构 ... */ tbl->ops = &tmpl->ops; /* ... 初始化其他数据结构 ... */ rcu_assign_pointer(net->nd_tbl, tbl); /* ... 初始化其他数据结构 ... */ return tbl; free_table: kfree(tbl); out: return NULL; } EXPORT_SYMBOL(neigh_table_init);
- 在这个初始化过程中,首先使用
kmemdup
复制一个模板的neigh_table
结构体,并为其分配一个新的哈希表。然后,将模板的参数赋值给新的neigh_table
。最后,使用rcu_assign_pointer
将新的neigh_table
赋值给网络命名空间的nd_tbl
字段。
-
-
如何将IP地址解析出对应的MAC地址
-
在 Linux 内核中,IP地址解析出对应的 MAC 地址是通过 ARP(Address Resolution Protocol)来实现的。以下是关键的源码片段,展示了 ARP 是如何进行 IP 地址到 MAC 地址的解析的:
-
ARP 请求:
-
int arp_create(int type, struct net_device *dev, __be32 sip, __be32 tip, const unsigned char *dmac, const unsigned char *smac) { struct sk_buff *skb; struct arphdr *arp; struct arpreq *r; struct rtable *rt; int len, err; /* ... 其他初始化 ... */ skb = arp_create_skb(dev, type, sip, tip, dmac, smac, NULL, &rt); if (!skb) goto out; /* ... 其他处理 ... */ if (type == ARPOP_REQUEST) { /* ... 设置 ARP 请求特有的字段 ... */ } else { /* ... 设置 ARP 应答特有的字段 ... */ } /* ... 其他处理 ... */ err = arp_xmit(skb, rt, type, sip, tip, dev, dmac, smac); /* ... 其他处理 ... */ out: return err; } EXPORT_SYMBOL(arp_create);
-
在这个函数中,
arp_create_skb
函数创建了一个 ARP 请求或应答的 Socket Buffer(sk_buff
)。接着,根据 ARP 的类型,设置了不同的 ARP 消息字段,然后调用arp_xmit
函数发送 ARP 消息。
-
-
ARP消息发送:
-
int arp_xmit(struct sk_buff *skb, struct rtable *rt, int type, __be32 sip, __be32 tip, struct net_device *dev, const unsigned char *dmac, const unsigned char *smac) { struct neighbour *neigh; struct net_device *real_dev = NULL; int err = -EINVAL; /* ... 其他初始化 ... */ if (!neigh) neigh = __ipv4_neigh_lookup_noref(rt->dst.dev, tip); /* ... 其他处理 ... */ if (neigh) neigh_ha_snapshot(neigh, dmac); /* ... 其他处理 ... */ if (dev) skb->dev = dev; /* ... 其他处理 ... */ if (skb_dst(skb)) { /* ... 其他处理 ... */ } /* ... 其他处理 ... */ /* 设置数据链路层头部信息,发送数据包 */ err = dev_queue_xmit(skb); /* ... 其他处理 ... */ out: return err; } EXPORT_SYMBOL(arp_xmit);
- 在这个函数中,首先尝试查找与目标 IP 地址关联的邻居(
neighbour
),如果找到了,就更新邻居的硬件地址。接着,设置 Socket Buffer 的设备字段,并调用dev_queue_xmit
函数将数据包发送出去。
-
-
跟踪TCP send过程中的路由查询和ARP解析的最底层实现
-
在 Linux 内核中,TCP/IP 协议栈的 send 过程涉及到路由查询和 ARP 解析。
TCP Send 过程:
-
TCP Send过程中的路由查询:
-
int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask) { struct inet_sock *inet = inet_sk(sk); struct rtable *rt; int ret = -EINVAL; /* ... 其他初始化 ... */ if (likely(!inet->opt)) inet->opt = ip_options_compile(sock_net(sk), sk, skb, NULL); rt = ip_route_output_ports(sock_net(sk), inet->opt, &fl4, sk); if (IS_ERR(rt)) { /* ... 处理路由查询失败的情况 ... */ goto out; } /* ... 其他处理 ... */ if (clone_it) skb = skb_clone(skb, gfp_mask); /* ... 其他处理 ... */ /* 在 skb 中设置路由信息 */ skb_dst_set(skb, &rt->dst); /* ... 其他处理 ... */ /* TCP 进一步处理发送 skb 的过程 */ ret = tcp_queue_skb(sk, skb, &fl4, 0); out: return ret; } EXPORT_SYMBOL(tcp_transmit_skb);
-
在这个函数中,
ip_route_output_ports
函数执行了路由查询,返回一个表示目标地址的路由缓存项(rtable
)。然后,将该路由信息设置到 Socket Buffer(skb
)中。文章来源:https://www.toymoban.com/news/detail-772386.html
-
-
TCP Send过程中的ARP解析:
-
int arp_create(int type, struct net_device *dev, __be32 sip, __be32 tip, const unsigned char *dmac, const unsigned char *smac) { struct sk_buff *skb; struct arphdr *arp; struct rtable *rt; int len, err; /* ... 其他初始化 ... */ skb = arp_create_skb(dev, type, sip, tip, dmac, smac, NULL, &rt); if (!skb) goto out; /* ... 其他处理 ... */ err = arp_xmit(skb, rt, type, sip, tip, dev, dmac, smac); /* ... 其他处理 ... */ out: return err; } EXPORT_SYMBOL(arp_create);
-
在这个函数中,
arp_xmit
函数负责发送 ARP 请求。在路由查询之后,通过arp_xmit
将 ARP 请求发送到目标 IP 地址,获取其对应的 MAC 地址。如果目标 IP 地址的硬件地址已经在 ARP 缓存中,arp_xmit
会直接使用缓存中的 MAC 地址。文章来源地址https://www.toymoban.com/news/detail-772386.html
-
到了这里,关于TCP/IP协议栈源代码分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!