网络程序设计实验-TCP/IP协议栈源代码分析

这篇具有很好参考价值的文章主要介绍了网络程序设计实验-TCP/IP协议栈源代码分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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)和下层数据链路层关联的简要解释:

  1. 套接口(Socket):

    • 用户空间的应用程序通过套接口(Socket)与TCP/IP协议栈进行交互。套接口提供了一组系统调用,应用程序通过这些调用实现与协议栈的通信。
  2. 协议族和套接口的创建:

    • 在Linux中,协议栈通过协议族(AF_INET,AF_INET6等)和套接口(socket)的组合与上层应用程序建立联系。
    • 当应用程序调用socket系统调用时,它指定了协议族(如AF_INET)和套接口类型(如SOCK_STREAM)。
  3. 创建套接口数据结构:

    • 在协议栈内部,创建一个数据结构来表示套接口。这通常是一个结构体,包含与套接口相关的信息,如协议族、套接口类型、协议参数等。
  4. 协议栈初始化:

    • 在协议栈初始化的过程中,会注册相应的协议处理函数。例如,TCP/IP协议栈会注册TCP和UDP的处理函数。
  5. 协议与套接口关联:

    • 当应用程序通过bind系统调用将一个套接口绑定到一个特定的IP地址和端口上时,协议栈内部会将这个套接口与相应的协议(TCP或UDP)关联起来。
    • 这时,套接口数据结构中的一些字段会被设置,如本地IP地址、端口等。
  6. 监听和连接建立:

    • 对于TCP套接口,应用程序可能会调用listen系统调用,用于指示协议栈开始监听连接请求。当有新的连接请求到达时,协议栈会创建一个新的套接口数据结构来表示该连接。
  7. 数据的上交和下发:

    • 当应用程序通过套接口读取数据时,协议栈会调用相应协议的处理函数,将数据从协议栈上交给应用程序。
    • 当应用程序通过套接口发送数据时,协议栈会将数据传递给相应的协议处理函数,然后通过网络接口向下传递,最终交给数据链路层处理。
  8. 数据链路层关联:

    • 协议栈与下层数据链路层的关联通常是通过网络设备(如以太网接口)的注册和绑定来实现的。
    • 协议栈通过网络设备的数据结构,将数据链路层的功能与协议栈关联在一起。

协议栈通过数据结构的关联和相应的处理函数,将套接口与上层应用程序和下层数据链路层连接起来。这样的设计允许协议栈处理网络通信的细节,同时提供简单的套接口供应用程序使用。

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

在Linux内核中,TCP的三次握手过程的源代码涉及多个文件和函数。以下是对TCP三次握手过程的源代码分析,主要关注设置和发送SYN/ACK的位置以及状态转换的位置。

1. TCP的三次握手流程

三次握手的过程包括以下步骤:

  1. 客户端发送SYN:

    • 客户端向服务器发送一个SYN(同步)报文,请求建立连接。
  2. 服务器回应SYN/ACK:

    • 服务器收到客户端的SYN后,回应一个SYN/ACK(同步/确认)报文,表示接受连接。
  3. 客户端发送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函数负责实现数据的发送逻辑。

  1. 用户空间调用:
    • 应用程序通过send系统调用发送数据。在用户空间,send通常是通过标准C库中的send函数实现的。
// 用户空间代码
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  1. 系统调用入口:
    • 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);
}
  1. 套接口的发送逻辑:
    • 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);
}
  1. TCP的发送逻辑:
    • 对于TCP协议,tcp_sendmsg函数是实际处理发送逻辑的函数,位于net/ipv4/tcp.c文件中。这个函数负责构建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);
}
  1. 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;
}
  1. 数据链路层的发送:
    • 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协议栈中的执行路径:

  1. 用户空间调用:
    • 应用程序通过recv系统调用接收数据。在用户空间,recv通常是通过标准C库中的recv函数实现的。
// 用户空间代码
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  1. 系统调用入口:
    • 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);
}
  1. 套接口的接收逻辑:
    • 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);
}
  1. TCP的接收逻辑:
    • 对于TCP协议,tcp_recvmsg函数是实际处理接收逻辑的函数,位于net/ipv4/tcp.c文件中。这个函数负责接收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;
}
  1. 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;
}
  1. 用户空间缓冲区的复制:
    • 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内核中,路由表的结构和初始化过程涉及多个文件和函数。以下是对路由表结构和初始化过程的简要分析:

