summaryrefslogtreecommitdiff
path: root/vendor/github.com/rootless-containers/rootlesskit/pkg/port/portutil/portutil.go
blob: a885a76cab0173b1a599fb7ff46cc24d6027abbb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package portutil

import (
	"net"
	"strconv"
	"strings"
	"text/scanner"

	"github.com/pkg/errors"

	"github.com/rootless-containers/rootlesskit/pkg/port"
)

// ParsePortSpec parses a Docker-like representation of PortSpec, but with
// support for both "parent IP" and "child IP" (optional);
// e.g. "127.0.0.1:8080:80/tcp", or "127.0.0.1:8080:10.0.2.100:80/tcp"
//
// Format is as follows:
//
//     <parent IP>:<parent port>[:<child IP>]:<child port>/<proto>
//
// Note that (child IP being optional) the format can either contain 5 or 4
// components. When using IPv6 IP addresses, addresses must use square brackets
// to prevent the colons being mistaken for delimiters. For example:
//
//     [::1]:8080:[::2]:80/udp
func ParsePortSpec(portSpec string) (*port.Spec, error) {
	const (
		parentIP   = iota
		parentPort = iota
		childIP    = iota
		childPort  = iota
		proto      = iota
	)

	var (
		s         scanner.Scanner
		err       error
		parts     = make([]string, 5)
		index     = parentIP
		delimiter = ':'
	)

	// First get the "proto" and "parent-port" at the end. These parts are
	// required, whereas "ParentIP" is optional. Removing them first makes
	// it easier to parse the remaining parts, as otherwise the third part
	// could be _either_ an IP-address _or_ a Port.

	// Get the proto
	protoPos := strings.LastIndex(portSpec, "/")
	if protoPos < 0 {
		return nil, errors.Errorf("missing proto in PortSpec string: %q", portSpec)
	}
	parts[proto] = portSpec[protoPos+1:]
	err = validateProto(parts[proto])
	if err != nil {
		return nil, errors.Wrapf(err, "invalid PortSpec string: %q", portSpec)
	}

	// Get the parent port
	portPos := strings.LastIndex(portSpec, ":")
	if portPos < 0 {
		return nil, errors.Errorf("unexpected PortSpec string: %q", portSpec)
	}
	parts[childPort] = portSpec[portPos+1 : protoPos]

	// Scan the remainder "<IP-address>:<port>[:<IP-address>]"
	s.Init(strings.NewReader(portSpec[:portPos]))

	for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
		if index > childPort {
			return nil, errors.Errorf("unexpected PortSpec string: %q", portSpec)
		}

		switch tok {
		case '[':
			// Start of IPv6 IP-address; value ends at closing bracket (])
			delimiter = ']'
			continue
		case delimiter:
			if delimiter == ']' {
				// End of IPv6 IP-address
				delimiter = ':'
				// Skip the next token, which should be a colon delimiter (:)
				tok = s.Scan()
			}
			index++
			continue
		default:
			parts[index] += s.TokenText()
		}
	}

	if parts[parentIP] != "" && net.ParseIP(parts[parentIP]) == nil {
		return nil, errors.Errorf("unexpected ParentIP in PortSpec string: %q", portSpec)
	}
	if parts[childIP] != "" && net.ParseIP(parts[childIP]) == nil {
		return nil, errors.Errorf("unexpected ParentIP in PortSpec string: %q", portSpec)
	}

	ps := &port.Spec{
		Proto:    parts[proto],
		ParentIP: parts[parentIP],
		ChildIP:  parts[childIP],
	}

	ps.ParentPort, err = strconv.Atoi(parts[parentPort])
	if err != nil {
		return nil, errors.Wrapf(err, "unexpected ChildPort in PortSpec string: %q", portSpec)
	}

	ps.ChildPort, err = strconv.Atoi(parts[childPort])
	if err != nil {
		return nil, errors.Wrapf(err, "unexpected ParentPort in PortSpec string: %q", portSpec)
	}

	return ps, nil
}

// ValidatePortSpec validates *port.Spec.
// existingPorts can be optionally passed for detecting conflicts.
func ValidatePortSpec(spec port.Spec, existingPorts map[int]*port.Status) error {
	if err := validateProto(spec.Proto); err != nil {
		return err
	}
	if spec.ParentIP != "" {
		if net.ParseIP(spec.ParentIP) == nil {
			return errors.Errorf("invalid ParentIP: %q", spec.ParentIP)
		}
	}
	if spec.ChildIP != "" {
		if net.ParseIP(spec.ChildIP) == nil {
			return errors.Errorf("invalid ChildIP: %q", spec.ChildIP)
		}
	}
	if spec.ParentPort <= 0 || spec.ParentPort > 65535 {
		return errors.Errorf("invalid ParentPort: %q", spec.ParentPort)
	}
	if spec.ChildPort <= 0 || spec.ChildPort > 65535 {
		return errors.Errorf("invalid ChildPort: %q", spec.ChildPort)
	}
	for id, p := range existingPorts {
		sp := p.Spec
		sameProto := sp.Proto == spec.Proto
		sameParent := sp.ParentIP == spec.ParentIP && sp.ParentPort == spec.ParentPort
		if sameProto && sameParent {
			return errors.Errorf("conflict with ID %d", id)
		}
	}
	return nil
}

func validateProto(proto string) error {
	switch proto {
	case "tcp", "udp", "sctp":
		return nil
	default:
		return errors.Errorf("unknown proto: %q", proto)
	}
}