查看原文
其他

使用 Golang 玩转 Bridge 与 NetNamespace 互联

ShadowYD 云原生实验室 2022-11-27


本文转自掘金,原文:https://juejin.cn/post/7074894564879761416,版权归原作者所有。欢迎投稿,投稿请添加微信好友:cloud-native-yang

前置知识:希望您对 Linux Namespace 有所了解,以及一些基础的网络知识。

简介

  • 先简略的介绍下 Bridge,它是一个虚拟的网络设备,linux 内核已经实现了该网络设备的功能。所以我们可以直接通过命令就能创建出 Bridge。很多人为了更好的理解 Bridge 这个网络设备都会将它类比成 交换机,但其实这种理解会很容易误导且对其他网络设备的理解会形成障碍。其实 Bridge 它就是一种协议或者是一种类型,进行实例化后形成二层网络设备对象,而网络设备分为二层三层设备。
  • Net Namespace 是实现网络虚拟化的重要功能,它能创建多个隔离的网络空间,它们有独自的网络栈信息。不管是虚拟机还是容器,运行的时候仿佛自己就在独立的网络中。什么是 Linux Namespace ?[1]
  • 最后同步一个概念
    • 二层交换:使用 MAC 地址进行通信;
    • 三层交换:使用 IP 地址进行通信,IP 就需要网关(下一跳地址);

Linux 环境实验

实验环境 Centos7.9 (3.10.0-1160.el7.x86_64)

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

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: 写路由时由哪个设备出是很重要的,由于不同的网络设备对数据包会做不同的封装。

Bridge、NetNamespace、RemoteHost 通信

上个小节写了实现了前面三种,这次只需要实现 Remotehost 通信即可。如果理解上面一节的三层交换含义,那么就能得知跨主机通信也是需要三层交换。

实验

架构图

* 主机 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) - 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

net0 (10.0.1.1) - 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 个库来完成代码的开发 netlinknetnsgo-iptables,废了一番功夫倒腾出来了,整个程序是幂等的,也就是可以多次运行结果都是一样的;

安装库

$ go get "github.com/coreos/go-iptables/iptables"
$ go get "github.com/vishvananda/netlink"

代码

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

运行

在 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

写在最后

  • 之前我个人对 linux网络设备的理解是误解,总以一种物理性质来理解它,比如一个网卡设备我很难理解为什么它还有子接口?如果将它以一种设备对象或软件化的去理解它就非常好理解。一个网卡设备默认有个主接口,那么相对应就可以创建子借口,再者想到网卡是一个多路复用和多路分解的网络设备,这样一来就容易消化了。
  • 网络设备的类型,需要明确知道那种类型是二层还是三层。好比实体化二层交换机只会包含二层类型的设备与协议, 三层交换机(核心交换)就包含了二层三层设备与协议。

引用链接

[1]

什么是 Linux Namespace ?: https://juejin.cn/post/6901477931265196039




你可能还喜欢

点击下方图片即可阅读

云原生是一种信仰 🤘

关注公众号

后台回复◉k8s◉获取史上最方便快捷的 Kubernetes 高可用部署工具,只需一条命令,连 ssh 都不需要!



点击 "阅读原文" 获取更好的阅读体验!


发现朋友圈变“安静”了吗?

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存