路由表结构:

  1. 结构体定义:
    • 在Linux内核中,路由表的结构体是struct rtable,每个路由表项由struct rtentry表示。这些结构体定义在include/net/route.h头文件中。
// 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;

    // ... 省略部分代码
};
  1. 数据结构关系:
    • struct rtable结构包含了struct rtentry,并且有一些额外的字段。每个struct rtentry表示一条路由表项。

路由表初始化过程:

  1. init_rtable函数:
    • init_rtable函数是负责路由表初始化的函数,定义在net/ipv4/route.c文件中。该函数在内核启动时被调用。
// net/ipv4/route.c

void __init init_rtable(void)
{
    // ... 省略部分代码

    init_special_routes();

    // ... 省略部分代码

    rt_cache_init();

    // ... 省略部分代码
}
  1. 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);

    // ... 省略部分代码
}
  1. 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);

    // ... 省略部分代码
}
  1. 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 {
    // 没有找到匹配的路由表项,处理错误
}

分析过程:

  1. fib_lookup

    • 用户空间或内核代码调用fib_lookup函数,传递目的IP地址和其他路由信息。
  2. __fib_lookup

    • fib_lookup函数内部调用__fib_lookup,该函数会执行路由表查找操作。
  3. 路由表查找:

    • __fib_lookup中,调用fib_select_path函数进行路由表的查找。fib_select_path会选择匹配的路由表项,并在struct fib_result中保存相关信息。
  4. 下一跳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_lookuparp_update等函数来实现。以下是对ARP缓存的数据结构和初始化过程的简要分析:

ARP缓存数据结构:

  1. struct neighbour定义:
    • ARP缓存的数据结构主要由struct neighbour定义,该结构体包含了与邻居节点相关的信息。
// 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;

    // ... 省略部分代码
};
  1. struct neigh_table定义:
    • struct neigh_table是邻居表的定义,包含了具体的邻居项。
// include/net/neighbour.h

struct neigh_table {
    // ... 省略部分代码

    struct neigh_parms     parms;

    // ... 省略部分代码

    struct neighbour      *hash_buckets;

    // ... 省略部分代码
};

ARP缓存初始化过程:

  1. arp_init函数:
    • arp_init函数是ARP缓存初始化的函数,定义在net/ipv4/arp.c文件中。该函数在内核启动时被调用。
// net/ipv4/arp.c

void __init arp_init(void)
{
    // ... 省略部分代码

    arp_tbl_init();

    // ... 省略部分代码
}
  1. arp_tbl_init函数:
    • arp_tbl_init函数负责初始化ARP缓存表,包括创建和初始化邻居表。
// net/ipv4/arp.c

void __init arp_tbl_init(void)
{
    // ... 省略部分代码

    neigh_parms_alloc(&arp_tbl_parms);

    // ... 省略部分代码
}
  1. 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;
}
  1. 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缓存查找和更新过程:

  1. 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;
}
  1. __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;
}
  1. 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地址过程:

  1. 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);
}
  1. __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;
}
  1. 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;
}
  1. 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);

    // ... 省略部分代码
}
  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);

        // ... 省略部分代码
    }

    // ... 省略部分代码
}
  1. 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文件中。

