github.com/rootless-containers/rootlesskit/v2@v2.3.4/pkg/port/portutil/portutil.go (about) 1 package portutil 2 3 import ( 4 "fmt" 5 "net" 6 "strconv" 7 "strings" 8 "text/scanner" 9 10 "github.com/rootless-containers/rootlesskit/v2/pkg/port" 11 ) 12 13 // ParsePortSpec parses a Docker-like representation of PortSpec, but with 14 // support for both "parent IP" and "child IP" (optional); 15 // e.g. "127.0.0.1:8080:80/tcp", or "127.0.0.1:8080:10.0.2.100:80/tcp" 16 // 17 // Format is as follows: 18 // 19 // <parent IP>:<parent port>[:<child IP>]:<child port>/<proto> 20 // 21 // Note that (child IP being optional) the format can either contain 5 or 4 22 // components. When using IPv6 IP addresses, addresses must use square brackets 23 // to prevent the colons being mistaken for delimiters. For example: 24 // 25 // [::1]:8080:[::2]:80/udp 26 func ParsePortSpec(portSpec string) (*port.Spec, error) { 27 const ( 28 parentIP = iota 29 parentPort = iota 30 childIP = iota 31 childPort = iota 32 proto = iota 33 ) 34 35 var ( 36 s scanner.Scanner 37 err error 38 parts = make([]string, 5) 39 index = parentIP 40 delimiter = ':' 41 ) 42 43 // First get the "proto" and "parent-port" at the end. These parts are 44 // required, whereas "ParentIP" is optional. Removing them first makes 45 // it easier to parse the remaining parts, as otherwise the third part 46 // could be _either_ an IP-address _or_ a Port. 47 48 // Get the proto 49 protoPos := strings.LastIndex(portSpec, "/") 50 if protoPos < 0 { 51 return nil, fmt.Errorf("missing proto in PortSpec string: %q", portSpec) 52 } 53 parts[proto] = portSpec[protoPos+1:] 54 err = validateProto(parts[proto]) 55 if err != nil { 56 return nil, fmt.Errorf("invalid PortSpec string: %q: %w", portSpec, err) 57 } 58 59 // Get the parent port 60 portPos := strings.LastIndex(portSpec, ":") 61 if portPos < 0 { 62 return nil, fmt.Errorf("unexpected PortSpec string: %q", portSpec) 63 } 64 parts[childPort] = portSpec[portPos+1 : protoPos] 65 66 // Scan the remainder "<IP-address>:<port>[:<IP-address>]" 67 s.Init(strings.NewReader(portSpec[:portPos])) 68 69 for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() { 70 if index > childPort { 71 return nil, fmt.Errorf("unexpected PortSpec string: %q", portSpec) 72 } 73 74 switch tok { 75 case '[': 76 // Start of IPv6 IP-address; value ends at closing bracket (]) 77 delimiter = ']' 78 continue 79 case delimiter: 80 if delimiter == ']' { 81 // End of IPv6 IP-address 82 delimiter = ':' 83 // Skip the next token, which should be a colon delimiter (:) 84 tok = s.Scan() 85 } 86 index++ 87 continue 88 default: 89 parts[index] += s.TokenText() 90 } 91 } 92 93 if parts[parentIP] != "" && net.ParseIP(parts[parentIP]) == nil { 94 return nil, fmt.Errorf("unexpected ParentIP in PortSpec string: %q", portSpec) 95 } 96 if parts[childIP] != "" && net.ParseIP(parts[childIP]) == nil { 97 return nil, fmt.Errorf("unexpected ParentIP in PortSpec string: %q", portSpec) 98 } 99 100 ps := &port.Spec{ 101 Proto: parts[proto], 102 ParentIP: parts[parentIP], 103 ChildIP: parts[childIP], 104 } 105 106 ps.ParentPort, err = strconv.Atoi(parts[parentPort]) 107 if err != nil { 108 return nil, fmt.Errorf("unexpected ChildPort in PortSpec string: %q: %w", portSpec, err) 109 } 110 111 ps.ChildPort, err = strconv.Atoi(parts[childPort]) 112 if err != nil { 113 return nil, fmt.Errorf("unexpected ParentPort in PortSpec string: %q: %w", portSpec, err) 114 } 115 116 return ps, nil 117 } 118 119 // ValidatePortSpec validates *port.Spec. 120 // existingPorts can be optionally passed for detecting conflicts. 121 func ValidatePortSpec(spec port.Spec, existingPorts map[int]*port.Status) error { 122 if err := validateProto(spec.Proto); err != nil { 123 return err 124 } 125 if spec.ParentIP != "" { 126 if net.ParseIP(spec.ParentIP) == nil { 127 return fmt.Errorf("invalid ParentIP: %q", spec.ParentIP) 128 } 129 } 130 if spec.ChildIP != "" { 131 if net.ParseIP(spec.ChildIP) == nil { 132 return fmt.Errorf("invalid ChildIP: %q", spec.ChildIP) 133 } 134 } 135 if spec.ParentPort <= 0 || spec.ParentPort > 65535 { 136 return fmt.Errorf("invalid ParentPort: %q", spec.ParentPort) 137 } 138 if spec.ChildPort <= 0 || spec.ChildPort > 65535 { 139 return fmt.Errorf("invalid ChildPort: %q", spec.ChildPort) 140 } 141 for id, p := range existingPorts { 142 sp := p.Spec 143 sameProto := sp.Proto == spec.Proto 144 sameParent := sp.ParentIP == spec.ParentIP && sp.ParentPort == spec.ParentPort 145 if sameProto && sameParent { 146 return fmt.Errorf("conflict with ID %d", id) 147 } 148 } 149 return nil 150 } 151 152 func validateProto(proto string) error { 153 switch proto { 154 case 155 "tcp", "tcp4", "tcp6", 156 "udp", "udp4", "udp6", 157 "sctp", "sctp4", "sctp6": 158 return nil 159 default: 160 return fmt.Errorf("unknown proto: %q", proto) 161 } 162 }