容器网络转发一次故障笔记
今天处理问题时,发现docker 容器内访问外网不通,tcpdump 抓包发现,只抓到veth 接口上的包,但是报文需要再宿主机物理网卡上转发到外网,此时这部分报文没看到,
目前容器使用桥接模式(Bridge Network)
容器 eth0 (veth0) ↓ veth_xmit() [veth.c] ↓ peer veth1 in host namespace ↓ netif_receive_skb() [dev.c] ↓ ip_rcv() [ip_input.c] ↓ if local: ip_local_deliver() else: ip_forward() [ip_forward.c] ↓ netfilter (FORWARD hook, NAT/MASQUERADE) ↓ dst_output() ↓ dev_queue_xmit() ↓ 宿主机物理网卡
也就是ip forward 被丢弃,查看ip_forward选项,发现net.ipv4.ip_forward = 0 ,设置为1后解决问题。
使用iptable -nvL -t nat可以看到
0 0 MASQUERADE all -- * * 192.168.122.0/24 !192.168.122.0/24
内网 192.168.122.0/24
的主机访问外部网络时,其源 IP 会被伪装成网关的公网 IP,实现 NAT 出口。
SNAT模块MASQUERADE
内核空间整个封包逻辑兼容的核心,包含了了
- 如何根据使用输入的参数来选择源端口
- 如何动态选择网路卡介面的IP地址,如果有多个会怎么选择
内核中相关内容在这个文件xt_MASQUERADE.c
static struct xt_target masquerade_tg_reg[] __read_mostly = { { .name = "MASQUERADE", .family = NFPROTO_IPV4, .target = masquerade_tg, .targetsize = sizeof(struct nf_nat_ipv4_multi_range_compat), .table = "nat", .hooks = 1 << NF_INET_POST_ROUTING, .checkentry = masquerade_tg_check, .destroy = masquerade_tg_destroy, .me = THIS_MODULE, } }; static unsigned int masquerade_tg(struct sk_buff *skb, const struct xt_action_param *par) { struct nf_nat_range2 range; const struct nf_nat_ipv4_multi_range_compat *mr; mr = par->targinfo; range.flags = mr->range[0].flags; range.min_proto = mr->range[0].min; range.max_proto = mr->range[0].max; return nf_nat_masquerade_ipv4(skb, xt_hooknum(par), &range, xt_out(par)); }
真正的核心逻辑处理函数式nf_nat_masquerade_ipv4
unsigned int nf_nat_masquerade_ipv4(struct sk_buff *skb, unsigned int hooknum, const struct nf_nat_range2 *range, const struct net_device *out) { struct nf_conn *ct; struct nf_conn_nat *nat; enum ip_conntrack_info ctinfo; struct nf_nat_range2 newrange; const struct rtable *rt; __be32 newsrc, nh; WARN_ON(hooknum != NF_INET_POST_ROUTING); ct = nf_ct_get(skb, &ctinfo); WARN_ON(!(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED || ctinfo == IP_CT_RELATED_REPLY))); /* Source address is 0.0.0.0 - locally generated packet that is * probably not supposed to be masqueraded. */ if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip == 0) return NF_ACCEPT; rt = skb_rtable(skb); nh = rt_nexthop(rt, ip_hdr(skb)->daddr); newsrc = inet_select_addr(out, nh, RT_SCOPE_UNIVERSE); if (!newsrc) { pr_info("%s ate my IP address\n", out->name); return NF_DROP; } nat = nf_ct_nat_ext_add(ct); if (nat) nat->masq_index = out->ifindex; /* Transfer from original range. */ memset(&newrange.min_addr, 0, sizeof(newrange.min_addr)); memset(&newrange.max_addr, 0, sizeof(newrange.max_addr)); newrange.flags = range->flags | NF_NAT_RANGE_MAP_IPS; newrange.min_addr.ip = newsrc; newrange.max_addr.ip = newsrc; newrange.min_proto = range->min_proto; newrange.max_proto = range->max_proto; /* Hand modified range to generic setup. */ return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_SRC); }
第一个重点,选出一个可用的地址作为封包之后的来源IP地址
rt = skb_rtable(skb); nh = rt_nexthop(rt, ip_hdr(skb)->daddr); newsrc = inet_select_addr(out, nh, RT_SCOPE_UNIVERSE);
- 从skb取出该封包对应的
routing entry
- 从该
routing entry
中根据封包的目的ip选出下一个节点next_hop - 接下来根据参数下一个节点的IP地址,已经决定好的输出网路接口来决定最后使用的IP
对于接口上配置了多个ip地址的情况,使用遍历方式去取出(in_dev)->ifa_list内的所有list,其实就是所有的IP地址,
然后根据额外的scope , netmask 进行判断是否选择这个ip作为源newsrc(遍历确认该接口上ip与next_hop 是否在同一个网段,是的话就可以用这个IP地址创建来源IP发送出去。)
- 针对所有非SECONDARY的IP地址依序检查
in_dev_for_each_ifa_rcu(ifa, in_dev) { if (ifa->ifa_flags & IFA_F_SECONDARY) continue; if (min(ifa->ifa_scope, localnet_scope) > scope) continue; if (!dst || inet_ifa_match(dst, ifa)) { addr = ifa->ifa_local; break; } if (!addr) addr = ifa->ifa_local; }
static inline bool inet_ifa_match(__be32 addr, const struct in_ifaddr *ifa) { return !((addr^ifa->ifa_address)&ifa->ifa_mask); }
Source Port端口选择
/* Transfer from original range. */ memset(&newrange.min_addr, 0, sizeof(newrange.min_addr)); memset(&newrange.max_addr, 0, sizeof(newrange.max_addr)); newrange.flags = range->flags | NF_NAT_RANGE_MAP_IPS; newrange.min_addr.ip = newsrc; newrange.max_addr.ip = newsrc; newrange.min_proto = range->min_proto; newrange.max_proto = range->max_proto; /* Hand modified range to generic setup. */ return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_SRC);
- 新范围.标志NF_NAT_RANGE_MAP_IPS
- newrange.min_proto
- newrange.max_proto
- newrange.min_addr.ip max_addr.ip
用户在操作MASQUERADE的时候设置了其他参数--random、--to-ports的话,上述的栏位会有不同的数值,接下来的逻辑处理根据这些数值来处理。
- - 随机的--to-ports
- 标志 = NF_NAT_RANGE_PROTO_RANDOM
- min_proto:max_proto => 用户设置的端口号区间
- 标志 = NF_NAT_RANGE_PROTO_SPECIFIED
对于 nf_nat_setup_info 参考nat分析
http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!!
但行好事 莫问前程
--身高体重180的胖子