edgemesh利用iptables劫持流量时遇到的问题分析

edgemesh利用iptables劫持流量时遇到的问题分析

概念

  1. 云边通信:云端容器,通过serviceName/clusterIP,访问边缘容器;
  2. 边云通信:相对上面而言,反过来,边缘容器,通过serviceName/clusterIP,访问云端容器;
  3. 边边通信:边缘主机上的容器,通过serviceName/clusterIP,访问该边缘主机或其他边缘主机的容器;

edgemesh架构

kubeedge相比kubernetes,主要的提供了跨子网构建集群的方案;其最强大的特点就是让kubernetes不在局限于一个物理机房之内;集群中的主机节点可以在不同的私有局域网内。

不同私有网络之下的主机要进行相互通信,最主要的就是受限于防火墙NAT形态:

  • 全锥形NAT

  • 受限锥形NAT

  • 端口受限锥型NAT

  • 对称型NAT

上面的各组网方案之中,依次安全性增强,在对称锥形的网络中,所有主机都在各自的防火墙背后,直接穿透访问就非常困难,所以直接edgemesh提供了两种穿透访问方案:

部署

edgemesh-server: Deployment 云端

edgemesh-agent: DaemonSet 云端+边缘

直接穿透访问

对于全锥形NAT、受限性NAT、部分端口受限锥形NAT访问可以直接采用如下方案;
在这里插入图片描述

中继穿透访问

对于直接穿透访问不了的请求,edgemesh-agent就会将流量转发到edgemesh-server上进行中继,因为所有的edgemesh-agent在启动后,就会于云端的edgemesh-server建立一个隧道网络;

在这里插入图片描述

edgemesh原理

在kubernetes+kubeedge组成的边缘云集群上,无论云端or边缘主机,都部署了edgemesh-agent,其有两个主要功能:

  • 对外暴露53端口,实现了通过serviceName查询k8s:service配置获取相应clusterIP的功能;
  • 在主机上创建一个DummyDevice网卡,默认暴露在169.254.96.16:40001上一个端口,并且通过iptables配置了PREROUTING和OUTPUT的EDGE-MESH链,用于劫持所有的流量访问。
    • 在如下的iptables的NAT表设置,可以看出在EDGE-MESH链中配置的相关服务目标地址:即不需要EDGEMESH代理的服务就会RETURN到它的下一行去做代理,即走了DOCKER->其他的代理服务(kube-proxy、kube-router…),这些服务的声明是要在k8s创建servcie的时候,增加noproxy=edgemesh的标签,就会被edgemesh-agent写入到主机iptables的这个位置。这些服务其实就是不需要云边通信的服务;
    • 当需要被edgemesh-agent代理的流量,即我们认为的需要云边通信、边云通信、边边通信,劫持到流量,流量会被内核劫持转发到169.254.96.16:40001上,然后经过edgemesh-agent的20006端口与远端的edgemesh-agent:20006或edgemesh-server:20004端口进行转发,最终目标端的edgemesh再将流量转发到被访问的业务容器中;
[root@edge001 ~]# iptables -t nat -nvL --line
Chain PREROUTING (policy ACCEPT 9734 packets, 833K bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1    32317 3700K EDGE-MESH  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* edgemesh root chain */
2    14436  930K DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 9734 packets, 833K bytes)
num   pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 278 packets, 18244 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1      893 60171 EDGE-MESH  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* edgemesh root chain */
2       47  3390 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT 278 packets, 18244 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1        0     0 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0           
2        0     0 RETURN     all  --  *      *       192.168.122.0/24     224.0.0.0/24        
3        0     0 RETURN     all  --  *      *       192.168.122.0/24     255.255.255.255     
4        0     0 MASQUERADE  tcp  --  *      *       192.168.122.0/24    !192.168.122.0/24     masq ports: 1024-65535
5        0     0 MASQUERADE  udp  --  *      *       192.168.122.0/24    !192.168.122.0/24     masq ports: 1024-65535
6        0     0 MASQUERADE  all  --  *      *       192.168.122.0/24    !192.168.122.0/24    

