内核网络命名空间
網絡命名空間將內核網絡協議棧(路由、流控、Netfilter、網橋等系統)虛擬成多個。內核默認創建的網絡命名空間init_net。
struct net init_net = {
? ? .count ? ? ?= ATOMIC_INIT(1),
? ? .dev_base_head ?= LIST_HEAD_INIT(init_net.dev_base_head),
};
網絡命名空間創建
iproute2工具集通過unshare(CLONE_NEWNET)系統調用創建新的網絡命名空間。到Linux內核中,由函數copy_net_ns處理。其一分配網絡命名空間內存;其二調用setup_net初始化命名空間中注冊的所有協議棧模塊。
struct net *copy_net_ns(unsigned long flags, struct user_namespace *user_ns, struct net *old_net) {struct net *net;if (!(flags & CLONE_NEWNET))return get_net(old_net);net = net_alloc();rv = setup_net(net, user_ns); }內核初始化時,各個協議棧模塊通過register_pernet_subsys或者register_pernet_device注冊其初始化函數到網絡命名空間系統中。在新的命名空間創建時,遍歷全局鏈表pernet_list,執行每個子模塊注冊的初始化函數。
static __net_init int setup_net(struct net *net, struct user_namespace *user_ns) { idr_init(&net->netns_ids);list_for_each_entry(ops, &pernet_list, list)error = ops_init(ops, net); }所有命名空間中注冊的子模塊(struct pernet_operations)都鏈接在全局鏈表pernet_list上。兩個命名空間子模塊注冊函數register_pernet_device和register_pernet_subsys用于注冊子模塊,功能基本相同,差別在于子模塊插入的位置。register_pernet_device將子模塊添加在pernet_list的尾部,而register_pernet_subsys將子模塊添加在第一個使用register_pernet_device注冊的子模塊在鏈表中的尾部。
|--<-----| prev| |head-->submod0-->submod1-->subdev0| | |---<----------------------| nextprev|--<------| | |--<-----| prev| | |head-->submod0-->submod1-->submod2-->subdev0| | |---<--------------------------------| next如上圖所示,如果鏈表中已經插入了一個device子模塊(subdev0),之后插入的subsys子模塊(submod2)將插入在subdev0之前。最終pernet_list鏈表中的元素排列為,所有使用register_pernet_subsys注冊的子模塊按照注冊順序位于鏈表頭,而使用register_pernet_device注冊的子模塊按順序位于鏈表尾部。目前內核中注冊的device子模塊為gre隧道處理、fou、vti、ipip等隧道設備模塊,由于位于鏈表尾部,這些設備模塊在創建命名空間時排在最后初始化。
對于采用模塊形式動態加載的網絡子模塊,其在注冊命名空間操作時,內核將遍歷所有已經創建的網絡命名空間,針對每一個命名空間執行其初始化函數。
static int __register_pernet_operations(struct list_head *list, struct pernet_operations *ops) {list_add_tail(&ops->list, list);if (ops->init || (ops->id && ops->size)) {for_each_net(net)error = ops_init(ops, net);} }網絡命名空間通用數據
網絡協議棧的各個模塊都會有私有的數據需要保存,網絡命名空間提供了net_generic結構可用來保存模塊私有數據的地址(指針)。這些私有數據基于命名空間,不同的命名空間保存有不同的模塊私有數據,每個模塊的私有數據指針按照id為索引保存在net_generic的指針數組成員ptr中。模塊的id值在函數register_pernet_device或者register_pernet_subsys的調用中生成,之后返回給調用模塊,并且根據參數中提供的私有數據大小,在返回前分配好模塊所需的私有數據空間。
static int register_pernet_operations(struct list_head *list, struct pernet_operations *ops) {if (ops->id) { again:error = ida_get_new_above(&net_generic_ids, MIN_PERNET_OPS_ID, ops->id);max_gen_ptrs = max(max_gen_ptrs, *ops->id + 1);} }函數ida_get_new_above獲取id值,max_gen_ptrs記錄系統中最大的id值,以便在分配net_generic成員ptr空間時,分配合適的大小。max_gen_ptrs初始值為INITIAL_NET_GEN_PTRS(13),隨著各個網絡命名空間中的子模塊注冊而遞增。
網絡各個子模塊可使用net_generic函數,指定參數net(網絡命名空間)和id值,即可取到私有數據空間的首指針,進行必要的初始化工作已經進行特定命名空間的操作。
struct net_generic {union {void *ptr[0];}; }; static inline void *net_generic(const struct net *net, unsigned int id) {struct net_generic *ng;ng = rcu_dereference(net->gen);ptr = ng->ptr[id]; }例如對于ipgre_net_ops子模塊,在調用register_pernet_device之后,網絡命名空間系統將分配的id值保存到變量ipgre_net_id中,并且已分配好大小為sizeof(struct ip_tunnel_net)的內存空間,之后像ipgre_rcv等函數,可使用net_generic獲取此空間。
static struct pernet_operations ipgre_net_ops = {.init = ipgre_init_net,.id = &ipgre_net_id,.size = sizeof(struct ip_tunnel_net), }; static int __init ipgre_init(void) {err = register_pernet_device(&ipgre_net_ops); } static int ipgre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi, int hdr_len) {struct net *net = dev_net(skb->dev);struct ip_tunnel_net *itn = net_generic(net, ipgre_net_id); }命名空間添加設備
內核為每個新建的網絡命名空間創建一個回環接口lo。使用ip命令添加設備到命名空間中,如將網絡設備eth1添加到網絡命名空間netns1中。對于回環接口、網橋設備、聚合設備bond和一些隧道設備,不能使用IP命名將其在命名空間之間移動,內核在設備的features中設置了NETIF_F_NETNS_LOCAL標志來標識這類設備,其僅屬于創建時的命名空間。
ip link set eth1 netns netns1
內核中dev_change_net_namespace函數處理設備在命名空間中的移動。目的是更改網絡設備net_device結構體中的成員nd_net指向新的網絡命名空間,但是在更改前后需要做一些清理和初始化工作。 首先在更改之前關閉設備,清理kobject、流控隊列,清空地址等,發送NETDEV_DOWN、NETDEV_GOING_DOWN、NETDEV_UNREGISTER等消息通知舊的命名空間的協議棧,如路由系統接收到NETDEV_DOWN消息將會清除此接口上的IP地址對應的路由信息。其次更改設備到新的命名空間,修改設備id,在新命名空間發送通知消息NETDEV_REGISTER等通知新命名空間的其它模塊。
鑒于以上操作,移動設備之后,之前配置的IP地址等信息將會被清除。
內核版本
Linux-4.15
?
總結
- 上一篇: 程序员简历最强攻略
- 下一篇: 把编程当小葵花妈妈课堂