天天看點

從ip addr add和ifconfig的差別看linux網卡ip位址的結構

今天一個老外在郵件清單上問了一個問題,就是ip addr add和ifconfig的差別,我給他進行了解答,可能因為英語不好吧,解答的很簡單,是以我還是要在這裡詳細說明一下。其實它們之間沒有什麼差別,隻 是表述方式不同罷了。如果你非常了解網絡協定的原理以及網絡的分層架構那麼我想你就不會有這個問題,實際上,每一個網卡裝置都有一個mac位址,但是卻可 以有多個網絡層位址,比如IP位址,然而這個事實無法很好地像使用者提供操作接口,是以就引出了ip别名(IP aliases)和輔助ip(secondary IP addresses)的概念。其實很容易了解這個事實,按照分層的思想,下層總是為上層服務,也就是為上層提供舞台,上層利用下層的服務,而不必讓下層知 道自己的情況,如果一個擁有合理mac位址的網卡沒有配置網絡層位址(比如IP位址)這件事合理的話,那麼為這個裝置配置多個IP位址也是合理的,正好像 一個ip可以對應多個應用層端口一樣,也就是說,下層對上層總是一對多的關系,在分層架構中這種關系是合理的。下面我們就看一下linux的網卡的ip地 址結構。剛才說了在linux中,一個網卡可以有多個IP,那麼這多個ip有什麼關系呢?其實這些ip組成了一個吊鍊結構,所謂吊鍊結構就是一些節點連結 成一條鍊,然後每個節點帶有自己的一條鍊,如下圖所示:

每個節點代表的ip位址辨別一個網段,這個節點的ip就是這個網段的 Primary位址,它下面所帶的ip就是這個網段的Secondary位址,也就是說一個網卡可以帶有各個節點所帶連結清單長度之和個ip位址,而且這些 ip不是線形的,而是上述的吊鍊結構。我們看一下這麼做有什麼好處。玩過Cisco路由器的朋友可能都知道有個Secondary IP的概念,這個特性可以建立邏輯子網,也就是說在一個實體網口上連接配接兩個子網,這咋看起來好像不可思議,其實很簡單,比如這個網口接到一台交換機上,如 果這個網口沒有配置Secondary IP的話,那麼這台交換機隻能連接配接一個網段的主機,比如192.168.1.1/24,但是,如果它配置了Secondary IP,那麼就可以連接配接兩個網段的主機,比如192.168.1.1/24和10.0.0.1/24,道理就是這麼簡單,但是卻很有用,該機制可以被路由彙 總政策所使用。注意上面這個例子中的Secondary IP不是這裡說的linux的Secondary address,在linux中恰恰相反,隻要一個網卡上配置的ip不是一個網段的,那麼都是Primary IP,就是吊鍊結構中上面的那條主鍊中的IP,linux中的Secondary address是主鍊結點的子鍊結點中的IP,這一點一定注意,概念是不能混淆的。前面說的隻是吊鍊中主鍊的作用,那麼子鍊呢?其實想象一下也很簡單,比 如一台機器上運作着一個代理伺服器或者負載均衡服務,代理伺服器或者負載均衡服務和主伺服器要監聽相同的端口,那麼就可以用secondary address來解決了,隻要需要在同一網段監聽同一個端口的應用都是吊鍊中子鍊存在的原因,是以可以說,主鍊對外部或者說對下面鍊路層虛拟了多塊網卡, 而子鍊向上層虛拟了多台機器,配置了吊鍊結構的linux主機如果說隻有一塊網卡,那麼外部會認為它有多塊網卡,對于内部,應用層會認為彼此在不同的主機 上,這就是效果。

除了上面大體的介紹之外,還有很多細節,吊鍊在主鍊上是沒有主次的,子鍊除了第一個節點其它節點也不分主次,都是平行的關系,但是子鍊中的第一個節點總是 連結在主鍊中,它們攜帶的位址就是primary位址,它們下面隸屬的子鍊攜帶的位址就是這個primary位址的secondary位址,如此看來,一 旦主鍊上一個節點被删除了,那麼它的子鍊也将不複存在,所謂皮之不存毛将焉附。但是這種政策總是顯得不是那麼優美,因為父親犯錯,兒子也要受連累,這在現 代社會早就不時行了,那麼就需要改變機制了,是以linux中特意有了一個選項,就是當一個primary位址被删除時,如果它有secondary位址 的話,那麼它的第一個secondary位址(長子)繼承被删除的primary位址的位置成為primary位址,這樣就顯得很合理了,要不然在删除 primary位址的時候,如果有程式用secondary位址,那麼要麼延遲删除,要麼程式崩潰,采用自動提升政策的話就不會出現問題。

