我从事防火墙的开发已经有三年半的时间了,对网络协议栈了解的还可以,后期,想要向漏洞挖掘方向进军,后续,会学习web安全,密码学,逆向等相关的内容,为后续的安全进军。

说了这么多,该入正题了,我会接着讲解linux内核网络协议栈的相关知识,也让我的文章能够更加的专向化,也欢迎对网络协议栈感兴趣的朋友一起讨论,当然也可以互相学习。

做网络管理的人都知道IP地址这个东西,它是主机与外界通信的基础,没有IP地址,你的主机将无法与外界进行通信,它就相当于一个人的名字一样,代表着你自己,IP地址同样如此,也代表着主机。

我们知道,我们想要和互联网通信,我们第一步要做的就是要给我们的主机给配置IP地址,配置完IP地址后,才有可能和互联网进行通信,当然,现在的电脑都是dhcp自动获取IP地址的,但这并不意味着电脑可以不配IP地址,而是有专门的程序帮你配置IP地址而已。

1. IP地址相关的结构介绍

1.1 IP地址

当我们在linux 系统上配置完IP地址后,就可以通过ifconfig看到我们所配置的IP地址的信息,那么,这个IP地址,一定是存在了linux系统的内存里,存在了哪里呢?既然IP地址是和接口相关的,所以,linux内核将IP地址存在了net_device结构体中,因为这个结构体就是和接口密切相关的数据结构。

如下图所示:




linux如何释放GPU linux如何释放ip_linux 释放ip

网络设备和ip配置快的关系图



1.1 struct in_device 结构体的介绍

这个结构体,我们通常叫它IP配置块,通过上图,我们知道,这个结构是嵌在了net_device中的,当用户通过ifconfig来配置ip地址时,IP地址就被写到了in_device结构中,内核提供了一个函数,可以访问到这个结构,这个函数就是in_dev_get,当访问结束后,要调用in_dev_put函数,因为in_dev_get会将in_device结构的引用计数加1,所以在不使用的时候,一定要调用in_dev_put将其引用计数减一,否则会造成很严重的后果。

struct in_device 的结构体如下所示:

struct in_device{struct net_device *dev;atomic_t refcnt;int dead;struct in_ifaddr *ifa_list; /* IP ifaddr chain */rwlock_t mc_list_lock;struct ip_mc_list *mc_list; /* IP multicast filter chain */int mc_count; /* Number of installed mcasts */spinlock_t mc_tomb_lock;struct ip_mc_list *mc_tomb;unsigned long mr_v1_seen;unsigned long mr_v2_seen;unsigned long mr_maxdelay;unsigned char mr_qrv;unsigned char mr_gq_running;unsigned char mr_ifc_count;struct timer_list mr_gq_timer; /* general query timer */struct timer_list mr_ifc_timer; /* interface change timer */ struct neigh_parms *arp_parms;struct ipv4_devconf cnf;struct rcu_head rcu_head;};

主要的成员介绍如下:

struct net_device *dev

是指向网络设备的指针

atomic_t refcnt

引用计数,使用该结构时,增加引用计数,不使用的时候,减少该引用计数。

int dead

释放标志位,置为1时,表示将要释放该结构,不允许再使用该结构。

struct in_ifaddr *ifa_list

是一个链表,用链表的主要原因是因为,一个设备可以配置多个IP地址,所以用链表将多个IP串起来。

1.1 struct in_ifaddr 结构体的介绍

这个结构体,我们通常叫它ip配置块,它是struct in_device的结构体的成员,也是最重要的成员,因为ip地址信息就存在了这个结构体中,结构如下所示:

struct in_ifaddr{struct in_ifaddr *ifa_next;struct in_device *ifa_dev;struct rcu_head rcu_head;__be32 ifa_local;__be32 ifa_address;__be32 ifa_mask;__be32 ifa_broadcast;unsigned char ifa_scope;unsigned char ifa_flags;unsigned char ifa_prefixlen;char ifa_label[IFNAMSIZ];};

struct in_ifaddr *ifa_next

上节中,我们说过,一个网络设备可以配置多个IP地址,所以用链表将它们组织起来,ifa_next就是组织链表的成员。

struct in_device *in_dev

指向它所在的IP配置块

_be32 ifa_local

_be32 ifa_address

一看这两个成员就是和IP地址相关,其实就是存储IP地址的成员。

_be32 ifa_mask

存储ip掩码的成员

2. IP地址相关的函数介绍

2.1 inetdev_init 函数

我们知道,ip地址的信息是存在了struct in_device中,那么linux内核必然会在一定的时机,来分配struct in_device结构,这个分配的函数就是inetdev_init。

static struct in_device *inetdev_init(struct net_device *dev){1: struct in_device *in_dev; 2: ASSERT_RTNL(); 3: in_dev = kzalloc(sizeof(*in_dev), GFP_KERNEL);4: if (!in_dev)goto out;5: memcpy(&in_dev->cnf, dev_net(dev)->ipv4.devconf_dflt,sizeof(in_dev->cnf));6: in_dev->cnf.sysctl = NULL;7: in_dev->dev = dev;8: if ((in_dev->arp_parms = neigh_parms_alloc(dev, &arp_tbl)) == NULL)9: goto out_kfree;10: if (IPV4_DEVCONF(in_dev->cnf, FORWARDING))11: dev_disable_lro(dev);12: /* Reference in_dev->dev */13: dev_hold(dev);14: /* Account for reference dev->ip_ptr (below) */15: in_dev_hold(in_dev); 16: devinet_sysctl_register(in_dev);17: ip_mc_init_dev(in_dev);18: if (dev->flags & IFF_UP)19:ip_mc_up(in_dev); /* we can receive as soon as ip_ptr is set -- do this last */20:rcu_assign_pointer(dev->ip_ptr, in_dev);21:out:22:return in_dev;23:out_kfree:24:kfree(in_dev);25:in_dev = NULL;26:goto out;}

