当前位置: 首页 > Linux

单机容器网络

时间:2023-04-06 06:21:28 Linux

基于linux内核5.4.54,昨天分享了veth原理:veth原理-两个容器通过veth通信时数据包的发送和接收路径一般情况下,容器不会直接通过veth通信,但会通过docker0桥通信今天分析容器通过veth与docker0桥的通信路径。独立容器的网络结构。当在宿主机上通过docker创建两个容器时,会自动生成如图所示的网络结构。将在主机上生成一个docker0网络。网桥容器1和docker0网桥通过veth连接,容器2简单查看Namespace网络设备的Namespace:注册网络设备时,会通过net_device->nd_net(网络设备结构体)设置NetNamespace场地)。分析结构图中设备的Namespace:veth0属于Namespace1;veth1属于Namespace2;eth0、docker0、docker0上的两个veth设备属于HostNamespace数据包的Namespace:数据包的Namespace是由skb_buff->dev->nd_net(数据包目的设备的Namespace)决定的Namespaceprocess:通过clone()创建进程时,会通过task_struct->nsproxy(进程结构域)为进程设置Namespace,而nsproxy->net_ns决定了进程的NetNamespace/*nsproxy结构,其中包含各种namespaceisolation和Cgroup,有空再去学习*/structnsproxy{atomic_tcount;结构uts_namespace*uts_ns;结构ipc_namespace*ipc_ns;结构mnt_namespace*mnt_ns;cgroup_ns;};Socket套接字的Namespace进程在创建Socket时,会将sock->sk_net设置为current->nsproxy->net_ns,即会将当前进程的NetNamespace传给sock套接字。直到这里i设备都有自己的队列,数据包路径和veth文档中两次veth通信的发送阶段完全一样,docker0网桥处理数据包主要在__netif_receive_skb_core,cpu处理网络包过程:do_softirq()|net_rx_action:网络软中断处理函数|napi_民意调查|n->poll:调用目标网络设备驱动的poll函数|veth设备没有定义poll,调用默认的poll函数-process_backlog|处理高达300skb,|清空处理队列后,拼接input_pkt_queue到process_queue|尾部__netif_receive_skb|...|__netif_receive_skb_core数据包处理代码分析:/**__netif_receive_skb_core代码分析*代码做了很多删减,将netBridge处理和数据包传输交给上层处理*其他很多部分如vlan、xdp、tcpdump等代码被删除*/staticint__netif_receive_skb_core(structsk_buff**pskb,boolpfmemalloc,structpacket_type**ppt_prev){structpacket_type*ptype,*pt_prev;rx_handler_func_t*rx_handler;结构sk_buff*skb=*pskb;结构net_device*orig_dev;嘘ldeliver_exact=false;intret=NET_RX_DROP;__be16类型;/*记录skb包的目的设备*/orig_dev=skb->dev;/*设置skb包的协议头指针*/skb_reset_network_header(skb);如果(!skb_transport_header_was_set(skb))skb_reset_transport_header(skb);skb_reset_mac_len(skb);pt_prev=NULL;another_round:.../***skb包的目的设备是docker-veth0,veth是bridge的一个接口*docker-veth0注册后会设置rx_handler为bridge的接收函数br_handle_frame*黄色的代码是调用bridge的br_handle_frame*/rx_handler=rcu_dereference(skb->dev->rx_handler);if(rx_handler){...switch(rx_handler(&skb)){caseRX_HANDLER_CONSUMED:/*已处理,无需进一步处理*/ret=NET_RX_SUCCESS;出去;caseRX_HANDLER_ANOTHER:/*再处理一次*/gotoanother_round;caseRX_HANDLER_EXACT:/*精确传递给ptype->dev==skb->dev*/deliver_exact=true;案例RX_HANDLER_PASS:中断;默认值:乙UG();}}.../*获取三层协议*/type=skb->protocol;/**调用指定协议的协议处理函数(如ip_rcv函数),将数据包传递给上层协议层进行处理*ip_rcv函数是网络协议栈的入口函数*当数据包到达时这里会经过netfilter,route,最后转发或者发送到上层协议栈*/deliver_ptype_list_skb(skb,&pt_prev,orig_dev,type,&orig_dev->ptype_specific);...if(pt_prev){if(不太可能(skb_orphan_frags_rx(skb,GFP_ATOMIC)))gotodrop;*ppt_prev=pt_prev;}else{drop:if(!deliver_exact)atomic_long_inc(&skb->dev->rx_dropped);elseatomic_long_inc(&skb->dev->rx_nohandler);kfree_skb(skb);ret=NET_RX_DROP;}out:*pskb=skb;returnret;}网桥处理代码分析:/*br_handle_frame,deleted*/rx_handler_result_tbr_handle_frame(structsk_buff**pskb){structnet_bridge_port*p;结构sk_buff*skb=*pskb;constunsignedchar*dest=eth_hdr(skb)->h_dest;...forward:switch(p->state){caseBR_STATE_FORWARDING:caseBR_STATE_LEARNING:/*目的地址是否为设备链路层地址*/if(ether_addr_equal(p->br->dev->dev_addr,dest))skb->pkt_type=PACKET_HOST;返回nf_hook_bridge_pre(skb,pskb);默认值:删除:kfree_skb(skb);}返回RX_HANDLER_CONSUMED;}nf_hook_bridge_pre|br_handle_frame_finish/*br_handle_frame_finish,已删除*/intbr_handle_frame_finish(structnet*uff{net,structsockport*buct_port*sk,struct*bstr_sk_)p=br_port_get_rcu(skb->dev);枚举br_pkt_typepkt_type=BR_PKT_UNICAST;结构net_bridge_fdb_entry*dst=NULL;结构net_bridge_mdb_entry*mdst;boollocal_rcv,mcast_hit=false;结构net_bridge*brif=0;u16dst){unsignedlongnow=jiffies;/*如果目的地是宿主机*/if(dst->is_local)/**这个函数把skb->dev变成了一个桥接设备,然后返回__netif_receive_skb_core*把skb发送给HostNetNamespace的三层协议栈处理*/returnbr_pass_frame_up(skb);如果(现在!=dst->已使用)dst->已使用=现在;/**目的端不是主机,将数据包转发到指定端口*代码实现是调用目的端口设备驱动的数据包接收函数*这次路径是调用docker-veth1的veth_xmit*以上对veth_xmit的分析会修改数据包的目的设备*由docker-veth1改为veth1,然后送入cpu队列等待处理*cpu处理数据包时,运行veth1的网络协议栈(也就是Namespace2)*最后容器2进程收到数据包*/br_forward(dst->dst,skb,local_rcv,false);}...out:return0;drop:kfree_skb(skb);gotoout;}总结路径:容器1进程生成数据包|通过Namespace1协议栈发送给veth0|veth0驱动将skb目的设备更改为docker-veth0,发送skb到cpu队列|cpu处理数据包,因为docker-veth0是一个网桥端口,调用网桥接收函数|bridge将skb目标设备修改为docker-veth1,调用docker-veth1驱动|docker-veth1driver将skb目的设备改为veth1,并发送skb到cpu队列|cpu处理数据包并调用veth1的接收包驱动函数,发送到veth1(Namespace2)的网络协议栈|容器2进程收到数据包2,容器1通过网桥将数据包发送给宿主机。代码之前已经分析过现在直接总结总结路径:容器1进程生成数据包|通过Namespace1协议栈发送给veth0|veth0驱动程序将skb目标设备更改为docker-veth0,将skb发送到cpu队列|网桥的一个端口,调用网桥接收函数|bridge判断destination为host,修改skbdestinationdevice为bridge,然后运行主机(HostNamespace)三层协议栈