Chain DOCKER (2 references)
num   pkts bytes target     prot opt in     out     source               destination         
1       27  1896 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0           

Chain EDGE-MESH (2 references)
num   pkts bytes target     prot opt in     out     source               destination         
1        0     0 RETURN     all  --  *      *       0.0.0.0/0            10.254.165.225       /* ignore kubeedge/cloudcore service */
2        0     0 RETURN     all  --  *      *       0.0.0.0/0            10.242.247.106       /* ignore kube-system/traefik-web-ui service */
3        0     0 RETURN     all  --  *      *       0.0.0.0/0            10.243.116.144       /* ignore kube-system/traefik-ingress-service service */
4        0     0 RETURN     all  --  *      *       0.0.0.0/0            10.255.163.200       /* ignore kube-system/prometheus-service service */
5        0     0 RETURN     all  --  *      *       0.0.0.0/0            10.240.83.24         /* ignore kube-system/metrics-server service */
6        0     0 RETURN     all  --  *      *       0.0.0.0/0            10.254.210.250       /* ignore kube-system/kube-dns service */
7        0     0 RETURN     all  --  *      *       0.0.0.0/0            10.240.0.1           /* ignore default/kubernetes service */
8        0     0 EDGE-MESH-TCP  tcp  --  *      *       0.0.0.0/0            10.240.0.0/12        /* tcp service proxy */

Chain EDGE-MESH-TCP (1 references)
num   pkts bytes target     prot opt in     out     source               destination         
1        0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            to:169.254.96.16:40001

正常访问情况分析

直接穿透请求端抓包

环境:

mkedge3 10.136.77.2 北四环办公室边缘盒子 edgemesh-agent role: edgenode kernel: 5.2.14-1.el7.elrepo.x86_64

mkedge2 10.201.82.131 天坛机房 edgemesh-agent role: edgenode kernel: 4.15.6-1.el7.elrepo.x86_64

用例:

在mkedge2上启动tcp-echo-cloud容器,通过mkedge3上的busybox容器进行远程telnet tcp-echo-cloud 2701

说明:

从实际流量抓包看来看,从办公室机房能够直接访问到天坛机房主机,不需要中继;

抓包详情

mkedge3发起请求tcp抓包
在这里插入图片描述
mkedge2上接收载荷tcp抓包
在这里插入图片描述

中继穿透请求端抓包

环境:

mkmaster1 10.200.50.118 国贸机房 edgemesh-server, edgemesh-agent role: master kernel: 5.2.14-1.el7.elrepo.x86_64

mkworker3 10.202.42.112 亦庄机房 edgemesh-agent role: cloudnode kernel: 5.2.14-1.el7.elrepo.x86_64

mkedge3 10.136.77.2 北四环办公室边缘盒子 edgemesh-agent role: edgenode kernel: 3.10.0-514.el7.x86_64

用例:

在mkworker3 上启动tcp-echo-cloud容器,通过mkedge3上的busybox容器进行远程telnet tcp-echo-cloud 2701

说明:

从实际流量抓包看来看,从办公室机房不能直接访问到亦庄机房主机,需要国贸机房的edgemesh-server中继;

抓包详情

mkedge3发起请求tcp抓包
在这里插入图片描述
mkmaster1进行中继tcp抓包
在这里插入图片描述

mkworker3上接收载荷tcp抓包
在这里插入图片描述

失败访问情况分析

先说结论,怀疑主机默认内核4.15.6-1.el7.elrepo.x86_64 (centos7) 存在调用系统内核函数getsockopt的严重bug!!!

失败穿透请求抓包

环境:

mkmaster1 10.200.50.118 国贸机房 edgemesh-server, edgemesh-agent role: master kernel: 5.2.14-1.el7.elrepo.x86_64

mkworker1 10.201.82.139 天坛机房 edgemesh-agent role: cloudnode kernel: 5.2.14-1.el7.elrepo.x86_64

mkworker2 10.201.83.74 天坛机房 edgemesh-agent role: cloudnode kernel: 5.2.14-1.el7.elrepo.x86_64

mkedge1 10.201.82.148 天坛机房 edgemesh-agent role: edgenode kernel: 4.15.6-1.el7.elrepo.x86_64

mkedge2 10.201.82.131 天坛机房 edgemesh-agent role: edgenode kernel: 4.15.6-1.el7.elrepo.x86_64

mkedge3 10.136.77.2 北四环办公室边缘盒子 edgemesh-agent role: edgenode kernel: 3.10.0-514.el7.x86_64

用例:

在mkworker1、mkworker2、mkedge1、mkedge2上启动同时启动tcp-echo-cloud容器,通过mkedge1上的busybox容器进行远程访问这些服务,发现一个都不通;

现状:

从实际流量抓包看来看,从业务容器发送请求给到edgemesh-agent:40001端口,之后流量就不见了踪影;

  1. 在反覆启停这些容器,经过多次测试,的确无法进行正常通信;
  2. 怀疑是iptables的问题,因丢失了DOCKER链之后,重启docker服务也无法创建规则链;
  3. 重启edgemesh-agent服务,发现其容器无法被清除,一直是Running状态,如果docker rm -f强制停止,会导致edgemesh-agent在宿主机上留下僵尸进程;
    在这里插入图片描述
初始排查阶段

究竟是Go的版本不对?容器镜像问题?逐个进行了排查:

给edgemesh-agent:proxy.go的Run()函数中,realServerAddress(&conn)调用之前,增加了打印日志,同时在这个函数调用中,增加了一些日志输出标记位,重新制作容器进行测试,发现输出日志555,但未输出666,同时进程状态异常变为了D

func (proxy *EdgeProxy) Run() {
	// ensure ipatbles
	proxy.Proxier.Start()

	// start tcp proxy
	for {
		conn, err := proxy.TCPProxy.Listener.Accept()
		if err != nil {
			klog.Warningf("get tcp conn error: %v", err)
			continue
		}
		klog.Info("!!! has workload !!!")
		ip, port, err := realServerAddress(&conn)
        ...
    }
}



// realServerAddress returns an intercepted connection's original destination.
func realServerAddress(conn *net.Conn) (string, int, error) {
	tcpConn, ok := (*conn).(*net.TCPConn)
	if !ok {
		return "", -1, fmt.Errorf("not a TCPConn")
	}
	klog.Info("111")
	file, err := tcpConn.File()
	if err != nil {
		return "", -1, err
	}
	defer file.Close()
	klog.Info("222")
	// To avoid potential problems from making the socket non-blocking.
	tcpConn.Close()
	*conn, err = net.FileConn(file)
	if err != nil {
		return "", -1, err
	}
	klog.Info("333")
	fd := file.Fd()
	klog.Info("444")
	var addr sockAddr
	size := uint32(unsafe.Sizeof(addr))
	err = getSockOpt(int(fd), syscall.SOL_IP, SoOriginalDst, uintptr(unsafe.Pointer(&addr)), &size)
	if err != nil {
		return "", -1, err
	}
	klog.Info("777")
	var ip net.IP
	switch addr.family {
	case syscall.AF_INET:
		ip = addr.data[2:6]
	default:
		return "", -1, fmt.Errorf("unrecognized address family")
	}
	klog.Info("888")
	port := int(addr.data[0])<<8 + int(addr.data[1])
	if err := syscall.SetNonblock(int(fd), true); err != nil {
		return "", -1, nil
	}

	return ip.String(), port, nil
}

func getSockOpt(s int, level int, name int, val uintptr, vallen *uint32) (err error) {
	klog.Info("555")
	_, _, e1 := syscall.Syscall6(syscall.SYS_GETSOCKOPT, uintptr(s), uintptr(level), uintptr(name), uintptr(val), uintptr(unsafe.Pointer(vallen)), 0)
	if e1 != 0 {
		err = e1
	}
	klog.Info("666")
	return
}

然后就开始怀疑是这个内核调用出现了问题,先编写了一个go程序:https://gitee.com/Hu-Lyndon/gogetsockopt.git

如下是在mkedge3宿主机上,kernel: 3.10.0-514.el7.x86_64,一个终端上先将程序启动监听18011端口,然后在另外一个连接mkedge3终端上,直接telnet这个端口,程序能够正确通信,并输出打印日志address: 192.168.127.1:18011
在这里插入图片描述
如下是在mkedge1上测试,kernel: 4.15.6-1.el7.elrepo.x86_64, 发现telnet之后,无法输出打印日志,并且程序在第三个终端窗口里也无法kill -9,并且进程状态变为了D
在这里插入图片描述

不死心,程序在mkedge2上依然如上;

不死心,找了台内核升级为kernel: 4.19.12-1.el7.elrepo.x86_64的主机,结果是正确的;
在这里插入图片描述

再次排查阶段

不死心:虽然严重怀疑了kernel: 4.15.6-1.el7.elrepo.x86_64,那么就近是Go的问题,还是系统内核的问题呢?于是又写了一个C程序来测试:https://gitee.com/Hu-Lyndon/unix-network-programming,在compile-on-kernel4.15.6分支的/sockopt/sockoptchk.c

依然出现了上面出现的问题,说明应该就是kernel的问题了,需要推荐运维以后不要安装这个版本的内核了,目前看4.19.12以上的版本或者干脆就是3.10的内核都是打过patch的。
在这里插入图片描述

相关资料

https://go.dev/play/p/GMAaKucHOr

https://elixir.bootlin.com/linux/latest/C/ident/sys_getsockopt

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/socket.c

https://bugzilla.kernel.org/show_bug.cgi?id=198791

http://patchwork.ozlabs.org/project/netfilter-devel/patch/a4752d0887579496c5db267d9db7ff77719436d8.1518088560.git.pabeni@redhat.com/

https://github.com/cybozu-go/transocks/pull/14

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
iptables是一个用于Linux系统的防火墙工具,它可以对网络流量进行过滤和控制。通过iptables,你可以定义规则来允许或拒绝特定类型的网络流量iptables使用规则链(rule chains)来管理流量过滤,包括输入链(INPUT)、输出链(OUTPUT)和转发链(FORWARD)。每个链中都包含一系列规则,这些规则定义了如何处理进入或离开系统的数据包。 在iptables中,每个规则由三个主要部分组成:匹配条件(match conditions)、动作(actions)和目标(targets)。匹配条件指定要应用规则的数据包的特征,例如源IP地址、目标端口等。动作定义了当匹配条件满足要执行的操作,例如允许通过或丢弃数据包。目标指定规则所属的链。 以下是iptables的一些常见命令和功能: 1. 添加规则:使用`iptables -A`命令添加一条新的规则,例如`iptables -A INPUT -s 192.168.1.0/24 -j ACCEPT`表示允许来自192.168.1.0/24子网的数据包通过输入链。 2. 删除规则:使用`iptables -D`命令删除指定的规则,例如`iptables -D INPUT 2`表示删除输入链中的第2条规则。 3. 显示规则:使用`iptables -L`命令查看当前所有规则,例如`iptables -L INPUT`表示显示输入链的规则列表。 4. 默认策略:使用`iptables -P`命令设置默认策略,当没有匹配任何规则将采取的动作。例如`iptables -P INPUT DROP`表示设置输入链的默认动作为丢弃(DROP)。 5. 网络地址转换(NAT):除了过滤功能外,iptables还可以用于进行网络地址转换。通过使用`-t nat`选项,可以创建NAT规则,实现端口转发、SNAT和DNAT等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值