主要的执行如下:

1:调用kzalloc分配一个IP配置块。

5-13: 初始化in_dev的成员。

8: 分配邻居相关的参数配置块。

操作成功后,返回相应的ip配置块。

2.2 inetdev_destroy 函数

既然有申请IP配置块的函数,当然linux内核也会提供一个销毁IP配置块的函数,否则的话,不就内存泄露了嘛,函数如下所示:

static void inetdev_destroy(struct in_device *in_dev){1: struct in_ifaddr *ifa;2: struct net_device *dev; 3: ASSERT_RTNL(); 4: dev = in_dev->dev; 5: in_dev->dead = 1; 6: ip_mc_destroy_dev(in_dev); 7: while ((ifa = in_dev->ifa_list) != NULL) {8: inet_del_ifa(in_dev, &in_dev->ifa_list, 0);9: inet_free_ifa(ifa);10: } 11: dev->ip_ptr = NULL; 12: devinet_sysctl_unregister(in_dev);13: neigh_parms_release(&arp_tbl, in_dev->arp_parms);14: arp_ifdown(dev); 15: call_rcu(&in_dev->rcu_head, in_dev_rcu_put);}

主要的代码流程如下:

5: 将in_dev中的dead 标识置为1,用来表示in_dev结构将要被释放。

6: 释放组播相关的配置,

7-10:释放所有的ip配置块。

11:将设备的dev中的ip_ptr置为空。

2.3 inet_select_addr 函数

当主机向外发送数据时,可以指定源IP发送,也可以不指定源IP发送,当没有指定源IP时,linux内核会调用inet_select_addr函数来选择一个源IP进行发送数据。

__be32 inet_select_addr(const struct net_device *dev, __be32 dst, int scope){__be32 addr = 0;struct in_device *in_dev;struct net *net = dev_net(dev); rcu_read_lock();in_dev = __in_dev_get_rcu(dev);if (!in_dev)goto no_in_dev; for_primary_ifa(in_dev) {if (ifa->ifa_scope > scope)continue;if (!dst || inet_ifa_match(dst, ifa)) {addr = ifa->ifa_local;break;}if (!addr)addr = ifa->ifa_local;} endfor_ifa(in_dev);

首先通过dev来获取ip配置快,然后对ip配置块进行检查,看是否有效,然后遍历ifa链表,找到符合条件的本地地址。

no_in_dev:rcu_read_unlock(); if (addr)goto out; /* Not loopback addresses on loopback should be preferred in this case. It is importnat that lo is the first interface in dev_base list. */read_lock(&dev_base_lock);rcu_read_lock();for_each_netdev(net, dev) {if ((in_dev = __in_dev_get_rcu(dev)) == NULL)continue; for_primary_ifa(in_dev) {if (ifa->ifa_scope != RT_SCOPE_LINK && ifa->ifa_scope <= scope) {addr = ifa->ifa_local;goto out_unlock_both;}} endfor_ifa(in_dev);}out_unlock_both:read_unlock(&dev_base_lock);rcu_read_unlock();out:return addr;}

如果在当前设备没有找到符合条件的IP地址,那么就会跳到no_in_dev这个点,这个点所做的工作,当然是继续寻找IP地址,这次不一样,是遍历整个系统所有的设备接口,来寻找IP地址。

2.4 inet_confirm_addr 函数

这个函数比较简单,主要的作用就是确认一下函数参数所给的地址是否存在而已,参数说明如下:

in_dev:通过ip配置块,找到它所在的网络命名空间,即net,然后遍历net上的所有的net_device。

dst:目的IP地址,当它有值时,用来和待确认的IP地址是否属于同一个网段。

local:待确认的IP地址。

scope:待确认的IP地址的最大范围。

__be32 inet_confirm_addr(struct in_device *in_dev, __be32 dst, __be32 local, int scope){__be32 addr = 0;struct net_device *dev;struct net *net; if (scope != RT_SCOPE_LINK)return confirm_addr_indev(in_dev, dst, local, scope); net = dev_net(in_dev->dev);read_lock(&dev_base_lock);rcu_read_lock();for_each_netdev(net, dev) {if ((in_dev = __in_dev_get_rcu(dev))) {addr = confirm_addr_indev(in_dev, dst, local, scope);if (addr)break;}}rcu_read_unlock();read_unlock(&dev_base_lock); return addr;}

上述代码比较简单,通过入参in_dev来获取网络命名空间,然后通过遍历net上的所有net_device来确认ip地址。

2.5 inet_addr_onlink 函数

这个函数更简单,就是比较两个地址是否在同一个网段。

int inet_addr_onlink(struct in_device *in_dev, __be32 a, __be32 b){rcu_read_lock();for_primary_ifa(in_dev) {if (inet_ifa_match(a, ifa)) {if (!b || inet_ifa_match(b, ifa)) {rcu_read_unlock();return 1;}}} endfor_ifa(in_dev);rcu_read_unlock();return 0;}

2.6 inetdev_by_index 函数

也是非常简短的函数,函数的主要目的是根据网络索引号,来获取索引所对应的网络设备dev中的ip配置块。

struct in_device *inetdev_by_index(struct net *net, int ifindex){struct net_device *dev;struct in_device *in_dev = NULL;read_lock(&dev_base_lock);dev = __dev_get_by_index(net, ifindex);if (dev)in_dev = in_dev_get(dev);read_unlock(&dev_base_lock);return in_dev;}