TCP/IP协议栈源代码分析结论:
1. inet_init是如何被调用的?从start_kernel到inet_init调用路径
inet_init代码如下:
static int __init inet_init(void)
{
struct sk_buff *dummy_skb;
struct inet_protocol *p;
struct inet_protosw *q;
struct list_head *r;
printk(KERN_INFO "NET4: Linux TCP/IP 1.0 for NET4.0/n");
if (sizeof(struct inet_skb_parm) > sizeof(dummy_skb->cb)) {
printk(KERN_CRIT "inet_proto_init: panic/n");
return -EINVAL;
}
/*
* Tell SOCKET that we are alive... 注册socket,告诉socket inet类型的地址族已经准备好了
*/
(void) sock_register(&inet_family_ops);
/*
* Add all the protocols. 包括arp,ip、ICMP、UPD、tcp_v4、tcp、igmp的初始化,主要初始化各种协议对应的inode和socket变量。
其中arp_init完成系统中路由部分neighbour表的初始化
ip_init完成ip协议的初始化。在这两个函数中,都通过定义一个packet_type结构的变量将这种数据包对应的协议发送数据、允许发送设备都做初始化。
*/
printk(KERN_INFO "IP Protocols: ");
for (p = inet_protocol_base; p != NULL;) {
struct inet_protocol *tmp = (struct inet_protocol *) p->next;
inet_add_protocol(p);
printk("%s%s",p->name,tmp?", ":"/n");
p = tmp;
}
/* Register the socket-side information for inet_create. */
for(r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
INIT_LIST_HEAD(r);
for(q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
inet_register_protosw(q);
/*
* Set the ARP module up
*/
arp_init();
/*
* Set the IP module up
*/
ip_init();
tcp_v4_init(&inet_family_ops);
/* Setup TCP slab cache for open requests. */
tcp_init();
/*
* Set the ICMP layer up
*/
icmp_init(&inet_family_ops);
/* I wish inet_add_protocol had no constructor hook...
I had to move IPIP from net/ipv4/protocol.c :-( --ANK
*/
#ifdef CONFIG_NET_IPIP
ipip_init();
#endif
#ifdef CONFIG_NET_IPGRE
ipgre_init();
#endif
/*
* Initialise the multicast router
*/
#if defined(CONFIG_IP_MROUTE)
ip_mr_init();
#endif
/*
* Create all the /proc entries.
*/
#ifdef CONFIG_PROC_FS
proc_net_create ("raw", 0, raw_get_info);
proc_net_create ("netstat", 0, netstat_get_info);
proc_net_create ("snmp", 0, snmp_get_info);
proc_net_create ("sockstat", 0, afinet_get_info);
proc_net_create ("tcp", 0, tcp_get_info);
proc_net_create ("udp", 0, udp_get_info);
#endif /* CONFIG_PROC_FS */
ipfrag_init();
return 0;
}
module_init(inet_init);
调用inet_init的过程中,涉及到的函数如下:
1. start_kernel:
-
start_kernel
是Linux内核的启动函数,定义在init/main.c
文件中。 - 在启动过程中,首先执行
start_kernel
,这个函数负责进行内核的初始化工作。
2. rest_init:
- 在
start_kernel
中,会调用rest_init
函数,该函数的主要作用是创建一个内核线程,并让这个线程执行kernel_init
函数。
3. kernel_init:
-
kernel_init
函数位于init/main.c
文件中,主要负责完成内核的初始化。 - 在
kernel_init
中,会调用do_basic_setup
函数进行基本的系统设置。
4. do_basic_setup:
-
do_basic_setup
函数中包含一系列基本的系统设置,最后调用kernel_init_freeable
函数。
5. kernel_init_freeable:
- 在
kernel_init_freeable
函数中,会调用kernel_init_freeable
宏,其中包括了一系列的初始化函数,负责初始化各个子系统。 - 其中会调用
init_post
函数,该函数位于kernel/init/main.c
文件中。
6. init_post:
-
init_post
函数中,会调用initcall_init
函数,该函数位于init/main.c
文件中。
-initcall_init
函数负责执行所有的初始化回调函数,这些回调函数在编译阶段被链接到.initcall.init
节中。
7. do_initcalls:
- 在
initcall_init
函数中,会调用do_initcalls
函数,该函数位于init/main.c
文件中。 -
do_initcalls
函数会依次执行.initcall.init
节中的所有初始化回调函数。
8. inet_init:
- 在执行所有的初始化回调函数后,
do_initcalls
函数中会调用do_basic_setup
函数,其中包括了对网络子系统的初始化。 - 最终,
do_basic_setup
函数会调用inet_init
函数,该函数位于net/ipv4/af_inet.c
文件中。 -
inet_init
函数负责初始化IPv4网络子系统。
2. 跟踪分析TCP/IP协议栈如何将自己与上层套接口与下层数据链路层关联起来的?
在Linux中,TCP/IP协议栈是在网络协议栈中的一部分,它负责处理网络通信的高层协议,如TCP和UDP。以下是对TCP/IP协议栈如何与上层套接口(Socket)和下层数据链路层关联的简要解释:
-
套接口(Socket):
- 用户空间的应用程序通过套接口(Socket)与TCP/IP协议栈进行交互。套接口提供了一组系统调用,应用程序通过这些调用实现与协议栈的通信。
-
协议族和套接口的创建:
- 在Linux中,协议栈通过协议族(AF_INET,AF_INET6等)和套接口(socket)的组合与上层应用程序建立联系。
- 当应用程序调用
socket
系统调用时,它指定了协议族(如AF_INET)和套接口类型(如SOCK_STREAM)。
-
创建套接口数据结构:
- 在协议栈内部,创建一个数据结构来表示套接口。这通常是一个结构体,包含与套接口相关的信息,如协议族、套接口类型、协议参数等。
-
协议栈初始化:
- 在协议栈初始化的过程中,会注册相应的协议处理函数。例如,TCP/IP协议栈会注册TCP和UDP的处理函数。
-
协议与套接口关联:
- 当应用程序通过
bind
系统调用将一个套接口绑定到一个特定的IP地址和端口上时,协议栈内部会将这个套接口与相应的协议(TCP或UDP)关联起来。 - 这时,套接口数据结构中的一些字段会被设置,如本地IP地址、端口等。
- 当应用程序通过
-
监听和连接建立:
- 对于TCP套接口,应用程序可能会调用
listen
系统调用,用于指示协议栈开始监听连接请求。当有新的连接请求到达时,协议栈会创建一个新的套接口数据结构来表示该连接。
- 对于TCP套接口,应用程序可能会调用
-
数据的上交和下发:
- 当应用程序通过套接口读取数据时,协议栈会调用相应协议的处理函数,将数据从协议栈上交给应用程序。
- 当应用程序通过套接口发送数据时,协议栈会将数据传递给相应的协议处理函数,然后通过网络接口向下传递,最终交给数据链路层处理。
-
数据链路层关联:
- 协议栈与下层数据链路层的关联通常是通过网络设备(如以太网接口)的注册和绑定来实现的。
- 协议栈通过网络设备的数据结构,将数据链路层的功能与协议栈关联在一起。
协议栈通过数据结构的关联和相应的处理函数,将套接口与上层应用程序和下层数据链路层连接起来。这样的设计允许协议栈处理网络通信的细节,同时提供简单的套接口供应用程序使用。
3. TCP的三次握手源代码跟踪分析,跟踪找出设置和发送SYN/ACK的位置,以及状态转换的位置
在Linux内核中,TCP的三次握手过程的源代码涉及多个文件和函数。以下是对TCP三次握手过程的源代码分析,主要关注设置和发送SYN/ACK的位置以及状态转换的位置。
1. TCP的三次握手流程
三次握手的过程包括以下步骤:
-
客户端发送SYN:
- 客户端向服务器发送一个SYN(同步)报文,请求建立连接。
-
服务器回应SYN/ACK:
- 服务器收到客户端的SYN后,回应一个SYN/ACK(同步/确认)报文,表示接受连接。
-
客户端发送ACK:
- 客户端收到服务器的SYN/ACK后,发送一个ACK(确认)报文,完成连接的建立。
2. 源代码分析
2.1. 设置和发送SYN/ACK
在Linux内核中,设置和发送SYN/ACK主要发生在服务器接收到客户端的SYN报文时。以下是相关源代码:
-
文件:
net/ipv4/tcp_ipv4.c
-
函数:
tcp_v4_syn_recv_sock
// net/ipv4/tcp_ipv4.c
static int tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
struct request_sock *req)
{
// ... 省略部分代码
/* Generate an ISN. The client will only send data when
* ACKing the ISN + 1.
*/
tcp_initialize_rcv_mss(sk);
/* Send out a synack here. */
tcp_send_synack(sk, req, req->rsk_rcv_wnd,
req->ts_recent,
req->ts_recent_stamp);
// ... 省略部分代码
/* Move to established and bump the window up. */
tcp_set_state(sk, TCP_ESTABLISHED);
tcp_initialize_rcv_mss(sk);
tcp_ecn_rcv_synack(sk, skb);
// ... 省略部分代码
return 0;
}
在 tcp_send_synack
函数中,会设置并发送SYN/ACK报文。
2.2. 状态转换
状态转换主要发生在服务器收到客户端的ACK报文后。以下是相关源代码:
-
文件:
net/ipv4/tcp_input.c
-
函数:
tcp_rcv_synsent_state_process
// net/ipv4/tcp_input.c
int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
struct tcphdr *th, unsigned len)
{
// ... 省略部分代码
if (TCP_SKB_CB(skb)->seq != TCP_SKB_CB(skb)->end_seq) {
// ... 省略部分代码
} else {
/* SYN packet that opens the connection */
// ... 省略部分代码
/* transition to the established state here. */
tcp_set_state(sk, TCP_ESTABLISHED);
// ... 省略部分代码
}
// ... 省略部分代码
return 0;
}
在上述代码中,当接收到客户端的ACK报文时,如果是一个SYN报文,就会进行状态转换,将状态设置为TCP_ESTABLISHED
,表示连接已建立。
3. 调用关系
- 当服务器接收到客户端的SYN报文时,调用
tcp_v4_syn_recv_sock
函数设置和发送SYN/ACK。 - 当服务器收到客户端的ACK报文时,调用
tcp_rcv_synsent_state_process
函数进行状态转换。
4. send在TCP/IP协议栈中的执行路径
send
是用于在TCP/IP协议栈中发送数据的系统调用。在Linux内核中,send
实际上是sys_sendto
的一个宏,sys_sendto
函数负责实现数据的发送逻辑。
-
用户空间调用:
- 应用程序通过
send
系统调用发送数据。在用户空间,send
通常是通过标准C库中的send
函数实现的。
- 应用程序通过
// 用户空间代码
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
-
系统调用入口:
-
sys_sendto
是实际处理send
系统调用的函数,位于net/socket.c
文件中。在这个函数中,会调用协议栈的sock_sendmsg
函数,负责实际的发送逻辑。
-
// net/socket.c
SYSCALL_DEFINE4(sendto, int, fd, void __user *, buff, size_t, len, unsigned, flags)
{
// ... 省略部分代码
return SYSC(sendto, fd, buff, len, flags);
}
static ssize_t sys_sendto(int fd, void __user *buff, size_t len, unsigned flags)
{
// ... 省略部分代码
return sock_sendmsg(sock, &msg, len);
}
-
套接口的发送逻辑:
-
sock_sendmsg
函数是协议栈中处理发送逻辑的关键函数。在net/socket.c
文件中,它会调用套接口对应协议族的发送函数,对于TCP来说,就是tcp_sendmsg
。
-
// net/socket.c
static ssize_t sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
{
// ... 省略部分代码
return sock->ops->sendmsg(sock, msg, len);
}
-
TCP的发送逻辑:
- 对于TCP协议,
tcp_sendmsg
函数是实际处理发送逻辑的函数,位于net/ipv4/tcp.c
文件中。这个函数负责构建TCP数据包,调用TCP协议栈的发送函数。
- 对于TCP协议,
// net/ipv4/tcp.c
static int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
{
// ... 省略部分代码
sk_stream_write_space(sk);
// ... 省略部分代码
return tcp_transmit_skb(sk, skb, msg->msg_flags, flags & MSG_DONTWAIT);
}
-
TCP数据包的构建和发送:
- 在
tcp_transmit_skb
函数中,TCP协议栈会构建TCP数据包,调用数据链路层的发送函数将数据包发送出去。
- 在
// net/ipv4/tcp.c
int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask)
{
// ... 省略部分代码
/* Pass the skb to the packet scheduler */
err = inet_sendmsg(sk, skb, size, mss_now, flags);
// ... 省略部分代码
return copied;
}
-
数据链路层的发送:
-
inet_sendmsg
函数位于net/ipv4/af_inet.c
文件中,负责将数据包传递给下层的数据链路层。
-
// net/ipv4/af_inet.c
int inet_sendmsg(struct sock *sk, struct sk_buff *skb, size_t size, int nonblock)
{
// ... 省略部分代码
return inet_sendpage(sk, skb, size, offset, flags);
}
5. recv在TCP/IP协议栈中的执行路径
recv
系统调用用于从套接口接收数据,实际上它是sys_recvfrom
的宏,而sys_recvfrom
函数实现了recv
系统调用的逻辑。以下是recv
在TCP/IP协议栈中的执行路径:
-
用户空间调用:
- 应用程序通过
recv
系统调用接收数据。在用户空间,recv
通常是通过标准C库中的recv
函数实现的。
- 应用程序通过
// 用户空间代码
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
-
系统调用入口:
-
sys_recvfrom
是处理recv
系统调用的函数,位于net/socket.c
文件中。在这个函数中,会调用协议栈的sock_recvmsg
函数,负责实际的接收逻辑。
-
// net/socket.c
SYSCALL_DEFINE4(recvfrom, int, fd, void __user *, ubuf, size_t, size, int, flags)
{
// ... 省略部分代码
return SYSC(recvfrom, fd, ubuf, size, flags);
}
static ssize_t sys_recvfrom(int fd, void __user *ubuf, size_t size, int flags)
{
// ... 省略部分代码
return sock_recvmsg(sock, &msg, size, flags);
}
-
套接口的接收逻辑:
-
sock_recvmsg
函数是协议栈中处理接收逻辑的关键函数。在net/socket.c
文件中,它会调用套接口对应协议族的接收函数,对于TCP来说,就是tcp_recvmsg
。
-
// net/socket.c
static ssize_t sock_recvmsg(struct socket *sock, struct msghdr *msg, size_t size, int flags)
{
// ... 省略部分代码
return sock->ops->recvmsg(sock, msg, size, flags);
}
-
TCP的接收逻辑:
- 对于TCP协议,
tcp_recvmsg
函数是实际处理接收逻辑的函数,位于net/ipv4/tcp.c
文件中。这个函数负责接收TCP数据包,处理并复制数据到用户空间缓冲区。
- 对于TCP协议,
// net/ipv4/tcp.c
int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t size, int nonblock, int flags, int *addr_len)
{
// ... 省略部分代码
copied = tcp_read_sock(sk, skb, len, flags, &recv_flags);
// ... 省略部分代码
return copied;
}
-
TCP数据包的接收和处理:
- 在
tcp_read_sock
函数中,TCP协议栈会接收TCP数据包,处理数据,并将数据复制到用户空间缓冲区。
- 在
// net/ipv4/tcp.c
int tcp_read_sock(struct sock *sk, struct sk_buff *skb, unsigned int len, int flags, int *recv_flags)
{
// ... 省略部分代码
copied = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
// ... 省略部分代码
return copied;
}
-
用户空间缓冲区的复制:
-
skb_copy_datagram_iovec
函数用于将数据从内核空间的skb
拷贝到用户空间的缓冲区。
-
// net/core/skbuff.c
unsigned int skb_copy_datagram_iovec(const struct sk_buff *from, int offset,
struct iovec *to, unsigned int size)
{
// ... 省略部分代码
ret = memcpy_toiovecend(to, size, skb_transport_header(from), skb->len - offset);
// ... 省略部分代码
return ret;
}
6. 路由表的结构和初始化过程
在Linux内核中,路由表的结构和初始化过程涉及多个文件和函数。以下是对路由表结构和初始化过程的简要分析:
路由表结构:
-
结构体定义:
- 在Linux内核中,路由表的结构体是
struct rtable
,每个路由表项由struct rtentry
表示。这些结构体定义在include/net/route.h
头文件中。
- 在Linux内核中,路由表的结构体是
// include/net/route.h
struct rtable {
// ... 省略部分代码
struct rtentry rt;
// ... 省略部分代码
};
struct rtentry {
// ... 省略部分代码
struct net_device *dst.dev;
struct in_device *idev;
struct in_ifaddr *rt_idev;
struct rtable *rt_next;
// ... 省略部分代码
};
-
数据结构关系:
-
struct rtable
结构包含了struct rtentry
,并且有一些额外的字段。每个struct rtentry
表示一条路由表项。
-
路由表初始化过程:
-
init_rtable函数:
-
init_rtable
函数是负责路由表初始化的函数,定义在net/ipv4/route.c
文件中。该函数在内核启动时被调用。
-
// net/ipv4/route.c
void __init init_rtable(void)
{
// ... 省略部分代码
init_special_routes();
// ... 省略部分代码
rt_cache_init();
// ... 省略部分代码
}
-
init_special_routes:
-
init_special_routes
函数负责初始化一些特殊的路由表项,如默认路由等。
-
// net/ipv4/route.c
static void __init init_special_routes(void)
{
// ... 省略部分代码
/* Initialize the default route */
ip_rt_init_default(in_dev_get(dev), rt->dst.dev, in_dev_get(rt->dst.dev));
rt->rt_idev = in_dev_get(rt->dst.dev);
// ... 省略部分代码
}
-
ip_rt_init_default:
-
ip_rt_init_default
函数初始化默认路由表项。
-
// net/ipv4/route.c
void __init ip_rt_init_default(struct in_device *in_dev, struct net_device *dev, struct in_device *out_dev)
{
struct rtable *rt;
// ... 省略部分代码
rt = ip_route_output_key(dev_net(dev), NULL, &fl);
if (IS_ERR(rt))
return;
// ... 省略部分代码
rt->idev = in_dev_get(out_dev);
rt->dst.dev = out_dev->dev;
rt->rt_idev = in_dev_get(out_dev);
// ... 省略部分代码
}
-
ip_route_output_key:
-
ip_route_output_key
函数用于查找目标IP地址对应的路由表项。它返回一个指向struct rtable
的指针,该结构表示了查找到的路由表项。
-
// net/ipv4/route.c
struct rtable *ip_route_output_key(struct net *net, struct flowi4 *flp)
{
// ... 省略部分代码
rt = ip_route_output_ports(net, flp);
if (IS_ERR(rt)) {
// ... 错误处理代码
goto e_nobufs;
}
// ... 省略部分代码
return rt;
e_nobufs:
// ... 错误处理代码
return ERR_PTR(-ENOBUFS);
}
7. 通过目的IP查询路由表的到下一跳的IP地址的过程
在Linux内核中,通过目的IP查询路由表的下一跳IP地址的过程主要涉及到fib_lookup
函数。这个函数用于在FIB(Forwarding Information Base)中查找与目的IP地址匹配的路由表项,并返回相应的下一跳IP地址。以下是对这个过程的详细分析:
fib_lookup
函数:
fib_lookup
函数定义在net/ipv4/fib_frontend.c
文件中。该函数的目的是根据输入的目的IP地址和网络设备,查找匹配的路由表项。
// net/ipv4/fib_frontend.c
struct fib_result {
// ... 结果信息
};
struct fib_result fib_lookup(struct net *net, const struct flowi4 *flp, int flags)
{
struct fib_result res;
// ... 省略部分代码
__fib_lookup(net, res, flp, flags);
// ... 省略部分代码
return res;
}
__fib_lookup
函数:
__fib_lookup
函数实际上进行了路由表的查找工作。该函数定义在net/ipv4/fib_frontend.c
文件中。
// net/ipv4/fib_frontend.c
static int __fib_lookup(struct net *net, struct fib_result *res,
const struct flowi4 *flp, int flags)
{
// ... 省略部分代码
fib_select_path(net, &res->nh, res->table, res->type, flags);
// ... 省略部分代码
return 0;
}
fib_select_path
函数:
fib_select_path
函数用于从匹配的路由表项中选择一个路径(下一跳)。该函数定义在net/ipv4/fib_frontend.c
文件中。
// net/ipv4/fib_frontend.c
int fib_select_path(struct net *net, struct nh_info *nhi,
struct fib_table *table, int type, int flags)
{
// ... 省略部分代码
struct fib_info *fi = fib_info_nh(net, nhi);
if (!fi)
goto errout;
// ... 省略部分代码
return 0;
errout:
// ... 错误处理代码
return -1;
}
调用关系:
- 用户空间或内核代码需要调用
fib_lookup
函数,提供目的IP地址和相关的路由信息。
struct flowi4 my_flow = {
// ... 初始化目的IP等信息
};
struct fib_result result = fib_lookup(sock_net(sk), &my_flow, 0);
if (result.fi) {
// 使用 result.fi->fib_nh->nh_gw 来获取下一跳IP地址
} else {
// 没有找到匹配的路由表项,处理错误
}
分析过程:
-
fib_lookup
:- 用户空间或内核代码调用
fib_lookup
函数,传递目的IP地址和其他路由信息。
- 用户空间或内核代码调用
-
__fib_lookup
:-
fib_lookup
函数内部调用__fib_lookup
,该函数会执行路由表查找操作。
-
-
路由表查找:
- 在
__fib_lookup
中,调用fib_select_path
函数进行路由表的查找。fib_select_path
会选择匹配的路由表项,并在struct fib_result
中保存相关信息。
- 在
-
下一跳IP地址获取:
- 如果找到匹配的路由表项,用户可以通过
result.fi->fib_nh->nh_gw
来获取下一跳IP地址。
- 如果找到匹配的路由表项,用户可以通过
fib_lookup
函数和相关的路由查找函数是Linux内核中实现路由表查找的关键部分。在实际应用中,用户可以根据需要调用这些函数,获取目的IP地址的下一跳IP地址。
8. ARP缓存的数据结构及初始化过程,包括ARP缓存的初始化
在Linux内核中,ARP(Address Resolution Protocol)缓存的数据结构主要通过struct neighbour
来表示。这个结构体包含了IP地址、MAC地址、ARP状态等信息。ARP缓存的初始化过程涉及到arp_init
函数,而ARP缓存的查找和更新则可以通过arp_lookup
和arp_update
等函数来实现。以下是对ARP缓存的数据结构和初始化过程的简要分析:
ARP缓存数据结构:
-
struct neighbour
定义:- ARP缓存的数据结构主要由
struct neighbour
定义,该结构体包含了与邻居节点相关的信息。
- ARP缓存的数据结构主要由
// include/net/neighbour.h
struct neighbour {
struct rcu_head rcu;
struct neigh_table *tbl;
struct neighbour *next, **pnext;
// ... 省略部分代码
struct timer_list timer;
unsigned long confirmed;
unsigned long used;
// ... 省略部分代码
};
-
struct neigh_table
定义:-
struct neigh_table
是邻居表的定义,包含了具体的邻居项。
-
// include/net/neighbour.h
struct neigh_table {
// ... 省略部分代码
struct neigh_parms parms;
// ... 省略部分代码
struct neighbour *hash_buckets;
// ... 省略部分代码
};
ARP缓存初始化过程:
-
arp_init
函数:-
arp_init
函数是ARP缓存初始化的函数,定义在net/ipv4/arp.c
文件中。该函数在内核启动时被调用。
-
// net/ipv4/arp.c
void __init arp_init(void)
{
// ... 省略部分代码
arp_tbl_init();
// ... 省略部分代码
}
-
arp_tbl_init
函数:-
arp_tbl_init
函数负责初始化ARP缓存表,包括创建和初始化邻居表。
-
// net/ipv4/arp.c
void __init arp_tbl_init(void)
{
// ... 省略部分代码
neigh_parms_alloc(&arp_tbl_parms);
// ... 省略部分代码
}
-
neigh_parms_alloc
函数:-
neigh_parms_alloc
函数用于为邻居参数分配内存,并将其初始化。
-
// net/core/neighbour.c
int neigh_parms_alloc(struct neigh_parms *parms)
{
// ... 省略部分代码
parms->tbl = neigh_tables_alloc(parms);
// ... 省略部分代码
return 0;
}
-
neigh_tables_alloc
函数:-
neigh_tables_alloc
函数用于为邻居表分配内存,并将其初始化。
-
// net/core/neighbour.c
struct neigh_table *neigh_tables_alloc(struct neigh_parms *parms)
{
// ... 省略部分代码
table->gc_task = alloc_gc_task();
if (!table->gc_task)
goto out_gc_task;
// ... 省略部分代码
return table;
out_gc_task:
// ... 错误处理代码
return NULL;
}
调用关系:
- 用户空间或内核代码需要调用
arp_lookup
函数,提供目标IP地址和相关的网络设备。
struct neighbour *neigh = arp_lookup(dev, ip_addr, 0);
if (neigh) {
// 使用 neigh->ha 来获取MAC地址等信息
} else {
// 没有找到匹配的ARP缓存项,可能需要发送ARP请求等操作
}
ARP缓存查找和更新过程:
-
arp_lookup
函数:- 用户空间或内核代码调用
arp_lookup
函数,传递目标IP地址和相关的网络设备。
- 用户空间或内核代码调用
// net/ipv4/arp.c
struct neighbour *arp_lookup(const struct net_device *dev, __be32 addr, int nud)
{
struct neighbour *neigh;
unsigned long now = jiffies;
// ... 省略部分代码
neigh = __neigh_lookup(&arp_tbl, &addr, dev, 0);
// ... 省略部分代码
return neigh;
}
-
__neigh_lookup
函数:-
__neigh_lookup
函数实际上进行了邻居表的查找工作。
-
// net/core/neighbour.c
struct neighbour *__neigh_lookup(struct neigh_table *tbl, const void *pkey,
struct net_device *dev, int create)
{
struct neighbour *n, **np;
struct neigh_hash_table *nht;
unsigned int hash_val;
// ... 省略部分代码
np = neigh_find_next(&nht->hash_buckets[hash_val], tbl, pkey, dev);
// ... 省略部分代码
return n;
}
-
neigh_find_next
函数:-
neigh_find_next
函数用于在邻居表中查找下一个邻居项。
-
// net/core/neighbour.c
struct neighbour **neigh_find_next(struct neighbour **n, struct neigh_table *tbl,
const void *pkey, struct net_device *dev)
{
struct neighbour *neigh;
// ... 省略部分代码
for (neigh = *n; neigh; neigh = neigh->next) {
// ... 省略部分代码
if (neigh->dev == dev) {
// ... 省略部分代码
return &neigh->next;
9. 如何将IP地址解析出对应的MAC地址
在Linux内核中,IP地址解析出对应的MAC地址主要通过ARP(Address Resolution Protocol)协议来实现。ARP缓存用于存储IP地址与MAC地址的映射关系,而dev_mc_sync
函数则用于同步ARP缓存和MAC地址表,确保ARP缓存中的MAC地址是最新的。以下是对这个过程的简要分析:
IP地址解析出对应的MAC地址过程:
-
ARP缓存查找:
- 当主机需要与另一个主机通信时,首先在ARP缓存中查找目标IP地址对应的MAC地址。
// net/core/neighbour.c
struct neighbour *neigh_lookup(const struct neighbour *neigh)
{
// ... 省略部分代码
return __neigh_lookup(neigh->tbl, neigh->primary_key, neigh->dev, 0);
}
-
__neigh_lookup
函数:-
__neigh_lookup
函数实际上进行了邻居表的查找工作。
-
// net/core/neighbour.c
struct neighbour *__neigh_lookup(struct neigh_table *tbl, const void *pkey,
struct net_device *dev, int create)
{
struct neighbour *n, **np;
struct neigh_hash_table *nht;
unsigned int hash_val;
// ... 省略部分代码
np = neigh_find_next(&nht->hash_buckets[hash_val], tbl, pkey, dev);
// ... 省略部分代码
return n;
}
-
neigh_find_next
函数:-
neigh_find_next
函数用于在邻居表中查找下一个邻居项。
-
// net/core/neighbour.c
struct neighbour **neigh_find_next(struct neighbour **n, struct neigh_table *tbl,
const void *pkey, struct net_device *dev)
{
struct neighbour *neigh;
// ... 省略部分代码
for (neigh = *n; neigh; neigh = neigh->next) {
// ... 省略部分代码
if (neigh->dev == dev) {
// ... 省略部分代码
return &neigh->next;
}
}
return NULL;
}
-
ARP请求包发送:
- 如果在ARP缓存中没有找到匹配的MAC地址,主机会发送一个ARP请求包,询问目标IP地址对应的MAC地址。
// net/ipv4/arp.c
void arp_solicit(struct neighbour *neigh, struct sk_buff *skb)
{
// ... 省略部分代码
arp_xmit(sk, neigh->dev, neigh->ha, neigh->dev->dev_addr, target, 1);
// ... 省略部分代码
}
-
ARP响应包接收:
- 收到ARP请求包的目标主机会发送一个ARP响应包,其中包含了自身的MAC地址。
// net/ipv4/arp.c
int arp_process(struct sk_buff *skb)
{
// ... 省略部分代码
if (arp->op_code == htons(ARPOP_REPLY)) {
arp_mc_map(skb, target);
// ... 省略部分代码
neigh_update(neigh, arp_hdr_ptr(skb)->ar_sha);
// ... 省略部分代码
}
// ... 省略部分代码
}
-
neigh_update
函数:-
neigh_update
函数用于更新邻居表中的项,包括将MAC地址添加到ARP缓存中。
-
// net/core/neighbour.c
void neigh_update(struct neighbour *neigh, const unsigned char *lladdr)
{
// ... 省略部分代码
neigh_update_stats(neigh, NUD_REACHABLE);
// ... 省略部分代码
}
MAC地址表与ARP缓存同步:
-
dev_mc_sync
函数用于同步ARP缓存和MAC地址表,确保ARP缓存中的MAC地址是最新的。
// net/core/neighbour.c
void dev_mc_sync(struct net_device *dev, struct netdev_hw_addr *ha,
void (*cb)(struct neighbour *))
{
// ... 省略部分代码
np = neigh_find_next(&nht->hash_buckets[hash_val], &arp_tbl, ha->addr, dev);
// ... 省略部分代码
}
10.跟踪TCP send过程中的路由查询和ARP解析的最底层实现
在TCP发送过程中,路由查询和ARP解析是协议栈中的关键步骤。路由查询和ARP解析的实现涉及多个关键函数和数据结构。例如,fib_lookup
函数用于路由表查询,而arp_send
函数负责ARP请求包的发送,arp_rcv
函数用于处理接收到的ARP响应包。这些函数和数据结构相互协作,构成了TCP发送过程中路由查询和ARP解析的基础机制。下面将分析在Linux内核中,TCP send过程中的路由查询和ARP解析的最底层实现,包括相关的函数和数据结构。
1. 路由查询(fib_lookup函数):
路由查询是通过fib_lookup
函数实现的,该函数用于在FIB(Forwarding Information Base)中查找与目的IP地址匹配的路由表项。以下是相关函数和数据结构的分析:
1.1. fib_lookup
函数:
fib_lookup
函数定义在net/ipv4/fib_frontend.c
文件中。
// net/ipv4/fib_frontend.c
struct fib_result fib_lookup(struct net *net, const struct flowi4 *flp, int flags)
{
struct fib_result res;
// ... 省略部分代码
__fib_lookup(net, res, flp, flags);
// ... 省略部分代码
return res;
}
1.2. __fib_lookup
函数:
__fib_lookup
函数实际上执行路由表的查找工作,定义在net/ipv4/fib_frontend.c
文件中。
// net/ipv4/fib_frontend.c
static int __fib_lookup(struct net *net, struct fib_result *res,
const struct flowi4 *flp, int flags)
{
// ... 省略部分代码
fib_select_path(net, &res->nh, res->table, res->type, flags);
// ... 省略部分代码
return 0;
}
1.3. fib_select_path
函数:
fib_select_path
函数用于从匹配的路由表项中选择一条路径(下一跳),定义在net/ipv4/fib_frontend.c
文件中。
// net/ipv4/fib_frontend.c
int fib_select_path(struct net *net, struct nh_info *nhi,
struct fib_table *table, int type, int flags)
{
// ... 省略部分代码
struct fib_info *fi = fib_info_nh(net, nhi);
if (!fi)
goto errout;
// ... 省略部分代码
return 0;
errout:
// ... 错误处理代码
return -1;
}
2. ARP解析:
ARP解析是通过发送ARP请求包和处理ARP响应包来实现的。以下是相关函数和数据结构的分析:
2.1. ARP请求包发送(arp_send函数):
ARP请求包的发送是通过arp_send
函数实现的,定义在net/ipv4/arp.c
文件中。
// net/ipv4/arp.c
int arp_send(int type, int ptype, __be32 dest_ip, struct net_device *dev,
const unsigned char *dest_hw, const unsigned char *src_hw,
__be32 src_ip, const unsigned char *target_hw,
const struct net_device *dst_dev)
{
// ... 省略部分代码
skb = arp_create(type, ptype, dev->dev_addr, src_ip,
(type == ARPOP_REQUEST) ? NULL : target_hw,
dest_ip, dst_dev, dest_hw);
if (!skb)
return -ENOMEM;
// ... 省略部分代码
dev_queue_xmit(skb);
// ... 省略部分代码
return 0;
}
2.2. ARP响应包处理(arp_rcv函数):
ARP响应包的处理是通过arp_rcv
函数实现的,定义在net/ipv4/arp.c
文件中。
// net/ipv4/arp.c
int arp_rcv(struct sk_buff *skb)
{
// ... 省略部分代码
if (arp->op_code == htons(ARPOP_REPLY)) {
arp_mc_map(skb, target);
// ... 省略部分代码
neigh_update(neigh, arp_hdr_ptr(skb)->ar_sha);
// ... 省略部分代码
}
// ... 省略部分代码
}
2.3. neigh_update
函数:
neigh_update
函数用于更新邻居表中的项,包括将MAC地址添加到ARP缓存中,定义在net/core/neighbour.c
文件中。文章来源:https://www.toymoban.com/news/detail-772831.html
// net/core/neighbour.c
void neigh_update(struct neighbour *neigh, const unsigned char *lladdr)
{
// ... 省略部分代码
neigh_update_stats(neigh, NUD_REACHABLE);
// ... 省略部分代码
}
3. 调用关系:
- 在TCP发送过程中,通过调用
fib_lookup
函数进行路由查询,找到合适的路由路径。 - 当需要将目的IP地址解析为MAC地址时,协议栈可能会调用
arp_send
函数发送ARP请求包,然后在收到ARP响应包时,调用arp_rcv
函数进行处理,最终通过neigh_update
函数将MAC地址添加到ARP缓存中。
心得与感谢:
孟宁老师的这门课令我受益匪浅。我在几次实验中学到了web开发、网络编程、Linux内核调试等技术,锻炼了我的代码和动手能力。虽然我的技术方向和未来发展并不和课程内容强相关,但是在孟宁老师的课堂上,我依然收获了很多知识,了解到了一些没有接触过的技术栈,虽然未来的职业生涯可能用不到一些具体的技术,但是却开拓了眼界,提高了计算机领域的素养,这些在未来可能受益终身。孟宁老师的课堂氛围也很宽松,非常适合根据自己的学习状态来调整自己的学习进度。感谢孟宁老师对本次课程倾注的热情和对我们的教导!
文章来源地址https://www.toymoban.com/news/detail-772831.html
到了这里,关于网络程序设计实验-TCP/IP协议栈源代码分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!