github.com/containerd/nerdctl@v1.7.7/pkg/portutil/portutil.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package portutil 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "net" 23 "strings" 24 25 gocni "github.com/containerd/go-cni" 26 "github.com/containerd/log" 27 "github.com/containerd/nerdctl/pkg/labels" 28 "github.com/containerd/nerdctl/pkg/rootlessutil" 29 "github.com/docker/go-connections/nat" 30 ) 31 32 // return respectively ip, hostPort, containerPort 33 func splitParts(rawport string) (string, string, string) { 34 parts := strings.Split(rawport, ":") 35 n := len(parts) 36 containerport := parts[n-1] 37 38 switch n { 39 case 1: 40 return "", "", containerport 41 case 2: 42 return "", parts[0], containerport 43 case 3: 44 return parts[0], parts[1], containerport 45 default: 46 return strings.Join(parts[:n-2], ":"), parts[n-2], containerport 47 } 48 } 49 50 // ParseFlagP parse port mapping pair, like "127.0.0.1:3000:8080/tcp", 51 // "127.0.0.1:3000-3001:8080-8081/tcp" and "3000:8080" ... 52 func ParseFlagP(s string) ([]gocni.PortMapping, error) { 53 proto := "tcp" 54 splitBySlash := strings.Split(s, "/") 55 switch len(splitBySlash) { 56 case 1: 57 // NOP 58 case 2: 59 proto = strings.ToLower(splitBySlash[1]) 60 switch proto { 61 case "tcp", "udp", "sctp": 62 default: 63 return nil, fmt.Errorf("invalid protocol %q", splitBySlash[1]) 64 } 65 default: 66 return nil, fmt.Errorf("failed to parse %q, unexpected slashes", s) 67 } 68 69 res := gocni.PortMapping{ 70 Protocol: proto, 71 } 72 73 mr := []gocni.PortMapping{} 74 75 ip, hostPort, containerPort := splitParts(splitBySlash[0]) 76 77 if containerPort == "" { 78 return nil, fmt.Errorf("no port specified: %s", splitBySlash[0]) 79 } 80 var startHostPort uint64 81 var endHostPort uint64 82 83 startPort, endPort, err := nat.ParsePortRange(containerPort) 84 if err != nil { 85 return nil, fmt.Errorf("invalid containerPort: %s", containerPort) 86 } 87 if hostPort == "" { 88 // AutoHostPort could not be supported in rootless mode right now, because we can't get correct network from /proc/net/* 89 if rootlessutil.IsRootless() { 90 return nil, fmt.Errorf("automatic port allocation is not implemented for rootless mode (Hint: specify the port like \"12345:%s\", not just \"%s\")", 91 containerPort, containerPort) 92 } 93 startHostPort, endHostPort, err = portAllocate(proto, ip, endPort-startPort+1) 94 if err != nil { 95 return nil, err 96 } 97 log.L.Debugf("There is no hostPort has been spec in command, the auto allocate port is from %d:%d to %d:%d", startHostPort, startPort, endHostPort, endPort) 98 } else { 99 startHostPort, endHostPort, err = nat.ParsePortRange(hostPort) 100 if err != nil { 101 return nil, fmt.Errorf("invalid hostPort: %s", hostPort) 102 } 103 } 104 if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) { 105 if endPort != startPort { 106 return nil, fmt.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) 107 } 108 } 109 110 for i := int32(0); i <= (int32(endPort) - int32(startPort)); i++ { 111 112 res.ContainerPort = int32(startPort) + i 113 res.HostPort = int32(startHostPort) + i 114 if ip == "" { 115 //TODO handle ipv6 116 res.HostIP = "0.0.0.0" 117 } else { 118 // TODO handle ipv6 119 if net.ParseIP(ip) == nil { 120 return nil, fmt.Errorf("invalid ip address: %s", ip) 121 } 122 res.HostIP = ip 123 } 124 125 mr = append(mr, res) 126 } 127 128 return mr, nil 129 } 130 131 // ParsePortsLabel parses JSON-marshalled string from label map 132 // (under `labels.Ports` key) and returns []gocni.PortMapping. 133 func ParsePortsLabel(labelMap map[string]string) ([]gocni.PortMapping, error) { 134 portsJSON := labelMap[labels.Ports] 135 if portsJSON == "" { 136 return []gocni.PortMapping{}, nil 137 } 138 var ports []gocni.PortMapping 139 if err := json.Unmarshal([]byte(portsJSON), &ports); err != nil { 140 return nil, fmt.Errorf("failed to parse label %q=%q: %s", labels.Ports, portsJSON, err.Error()) 141 } 142 return ports, nil 143 }