至于說IP aliases,那是以前版本有的了,就是一個實作問題,解決的問題和現在的secondary IP機制一樣,它主要就是在實體網卡名字後面加上字尾進而成為虛拟網絡接口,本質上和secondary IP機制沒有差別,差別就是IP aliases顯得不是那麼直覺,而secondary IP卻是真正讓應用看到了一個網卡的多個位址,比如你要是用IP aliases的話,有的時候你總是會問eth0:0是什麼?我就曾經在核心裡面拼命找eth0:0這個網絡裝置的注冊代碼,都要瘋掉了也沒有找到,其實 我并不是很傻,但是我卻因為那個該死的名字作出了傻事。

下面就可以看看linux核心的實作代碼了,首先弄明白一些資料結構,最重要的就是net_device,其次就是in_device,然後就是in_ifaddr,明白了這三個資料結構,一切就明白了,這是真的。

struct net_device

{

...

     void                    *ip_ptr;       //指向一個in_device結構,這個字段從net_device中分離表明一個網卡可以支援多種網絡層協定的

}

struct in_device

         struct net_device       *dev;           //指向它隸屬的net_device,也就是網卡

         atomic_t                refcnt;         //引用計數

         int                     dead;

         struct in_ifaddr        *ifa_list;      //所有的ip位址連結清單

};

struct in_ifaddr   //代表一個ip位址

         struct in_ifaddr        *ifa_next;       //上面的in_device中的ifa_list字段就是靠這個字段連成鍊的

         struct in_device        *ifa_dev;        //回指in_device結構

         struct rcu_head         rcu_head;

         u32                     ifa_local;       //ip位址

         u32                     ifa_address;

         u32                     ifa_mask;        //掩碼

         u32                     ifa_broadcast;   //廣播位址

         u32                     ifa_anycast;

         unsigned char           ifa_scope; 

         unsigned char           ifa_flags;           //隻有IFA_F_SECONDARY标志,因為除了這個就是primary位址了

         unsigned char           ifa_prefixlen;

         char                    ifa_label[IFNAMSIZ]; //名字,在ip aliases時代,它就可能是ethx:y的形式,在secondary ip時代,它統一就是ethx

注 意,上面的結構并沒有将linux網卡的ip位址結構表示為吊鍊結構,所謂的吊鍊結構隻是邏輯上的,在資料結構上,一個網卡所有的ip位址全部都在 ifa_list中被連結成一個線性的連結清單,至于是primary位址還是secondary位址就看in_ifaddr的ifa_flags字段了。每 當有新的位址被設定的時候,inet_insert_ifa總是被調用,linux為何沒有在代碼上将ip位址表示為吊鍊結構呢?我也不知道,個人感覺一 個net_device帶有一個primary ip連結清單,然後每個primary ip節點帶有一個secondary ip連結清單,這樣會更好一些的,我覺得inet_insert_ifa實作的十分拙劣。添加位址可以通過兩個使用者空間程式搞定,一個是ifconfig,另 一個是ip addr add,ifconfig是基于ioctl進行位址添加的,而ip程式是基于netlink進行位址添加的,不管哪一種方式都可以達到目的,現在就可以看 看另一個問題了:為何用ip addr add添加的ip位址用ifconfig看不到,而ifconfig設定的位址ip addr show卻是可以看到。這個問題通過看代碼一眼就可以明白,在ifconfig獲得ip位址的時候,代碼:

for (ifap = &in_dev->ifa_list; (ifa = *ifap) != NULL; ifap = &ifa->ifa_next)

    if (!strcmp(ifr.ifr_name, ifa->ifa_label) && sin_orig.sin_addr.s_addr == ifa->ifa_address)

    {

        break;

    }