// 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模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 粉丝提问:设计和实现一个TCP协议半连接的端口扫描程序

    某学生粉丝发来问题: 这个题目一看就知道这位同学是网络安全相关专业。 很多粉丝以为彭老师知识搞驱动的, 但是其实作为一个拥有多篇网络协议专利的老鸟, 网络知识还是比较擅长的! 应用层套接字、组网、网卡驱动都有所涉猎, 目前还缺Linux内核协议栈这块没深入

    2023年04月14日
    浏览(42)
  • 用Rust设计一个并发的Web服务:常用Rust库如Tokio、Hyper等,基于TCP/IP协议栈,实现了一个简单的并发Web服务器,并结合具体的代码讲解如何编写并发Web服务器的程序

    作者:禅与计算机程序设计艺术 1994年,互联网泡沫破裂,一批优秀的程序员、工程师纷纷加入到web开发领域。而其中的Rust语言却备受瞩目,它是一种现代系统编程语言,专注于安全和并发。因此,Rust在当下成为最流行的编程语言之一,很多框架也开始使用Rust重构,这使得

    2024年02月06日
    浏览(61)
  • QT程序设计多人聊天室(基于QT、sqlite3、TCP/IP)

    目录 技术路线 效果展示 程序主体 sqoperator.h mylogin.h myenroll.h chatinterface.h tips.h myapp.h ******************* sqoperator.cpp mylogin.cpp myenroll.cpp chatinterface.cpp tips.cpp myapp.cpp main.cpp widget.h widget.cpp main.cpp QT程序设计、sqlite数据库调用、TCP/IP客户端与服务端的搭建 通过次程序代码,可以学习如

    2024年02月09日
    浏览(63)
  • TCP/IP协议栈源代码分析

    在 Linux 内核启动过程中,inet_init 是在网络子系统初始化的一部分,负责初始化 TCP/IP 协议栈。下面是从 start_kernel 到 inet_init 的调用路径: start_kernel: start_kernel() 是 Linux 内核启动的入口函数,位于 init/main.c 文件中。 kernel_init: kernel_init() 在 start_kernel 中被调用,它位于 init/ma

    2024年02月03日
    浏览(46)
  • 实验六 Java流式编程与网络程序设计

    Client Server ClientPlus ServerPlus ReceiveThread 本关任务:编写应用程序(SortArray.java),使用字节输入/输出流实现数据的保存和读取。 Java 流功能相关的类都封装在 java.io包中,所以要使用流类,必须导入java.io包。数据流是 Java 进行 I/O 操作的对象,它按照不同的标准可以分为不同的

    2024年02月03日
    浏览(51)
  • Linux内核中的TCP/IP协议栈源代码分析

    目录 背景知识-Linux源码简介 TCP/IP协议栈相关问题 inet_init是如何被调用的?从start_kernel到inet_init调用路径 1.start_kernel(): 2.inet_init() : 3.fs_initcall() 跟踪分析TCP/IP协议栈如何将自己与上层套接口与下层数据链路层关联起来的? TCP的三次握手源代码跟踪分析,跟踪找出设置和发

    2024年02月02日
    浏览(69)
  • C#网络编程TCP程序设计(Socket类、TcpClient类和 TcpListener类)

    目录 一、Socket类 1.Socket类的常用属性及说明 2.Socket类的常用方法及说明 二、TcpClient类 三、TcpListener类  四、示例 1.源码 2.生成效果         TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在C#中,TCP程序设计是指利用 Socket类、T c

    2024年02月04日
    浏览(49)
  • TCP/IP协议栈源代码分析:GDB调试环境搭建及源码分析

    Ubuntu 22.04 LTS Linux-5.4.34 busybox-1.36.0 2.1 安装相关工具 axel是一款多线程下载工具,用于下载Linux内核源代码及其他大文件;build-essential软件包里面包含了很多开发必要的软件工具,比如make、gcc等;QEMU是一种通用的开源计算机仿真器和虚拟器,为自己编译构建的Linux系统运行提供

    2024年02月03日
    浏览(60)
  • 程序猿眼中的协议:TCP / IP 五层网络模型

    哈喽,大家好~我是你们的老朋友: 保护小周ღ ,本期为大家带来的是 网络基础原理中的 TCP / IP 五层网络模型,主要从协议的概念,网络模型,数据分层传输的流程,几个方面讲解,看完之后可以轻松的理解数据是如何在网络中传输的,确定不来看看嘛~~ 更多精彩敬请期待

    2023年04月19日
    浏览(52)
  • 合肥工业大学宣城校区Java技术实验二 基于GUI的网络通信程序设计

    1.掌握Java中GUI程序的编写,包括事件监听机制。 2.掌握Java的网络通信编程,ServerSocket,Socket类的使用。 3.掌握Java中多线程的编程,Thread类,Runnable接口的使用。 4.掌握用面向对象的方法分析和解决复杂问题。 编写程序完成以下功能: 1.设计一个基于GUI的客户-服务器的

    2023年04月24日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包