linux 網絡協議棧,源碼剖析第四節

我從事防火牆的開發已經有三年半的時間了,對網絡協議棧瞭解的還可以,後期,想要向漏洞挖掘方向進軍,後續,會學習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 網絡協議棧,源碼剖析第四節

網絡設備和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;
}

未完待續


分享到:


相關文章: