大家好,我是考100分的小小码 ,祝大家学习进步,加薪顺利呀。今天说一说golang socket框架_go netty,希望您对编程的造诣更进一步.
前置知识:希望您对 Linux Namespace 有所了解,以及一些基础的网络知识。
1. 简介
-
先简略的介绍下
Bridge
,它是一个虚拟的网络设备,linux 内核已经实现了该网络设备的功能。所以我们可以直接通过命令就能创建出 Bridge。很多人为了更好的理解 Bridge 这个网络设备都会将它类比成交换机
,但其实这种理解会很容易误导且对其他网络设备的理解会形成障碍。其实 Bridge 它就是一种协议或者是一种类型,进行实例化后形成二层网络设备
对象,而网络设备分为二层三层设备。 -
Net Namespace
是实现网络虚拟化的重要功能,它能创建多个隔离的网络空间,它们有独自的网络栈信息。不管是虚拟机还是容器,运行的时候仿佛自己就在独立的网络中。什么是Linux Namespace ? -
最后同步一个概念
二层交换
:使用 MAC 地址进行通信;三层交换
:使用 IP 地址进行通信,IP就需要网关(下一跳地址);
2. Linux 环境实验
实验环境 Centos7.9 (3.10.0-1160.el7.x86_64)
2.1 NetNamespace 间通信
- 图示,这种做法只是为了展示namespace 之间通信,生产上很少出现这样的场景;
- Console (实验)
# 创建net namespace, ns0 和 ns1 > ip netns add ns0 > ip netns add ns1 > ip netns list ns1 (id: 1) ns0 (id: 0) # 创建 veth 以太对设备,这是一个成对出现的设备,用于在两个net namespace中传输数据 # 创建一个veth0 和 veth1 并将veth1绑定在 ns1 中 > ip link add veth0 type veth peer name veth1 netns ns1 # 将 veth0 绑定到 ns0 > ip link set veth0 netns ns0 # 查看 ns0 设备, 两个设备都为 DOWN > ip -n ns0 link 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state DOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 11: veth0@if10: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default qlen 1000 link/ether 2e:73:f7:b1:52:c2 brd ff:ff:ff:ff:ff:ff link-netnsid 0 # 为两个veth设置地址,并启用 lo,veth > ip -n ns0 addr add 10.0.1.1/24 dev veth0 > ip -n ns0 link set veth0 up > ip -n ns0 link set lo up > ip -n ns1 addr add 10.0.1.2/24 dev veth1 > ip -n ns1 link set veth1 up > ip -n ns1 link set lo up # 最后做ping测试 > ip netns exec ns1 ping 10.0.1.1 PING 10.0.1.1 (10.0.1.1) 56(84) bytes of data. 64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.232 ms > ip netns exec ns0 ping 10.0.1.2 PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data. 64 bytes from 10.0.1.2: icmp_seq=1 ttl=64 time=0.293 ms
2.2 Bridge、NetNamespace、Localhost 通信
- 这种做法是一种常态做法,在K8s 和 Docker中都是常见的场景;下面会顺带解析二层mac交换与三层IP交换;架构图示:
-
Console(实验),图中有3个namespace,下面实验只建立两个做示范
# 安装bridge 查看工具 > yum install -y bridge-utils # 创建 bridge (创建bridge默认创建一个vlan1),绑定一个IP后启用它。 > ip link add bridge0 type bridge > ip addr add 10.0.1.254/24 dev bridge0 > ip link set bridge0 up # 创建 net0 namespace,并创建veth(和第一个实验类似) > ip netns add net0 # 创建 veth 将eth0 放入 net0 > ip link add veth0 type veth peer name eth0 netns net0 # 将veth0 绑定到 bridge0 上 > ip link set veth0 master bridge0 # 为 eth0 绑定ip地址 > ip -n net0 addr add 10.0.1.1/24 dev eth0 brd + # 启动设备 > ip -n net0 link set dev eth0 up > ip -n net0 link set dev lo up > ip link set dev veth0 up # 查看net0设备情况 > ip -n net0 link 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo ... 2: eth0@if18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 46:ed:ec:f0:63:07 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.0.1.1/24 scope global eth0 ... # 这里在说一个知识点 eth0@if18 的 if18对应的是主namespace 下 ip link 的18号设备,如果你删除外面的这个以太对设备,那么net0里面的也会一并删除。 > ip link ... 18: veth0@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master bridge0 state UP mode DEFAULT group default qlen 1000 link/ether 0e:d5:0e:0c:e7:73 brd ff:ff:ff:ff:ff:ff link-netnsid 0 ... # 下面是创建net1的操作 > ip netns add net1 > ip link add veth1 type veth peer name eth1 netns net1 > ip link set veth1 master bridge0 > ip -n net1 addr add 10.0.1.2/24 dev eth0 brd + > ip -n net1 link set dev eth1 up > ip -n net1 link set dev lo up > ip link set dev veth1 up # 查看现在bridge0 中拥有的veth设备数量,可以看到是2个 > brctl show bridge0 bridge name bridge id STP enabled interfaces bridge0 8000.0ed50e0ce773 no veth0 veth1
-
下面进行bridge0 内 netns 互通测试,可以看到是没有问题
# ip netns exec net0 ping 10.0.1.2 PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data. 64 bytes from 10.0.1.2: icmp_seq=1 ttl=64 time=0.061 ms 64 bytes from 10.0.1.2: icmp_seq=2 ttl=64 time=0.080 ms ... # 这是通过二层mac寻址(二层交换),可以看看在linux上如何查看他们 # net0上来记录对端的mac地址以及ip地址 > ip netns exec net0 ip neigh 10.0.1.2 dev eth0 lladdr 56:01:64:48:c3:87 STALE 10.0.1.254 dev eth0 lladdr 0e:d5:0e:0c:e7:73 STALE # 可以看到net1 的mac地址 56:01:64:48:c3:87 >ip -n net1 link ... 2: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 56:01:64:48:c3:87 brd ff:ff:ff:ff:ff:ff link-netnsid 0
-
那我们继续测试netns 与 LocalHost 主机是否互通,我这里的主机地址是192.168.0.3
> ip netns exec net0 ping 192.168.0.3 connect: 网络不可达
OMG,可以看到 netns 到达主机是不通。因为 net0 所在网段是10.0.1.0/24 这个网段,而我的主机网段是192.168.0.0/24,二层广播域也是不通的所以无法进行二层交换。(还有一点是net0是通过bridge0连接主机,而bridge0本身再创建的时候默认划分了一个vlan1。)
由此我们应该想到就是走三层交换,即通过路由寻址。
# 分别给给net0、net1 加上默认路由, 所有流量下一跳都走 10.0.1.254 由eth0 出。 > ip netns exec net0 ip route add default via 10.0.1.254 dev eth0 > ip netns exec net1 ip route add default via 10.0.1.254 dev eth0 # 查看net0 的路由 > ip netns exec net0 route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 10.0.1.254 0.0.0.0 UG 0 0 0 eth0 10.0.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 # 还需要一条在主机上回包的路由,去往10.0.1.0/24网段的流量走 10.0.1.254 由bridge0这个设备出 # 先删除创建bridge时系统自动创建的一条默认路由 > ip route del 10.0.1.0/24 > ip route add 10.0.1.0/24 via 10.0.1.254 dev bridge0 # 查看主机的路由 > route -n Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.0.1 0.0.0.0 UG 100 0 0 eth0 10.0.1.0 10.0.1.254 255.255.255.0 UG 0 0 0 bridge0 192.168.0.0 0.0.0.0 255.255.255.0 U 100 0 0 eth0 # 再测试下 > ip netns exec net0 ping 192.168.0.3 PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data. 64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.061 ms 64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=0.080 ms
PS: 写路由时由哪个设备出是很重要的,由于不同的网络设备对数据包会做不同的封装。
2.3 Bridge、NetNamespace、RemoteHost 通信
- 上个小节写了实现了前面三种,这次只需要实现Remotehost通信即可。如果理解上面一节的三层交换含义,那么就能得知跨主机通信也是需要三层交换。
2.3.1 实验
-
架构图
* 主机A: 192.168.0.3;net0 网段:10.0.1.0/24 * 主机B: 192.168.0.2;net0 网段:10.0.2.0/24 * 默认网关: 192.168.0.1 * 实验内容: 两台机器的net0 namespace间互通,相当于 10.0.1.1 需要 ping 通 10.0.1.2;以及10.0.1.1 ping 通 192.168.0.2
-
构建出 net0 到本机的互通 (192.168.0.3)
# 和上一个实验是一样的流程配置即可
-
构建出 net0 到本机的互通 (192.168.0.2)
# bridge # > ip link add dev bridge0 type bridge > ip link set dev bridge0 up > ip addr add 10.0.2.254/24 dev bridge0 # net0 # > ip netns add net0 > ip link add dev veth0 type veth peer name eth0 netns net0 # brd + 是设置 10.0.2.255 为广播地址 > ip -n net0 addr add 10.0.2.1/24 dev eth0 brd + > ip -n net0 link set dev eth0 up > ip -n net0 link set dev lo up > ip link set dev veth0 master bridge0 > ip link set dev veth0 up # 查看 veth0 bridge0 都是up > ip link # 配置net0 默认路由 > ip netns exec net0 ip route add default via 10.0.2.254 dev eth0 # 配置主机路由 > ip route del 10.0.2.0/24 > ip route add 10.0.2.0/24 via 10.0.2.254 dev bridge0 # ping 通测试 > ip netns exec net0 ping 192.168.0.2
-
到此两台机器的net0都是可以ping 通主机的出口IP,下面正式进行跨主机联通;
-
net0 (10.0.1.1) -> 主机(192.168.0.2)-> GW (192.168.0.,1)
- 通过iptables进行伪装发送即可,这样的做法即可以把网络包发送到网关和ping通主机,但是还不能解决跨主机的namespace通信。
# 这里其实有两种做法 # 。 # 192.168.0.3 > echo 1 > /proc/sys/net/ipv4/ip_forward > iptables -t nat -A POSTROUTING -s 10.0.1.0/24 -j MASQUERADE # 192.168.0.2 > echo 1 > /proc/sys/net/ipv4/ip_forward > iptables -t nat -A POSTROUTING -s 10.0.2.0/24 -j MASQUERADE # 现在就可以测试 ping > ip netns exec net0 ping 192.168.0.2
- 通过iptables进行伪装发送即可,这样的做法即可以把网络包发送到网关和ping通主机,但是还不能解决跨主机的namespace通信。
-
net0 (10.0.1.1) -> 主机(192.168.0.2)-> net0 (10.0.2.1)
-
其实最简单的办法就是再两个机器上都各自写一条路由,做三层交换即可
# 192.168.0.3 > ip route del 10.0.1.0/24 # 直接为目标 10.0.2.0 的网段指定下一跳 > ip route 10.0.2.0/24 via 192.168.0.2 dev eth0 # 192.168.0.2 > ip route del 10.0.2.0/24 # 直接为目标 10.0.1.0 的网段指定下一跳 > ip route 10.0.1.0/24 via 192.168.0.3 dev eth0 # 测试 ping 主机 > ip netns exec net0 ping 192.168.0.2 # 测试 net0 (10.0.2.1) > ip netns exec net0 ping 10.0.2.1
-
3. 代码演示
- 这里使用用了3个库来完成代码的开发
netlink
、netns
和go-iptables
,废了一番功夫倒腾出来了,整个程序是幂等的,也就是可以多次运行结果都是一样的;
3.1 安装库
> go get "github.com/coreos/go-iptables/iptables"
> go get "github.com/vishvananda/netlink"
3.2 代码
- netfilter_study.go,最终允许是分别在两台主机上运行的,实现了2.3.1的效果,代码都比较简单结合注释和末尾会有一些难点解析。
package main import ( "fmt" "github.com/coreos/go-iptables/iptables" "github.com/vishvananda/netlink" "github.com/vishvananda/netns" "log" "net" "os" "os/exec" "runtime" ) /* #实验环境# 主机A: HOST: 192.168.0.3 ns0: 10.0.1.1/24 bridge0: 10.0.1.254/24 主机B: HOST: 192.168.0.2 ns0: 10.0.2.1/24 bridge0: 10.0.2.254/24 #实现目标# 10.0.1.1 -> 192.168.0.2 10.0.1.1 -> 10.0.2.1 */ var ( IsHostA = false ) const ( EnvName = "IS_HOST_A" // 无需修改 NSName = "ns0" BridgeName = "bridge0" VEth0 = "veth0" NsEth0 = "nseth0" // 如果需要在自己的机器上实现,需要改动HostA 和 HostB 变量即可 HostA = "192.168.0.3/24" HostANS0 = "10.0.1.1/24" HostABridge0 = "10.0.1.254/24" HostB = "192.168.0.2/24" HostBNS0 = "10.0.2.1/24" HostBBridge0 = "10.0.2.254/24" ) func Error(e error) { if e != nil { log.Fatalln(e) } } type DoFunc func(oNs, nNs *netns.NsHandle) error func SetupBridge() *netlink.Bridge { log.Println("SetupBridge...runing") linkDev, _ := netlink.LinkByName(BridgeName) if linkDev != nil { if _, ok := linkDev.(*netlink.Bridge); ok { Error(netlink.LinkDel(linkDev)) } } br0 := &netlink.Bridge{ LinkAttrs: netlink.LinkAttrs{ Name: BridgeName, MTU: 1500, TxQLen: -1, }, } // 添加 Error(netlink.LinkAdd(br0)) // 启动 Error(netlink.LinkSetUp(br0)) // 设置ip hb := HostABridge0 if !IsHostA { hb = HostBBridge0 } ipv4Net, err := netlink.ParseIPNet(hb) Error(err) Error(netlink.AddrAdd(br0, &netlink.Addr{ IPNet: ipv4Net, })) log.Println("SetupBridge...done") return br0 } func SetupNetNamespace() *netns.NsHandle { runtime.LockOSThread() defer runtime.UnlockOSThread() log.Println("SetupNetNamespace...running") origns, _ := netns.Get() defer origns.Close() _, err := netns.GetFromName(NSName) Error(netns.Set(origns)) if err == nil { log.Printf("%s net ns is exists. Delete netns %s\n", NSName, NSName) cmd := exec.Command( "sh", "-c", fmt.Sprintf("/usr/sbin/ip netns del %s", NSName)) Error(cmd.Run()) } // 由于程序上启动netns 是需要附着于进程上的,所以这里直接使用 ip netns 来创建net namespace cmd := exec.Command("sh", "-c", fmt.Sprintf("/usr/sbin/ip netns add %s", NSName)) Error(cmd.Run()) ns, err := netns.GetFromName(NSName) Error(err) log.Println("SetupNetNamespace...done") return &ns } func SetupVEthPeer(br *netlink.Bridge, ns *netns.NsHandle) { log.Println("SetupVEth...running") oBrVEth, _ := netlink.LinkByName(VEth0) if oBrVEth != nil { log.Printf("%s is exists, it will be delete. \n", VEth0) Error(netlink.LinkDel(oBrVEth)) } log.Printf("Create vethpeer %s peer name %s\n", VEth0, NsEth0) vethPeer := &netlink.Veth{ LinkAttrs: netlink.LinkAttrs{ Name: VEth0, }, PeerName: NsEth0, } Error(netlink.LinkAdd(vethPeer)) log.Printf("Set %s to %s \n", vethPeer.PeerName, NSName) // 获取 ns 的 veth nsVeth, err := netlink.LinkByName(vethPeer.PeerName) Error(err) // 设置ns 的 veth Error(netlink.LinkSetNsFd(nsVeth, int(*ns))) log.Printf("Set %s master to %s \n", vethPeer.Name, BridgeName) // 获取 host 的 veth brVeth, err := netlink.LinkByName(vethPeer.Name) Error(err) // 设置 bridge 的 veth Error(netlink.LinkSetMaster(brVeth, br)) log.Printf("Set up %s \n", vethPeer.Name) Error(netlink.LinkSetUp(brVeth)) Error(NsDo(func(oNs, nNs *netns.NsHandle) error { // 设置IP地址 hn := HostANS0 if !IsHostA { hn = HostBNS0 } log.Printf("Addr add ip to %s \n", vethPeer.Name) ipv4Net, err := netlink.ParseIPNet(hn) Error(err) Error(netlink.AddrAdd(nsVeth, &netlink.Addr{ IPNet: ipv4Net, })) log.Printf("Set up %s \n", vethPeer.PeerName) // 启动设备 Error(netlink.LinkSetUp(nsVeth)) log.Println("SetupVEth...done") return nil })) } func SetupNsDefaultRoute() { Error(NsDo(func(oNs, nNs *netns.NsHandle) error { // add default gate way log.Println("SetupNsDefaultRouter... running") log.Printf("Add net namespace %s default gateway.", NSName) gwAddr := HostBBridge0 if IsHostA { gwAddr = HostABridge0 } gw, err := netlink.ParseIPNet(gwAddr) Error(err) defaultRoute := &netlink.Route{ Dst: nil, Gw: gw.IP, } Error(netlink.RouteAdd(defaultRoute)) log.Println("SetupNsDefaultRouter... done") return nil })) } func SetupIPTables() { log.Println("Setup IPTables for ns transfer packet to remote host...running") // 读取本地iptables,默认是ipv4 ipt, err := iptables.New() Error(err) // iptables -t nat -A POSTROUTING -s 10.0.1.0/24 -j MASQUERADE nsAddr := HostBNS0 if IsHostA { nsAddr = HostANS0 } _, srcNet, err := net.ParseCIDR(nsAddr) Error(err) rule := []string{"-s", srcNet.String(), "-j", "MASQUERADE"} // 先进行清除后再添加,保持简易幂等 _ = ipt.Delete("nat", "POSTROUTING", rule...) Error(ipt.Append("nat", "POSTROUTING", rule...)) log.Println("Setup IPTables done") } func SetupRouteNs2Ns() { log.Println("Setup route HostA(net0) <==> HostB(net0)... running") // 10.0.1.0/24 via 192.168.0.3 dev eth0 gwAddr := HostA dstAddr := HostANS0 if IsHostA { gwAddr = HostB dstAddr = HostBNS0 } _, dstNet, err := net.ParseCIDR(dstAddr) Error(err) gwNet, err := netlink.ParseIPNet(gwAddr) Error(err) // 先删除后增加 _ = netlink.RouteDel(&netlink.Route{ LinkIndex: netlink.NewLinkAttrs().Index, Dst: dstNet, }) Error(netlink.RouteAdd(&netlink.Route{ LinkIndex: netlink.NewLinkAttrs().Index, Dst: dstNet, Gw: gwNet.IP, })) log.Println("Setup route HostA(net0) <==> HostB(net0) done") } func NsDo(doFunc DoFunc) error { // 进入netns, 使用线程锁固定当前routine 不会其他线程执行,避免namespace 执行出现异常。 // 因为如果在执行过程中切换了线程,可能找不到已经建立好的namespace。 runtime.LockOSThread() defer runtime.UnlockOSThread() log.Printf("Switch net namespace to %s \n", NSName) originNs, err := netns.Get() Error(err) // 切换回原始ns defer func() { log.Println("Switch net namespace to origin") Error(netns.Set(originNs)) }() ns, err := netns.GetFromName(NSName) Error(err) Error(netns.Set(ns)) return doFunc(&originNs, &ns) } func main() { if os.Getenv(EnvName) == "1" { IsHostA = true } ns := SetupNetNamespace() bridge := SetupBridge() SetupVEthPeer(bridge, ns) SetupNsDefaultRoute() SetupIPTables() SetupRouteNs2Ns() log.Println("Config Finished.") }
- 从名字上看基本和之前在命令的使用是一致的,创建namespace、bridge 和 vethpeer 然后进行veth绑定,最后配置路由和iptables。
- 唯一有个疑点是在
NsDo
这个函数,这个函数是一个必包函数,主要为了锁定线程;在go里,每个操作系统线程都可能对应不同的network namespace,但由于go线程调度机制的特点,在我们的代码逻辑开始执行的时候,并不能够保证当前的network namespace就是我们创建的或者是我们开始时候的namespace,这样会导致我们不知道当前身处什么space下。(涉及知识点:GO调度模型(GPM)
,Linux Process Namespace
)
3.3 运行
- 在HostA 上运行:
export IS_HOST_A=1; go run netfilter_study.go
- 在HostB 上运行:
export IS_HOST_A=0; go run netfilter_study.go
- 运行后效果,看到 Config Finished 即可。
> export IS_HOST_A=1; go run netlink_study.go 2022/03/14 17:23:58 SetupNetNamespace...running 2022/03/14 17:23:58 ns0 net ns is exists. Delete netns ns0 2022/03/14 17:23:58 SetupNetNamespace...done 2022/03/14 17:23:58 SetupBridge...runing 2022/03/14 17:23:58 SetupBridge...done 2022/03/14 17:23:58 SetupVEth...running 2022/03/14 17:23:58 veth0 is exists, it will be delete. 2022/03/14 17:23:58 Create vethpeer veth0 peer name nseth0 2022/03/14 17:23:58 Set nseth0 to ns0 2022/03/14 17:23:58 Set veth0 master to bridge0 2022/03/14 17:23:58 Set up veth0 2022/03/14 17:23:58 Switch net namespace to ns0 2022/03/14 17:23:58 Addr add ip to veth0 2022/03/14 17:23:58 Set up nseth0 2022/03/14 17:23:58 SetupVEth...done 2022/03/14 17:23:58 Switch net namespace to origin 2022/03/14 17:23:58 Switch net namespace to ns0 2022/03/14 17:23:58 SetupNsDefaultRouter... running 2022/03/14 17:23:58 Add net namespace ns0 default gateway. 2022/03/14 17:23:58 SetupNsDefaultRouter... done 2022/03/14 17:23:58 Switch net namespace to origin 2022/03/14 17:23:58 Setup IPTables for ns transfer packet to remote host...running 2022/03/14 17:23:58 Setup IPTables done 2022/03/14 17:23:58 Setup route HostA(net0) <==> HostB(net0)... running 2022/03/14 17:23:58 Setup route HostA(net0) <==> HostB(net0) done 2022/03/14 17:23:58 Config Finished.
- HostA上运行测试命令
> ip netns exec ns0 ping 10.0.2.1
4. 写在最后
-
之前我个人对linux
网络设备
的理解是误解,总以一种物理性质来理解它,比如一个网卡设备我很难理解为什么它还有子接口?如果将它以一种设备对象或软件化的去理解它就非常好理解。一个网卡设备默认有个主接口,那么相对应就可以创建子借口,再者想到网卡是一个多路复用和多路分解的网络设备,这样一来就容易消化了。 -
网络设备的
类型
,需要明确知道那种类型是二层还是三层。好比实体化二层交换机只会包含二层类型的设备与协议, 三层交换机(核心交换)就包含了二层三层设备与协议。 -
都看到这了给个
赞
再走吧。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13266.html