取 的是這個被找到的ifa的ip位址,而我們知道,所有的ifa連結成一個線性連結清單,那麼找到了第一個就不會再往後走了,是以隻能得到一個結果,就是連結清單最 前面的那個,而ip add show就不同了,具體在函數inet_dump_ifaddr中實作,該函數周遊所有的ifa,并且傳到使用者空間緩沖區。這裡可以做一個實驗:首先用 ip addr add添加幾個不在同一個網段的primary ip位址,然後再ifconfig一個和前面的ip都不在一個網段的ip,然後可以用ifconfig檢視一下,發現不是剛剛用ifconfig設定進去 的那個ip,而是用ip addr add添加進去的,這就說明ifconfig永遠都是取的ifa連結清單最前面的那一個,還有一點要注意,就是如果你用ip addr add添加了很多的secondary ip位址,那麼恰好你用ifconfig設定的ip位址和那些secondary ip在一個網段,那麼所有的secondary ip都将被删除,這些都是sencondary ip的規範決定的,而且在代碼中也有展現。另外還要注意,路由表的表項都是基于primary ip的,因為所有的操作都是以primary ip為主的,比如在添加路由的時候:

void fib_add_ifaddr(struct in_ifaddr *ifa)

         struct in_device *in_dev = ifa->ifa_dev;

         struct net_device *dev = in_dev->dev;

         struct in_ifaddr *prim = ifa;

         if (ifa->ifa_flags&IFA_F_SECONDARY) {   //如果ifa是個sencondary位址,那麼就找到它隸屬的primary位址後然後以這個primary為主進行設定

                 prim = inet_ifa_byprefix(in_dev, prefix, mask);

                 if (prim == NULL) {

                         printk(KERN_DEBUG "fib_add_ifaddr: bug: prim == NULL/n");

                         return;

                 }

         }

         fib_magic(RTM_NEWROUTE, RTN_LOCAL, addr, 32, prim);    //添加進路由表

到 此為止我們知道了不少東西,最重要的就是linux中網卡ip位址的吊鍊結構以及這麼設計的好處,另外就是設定ip位址的方式有ioctl和 netlink。其實網卡擁有多個ip并不會帶來什麼沖突,本質上ip和網卡沒有什麼關系,它們唯一的關系就是靠網絡分層模型聯系在一起的,細節上就是靠 路由聯系在一起的,比如我添加路由的時候指定了一個目的位址和下一跳ip位址以及一個網卡出口,那麼核心會根據提供的目的位址将路由插在合式的位置,然後 将nh的網絡裝置設定為你提供的網卡出口,等到傳輸資料的時候就會查找路由進而找到出口,就是這麼簡單,你自己手動設定的路由可以随意設定,即使完全錯誤 核心也會将之加入路由表的,還有一種路由是核心自動生成的,就是在網卡剛剛up的時候,這時通過網卡的net_device找到其in_device然後 找到其ip位址,這樣的路由稱為鍊路路由。

通過secondary IP機制,你可以認為你的機器有很多網卡,對于應用,監聽同一端口的應用會認為它們在區域網路中不同的機器上,你可以随意使用這些ip位址而不會發生混亂,路由和底層的arp會處理好這一切,當然前提是你将路由設定對。

附: 使用者空間有ifup/ifdown,/sbin/ip,ifconfig,還有netplugd守護程序,這些有何關系嗎?這中間ip程式是最基本的,沒 有任何政策,政策就是參數指定,要麼就是别的程式調用它,而netplugd就是一個監控守護程序,通過netlink監控網卡狀态,然後根據不同的監控 結果調用/etc/netplug.d/netplug腳本,進而可能調用ifup/ifdown腳本,而後者就是腳本,其中會調用ifup-eth腳 本,最終整理好參數後調用ip程式(典型的就是:ip link set eth0 up/down),當然ip程式完全可以自己調用,比如ip addr add以及ip route add等等,而ifconfig沒有那麼繞圈子,就是通過ioctl進行設定,可以通過strace來觀察。這其中奧妙大了去了,說白了就是政策和機制分 離,另外還展現出linux中的很多功能都是很小的程式組合而成的。

Linux的ip位址的吊鍊結構以及ip位址的尋址特性(詳見《關于IP網段間互訪的問題—路由是根本》)充分說明了linux的協定棧實作多麼的完美,完全符合分層和封裝模型,使得下層的邏輯和上層的邏輯完全解除耦合,也就是說ip層完全不依賴鍊路層以及實體層的實體布局,最後記住,ip層事情比如尋址路由隻由ip層實作,之所有有鍊路層發現的路由,完全是為了友善。

原文位址:https://blog.csdn.net/dog250/article/details/5303542

繼續閱讀