github.com/apptainer/singularity@v3.1.1+incompatible/pkg/network/network.go (about) 1 package network 2 3 import ( 4 "fmt" 5 "net" 6 "os" 7 "sort" 8 "strconv" 9 "strings" 10 11 "github.com/containernetworking/cni/libcni" 12 "github.com/containernetworking/cni/pkg/types" 13 "github.com/containernetworking/cni/pkg/types/current" 14 "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" 15 "github.com/sylabs/singularity/internal/pkg/util/env" 16 ) 17 18 type netError string 19 20 func (e netError) Error() string { return string(e) } 21 22 const ( 23 // ErrNoCNIConfig corresponds to a missing CNI configuration path 24 ErrNoCNIConfig = netError("no CNI configuration path provided") 25 // ErrNoCNIPlugin corresponds to a missing CNI plugin path 26 ErrNoCNIPlugin = netError("no CNI plugin path provided") 27 ) 28 29 // CNIPath contains path to CNI configuration directory and path to executable 30 // CNI plugins directory 31 type CNIPath struct { 32 Conf string 33 Plugin string 34 } 35 36 // Setup contains network installation setup 37 type Setup struct { 38 networks []string 39 networkConfList []*libcni.NetworkConfigList 40 runtimeConf []*libcni.RuntimeConf 41 result []types.Result 42 cniPath *CNIPath 43 containerID string 44 netNS string 45 envPath string 46 } 47 48 // PortMapEntry describes a port mapping between host and container 49 type PortMapEntry struct { 50 HostPort int `json:"hostPort"` 51 ContainerPort int `json:"containerPort"` 52 Protocol string `json:"protocol"` 53 HostIP string `json:"hostIP,omitempty"` 54 } 55 56 // GetAllNetworkConfigList lists configured networks in configuration path directory 57 // provided by cniPath 58 func GetAllNetworkConfigList(cniPath *CNIPath) ([]*libcni.NetworkConfigList, error) { 59 networks := make([]*libcni.NetworkConfigList, 0) 60 61 if cniPath == nil { 62 return networks, ErrNoCNIConfig 63 } 64 if cniPath.Conf == "" { 65 return networks, ErrNoCNIConfig 66 } 67 68 files, err := libcni.ConfFiles(cniPath.Conf, []string{".conf", ".json", ".conflist"}) 69 if err != nil { 70 return nil, err 71 } else if len(files) == 0 { 72 return nil, libcni.NoConfigsFoundError{Dir: cniPath.Conf} 73 } 74 sort.Strings(files) 75 76 for _, file := range files { 77 if strings.HasSuffix(file, ".conflist") { 78 conf, err := libcni.ConfListFromFile(file) 79 if err != nil { 80 return nil, err 81 } 82 networks = append(networks, conf) 83 } else { 84 conf, err := libcni.ConfFromFile(file) 85 if err != nil { 86 return nil, err 87 } 88 confList, err := libcni.ConfListFromConf(conf) 89 if err != nil { 90 return nil, err 91 } 92 networks = append(networks, confList) 93 } 94 } 95 96 return networks, nil 97 } 98 99 // NewSetup creates and returns a network setup to configure, add and remove 100 // network interfaces in container 101 func NewSetup(networks []string, containerID string, netNS string, cniPath *CNIPath) (*Setup, error) { 102 id := containerID 103 104 if id == "" { 105 id = strconv.Itoa(os.Getpid()) 106 } 107 108 if cniPath == nil { 109 return nil, ErrNoCNIConfig 110 } 111 if cniPath.Conf == "" { 112 return nil, ErrNoCNIConfig 113 } 114 if cniPath.Plugin == "" { 115 return nil, ErrNoCNIPlugin 116 } 117 118 networkConfList := make([]*libcni.NetworkConfigList, 0) 119 runtimeConf := make([]*libcni.RuntimeConf, 0) 120 121 ifIndex := 0 122 for _, network := range networks { 123 nlist, err := libcni.LoadConfList(cniPath.Conf, network) 124 if err != nil { 125 return nil, err 126 } 127 128 rt := &libcni.RuntimeConf{ 129 ContainerID: containerID, 130 NetNS: netNS, 131 IfName: fmt.Sprintf("eth%d", ifIndex), 132 CapabilityArgs: make(map[string]interface{}, 0), 133 Args: make([][2]string, 0), 134 } 135 136 runtimeConf = append(runtimeConf, rt) 137 networkConfList = append(networkConfList, nlist) 138 139 ifIndex++ 140 } 141 142 return &Setup{ 143 networks: networks, 144 networkConfList: networkConfList, 145 runtimeConf: runtimeConf, 146 cniPath: cniPath, 147 netNS: netNS, 148 containerID: id, 149 }, 150 nil 151 } 152 153 // NewSetupFromConfig creates and returns network setup to configure from 154 // a network configuration list 155 func NewSetupFromConfig(networkConfList []*libcni.NetworkConfigList, containerID string, netNS string, cniPath *CNIPath) (*Setup, error) { 156 id := containerID 157 158 if id == "" { 159 id = strconv.Itoa(os.Getpid()) 160 } 161 162 if cniPath == nil { 163 return nil, ErrNoCNIConfig 164 } 165 if cniPath.Conf == "" { 166 return nil, ErrNoCNIConfig 167 } 168 if cniPath.Plugin == "" { 169 return nil, ErrNoCNIPlugin 170 } 171 172 runtimeConf := make([]*libcni.RuntimeConf, len(networkConfList)) 173 networks := make([]string, len(networkConfList)) 174 175 ifIndex := 0 176 for i, conf := range networkConfList { 177 runtimeConf[i] = &libcni.RuntimeConf{ 178 ContainerID: containerID, 179 NetNS: netNS, 180 IfName: fmt.Sprintf("eth%d", ifIndex), 181 CapabilityArgs: make(map[string]interface{}, 0), 182 Args: make([][2]string, 0), 183 } 184 185 networks[i] = conf.Name 186 187 ifIndex++ 188 } 189 190 return &Setup{ 191 networks: networks, 192 networkConfList: networkConfList, 193 runtimeConf: runtimeConf, 194 cniPath: cniPath, 195 netNS: netNS, 196 containerID: id, 197 }, 198 nil 199 } 200 201 func parseArg(arg string) ([][2]string, error) { 202 argList := make([][2]string, 0) 203 204 pairs := strings.Split(arg, ";") 205 for _, pair := range pairs { 206 keyVal := strings.Split(pair, "=") 207 if len(keyVal) != 2 { 208 return nil, fmt.Errorf("invalid argument: %s", pair) 209 } 210 argList = append(argList, [2]string{keyVal[0], keyVal[1]}) 211 } 212 return argList, nil 213 } 214 215 // SetCapability sets capability arguments for the corresponding network plugin 216 // uses by a configured network 217 func (m *Setup) SetCapability(network string, capName string, args interface{}) error { 218 for i := range m.networks { 219 if m.networks[i] == network { 220 hasCap := false 221 for _, plugin := range m.networkConfList[i].Plugins { 222 if plugin.Network.Capabilities[capName] { 223 hasCap = true 224 break 225 } 226 } 227 228 if !hasCap { 229 return fmt.Errorf("%s network doesn't have %s capability", network, capName) 230 } 231 232 switch args.(type) { 233 case PortMapEntry: 234 if m.runtimeConf[i].CapabilityArgs[capName] == nil { 235 m.runtimeConf[i].CapabilityArgs[capName] = make([]PortMapEntry, 0) 236 } 237 m.runtimeConf[i].CapabilityArgs[capName] = append( 238 m.runtimeConf[i].CapabilityArgs[capName].([]PortMapEntry), 239 args.(PortMapEntry), 240 ) 241 case []allocator.Range: 242 if m.runtimeConf[i].CapabilityArgs[capName] == nil { 243 m.runtimeConf[i].CapabilityArgs[capName] = []allocator.RangeSet{args.([]allocator.Range)} 244 } 245 } 246 } 247 } 248 return nil 249 } 250 251 // SetArgs affects arguments to corresponding network plugins 252 func (m *Setup) SetArgs(args []string) error { 253 if len(m.networks) < 1 { 254 return fmt.Errorf("there is no configured network in list") 255 } 256 257 for _, arg := range args { 258 var splitted []string 259 networkName := "" 260 261 if strings.IndexByte(arg, ':') > strings.IndexByte(arg, '=') { 262 splitted = []string{m.networks[0], arg} 263 } else { 264 splitted = strings.SplitN(arg, ":", 2) 265 } 266 if len(splitted) < 1 && len(splitted) > 2 { 267 return fmt.Errorf("argument must be of form '<network>:KEY1=value1;KEY2=value1' or 'KEY1=value1;KEY2=value1'") 268 } 269 n := len(splitted) - 1 270 if n == 0 { 271 networkName = m.networks[0] 272 } else { 273 networkName = splitted[0] 274 } 275 hasNetwork := false 276 for _, network := range m.networks { 277 if network == networkName { 278 hasNetwork = true 279 break 280 } 281 } 282 if !hasNetwork { 283 return fmt.Errorf("network %s wasn't specified in --network option", networkName) 284 } 285 argList, err := parseArg(splitted[n]) 286 if err != nil { 287 return err 288 } 289 for _, kv := range argList { 290 key := kv[0] 291 value := kv[1] 292 if key == "portmap" { 293 pm := &PortMapEntry{} 294 295 splittedPort := strings.SplitN(value, "/", 2) 296 if len(splittedPort) != 2 { 297 return fmt.Errorf("badly formatted portmap argument '%s', must be of form portmap=hostPort:containerPort/protocol", splitted[1]) 298 } 299 pm.Protocol = splittedPort[1] 300 if pm.Protocol != "tcp" && pm.Protocol != "udp" { 301 return fmt.Errorf("only tcp and udp protocol can be specified") 302 } 303 ports := strings.Split(splittedPort[0], ":") 304 if len(ports) != 1 && len(ports) != 2 { 305 return fmt.Errorf("portmap port argument is badly formatted") 306 } 307 if n, err := strconv.ParseInt(ports[0], 0, 16); err == nil { 308 pm.HostPort = int(n) 309 if pm.HostPort <= 0 { 310 return fmt.Errorf("host port must be greater than zero") 311 } 312 } else { 313 return fmt.Errorf("can't convert host port '%s': %s", ports[0], err) 314 } 315 if len(ports) == 2 { 316 if n, err := strconv.ParseInt(ports[1], 0, 16); err == nil { 317 pm.ContainerPort = int(n) 318 if pm.ContainerPort <= 0 { 319 return fmt.Errorf("container port must be greater than zero") 320 } 321 } else { 322 return fmt.Errorf("can't convert container port '%s': %s", ports[1], err) 323 } 324 } else { 325 pm.ContainerPort = pm.HostPort 326 } 327 if err := m.SetCapability(networkName, "portMappings", *pm); err != nil { 328 return err 329 } 330 } else if key == "ipRange" { 331 ipRange := make([]allocator.Range, 1) 332 _, subnet, err := net.ParseCIDR(value) 333 if err != nil { 334 return err 335 } 336 ipRange[0].Subnet = types.IPNet(*subnet) 337 if err := m.SetCapability(networkName, "ipRanges", ipRange); err != nil { 338 return err 339 } 340 } else { 341 for i := range m.networks { 342 if m.networks[i] == networkName { 343 m.runtimeConf[i].Args = append(m.runtimeConf[i].Args, kv) 344 } 345 } 346 } 347 } 348 } 349 return nil 350 } 351 352 // GetNetworkIP returns IP associated with a configured network, if network 353 // is empty, the function returns IP for the first configured network 354 func (m *Setup) GetNetworkIP(network string, version string) (net.IP, error) { 355 n := network 356 if n == "" && len(m.networkConfList) > 0 { 357 n = m.networkConfList[0].Name 358 } 359 360 for i := 0; i < len(m.networkConfList); i++ { 361 if m.networkConfList[i].Name == n { 362 res, _ := current.GetResult(m.result[i]) 363 for _, ipResult := range res.IPs { 364 if ipResult.Version == version { 365 return ipResult.Address.IP, nil 366 } 367 } 368 break 369 } 370 } 371 372 return nil, fmt.Errorf("no IP found for network %s", network) 373 } 374 375 // GetNetworkInterface returns container network interface associated 376 // with a network, if network is empty, the function returns interface 377 // for the first configured network 378 func (m *Setup) GetNetworkInterface(network string) (string, error) { 379 n := network 380 if n == "" && len(m.networkConfList) > 0 { 381 n = m.networkConfList[0].Name 382 } 383 384 for i := 0; i < len(m.networkConfList); i++ { 385 if m.networkConfList[i].Name == network { 386 return m.runtimeConf[i].IfName, nil 387 } 388 } 389 390 return "", fmt.Errorf("no interface found for network %s", network) 391 } 392 393 // SetEnvPath allows to define custom paths for PATH environment 394 // variables used during CNI plugin execution 395 func (m *Setup) SetEnvPath(envPath string) { 396 m.envPath = envPath 397 } 398 399 // AddNetworks brings up networks interface in container 400 func (m *Setup) AddNetworks() error { 401 return m.command("ADD") 402 } 403 404 // DelNetworks tears down networks interface in container 405 func (m *Setup) DelNetworks() error { 406 return m.command("DEL") 407 } 408 409 func (m *Setup) command(command string) error { 410 if m.envPath != "" { 411 backupEnv := os.Environ() 412 os.Clearenv() 413 os.Setenv("PATH", m.envPath) 414 defer env.SetFromList(backupEnv) 415 } 416 417 config := &libcni.CNIConfig{Path: []string{m.cniPath.Plugin}} 418 419 if command == "ADD" { 420 m.result = make([]types.Result, len(m.networkConfList)) 421 for i := 0; i < len(m.networkConfList); i++ { 422 var err error 423 if m.result[i], err = config.AddNetworkList(m.networkConfList[i], m.runtimeConf[i]); err != nil { 424 for j := i - 1; j >= 0; j-- { 425 if err := config.DelNetworkList(m.networkConfList[j], m.runtimeConf[j]); err != nil { 426 return err 427 } 428 } 429 return err 430 } 431 } 432 } else if command == "DEL" { 433 for i := 0; i < len(m.networkConfList); i++ { 434 if err := config.DelNetworkList(m.networkConfList[i], m.runtimeConf[i]); err != nil { 435 return err 436 } 437 } 438 } 439 return nil 440 }