diff --git a/0006-support-ipv6.patch b/0006-support-ipv6.patch new file mode 100644 index 0000000..64af5ad --- /dev/null +++ b/0006-support-ipv6.patch @@ -0,0 +1,2085 @@ +From 9acfe3e5c2889c511d28447f1660911c4ce17cc5 Mon Sep 17 00:00:00 2001 +From: vegbir +Date: Thu, 10 Aug 2023 02:44:07 +0000 +Subject: [PATCH] support ipv6 + +Signed-off-by: vegbir +--- + Makefile | 2 +- + config/config_network.go | 3 +- + libnetwork/drivers/common/driver.go | 11 + + libnetwork/drivers/common/driver_test.go | 68 +++ + libnetwork/drivers/driver.go | 30 ++ + libnetwork/drivers/driver_test.go | 147 +++++++ + libnetwork/drivers/eth/driver.go | 59 ++- + libnetwork/drivers/eth/driver_test.go | 397 +++++++++++++++++ + libnetwork/interfaces.go | 37 +- + libnetwork/interfaces_test.go | 255 +++++++++++ + libnetwork/route.go | 12 - + libnetwork/route_test.go | 143 +++++++ + network.go | 14 +- + types/network.go | 49 ++- + types/network_test.go | 514 +++++++++++++++++++++++ + 15 files changed, 1699 insertions(+), 42 deletions(-) + create mode 100644 libnetwork/drivers/common/driver_test.go + create mode 100644 libnetwork/drivers/driver_test.go + create mode 100644 libnetwork/drivers/eth/driver_test.go + create mode 100644 libnetwork/interfaces_test.go + create mode 100644 libnetwork/route_test.go + create mode 100644 types/network_test.go + +diff --git a/Makefile b/Makefile +index 556382c..ede386b 100644 +--- a/Makefile ++++ b/Makefile +@@ -48,7 +48,7 @@ syscontainer-hooks: $(SOURCES) | $(DEPS_LINK) + @echo "Done!" + + localtest: +- ${ENV} go test -mod=vendor -tags ${TAGS} -ldflags ${GO_LDFLAGS} -v ./... ++ go test -mod=vendor -v ./... + + clean: + rm -rf build +diff --git a/config/config_network.go b/config/config_network.go +index 4e0da46..1bb9c7f 100644 +--- a/config/config_network.go ++++ b/config/config_network.go +@@ -15,8 +15,9 @@ package config + + import ( + "fmt" +- "isula.org/syscontainer-tools/types" + "path/filepath" ++ ++ "isula.org/syscontainer-tools/types" + ) + + var ( +diff --git a/libnetwork/drivers/common/driver.go b/libnetwork/drivers/common/driver.go +index c2dadd7..a2158a1 100644 +--- a/libnetwork/drivers/common/driver.go ++++ b/libnetwork/drivers/common/driver.go +@@ -27,6 +27,7 @@ type Driver struct { + hostName string + mac *net.HardwareAddr + ip *net.IPNet ++ ip6 *net.IPNet + bridge string + bridgeDriver api.BridgeDriver + mtu int +@@ -73,6 +74,16 @@ func (d *Driver) GetIP() *net.IPNet { + return d.ip + } + ++// SetIP6 will set the network interface ip6 ++func (d *Driver) SetIP6(addr *net.IPNet) { ++ d.ip6 = addr ++} ++ ++// GetIP6 will get the network interface ip6 ++func (d *Driver) GetIP6() *net.IPNet { ++ return d.ip6 ++} ++ + // SetMac will set the network interface mac + func (d *Driver) SetMac(mac *net.HardwareAddr) { + d.mac = mac +diff --git a/libnetwork/drivers/common/driver_test.go b/libnetwork/drivers/common/driver_test.go +new file mode 100644 +index 0000000..7247232 +--- /dev/null ++++ b/libnetwork/drivers/common/driver_test.go +@@ -0,0 +1,68 @@ ++// Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. ++// syscontainer-tools licensed under the Mulan PSL v2. ++// You can use this software according to the terms and conditions of the Mulan PSL v2. ++// You may obtain a copy of Mulan PSL v2 at: ++// http://license.coscl.org.cn/MulanPSL2 ++// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++// PURPOSE. ++// See the Mulan PSL v2 for more details. ++// Author: Jiaqi Yang ++// Date: 2023-05-03 ++// Description: This file is used for test common.driver package ++ ++// package common is common network driver implementation ++package common ++ ++import ( ++ "net" ++ "testing" ++) ++ ++// TestDriver_GetAndSet tests setter & getter methods ++func TestDriver_GetAndSet(t *testing.T) { ++ type fields struct { ++ nsPath string ++ ctrName string ++ hostName string ++ mac *net.HardwareAddr ++ ip *net.IPNet ++ ip6 *net.IPNet ++ bridge string ++ mtu int ++ qlen int ++ } ++ tests := []struct { ++ name string ++ fields fields ++ }{ ++ { ++ name: "TC1-sety & get sucessfully", ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ d := &Driver{} ++ d.SetIP6(tt.fields.ip6) ++ d.SetIP(tt.fields.ip) ++ d.SetBridge(tt.fields.bridge) ++ d.SetCtrNicName(tt.fields.ctrName) ++ d.SetHostNicName(tt.fields.hostName) ++ d.SetMac(tt.fields.mac) ++ d.SetMtu(tt.fields.mtu) ++ d.SetQlen(tt.fields.qlen) ++ d.SetNsPath(tt.fields.nsPath) ++ ++ d.GetBridge() ++ d.GetCtrNicName() ++ d.GetHostNicName() ++ d.GetIP() ++ d.GetIP6() ++ d.GetMac() ++ d.GetMtu() ++ d.GetQlen() ++ d.GetNsPath() ++ d.GetBridgeDriver() ++ }) ++ } ++} +diff --git a/libnetwork/drivers/driver.go b/libnetwork/drivers/driver.go +index a87831d..86cac7a 100644 +--- a/libnetwork/drivers/driver.go ++++ b/libnetwork/drivers/driver.go +@@ -111,16 +111,46 @@ func NicOptionHostNicName(name string) DriverOptions { + func NicOptionIP(ip string) DriverOptions { + return func(d *common.Driver) error { + ip = strings.TrimSpace(ip) ++ if len(ip) == 0 { ++ return nil ++ } ++ + ipnet, err := netlink.ParseIPNet(ip) + if err != nil { + return err + } ++ if ipnet.IP.To4() == nil { ++ // fail to get ip4 ++ return fmt.Errorf("ip only accepts CIDR data in ipv4 format, not %v", ipnet.String()) ++ } + + d.SetIP(ipnet) + return nil + } + } + ++// NicOptionIP6 handles network interface ip6 option ++func NicOptionIP6(ip6 string) DriverOptions { ++ return func(d *common.Driver) error { ++ ip6 = strings.TrimSpace(ip6) ++ if len(ip6) == 0 { ++ return nil ++ } ++ ++ ipnet6, err := netlink.ParseIPNet(ip6) ++ if err != nil { ++ return err ++ } ++ if ipnet6.IP.To4() != nil { ++ // can get ip4 ++ return fmt.Errorf("ip6 only accepts CIDR data in ipv6 format, not %v", ipnet6.String()) ++ } ++ ++ d.SetIP6(ipnet6) ++ return nil ++ } ++} ++ + // NicOptionMac handles network interface mac option + func NicOptionMac(mac string) DriverOptions { + return func(d *common.Driver) error { +diff --git a/libnetwork/drivers/driver_test.go b/libnetwork/drivers/driver_test.go +new file mode 100644 +index 0000000..5640130 +--- /dev/null ++++ b/libnetwork/drivers/driver_test.go +@@ -0,0 +1,147 @@ ++// Copyright (c) Huawei Technologies Co., Ltd. 2018-2023. All rights reserved. ++// syscontainer-tools is licensed under the Mulan PSL v2. ++// You can use this software according to the terms and conditions of the Mulan PSL v2. ++// You may obtain a copy of Mulan PSL v2 at: ++// http://license.coscl.org.cn/MulanPSL2 ++// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++// PURPOSE. ++// See the Mulan PSL v2 for more details. ++// Description: network interface driver ++// Author: Jiaqi Yang ++// Create: 2023-04-04 ++ ++package drivers ++ ++import ( ++ "fmt" ++ "testing" ++ ++ "isula.org/syscontainer-tools/types" ++) ++ ++// TestNew tests New ++func TestNew(t *testing.T) { ++ const ( ++ ctrNicName = "eth0" ++ hostNicName = "eth0" ++ nsPath = "/proc/1/ns/net" ++ ip = types.CIDRIpExample1 ++ ip6 = types.CIDRIp6Example1 ++ mac = "aa:bb:cc:dd:ee:aa" ++ mtu = 1500 ++ qlen = 1000 ++ bridge = "" ++ ) ++ type args struct { ++ driverType string ++ options []DriverOptions ++ } ++ tests := []struct { ++ name string ++ args args ++ want Driver ++ wantErr bool ++ }{ ++ { ++ name: "TC1-new eth driver success", ++ args: args{ ++ driverType: "eth", ++ options: []DriverOptions{ ++ NicOptionCtrNicName(ctrNicName), ++ NicOptionHostNicName(hostNicName), ++ NicOptionNsPath(nsPath), ++ NicOptionIP(ip), ++ NicOptionIP6(ip6), ++ NicOptionMac(mac), ++ NicOptionMtu(mtu), ++ NicOptionQlen(qlen), ++ NicOptionBridge(bridge), ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC2-ip6 is not CIDR address", ++ args: args{ ++ driverType: "eth", ++ options: []DriverOptions{ ++ NicOptionCtrNicName(ctrNicName), ++ NicOptionHostNicName(hostNicName), ++ NicOptionNsPath(nsPath), ++ NicOptionIP6(mac), ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC3-ip6 is empty", ++ args: args{ ++ driverType: "eth", ++ options: []DriverOptions{ ++ NicOptionCtrNicName(ctrNicName), ++ NicOptionHostNicName(hostNicName), ++ NicOptionNsPath(nsPath), ++ NicOptionIP6(bridge), ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC4-ip is not CIDR address", ++ args: args{ ++ driverType: "eth", ++ options: []DriverOptions{ ++ NicOptionCtrNicName(ctrNicName), ++ NicOptionHostNicName(hostNicName), ++ NicOptionNsPath(nsPath), ++ NicOptionIP(mac), ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC5-ip is empty", ++ args: args{ ++ driverType: "eth", ++ options: []DriverOptions{ ++ NicOptionCtrNicName(ctrNicName), ++ NicOptionHostNicName(hostNicName), ++ NicOptionNsPath(nsPath), ++ NicOptionIP(bridge), ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC6.1-set ip6 to ip", ++ args: args{ ++ driverType: "eth", ++ options: []DriverOptions{ ++ NicOptionIP(ip6), ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC6.2-set ip to ip6", ++ args: args{ ++ driverType: "eth", ++ options: []DriverOptions{ ++ NicOptionIP6(ip), ++ }, ++ }, ++ wantErr: true, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ _, err := New(tt.args.driverType, tt.args.options...) ++ fmt.Printf("%v\n", err) ++ if (err != nil) != tt.wantErr { ++ t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) ++ return ++ } ++ }) ++ } ++} +diff --git a/libnetwork/drivers/eth/driver.go b/libnetwork/drivers/eth/driver.go +index cd70ddf..901ec9c 100644 +--- a/libnetwork/drivers/eth/driver.go ++++ b/libnetwork/drivers/eth/driver.go +@@ -15,6 +15,7 @@ package eth + + import ( + "fmt" ++ "net" + "os" + "strings" + +@@ -178,6 +179,44 @@ func (d *ethDriver) DeleteIf() (rErr error) { + return err + } + ++func (d *ethDriver) setNicIP(nic netlink.Link, ipType int) error { ++ if nic == nil { ++ return fmt.Errorf("nic is nil") ++ } ++ var ( ++ ipNet *net.IPNet ++ typ string ++ ) ++ switch ipType { ++ case netlink.FAMILY_V4: ++ ipNet = d.GetIP() ++ typ = "ip" ++ case netlink.FAMILY_V6: ++ ipNet = d.GetIP6() ++ typ = "ip6" ++ default: ++ return fmt.Errorf("unsupported IP type") ++ } ++ ++ // delete original ip/ip6 address ++ oldAddr, err := netlink.AddrList(nic, ipType) ++ if err != nil { ++ logrus.Infof("Fail to get origin %v addr: %v", typ, err) ++ } ++ if len(oldAddr) > 0 { ++ // we only have an IP set for the interface ++ if err := netlink.AddrDel(nic, &oldAddr[0]); err != nil { ++ return fmt.Errorf("failed to delete old %v address %v: %v", typ, oldAddr[0].IP, err) ++ } ++ } ++ // set new ipv4/ipv6 address ++ ipAddr := &netlink.Addr{IPNet: ipNet, Label: ""} ++ if err := netlink.AddrAdd(nic, ipAddr); err != nil { ++ return fmt.Errorf("failed to configure %v address %v: %v", typ, ipAddr, err) ++ } ++ return nil ++} ++ + func (d *ethDriver) setNicConfigure(nic netlink.Link) (rErr error) { + // set MAC + if d.GetMac() != nil { +@@ -197,18 +236,18 @@ func (d *ethDriver) setNicConfigure(nic netlink.Link) (rErr error) { + return fmt.Errorf("failed to set qlen(%d) for nic(%s)", d.GetQlen(), d.GetCtrNicName()) + } + +- // set ipv4 address (TODO: ipv6 support?) +- oldAddr, _ := netlink.AddrList(nic, netlink.FAMILY_V4) +- if oldAddr != nil { +- // we only have on IP set for the interface +- if err := netlink.AddrDel(nic, &oldAddr[0]); err != nil { +- return fmt.Errorf("failed to delete old ip address: %v", err) ++ // set ipv4 ++ if d.GetIP() != nil { ++ if err := d.setNicIP(nic, netlink.FAMILY_V4); err != nil { ++ return err + } + } +- // set ipv4 address (TODO: ipv6 support?) +- ipAddr := &netlink.Addr{IPNet: d.GetIP(), Label: ""} +- if err := netlink.AddrAdd(nic, ipAddr); err != nil { +- return fmt.Errorf("failed to configure ip address: %v", err) ++ ++ // set ipv6 ++ if d.GetIP6() != nil { ++ if err := d.setNicIP(nic, netlink.FAMILY_V6); err != nil { ++ return err ++ } + } + + return nil +diff --git a/libnetwork/drivers/eth/driver_test.go b/libnetwork/drivers/eth/driver_test.go +new file mode 100644 +index 0000000..9596a9a +--- /dev/null ++++ b/libnetwork/drivers/eth/driver_test.go +@@ -0,0 +1,397 @@ ++// Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. ++// syscontainer-tools is licensed under the Mulan PSL v2. ++// You can use this software according to the terms and conditions of the Mulan PSL v2. ++// You may obtain a copy of Mulan PSL v2 at: ++// http://license.coscl.org.cn/MulanPSL2 ++// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++// PURPOSE. ++// See the Mulan PSL v2 for more details. ++// Description: ethetic network driver ++// Author: Jiaqi Yang ++// Create: 2023-05-03 ++ ++package eth ++ ++import ( ++ "fmt" ++ "io/ioutil" ++ "net" ++ "os" ++ "testing" ++ ++ "github.com/vishvananda/netlink" ++ ++ "isula.org/syscontainer-tools/libnetwork/drivers/common" ++ "isula.org/syscontainer-tools/types" ++) ++ ++func getPhysicalNics() (map[string]struct{}, error) { ++ const ( ++ allNicDir = "/sys/class/net" ++ virtualNicDir = "/sys/devices/virtual/net" ++ ) ++ getFileName := func(dirName string) (map[string]struct{}, error) { ++ fis, err := ioutil.ReadDir(dirName) ++ if err != nil { ++ return nil, err ++ } ++ var fileNames = make(map[string]struct{}) ++ for _, fi := range fis { ++ fileNames[fi.Name()] = struct{}{} ++ } ++ return fileNames, nil ++ } ++ allNics, err := getFileName(allNicDir) ++ if err != nil { ++ return nil, err ++ } ++ virtualNics, err := getFileName(virtualNicDir) ++ if err != nil { ++ return nil, err ++ } ++ var res = make(map[string]struct{}) ++ for name := range allNics { ++ if _, ok := virtualNics[name]; !ok { ++ res[name] = struct{}{} ++ } ++ } ++ return res, nil ++} ++ ++func getUnusedNic() (string, error) { ++ nicNames, err := getPhysicalNics() ++ if err != nil { ++ return "", err ++ } ++ inters, err := net.Interfaces() ++ if err != nil { ++ return "", err ++ } ++ for _, i := range inters { ++ if _, ok := nicNames[i.Name]; !ok { ++ continue ++ } ++ addrs, err := i.Addrs() ++ if err != nil { ++ fmt.Printf("%v can not get its addr: %v\n", i.Name, err) ++ continue ++ } ++ if len(addrs) > 0 { ++ continue ++ } ++ return i.Name, nil ++ } ++ return "", fmt.Errorf("can not find any unused card") ++} ++ ++func getIPAddr(l netlink.Link, t int) (string, error) { ++ var typ string = "ip4" ++ if t == netlink.FAMILY_V6 { ++ typ = "ip6" ++ } ++ addrs, err := netlink.AddrList(l, t) ++ if err != nil { ++ return "", fmt.Errorf("fail to get %v: %v", typ, err) ++ } ++ if len(addrs) > 0 { ++ if typ == "ip6" { ++ return addrs[0].IP.To16().String(), nil ++ } ++ return addrs[0].IP.To4().String(), nil ++ } ++ return "", fmt.Errorf("empty %v", typ) ++} ++ ++func getIp6(l netlink.Link) (string, error) { ++ return getIPAddr(l, netlink.FAMILY_V6) ++} ++ ++func getIp(l netlink.Link) (string, error) { ++ return getIPAddr(l, netlink.FAMILY_V4) ++} ++ ++func removeAllAddrOfNic(nicName string) { ++ rmNic := func(ip string, nic netlink.Link) { ++ ipNet, err := netlink.ParseIPNet(ip) ++ if err != nil { ++ fmt.Printf("fail to create ip: %v\n", err) ++ return ++ } ++ ipaddr := &netlink.Addr{IPNet: ipNet} ++ err = netlink.AddrDel(nic, ipaddr) ++ if err != nil { ++ fmt.Printf("fail to del ip: %v\n", err) ++ } ++ } ++ ++ nic, err := netlink.LinkByName(nicName) ++ if err != nil { ++ fmt.Printf("should find nic: %v", err) ++ return ++ } ++ ip, _ := getIp(nic) ++ if ip != "" { ++ rmNic(ip+"/16", nic) ++ } ++ ip6, _ := getIp6(nic) ++ if ip6 != "" { ++ rmNic(ip6+"/64", nic) ++ } ++} ++ ++func hasPerm() error { ++ filePath := "/proc/1/ns/net" ++ ++ // is existed ++ fileInfo, err := os.Stat(filePath) ++ if err != nil { ++ return err ++ } ++ ++ // get file info ++ fileMode := fileInfo.Mode() ++ ++ // Check if the current user has permission to access the file ++ if fileMode.Perm()&(1<<(uint(7))) == 0 { ++ return fmt.Errorf("no permission to access the file") ++ } ++ return nil ++} ++ ++// Test_ethDriver_setNicConfigure tests setNicConfigure of ethDriver ++func Test_ethDriver_setNicConfigure(t *testing.T) { ++ const ( ++ expIP = types.CIDRIpExample1 ++ expIP6 = types.CIDRIp6Example1 ++ expMTU = 1500 ++ expQlen = 1000 ++ invalidMtu = -1 ++ invalidMtu2 = 65536 ++ invalidIP = "xxx" ++ ) ++ ++ var ( ++ invalidBytes = []byte("ABCDEFG") ++ invalidMac = net.HardwareAddr(invalidBytes) ++ expIPNet, _ = netlink.ParseIPNet(expIP) ++ expIP6Net, _ = netlink.ParseIPNet(expIP6) ++ invalidIPNet = net.IPNet{IP: invalidBytes} ++ ) ++ if hasPerm() != nil { ++ return ++ } ++ ++ nicName, err := getUnusedNic() ++ if err != nil { ++ fmt.Printf("skip this tests: %v\n", err) ++ return ++ } ++ fmt.Printf("use nics: %v\n", nicName) ++ ++ type fields struct { ++ ip *net.IPNet ++ ip6 *net.IPNet ++ mtu int ++ qlen int ++ mac *net.HardwareAddr ++ } ++ tests := []struct { ++ name string ++ fields fields ++ wantErr bool ++ post func(t *testing.T, nic netlink.Link) ++ }{ ++ { ++ name: "TC1-success", ++ fields: fields{ ++ ip: expIPNet, ++ ip6: expIP6Net, ++ mtu: expMTU, ++ qlen: expQlen, ++ }, ++ post: func(t *testing.T, nic netlink.Link) { ++ ip, err := getIp(nic) ++ if err != nil { ++ t.Errorf("fail to get ip: %v", err) ++ } ++ ip6, err := getIp6(nic) ++ if err != nil { ++ t.Errorf("fail to get ip6: %v", err) ++ } ++ if ip+"/24" != expIP && ip6+"/64" != expIP6 { ++ t.Errorf("not same") ++ } ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC2-invalid mac addr", ++ fields: fields{ ++ mac: &invalidMac, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC3-invalid MTU", ++ fields: fields{ ++ mtu: invalidMtu, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC3.1-invalid MTU", ++ fields: fields{ ++ mtu: invalidMtu2, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC4-invalid IP", ++ fields: fields{ ++ mtu: expMTU, ++ qlen: expQlen, ++ ip: &invalidIPNet, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC5-invalid IP6", ++ fields: fields{ ++ mtu: expMTU, ++ qlen: expQlen, ++ ip6: &invalidIPNet, ++ }, ++ wantErr: true, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ d := ðDriver{ ++ Driver: &common.Driver{}, ++ } ++ if tt.fields.ip != nil { ++ d.SetIP(tt.fields.ip) ++ fmt.Printf("ip %v\n", tt.fields.ip) ++ } ++ if tt.fields.ip6 != nil { ++ d.SetIP6(tt.fields.ip6) ++ fmt.Printf("ip6 %v\n", tt.fields.ip6) ++ } ++ ++ d.SetMtu(tt.fields.mtu) ++ d.SetQlen(tt.fields.qlen) ++ d.SetMac(tt.fields.mac) ++ ++ nic, err := netlink.LinkByName(nicName) ++ if err != nil { ++ t.Errorf("should find nic: %v", err) ++ return ++ } ++ defer removeAllAddrOfNic(nicName) ++ ++ if err := d.setNicConfigure(nic); (err != nil) != tt.wantErr { ++ t.Errorf("ethDriver.setNicConfigure() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ if tt.post != nil { ++ tt.post(t, nic) ++ } ++ }) ++ } ++} ++ ++// Test_ethDriver_setNicIP tests setNicIP ++func Test_ethDriver_setNicIP(t *testing.T) { ++ const ( ++ originIP = types.CIDRIpExample1 ++ curIP = types.CIDRIpExample2 ++ invalidTyp = 111111 ++ ) ++ var ( ++ originIPNet, _ = netlink.ParseIPNet(originIP) ++ curIPNet, _ = netlink.ParseIPNet(curIP) ++ ) ++ ++ if hasPerm() != nil { ++ return ++ } ++ ++ type fields struct { ++ ip *net.IPNet ++ } ++ type args struct { ++ nic netlink.Link ++ ipType int ++ } ++ ++ nicName, err := getUnusedNic() ++ if err != nil { ++ fmt.Printf("skip this tests: %v\n", err) ++ return ++ } ++ fmt.Printf("use nics: %v\n", nicName) ++ nic, err := netlink.LinkByName(nicName) ++ if err != nil { ++ t.Errorf("should find nic: %v", err) ++ return ++ } ++ defer removeAllAddrOfNic(nicName) ++ ++ tests := []struct { ++ name string ++ args args ++ fields fields ++ pre func(t *testing.T) ++ wantErr bool ++ }{ ++ { ++ name: "TC1-invalid IPType", ++ args: args{ ++ ipType: invalidTyp, ++ nic: nic, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC2-invalid nic", ++ args: args{ ++ ipType: netlink.FAMILY_V4, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC3-delete origin ip", ++ fields: fields{ ++ ip: curIPNet, ++ }, ++ args: args{ ++ ipType: netlink.FAMILY_V4, ++ nic: nic, ++ }, ++ pre: func(t *testing.T) { ++ ipAddr := &netlink.Addr{IPNet: originIPNet, Label: ""} ++ if err := netlink.AddrAdd(nic, ipAddr); err != nil { ++ t.Errorf("failed to configure ip4 address %v: %v", ipAddr, err) ++ } ++ }, ++ wantErr: false, ++ }, ++ } ++ ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ d := ðDriver{ ++ Driver: &common.Driver{}, ++ } ++ if tt.fields.ip != nil { ++ d.SetIP(tt.fields.ip) ++ } ++ if tt.pre != nil { ++ tt.pre(t) ++ } ++ if err := d.setNicIP(tt.args.nic, tt.args.ipType); (err != nil) != tt.wantErr { ++ t.Errorf("ethDriver.setNicIP() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} +diff --git a/libnetwork/interfaces.go b/libnetwork/interfaces.go +index 46a51aa..121243a 100644 +--- a/libnetwork/interfaces.go ++++ b/libnetwork/interfaces.go +@@ -71,6 +71,7 @@ func AddNicToContainer(nsPath string, config *types.InterfaceConf) (rErr error) + drivers.NicOptionHostNicName(config.HostNicName), + drivers.NicOptionNsPath(nsPath), + drivers.NicOptionIP(config.IP), ++ drivers.NicOptionIP6(config.IP6), + drivers.NicOptionMac(config.Mac), + drivers.NicOptionMtu(config.Mtu), + drivers.NicOptionQlen(config.Qlen), +@@ -94,6 +95,7 @@ func UpdateNicInContainer(nsPath string, config *types.InterfaceConf) (rErr erro + drivers.NicOptionHostNicName(config.HostNicName), + drivers.NicOptionNsPath(nsPath), + drivers.NicOptionIP(config.IP), ++ drivers.NicOptionIP6(config.IP6), + drivers.NicOptionMac(config.Mac), + drivers.NicOptionMtu(config.Mtu), + drivers.NicOptionQlen(config.Qlen), +@@ -151,6 +153,7 @@ func DelNicFromContainer(nsPath string, config *types.InterfaceConf) error { + drivers.NicOptionHostNicName(config.HostNicName), + drivers.NicOptionNsPath(nsPath), + drivers.NicOptionIP(config.IP), ++ drivers.NicOptionIP6(config.IP6), + drivers.NicOptionMac(config.Mac), + drivers.NicOptionMtu(config.Mtu), + drivers.NicOptionBridge(config.Bridge)) +@@ -176,21 +179,29 @@ func UpdateNic(ctr *container.Container, config *types.InterfaceConf, updateConf + var tmpConfig = new(types.InterfaceConf) + tmpConfig.CtrNicName = config.CtrNicName + +- var newConfig *types.InterfaceConf +- if newConfig = hConfig.FindInterfaceByName(tmpConfig); newConfig == nil { ++ var oldConfig *types.InterfaceConf ++ if oldConfig = hConfig.FindInterfaceByName(tmpConfig); oldConfig == nil { + return fmt.Errorf("Network interface %s,%s with type %s not exist in container %s", config.HostNicName, config.CtrNicName, config.Type, ctr.Name()) + } + + if config.IP == "" { +- tmpConfig.IP = newConfig.IP ++ tmpConfig.IP = oldConfig.IP + } else { + tmpConfig.IP = config.IP + msg := fmt.Sprintf("Update IP address for network interface (%s,%v) done", config.CtrNicName, config.IP) + fmt.Fprintln(os.Stdout, msg) + logrus.Info(msg) + } ++ if config.IP6 == "" { ++ tmpConfig.IP6 = oldConfig.IP6 ++ } else { ++ tmpConfig.IP6 = config.IP6 ++ msg := fmt.Sprintf("Update IP6 address for network interface (%s,%v) done", config.CtrNicName, config.IP6) ++ fmt.Fprintln(os.Stdout, msg) ++ logrus.Info(msg) ++ } + if config.Mac == "" { +- tmpConfig.Mac = newConfig.Mac ++ tmpConfig.Mac = oldConfig.Mac + } else { + tmpConfig.Mac = config.Mac + msg := fmt.Sprintf("Update MAC address for network interface (%s,%v) done", config.CtrNicName, config.Mac) +@@ -199,7 +210,7 @@ func UpdateNic(ctr *container.Container, config *types.InterfaceConf, updateConf + + } + if config.Bridge == "" { +- tmpConfig.Bridge = newConfig.Bridge ++ tmpConfig.Bridge = oldConfig.Bridge + } else { + tmpConfig.Bridge = config.Bridge + msg := fmt.Sprintf("Update Bridge for network interface (%s,%v) done", config.CtrNicName, config.Bridge) +@@ -208,7 +219,7 @@ func UpdateNic(ctr *container.Container, config *types.InterfaceConf, updateConf + + } + if config.Mtu == 0 { +- tmpConfig.Mtu = newConfig.Mtu ++ tmpConfig.Mtu = oldConfig.Mtu + } else { + tmpConfig.Mtu = config.Mtu + msg := fmt.Sprintf("Update Mtu for network interface (%s,%v) done", config.CtrNicName, config.Mtu) +@@ -218,26 +229,28 @@ func UpdateNic(ctr *container.Container, config *types.InterfaceConf, updateConf + } + // we use qlen < 0 to check if the user has set parameter qlen or not + if config.Qlen < 0 { +- tmpConfig.Qlen = newConfig.Qlen ++ tmpConfig.Qlen = oldConfig.Qlen + } else { + tmpConfig.Qlen = config.Qlen + msg := fmt.Sprintf("Update Qlen for network interface (%s,%v)", config.CtrNicName, config.Qlen) + fmt.Fprintln(os.Stdout, msg) + logrus.Info(msg) + } +- tmpConfig.Type = newConfig.Type +- tmpConfig.HostNicName = newConfig.HostNicName ++ tmpConfig.Type = oldConfig.Type ++ tmpConfig.HostNicName = oldConfig.HostNicName + + if hConfig.IsSameInterface(tmpConfig) { + logrus.Infof("Network interface in container (%s, %s): Identical setting, nothing to change", + config.CtrNicName, ctr.Name()) + return nil + } +- if err := hConfig.UpdateNetworkInterface(newConfig, false); err != nil { ++ ++ if err := hConfig.UpdateNetworkInterface(oldConfig, false); err != nil { + return err + } ++ + if err := hConfig.IsConflictInterface(tmpConfig); err != nil { +- if err := hConfig.UpdateNetworkInterface(newConfig, true); err != nil { ++ if err := hConfig.UpdateNetworkInterface(oldConfig, true); err != nil { + return err + } + return err +@@ -245,7 +258,7 @@ func UpdateNic(ctr *container.Container, config *types.InterfaceConf, updateConf + + if !updateConfigOnly && ctr.Pid() > 0 && ctr.CheckPidExist() { + if err := UpdateNicInContainer(ctr.NetNsPath(), tmpConfig); err != nil { +- if err := hConfig.UpdateNetworkInterface(newConfig, true); err != nil { ++ if err := hConfig.UpdateNetworkInterface(oldConfig, true); err != nil { + return err + } + return err +diff --git a/libnetwork/interfaces_test.go b/libnetwork/interfaces_test.go +new file mode 100644 +index 0000000..88b8e68 +--- /dev/null ++++ b/libnetwork/interfaces_test.go +@@ -0,0 +1,255 @@ ++// Copyright (c) Huawei Technologies Co., Ltd. 2018-2023. All rights reserved. ++// syscontainer-tools is licensed under the Mulan PSL v2. ++// You can use this software according to the terms and conditions of the Mulan PSL v2. ++// You may obtain a copy of Mulan PSL v2 at: ++// http://license.coscl.org.cn/MulanPSL2 ++// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++// PURPOSE. ++// See the Mulan PSL v2 for more details. ++// Description: network interface operation ++// Author: Jiaqi Yang ++// Create: 2023-05-03 ++ ++// package libnetwork is network library ++package libnetwork ++ ++import ( ++ "fmt" ++ "os" ++ "os/exec" ++ "path/filepath" ++ "testing" ++ ++ "isula.org/syscontainer-tools/container" ++ "isula.org/syscontainer-tools/types" ++) ++ ++const ( ++ ctrNicName = "ctr" ++ hostNicName = "host" ++ ip6 = types.CIDRIp6Example1 ++ mtu = 1500 ++ procOneNsPath = "/proc/1/ns/net" ++ nonExistBridge = "test" ++) ++ ++// TestAddNicToContainer tests AddNicToContainer ++func TestAddNicToContainer(t *testing.T) { ++ type args struct { ++ nsPath string ++ config *types.InterfaceConf ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ { ++ name: "TC1-fail to add nic", ++ args: args{ ++ nsPath: procOneNsPath, ++ config: &types.InterfaceConf{ ++ CtrNicName: ctrNicName, ++ HostNicName: hostNicName, ++ IP6: ip6, ++ Mtu: mtu, ++ Bridge: nonExistBridge, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC2-invalid IP", ++ args: args{ ++ nsPath: procOneNsPath, ++ config: &types.InterfaceConf{ ++ CtrNicName: ctrNicName, ++ HostNicName: hostNicName, ++ }, ++ }, ++ wantErr: true, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := AddNicToContainer(tt.args.nsPath, tt.args.config); (err != nil) != tt.wantErr { ++ t.Errorf("AddNicToContainer() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} ++ ++// TestUpdateNicInContainer test UpdateNicInContainer ++func TestUpdateNicInContainer(t *testing.T) { ++ type args struct { ++ nsPath string ++ config *types.InterfaceConf ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ { ++ name: "TC1-fail to update nic", ++ args: args{ ++ nsPath: procOneNsPath, ++ config: &types.InterfaceConf{ ++ CtrNicName: ctrNicName, ++ HostNicName: hostNicName, ++ IP6: ip6, ++ Mtu: mtu, ++ Bridge: nonExistBridge, ++ }, ++ }, ++ wantErr: true, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := UpdateNicInContainer(tt.args.nsPath, tt.args.config); (err != nil) != tt.wantErr { ++ t.Errorf("UpdateNicInContainer() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} ++ ++// TestDelNicFromContainer tests DelNicFromContainer ++func TestDelNicFromContainer(t *testing.T) { ++ type args struct { ++ nsPath string ++ config *types.InterfaceConf ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ { ++ name: "TC1-delete non-existed nic", ++ args: args{ ++ nsPath: procOneNsPath, ++ config: &types.InterfaceConf{ ++ CtrNicName: ctrNicName, ++ HostNicName: hostNicName, ++ IP6: ip6, ++ Mtu: mtu, ++ Bridge: nonExistBridge, ++ }, ++ }, ++ wantErr: false, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := DelNicFromContainer(tt.args.nsPath, tt.args.config); (err != nil) != tt.wantErr { ++ t.Errorf("DelNicFromContainer() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} ++ ++const ctrName = "ctrName" ++ ++func createContainer() error { ++ const ( ++ imageName = "rnd-dockerhub.huawei.com/official/ubuntu" ++ ctrCmd = "bash" ++ ) ++ // cmdValue := "isula run -tid -name " + ctrName + " " + imageName + " " ++ cmd := exec.Command("isula", "run", "-tid", "--name", "ctrName", imageName, ctrCmd) ++ out, err := cmd.CombinedOutput() ++ if err != nil { ++ return fmt.Errorf("%s: %v", string(out), err) ++ } ++ return nil ++} ++ ++func removeContainer() { ++ cmd := exec.Command("bash", "-c", "isula rm -f `isula ps -aq`") ++ out, err := cmd.CombinedOutput() ++ if err != nil { ++ fmt.Printf("%s: %v\n", string(out), err) ++ } ++} ++ ++// TestUpdateNic tests UpdateNic ++func TestUpdateNic(t *testing.T) { ++ type args struct { ++ ctr *container.Container ++ config *types.InterfaceConf ++ updateConfigOnly bool ++ data string ++ } ++ if err := createContainer(); err != nil { ++ fmt.Printf("skip this usecase: %v\n", err) ++ return ++ } ++ ctr, err := container.New(ctrName) ++ if err != nil { ++ return ++ } ++ defer removeContainer() ++ ++ const ( ++ defaultConfigFile = "device_hook.json" ++ isuladPath = "/var/lib/isulad/engines/lcr" ++ filePerm = 0750 ++ invalidQlen = -1 ++ ip6 = "abc" ++ ) ++ var ( ++ data1 = "{\"networkInterfaces\":[{\"Ip\":\"\",\"Ip6\":\"\",\"Mac\":\"\",\"Mtu\":1,\"Qlen\":1," + ++ "\"Type\":\"eth\",\"Bridge\":\"\",\"HostNicName\":\"host\",\"CtrNicName\":\"ctr\"}]}" ++ ) ++ ctrID := ctr.ContainerID() ++ ++ if err := os.WriteFile(filepath.Join(isuladPath, ctrID, defaultConfigFile), []byte(data1), filePerm); err != nil { ++ fmt.Printf("write fail: %v\n", err) ++ return ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ { ++ name: "TC1-same config", ++ args: args{ ++ ctr: ctr, ++ config: &types.InterfaceConf{ ++ CtrNicName: ctrNicName, ++ HostNicName: hostNicName, ++ Qlen: invalidQlen, ++ }, ++ data: data1, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC2-ip6 changed", ++ args: args{ ++ ctr: ctr, ++ config: &types.InterfaceConf{ ++ CtrNicName: ctrNicName, ++ HostNicName: hostNicName, ++ IP6: ip6, ++ }, ++ data: data1, ++ }, ++ wantErr: true, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := os.WriteFile(filepath.Join(isuladPath, ctrID, defaultConfigFile), []byte(tt.args.data), filePerm); err != nil { ++ fmt.Printf("write fail: %v\n", err) ++ return ++ } ++ if err := UpdateNic(tt.args.ctr, tt.args.config, tt.args.updateConfigOnly); (err != nil) != tt.wantErr { ++ t.Errorf("UpdateNic() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} +diff --git a/libnetwork/route.go b/libnetwork/route.go +index 0be5a76..2b72c3e 100644 +--- a/libnetwork/route.go ++++ b/libnetwork/route.go +@@ -92,16 +92,10 @@ func AddRouteToContainer(nsPath string, route *types.Route) error { + + if len(src) != 0 { + rule.Src = net.ParseIP(src) +- if err != nil { +- return fmt.Errorf("failed to parse src ip") +- } + } + + if len(gw) != 0 { + rule.Gw = net.ParseIP(gw) +- if err != nil { +- return fmt.Errorf("failed to parse gw ip") +- } + } + + return nsutils.NsInvoke(nsPath, +@@ -195,16 +189,10 @@ func DelRouteFromContainer(nsPath string, route *types.Route) error { + + if len(src) != 0 { + rule.Src = net.ParseIP(src) +- if err != nil { +- return fmt.Errorf("failed to parse src ip") +- } + } + + if len(gw) != 0 { + rule.Gw = net.ParseIP(gw) +- if err != nil { +- return fmt.Errorf("failed to parse gw ip") +- } + } + + return nsutils.NsInvoke(nsPath, +diff --git a/libnetwork/route_test.go b/libnetwork/route_test.go +new file mode 100644 +index 0000000..2f8ab38 +--- /dev/null ++++ b/libnetwork/route_test.go +@@ -0,0 +1,143 @@ ++// Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. ++// syscontainer-tools is licensed under the Mulan PSL v2. ++// You can use this software according to the terms and conditions of the Mulan PSL v2. ++// You may obtain a copy of Mulan PSL v2 at: ++// http://license.coscl.org.cn/MulanPSL2 ++// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++// PURPOSE. ++// See the Mulan PSL v2 for more details. ++// Description: network routes operation ++// Author: zhangwei ++// Create: 2018-01-18 ++ ++// package libnetwork is network library ++package libnetwork ++ ++import ( ++ "fmt" ++ "os" ++ "testing" ++ ++ "isula.org/syscontainer-tools/types" ++) ++ ++func hasPerm() error { ++ filePath := "/proc/1/ns/net" ++ ++ // is existed ++ fileInfo, err := os.Stat(filePath) ++ if err != nil { ++ return err ++ } ++ ++ // get file info ++ fileMode := fileInfo.Mode() ++ ++ // Check if the current user has permission to access the file ++ if fileMode.Perm()&(1<<(uint(7))) == 0 { ++ return fmt.Errorf("no permission to access the file") ++ } ++ return nil ++} ++ ++// TestAddRouteToContainer tests AddRouteToContainer and DelRouteToContainer ++func TestAddDelRouteToContainer(t *testing.T) { ++ const ( ++ normalIP = types.IPExample1 ++ cidrIP = normalIP + "/16" ++ dev = "netDev" ++ ) ++ ++ if hasPerm() != nil { ++ return ++ } ++ ++ type args struct { ++ nsPath string ++ route *types.Route ++ } ++ tests := []struct { ++ name string ++ args args ++ addWantErr bool ++ delWantErr bool ++ }{ ++ { ++ name: "TC1.1-fail to parse invalaid src", ++ args: args{ ++ nsPath: procOneNsPath, ++ route: &types.Route{ ++ Src: cidrIP, ++ }, ++ }, ++ addWantErr: true, ++ delWantErr: true, ++ }, ++ { ++ name: "TC1.2-fail to parse invalaid gw", ++ args: args{ ++ nsPath: procOneNsPath, ++ route: &types.Route{ ++ Gw: cidrIP, ++ }, ++ }, ++ addWantErr: true, ++ delWantErr: true, ++ }, ++ { ++ name: "TC1.3-fail to parse invalid dest", ++ args: args{ ++ nsPath: procOneNsPath, ++ route: &types.Route{ ++ Dest: normalIP, ++ Dev: dev, ++ }, ++ }, ++ addWantErr: true, ++ delWantErr: true, ++ }, ++ { ++ name: "TC1.4-lack of dev, gw & src", ++ args: args{ ++ nsPath: procOneNsPath, ++ route: &types.Route{}, ++ }, ++ addWantErr: true, ++ delWantErr: true, ++ }, ++ { ++ name: "TC1.5-non existed dev", ++ args: args{ ++ nsPath: procOneNsPath, ++ route: &types.Route{ ++ Dev: dev, ++ }, ++ }, ++ addWantErr: true, ++ delWantErr: true, ++ }, ++ { ++ name: "TC2-default dest", ++ args: args{ ++ nsPath: procOneNsPath, ++ route: &types.Route{ ++ Dest: "default", ++ Gw: normalIP, ++ }, ++ }, ++ addWantErr: false, ++ delWantErr: false, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := AddRouteToContainer(tt.args.nsPath, tt.args.route); (err != nil) != tt.addWantErr { ++ t.Errorf("AddRouteToContainer() error = %v, wantErr %v", err, tt.addWantErr) ++ } ++ if err := DelRouteFromContainer(tt.args.nsPath, tt.args.route); (err != nil) != tt.delWantErr { ++ t.Errorf("DelRouteFromContainer() error = %v, wantErr %v", err, tt.addWantErr) ++ } ++ }) ++ } ++} +diff --git a/network.go b/network.go +index e8eaa68..6e32dc4 100644 +--- a/network.go ++++ b/network.go +@@ -47,7 +47,11 @@ and configure it as you wanted, then attach to specified bridge. + }, + cli.StringFlag{ + Name: "ip", +- Usage: "set ip address. E.g. 172.17.28.2/24", ++ Usage: "set ip address. E.g. " + types.CIDRIpExample1, ++ }, ++ cli.StringFlag{ ++ Name: "ip6", ++ Usage: "set ipv6 address. E.g. " + types.CIDRIp6Example1, + }, + cli.StringFlag{ + Name: "mac", +@@ -101,6 +105,7 @@ and configure it as you wanted, then attach to specified bridge. + + nicConf := &types.InterfaceConf{ + IP: context.String("ip"), ++ IP6: context.String("ip6"), + Mac: context.String("mac"), + Mtu: context.Int("mtu"), + Type: context.String("type"), +@@ -207,7 +212,11 @@ var updateNicCommand = cli.Command{ + }, + cli.StringFlag{ + Name: "ip", +- Usage: "set ip address. E.g. 172.17.28.2/24", ++ Usage: "set ip address. E.g. " + types.CIDRIpExample1, ++ }, ++ cli.StringFlag{ ++ Name: "ip6", ++ Usage: "set ipv6 address. E.g. " + types.CIDRIp6Example1, + }, + cli.StringFlag{ + Name: "mac", +@@ -264,6 +273,7 @@ var updateNicCommand = cli.Command{ + + nicConf := &types.InterfaceConf{ + IP: context.String("ip"), ++ IP6: context.String("ip6"), + Mac: context.String("mac"), + Mtu: context.Int("mtu"), + Bridge: context.String("bridge"), +diff --git a/types/network.go b/types/network.go +index 74231f9..524e1d5 100644 +--- a/types/network.go ++++ b/types/network.go +@@ -21,6 +21,26 @@ import ( + "github.com/vishvananda/netlink" + ) + ++const ( ++ // example1 of ip address ++ IPExample1 = "172.17.28.2" ++ // example2 of ip address ++ IPExample2 = "172.17.28.3" ++ // example1 of cidr format ip address (with mask) ++ CIDRIpExample1 = IPExample1 + "/24" ++ // example2 of cidr format ip address (with mask) ++ CIDRIpExample2 = IPExample2 + "/16" ++ ++ // example1 of ip6 address ++ IP6Example1 = "2001:0db8:0:f101::1" ++ // example2 of ip6 address ++ IP6Example2 = "fe80::2aee:d4ef:fe:b890" ++ // example1 of cidr format ip6 address (with mask) ++ CIDRIp6Example1 = IP6Example1 + "/64" ++ // example2 of cidr format ip6 address (with mask) ++ CIDRIp6Example2 = IP6Example2 + "/64" ++) ++ + // NamespacePath namespace paths + type NamespacePath struct { + Pid string `json:"pid,omitempty"` +@@ -34,6 +54,7 @@ type NamespacePath struct { + // InterfaceConf is the network interface config + type InterfaceConf struct { + IP string `json:"Ip"` ++ IP6 string `json:"Ip6"` + Mac string `json:"Mac"` + Mtu int `json:"Mtu"` + Qlen int `json:"Qlen"` +@@ -72,9 +93,12 @@ func IsConflictNic(nic1, nic2 *InterfaceConf) error { + if nic1.Mac != "" && (nic1.Mac == nic2.Mac) { + return fmt.Errorf("interface mac conflict: %s", nic1.Mac) + } +- if nic1.IP == nic2.IP { ++ if nic1.IP != "" && nic1.IP == nic2.IP { + return fmt.Errorf("interface ip conflict: %s", nic1.IP) + } ++ if nic1.IP6 != "" && nic1.IP6 == nic2.IP6 { ++ return fmt.Errorf("interface ip6 conflict: %s", nic1.IP6) ++ } + return nil + } + +@@ -83,6 +107,9 @@ func IsSameNic(obj, src *InterfaceConf) bool { + if obj.IP != src.IP && obj.IP != "" { + return false + } ++ if obj.IP6 != src.IP6 && obj.IP6 != "" { ++ return false ++ } + if obj.Mac != src.Mac && obj.Mac != "" { + return false + } +@@ -134,10 +161,24 @@ func IsSameRoute(obj, src *Route) bool { + + // ValidNetworkConfig validate network config + func ValidNetworkConfig(conf *InterfaceConf) error { +- // check IP here + conf.IP = strings.TrimSpace(conf.IP) +- if _, err := netlink.ParseIPNet(conf.IP); err != nil { +- return err ++ conf.IP6 = strings.TrimSpace(conf.IP6) ++ if len(conf.IP) == 0 && len(conf.IP6) == 0 { ++ return fmt.Errorf("either ip or ipv6 must be specified") ++ } ++ ++ // check IP here ++ if len(conf.IP) != 0 { ++ if _, err := netlink.ParseIPNet(conf.IP); err != nil { ++ return err ++ } ++ } ++ ++ // check IP6 here ++ if len(conf.IP6) != 0 { ++ if _, err := netlink.ParseIPNet(conf.IP6); err != nil { ++ return err ++ } + } + + // Check mac here +diff --git a/types/network_test.go b/types/network_test.go +new file mode 100644 +index 0000000..fecf8b6 +--- /dev/null ++++ b/types/network_test.go +@@ -0,0 +1,514 @@ ++// Copyright (c) Huawei Technologies Co., Ltd. 2018-2023. All rights reserved. ++// syscontainer-tools is licensed under the Mulan PSL v2. ++// You can use this software according to the terms and conditions of the Mulan PSL v2. ++// You may obtain a copy of Mulan PSL v2 at: ++// http://license.coscl.org.cn/MulanPSL2 ++// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++// PURPOSE. ++// See the Mulan PSL v2 for more details. ++// Description: network interface type ++// Author: Jiaqi Yang ++// Create: 2023-05-03 ++ ++// package types defines type used by libnetwork ++package types ++ ++import ( ++ "testing" ++) ++ ++// TestIsConflictNic tests IsConflictNic ++func TestIsConflictNic(t *testing.T) { ++ const ( ++ testCtrNicName1 = "ctr1" ++ testCtrNicName2 = "ctr2" ++ testHostNicName1 = "host1" ++ testHostNicName2 = "host2" ++ testMac1 = "aa:bb:cc:dd:ee:ff" ++ testIP41 = CIDRIpExample1 ++ testIP42 = CIDRIpExample2 ++ testIP61 = CIDRIp6Example1 ++ testIP62 = CIDRIp6Example2 ++ testMTU = 1500 ++ ) ++ type args struct { ++ nic1 *InterfaceConf ++ nic2 *InterfaceConf ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ { ++ name: "TC1-same ctrName(ctrName can not be empty)", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC2-same hostName(hostName can not be empty)", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName1, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC3.1-same mac", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ Mac: testMac1, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ Mac: testMac1, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC3.2-diffrent mac address", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ Mac: testMac1, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC3.3-both not have a mac address", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC4.1-smae IP4", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ IP: testIP41, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ IP: testIP41, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC4.2-different IP4 & not set IP6", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ IP: testIP41, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ IP: testIP42, ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC4.3-both not have a IP4 address", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC5.1-smae IP6", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ IP6: testIP61, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ IP6: testIP61, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC5.2-different IP6", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ IP6: testIP61, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ IP6: testIP62, ++ }, ++ }, ++ wantErr: false, ++ }, ++ { ++ name: "TC5.3-both not have a IP6 address", ++ args: args{ ++ nic1: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ HostNicName: testHostNicName1, ++ }, ++ nic2: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ HostNicName: testHostNicName2, ++ }, ++ }, ++ wantErr: false, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := IsConflictNic(tt.args.nic1, tt.args.nic2); (err != nil) != tt.wantErr { ++ t.Errorf("IsConflictNic() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} ++ ++// TestIsSameNic tests IsSameNic ++func TestIsSameNic(t *testing.T) { ++ const ( ++ testCtrNicName1 = "ctr1" ++ testCtrNicName2 = "ctr2" ++ testHostNicName1 = "host1" ++ testHostNicName2 = "host2" ++ testMac1 = "aa:bb:cc:dd:ee:ff" ++ testMac2 = "ff:ee:dd:cc:bb:aa" ++ testIP41 = CIDRIpExample1 ++ testIP42 = CIDRIpExample2 ++ testIP61 = CIDRIp6Example1 ++ testIP62 = CIDRIp6Example2 ++ testMTU1 = 1500 ++ testMTU2 = 1200 ++ testQlen1 = 500 ++ testQlen2 = 1000 ++ testBridge1 = "test1" ++ testBridge2 = "test2" ++ testType1 = "eth" ++ testType2 = "veth" ++ ) ++ type args struct { ++ obj *InterfaceConf ++ src *InterfaceConf ++ } ++ tests := []struct { ++ name string ++ args args ++ want bool ++ }{ ++ { ++ name: "TC1-all data is empty", ++ args: args{ ++ obj: &InterfaceConf{}, ++ src: &InterfaceConf{}, ++ }, ++ want: true, ++ }, ++ { ++ name: "TC2-different IP", ++ args: args{ ++ obj: &InterfaceConf{ ++ IP: testIP41, ++ }, ++ src: &InterfaceConf{ ++ IP: testIP42, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC3-different IP6", ++ args: args{ ++ obj: &InterfaceConf{ ++ IP6: testIP61, ++ }, ++ src: &InterfaceConf{ ++ IP6: testIP62, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC4-different Mac", ++ args: args{ ++ obj: &InterfaceConf{ ++ Mac: testMac1, ++ }, ++ src: &InterfaceConf{ ++ Mac: testMac2, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC5-different Mtu", ++ args: args{ ++ obj: &InterfaceConf{ ++ Mtu: testMTU1, ++ }, ++ src: &InterfaceConf{ ++ Mtu: testMTU2, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC6-different Qlen", ++ args: args{ ++ obj: &InterfaceConf{ ++ Qlen: testQlen1, ++ }, ++ src: &InterfaceConf{ ++ Qlen: testQlen2, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC7-different Type", ++ args: args{ ++ obj: &InterfaceConf{ ++ Type: testType1, ++ }, ++ src: &InterfaceConf{ ++ Type: testType2, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC8-different Bridge", ++ args: args{ ++ obj: &InterfaceConf{ ++ Bridge: testBridge1, ++ }, ++ src: &InterfaceConf{ ++ Bridge: testBridge2, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC9-different host Name", ++ args: args{ ++ obj: &InterfaceConf{ ++ HostNicName: testHostNicName1, ++ }, ++ src: &InterfaceConf{ ++ HostNicName: testHostNicName2, ++ }, ++ }, ++ want: false, ++ }, ++ { ++ name: "TC10-different Ctr Name", ++ args: args{ ++ obj: &InterfaceConf{ ++ CtrNicName: testCtrNicName1, ++ }, ++ src: &InterfaceConf{ ++ CtrNicName: testCtrNicName2, ++ }, ++ }, ++ want: false, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if got := IsSameNic(tt.args.obj, tt.args.src); got != tt.want { ++ t.Errorf("IsSameNic() = %v, want %v", got, tt.want) ++ } ++ }) ++ } ++} ++ ++// TestValidNetworkConfig tests ValidNetworkConfig ++func TestValidNetworkConfig(t *testing.T) { ++ const ( ++ testCtrNicName = "ctr1" ++ testHostNicName = "host1" ++ testMac1 = "aa:bb:cc:dd:ee:ff" ++ invalidMac = "ABCDEFG" ++ testIP41 = CIDRIpExample1 ++ testIP42 = CIDRIpExample2 ++ testIP61 = CIDRIp6Example1 ++ testIP62 = CIDRIp6Example2 ++ testIP4 = CIDRIpExample1 ++ invalidIP4 = IPExample1 ++ testIP6 = CIDRIp6Example1 ++ invalidIP6 = IP6Example1 ++ testBridge = "test1" ++ ethType = "eth" ++ vethType = "veth" ++ invalidType = "fake" ++ ) ++ type args struct { ++ conf *InterfaceConf ++ } ++ tests := []struct { ++ name string ++ args args ++ wantErr bool ++ }{ ++ { ++ name: "TC1-No IP & IP6", ++ args: args{ ++ conf: &InterfaceConf{}, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC2-invalid ip", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: invalidIP4, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC3-invalid ip6", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP6: invalidIP6, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC4-invalid Mac", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: testIP4, ++ Mac: invalidMac, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC5-No such eth nic", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: testIP4, ++ IP6: testIP6, ++ HostNicName: testHostNicName, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC6-veth:No host nic name is not existed", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: testIP4, ++ IP6: testIP6, ++ HostNicName: testHostNicName, ++ Type: vethType, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC7-eth:empty host nic name", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: testIP4, ++ IP6: testIP6, ++ Type: ethType, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC7.1-eth:empty bridge", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: testIP4, ++ IP6: testIP6, ++ HostNicName: testHostNicName, ++ Type: ethType, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC7.2-eth can not find eth", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: testIP4, ++ IP6: testIP6, ++ HostNicName: testHostNicName, ++ Bridge: testBridge, ++ Type: ethType, ++ }, ++ }, ++ wantErr: true, ++ }, ++ { ++ name: "TC8-invalid type", ++ args: args{ ++ conf: &InterfaceConf{ ++ IP: testIP4, ++ Type: invalidType, ++ }, ++ }, ++ wantErr: true, ++ }, ++ } ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ if err := ValidNetworkConfig(tt.args.conf); (err != nil) != tt.wantErr { ++ t.Errorf("ValidNetworkConfig() error = %v, wantErr %v", err, tt.wantErr) ++ } ++ }) ++ } ++} +-- +2.41.0 + diff --git a/0007-use-file-locks-to-avoid-remounting-the-sharepath-mas.patch b/0007-use-file-locks-to-avoid-remounting-the-sharepath-mas.patch new file mode 100644 index 0000000..7a6a3e0 --- /dev/null +++ b/0007-use-file-locks-to-avoid-remounting-the-sharepath-mas.patch @@ -0,0 +1,88 @@ +From 39ff0ed013102542292f3270ed99babe3964c2a8 Mon Sep 17 00:00:00 2001 +From: yangjiaqi +Date: Fri, 11 Aug 2023 14:47:05 +0800 +Subject: [PATCH] use file locks to avoid remounting the sharepath/master dir + +Signed-off-by: yangjiaqi +--- + utils/transfer.go | 40 +++++++++++++++++++++++++++++++++++----- + 1 file changed, 35 insertions(+), 5 deletions(-) + +diff --git a/utils/transfer.go b/utils/transfer.go +index 9c6d527..789cb99 100644 +--- a/utils/transfer.go ++++ b/utils/transfer.go +@@ -20,17 +20,20 @@ import ( + "path/filepath" + "syscall" + +- mymount "isula.org/syscontainer-tools/pkg/mount" + "github.com/docker/docker/pkg/mount" ++ "github.com/sirupsen/logrus" ++ "golang.org/x/sys/unix" + ++ hconfig "isula.org/syscontainer-tools/config" ++ mymount "isula.org/syscontainer-tools/pkg/mount" + "isula.org/syscontainer-tools/types" +- "github.com/sirupsen/logrus" + ) + + const ( + masterPath = "/.sharedpath/master" + midTransferPath = "/.sharedpath/midpath" + slavePath = "/.sharedpath" ++ lockFile = "master_locker" + ) + + /* Add path to container when it is running +@@ -171,17 +174,44 @@ func GetSlavePath() string { + + // PrepareHostPath prepare host path + func PrepareHostPath(id string) error { ++ lockFile := filepath.Join(hconfig.IsuladToolsDir, lockFile) ++ // get lock file handler ++ f, err := os.OpenFile(lockFile, os.O_RDONLY|os.O_CREATE, 0600) ++ if err != nil { ++ return err ++ } ++ defer f.Close() + +- if err := os.MkdirAll(masterPath, 0600); err != nil { +- return fmt.Errorf("create host shared path failed, err: %s", err) ++ // lock ++ if err := unix.Flock(int(f.Fd()), unix.LOCK_EX); err != nil { ++ return fmt.Errorf("fail to lock file %v: %v", f.Name(), err) + } +- if m, _ := mount.Mounted(masterPath); m != true { ++ mountErr := func() error { ++ mounted, err := mount.Mounted(masterPath) ++ if err != nil { ++ return fmt.Errorf("fail to know whether %v is mounted: %v", masterPath, err) ++ } ++ // has mounted ++ if mounted { ++ return nil ++ } ++ // do nothing if the directory has already been existed ++ if err := os.MkdirAll(masterPath, 0600); err != nil { ++ return fmt.Errorf("create host shared path failed, err: %s", err) ++ } + if err := mount.Mount("none", masterPath, "tmpfs", "size=16m"); err != nil { + return fmt.Errorf("mount host shared path failed:, %s", err) + } + if err := syscall.Mount("none", masterPath, "none", syscall.MS_SHARED|syscall.MS_REC, ""); err != nil { + return fmt.Errorf("failed to make mountpoint shared, err: %s", err) + } ++ return nil ++ }() ++ if unlockErr := unix.Flock(int(f.Fd()), unix.LOCK_UN); unlockErr != nil { ++ logrus.Errorf("failed to unlock %v : %v", f.Name(), unlockErr) ++ } ++ if mountErr != nil { ++ return mountErr + } + + if err := os.MkdirAll(filepath.Join(masterPath, id), 0600); err != nil { +-- +2.30.0 + diff --git a/0008-clean-up-run-syscontainer-tools-netns-containerid-di.patch b/0008-clean-up-run-syscontainer-tools-netns-containerid-di.patch new file mode 100644 index 0000000..f34b0f4 --- /dev/null +++ b/0008-clean-up-run-syscontainer-tools-netns-containerid-di.patch @@ -0,0 +1,58 @@ +From 7581283eb8e235ae0923e8c68677e059895a3c9b Mon Sep 17 00:00:00 2001 +From: yangjiaqi +Date: Thu, 17 Aug 2023 20:12:49 +0800 +Subject: [PATCH] clean up run/syscontainer-tools/netns/containerid dir residue + +Signed-off-by: yangjiaqi +--- + hooks/syscontainer-hooks/prestart.go | 22 +++++++++++++++++++++- + 1 file changed, 21 insertions(+), 1 deletion(-) + +diff --git a/hooks/syscontainer-hooks/prestart.go b/hooks/syscontainer-hooks/prestart.go +index a71d26b..8b5756e 100644 +--- a/hooks/syscontainer-hooks/prestart.go ++++ b/hooks/syscontainer-hooks/prestart.go +@@ -23,11 +23,13 @@ import ( + "strconv" + "strings" + ++ "github.com/docker/docker/pkg/mount" + "github.com/opencontainers/runc/libcontainer/configs" + _ "github.com/opencontainers/runc/libcontainer/nsenter" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" ++ + hconfig "isula.org/syscontainer-tools/config" + "isula.org/syscontainer-tools/libdevice" + "isula.org/syscontainer-tools/libdevice/nsexec" +@@ -251,7 +253,25 @@ func UpdateNetwork(state *configs.HookState, hookConfig *hconfig.ContainerHookCo + logrus.Errorf("[device-hook] Failed to Create netns dir %v", err) + return err + } +- file, err := os.Create(filepath.Join(hconfig.IsuladToolsDirNetns, state.ID)) ++ /* ++ each container can only be in prestart, running or poststop at the same time, ++ so there is no lock protection for the file /run/syscontainer-tools/netns/containerid. ++ */ ++ netnsMountpoint := filepath.Join(hconfig.IsuladToolsDirNetns, state.ID) ++ // see if the current network namespace file is mounted ++ mounted, err := mount.Mounted(netnsMountpoint) ++ if err != nil { ++ logrus.Errorf("[device-hook] Failed to get mount info %v: %v", netnsMountpoint, err) ++ } ++ // has mounted ++ if mounted { ++ // maybe the previous poststop command was not executed or interrupted, resulting in residual ++ if removeErr := RemoveNetworkDevices(state, hookConfig, spec); removeErr != nil { ++ logrus.Errorf("[device-hook] Failed to remove network devices and umount netns file and %v is still mounted: %v", ++ netnsMountpoint, removeErr) ++ } ++ } ++ file, err := os.Create(netnsMountpoint) + if err != nil { + logrus.Errorf("[device-hook] Failed to Create netns file %v", err) + return err +-- +2.30.0 + diff --git a/0009-drop-useless-function-error-and-info.patch b/0009-drop-useless-function-error-and-info.patch new file mode 100644 index 0000000..7def6f9 --- /dev/null +++ b/0009-drop-useless-function-error-and-info.patch @@ -0,0 +1,265 @@ +From f5cc3ac5b6ecd78adbccd6792c2705c850a1c189 Mon Sep 17 00:00:00 2001 +From: vegbir +Date: Tue, 29 Aug 2023 09:01:50 +0000 +Subject: [PATCH] drop useless function error and info + +Signed-off-by: vegbir +--- + container/container.go | 14 +++++++++----- + hack/syscontainer-tools_wrapper | 1 - + hooks/syscontainer-hooks/poststop.go | 2 +- + libdevice/binds_unix.go | 2 +- + libdevice/container_work.go | 7 ++++--- + libnetwork/drivers/eth/driver.go | 9 +++++---- + pkg/udevd/udevd.go | 9 ++++++--- + pkg/udevd/udevd_controller.go | 6 ++++-- + utils/transfer.go | 2 +- + utils/utils.go | 12 ++++++++++++ + 10 files changed, 43 insertions(+), 21 deletions(-) + +diff --git a/container/container.go b/container/container.go +index 67f6b53..fb9b20f 100644 +--- a/container/container.go ++++ b/container/container.go +@@ -125,7 +125,9 @@ func (c *Container) Lock() error { + // LOCK_EX means only one process could lock it at one time. + // LOCK_NB is not set, using block mode. + if err := unix.Flock(int(f.Fd()), unix.LOCK_EX); err != nil { +- f.Close() ++ if closeErr := f.Close(); closeErr != nil { ++ logrus.Warnf("fail to close %v: %v", fileName, closeErr) ++ } + return err + } + c.lock = f +@@ -147,9 +149,9 @@ func (c *Container) GetCgroupPath() (string, error) { + return "", fmt.Errorf("%s: %v", string(out), err) + } + cgroupPath := strings.Trim(string(out), "\n") +- if len(cgroupPath) >= 2 { +- cgroupPath = cgroupPath[1 : len(cgroupPath)-1] +- } ++ if len(cgroupPath) >= 2 { ++ cgroupPath = cgroupPath[1 : len(cgroupPath)-1] ++ } + if cgroupPath == "" { + // by default, the cgroup path is "/isulad/" + cgroupPath = "/isulad" +@@ -204,7 +206,9 @@ func getIsuladContainerSpec(id string) (spec *specs.Spec, err error) { + } + defer func() { + if config != nil { +- config.Close() ++ if closeErr := config.Close(); closeErr != nil { ++ logrus.Warnf("fail to close %v: %v", configPath, closeErr) ++ } + } + }() + if err := json.NewDecoder(config).Decode(&spec); err != nil { +diff --git a/hack/syscontainer-tools_wrapper b/hack/syscontainer-tools_wrapper +index 671eec3..33db18e 100644 +--- a/hack/syscontainer-tools_wrapper ++++ b/hack/syscontainer-tools_wrapper +@@ -9,7 +9,6 @@ + # PURPOSE. + # See the Mulan PSL v2 for more details. + # Description: syscontainer tools wrapper +-# Author: zhangsong234 + # Create: 2020-01-17 + + LOG_DIR=/var/log/hyperagent +diff --git a/hooks/syscontainer-hooks/poststop.go b/hooks/syscontainer-hooks/poststop.go +index 35f648e..8f03bd7 100644 +--- a/hooks/syscontainer-hooks/poststop.go ++++ b/hooks/syscontainer-hooks/poststop.go +@@ -86,7 +86,7 @@ func RemoveNetworkDevices(state *configs.HookState, hookConfig *hconfig.Containe + if err := os.Remove(file.Name()); err != nil { + logrus.Errorf("Failed to remove fileName err: %v", err) + } +- file.Close() ++ utils.DropError(file.Close()) + }() + + for _, nic := range hookConfig.NetworkInterfaces { +diff --git a/libdevice/binds_unix.go b/libdevice/binds_unix.go +index 2c45700..ca784c0 100644 +--- a/libdevice/binds_unix.go ++++ b/libdevice/binds_unix.go +@@ -128,7 +128,7 @@ func findPathDevice(path string) (*types.Device, string, error) { + reader := bufio.NewReader(stdout) + + // ignore first line. +- reader.ReadString('\n') ++ utils.DropError(reader.ReadString('\n')) + line, err := reader.ReadString('\n') + if err != nil { + logrus.Errorf("reader.ReadString error: %v", err) +diff --git a/libdevice/container_work.go b/libdevice/container_work.go +index 050438f..07bfdfa 100644 +--- a/libdevice/container_work.go ++++ b/libdevice/container_work.go +@@ -16,7 +16,6 @@ package libdevice + import ( + "encoding/json" + "fmt" +- "github.com/sirupsen/logrus" + "io/ioutil" + "os" + "path" +@@ -26,6 +25,8 @@ import ( + "syscall" + + "github.com/docker/docker/pkg/reexec" ++ "github.com/sirupsen/logrus" ++ + "isula.org/syscontainer-tools/libdevice/nsexec" + "isula.org/syscontainer-tools/pkg/mount" + "isula.org/syscontainer-tools/types" +@@ -158,7 +159,7 @@ func doMount(pipe *os.File) error { + if err != nil { + return fmt.Errorf("fail to create %s, err: %s", mnt.Destination, err) + } +- f.Close() ++ utils.DropError(f.Close()) + } + return mount.Mount(mnt.Source, mnt.Destination, "none", mnt.Options) + } +@@ -290,7 +291,7 @@ func doAddBind(pipe *os.File) error { + if err != nil { + return fmt.Errorf("fail to create transfer path,err: %s", err) + } +- f.Close() ++ utils.DropError(f.Close()) + } + + if err := os.Chown(bind.ContainerPath, bind.UID, bind.GID); err != nil { +diff --git a/libnetwork/drivers/eth/driver.go b/libnetwork/drivers/eth/driver.go +index 901ec9c..549e186 100644 +--- a/libnetwork/drivers/eth/driver.go ++++ b/libnetwork/drivers/eth/driver.go +@@ -26,6 +26,7 @@ import ( + "isula.org/syscontainer-tools/libnetwork/drivers/common" + "isula.org/syscontainer-tools/libnetwork/nsutils" + "isula.org/syscontainer-tools/pkg/ethtool" ++ "isula.org/syscontainer-tools/utils" + ) + + type ethDriver struct { +@@ -164,10 +165,10 @@ func (d *ethDriver) DeleteIf() (rErr error) { + // move the network interface back to the container + if err := netlink.LinkSetNsFd(hostNic, int(nsFD.Fd())); err != nil { + logrus.Errorf("Recover on failure: failed to move nic(%s) back to container: %v", d.GetHostNicName(), err) +- nsFD.Close() ++ utils.DropError(nsFD.Close()) + return + } +- nsFD.Close() ++ utils.DropError(nsFD.Close()) + } + }() + // set iface to user desired name +@@ -318,10 +319,10 @@ func (d *ethDriver) JoinAndConfigure() (rErr error) { + // move the network interface back to the host + if err := netlink.LinkSetNsFd(ctrNic, int(initnsFD.Fd())); err != nil { + logrus.Errorf("Recover on failure: failed to move nic(%s) back to host: %v", d.GetHostNicName(), err) +- initnsFD.Close() ++ utils.DropError(initnsFD.Close()) + return + } +- initnsFD.Close() ++ utils.DropError(initnsFD.Close()) + } + }() + // set iface to user desired name +diff --git a/pkg/udevd/udevd.go b/pkg/udevd/udevd.go +index 22b0cba..2465066 100644 +--- a/pkg/udevd/udevd.go ++++ b/pkg/udevd/udevd.go +@@ -16,11 +16,14 @@ package udevd + import ( + "bufio" + "fmt" +- "github.com/sirupsen/logrus" + "os" + "os/exec" + "path/filepath" + "strings" ++ ++ "github.com/sirupsen/logrus" ++ ++ "isula.org/syscontainer-tools/utils" + ) + + func usingUdevd() (bool, error) { +@@ -42,8 +45,8 @@ func saveRules(path string, rules []*Rule) error { + return err + } + +- f.WriteString("## This File is auto-generated by syscontainer-tools.\n") +- f.WriteString("## DO NOT EDIT IT\n\n") ++ utils.DropError(f.WriteString("## This File is auto-generated by syscontainer-tools.\n")) ++ utils.DropError(f.WriteString("## DO NOT EDIT IT\n\n")) + for _, r := range rules { + if _, err := f.WriteString(fmt.Sprintf("%s\n", r.ToUdevRuleString())); err != nil { + logrus.Errorf("f.WriteString err: %s", err) +diff --git a/pkg/udevd/udevd_controller.go b/pkg/udevd/udevd_controller.go +index d8ca84e..71138d5 100644 +--- a/pkg/udevd/udevd_controller.go ++++ b/pkg/udevd/udevd_controller.go +@@ -18,8 +18,10 @@ import ( + "os" + "path/filepath" + +- hconfig "isula.org/syscontainer-tools/config" + "golang.org/x/sys/unix" ++ ++ hconfig "isula.org/syscontainer-tools/config" ++ "isula.org/syscontainer-tools/utils" + ) + + var ( +@@ -97,7 +99,7 @@ func (sc *udevdController) Lock() error { + // LOCK_EX means only one process could lock it at one time. + // LOCK_NB is not set, using block mode. + if err := unix.Flock(int(f.Fd()), unix.LOCK_EX); err != nil { +- f.Close() ++ utils.DropError(f.Close()) + return err + } + sc.lock = f +diff --git a/utils/transfer.go b/utils/transfer.go +index a7f4b31..f164857 100644 +--- a/utils/transfer.go ++++ b/utils/transfer.go +@@ -106,7 +106,7 @@ func parepareMountpoint(sPath, dPath, mOpt string, isDir bool) error { + if err != nil { + return fmt.Errorf("Fail to create transfer path,err: %s", err) + } +- f.Close() ++ DropError(f.Close()) + } + + if m, err := mount.Mounted(dPath); err != nil { +diff --git a/utils/utils.go b/utils/utils.go +index 31c8d14..3adbf2d 100644 +--- a/utils/utils.go ++++ b/utils/utils.go +@@ -184,3 +184,15 @@ func RandomFile(folder string) string { + } + return path + } ++ ++// DropError drop unused error ++func DropError(args ...interface{}) { ++ argn := len(args) ++ if argn == 0 { ++ return ++ } ++ arg := args[argn-1] ++ if arg != nil { ++ logrus.Warnf("drop error: %v\n", arg) ++ } ++} +-- +2.41.0 + diff --git a/0010-fix-log-of-removeUdevRule.patch b/0010-fix-log-of-removeUdevRule.patch new file mode 100644 index 0000000..17dc6be --- /dev/null +++ b/0010-fix-log-of-removeUdevRule.patch @@ -0,0 +1,25 @@ +From 56a42267650b11f5eb691cb852f0f7a827ed715a Mon Sep 17 00:00:00 2001 +From: yangjiaqi +Date: Thu, 21 Sep 2023 11:07:21 +0800 +Subject: [PATCH] fix log of RemoveUdevRule + +--- + hooks/syscontainer-hooks/poststop.go | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/hooks/syscontainer-hooks/poststop.go b/hooks/syscontainer-hooks/poststop.go +index 8f03bd7..f422c4e 100644 +--- a/hooks/syscontainer-hooks/poststop.go ++++ b/hooks/syscontainer-hooks/poststop.go +@@ -50,7 +50,7 @@ func RemoveUdevRule(state *configs.HookState, hookConfig *hconfig.ContainerHookC + resolvDev := calcPathForDevice(state.Root, dev) + device, err := libdevice.ParseDevice(resolvDev) + if err != nil { +- logrus.Errorf("[device-hook] Add device (%s), parse device failed: %v", resolvDev, err) ++ logrus.Errorf("[device-hook] RemoveUdevRule (%s), parse device failed: %v", resolvDev, err) + continue + } + +-- +2.30.0 + diff --git a/syscontainer-tools.spec b/syscontainer-tools.spec index 3db6c2f..4ce8dfd 100644 --- a/syscontainer-tools.spec +++ b/syscontainer-tools.spec @@ -1,7 +1,7 @@ #Basic Information Name: syscontainer-tools Version: 0.9 -Release: 55 +Release: 63 Summary: syscontainer tools for IT, work with iSulad License: Mulan PSL v2 URL: https://gitee.com/openeuler/syscontainer-tools @@ -13,6 +13,12 @@ Patch2: 0002-syscontainer-tools-build-security-option.patch Patch3: 0003-enable-external-linkmode-for-cgo-build.patch Patch4: 0004-add-dt-test.patch Patch5: 0005-add-riscv64-to-syscall-build.patch +Patch6: 0006-support-ipv6.patch +Patch7: 0007-use-file-locks-to-avoid-remounting-the-sharepath-mas.patch +Patch8: 0008-clean-up-run-syscontainer-tools-netns-containerid-di.patch +Patch9: 0009-drop-useless-function-error-and-info.patch +Patch10: 0010-fix-log-of-removeUdevRule.patch + %ifarch sw_64 Patch1000: 1000-add-sw_64-support-not-upstream-modified-files.patch %endif @@ -58,8 +64,8 @@ make localtest %preun %post -GRAPH=`isula info | grep -Eo "iSulad Root Dir:.+" | grep -Eo "\/.*"` -if [ "$GRAPH" == "" ]; then +GRAPH=`isula info 2>/dev/null | grep -Eo "iSulad Root Dir:.+" | grep -Eo "\/.*"` +if [ x"$GRAPH" == "x" ]; then GRAPH="/var/lib/isulad" fi @@ -120,6 +126,54 @@ chmod 0640 ${HOOK_SPEC}/hookspec.json rm -rfv %{buildroot} %changelog +* Mon Dec 25 2023 yangjiaqi - 0.9-63 +- Type:bugfix +- CVE:NA +- SUG:NA +- DESC:hide error when isula info is abnormal + +* Mon Dec 25 2023 yangjiaqi - 0.9-62 +- Type:bugfix +- CVE:NA +- SUG:restart +- DESC:fix log of removeUdevRule + +* Mon Dec 25 2023 yangjiaqi - 0.9-61 +- Type:bugfix +- CVE:NA +- SUG:restart +- DESC:adjust the position of Shenwei patch + +* Mon Dec 25 2023 yangjiaqi - 0.9-60 +- Type:bugfix +- CVE:NA +- SUG:restart +- DESC:drop useless function error and info + +* Mon Dec 25 2023 yangjiaqi - 0.9-59 +- Type:bugfix +- CVE:NA +- SUG:restart +- DESC:remove redundant symbol + +* Mon Dec 25 2023 yangjiaqi - 0.9-58 +- Type:bugfix +- CVE:NA +- SUG:restart +- DESC:clean up run/syscontainer-tools/netns/container_id directory residues in the prestart phase + +* Mon Dec 25 2023 yangjiaqi - 0.9-57 +- Type:bugfix +- CVE:NA +- SUG:restart +- DESC:use file locks to avoid remounting the sharepath/master dir + +* Mon Dec 25 2023 yangjiaqi - 0.9-56 +- Type:bugfix +- CVE:NA +- SUG:restart +- DESC:support ip6 + * Tue Oct 17 2023 yangjiaqi - 0.9-55 - remove useless patch