mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
api/types/container: add network port and port range types
Co-authored-by: Sebastiaan van Stijn <github@gone.nl> Co-authored-by: Cory Snider <csnider@mirantis.com> Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
This commit is contained in:
@@ -3,7 +3,6 @@ module github.com/moby/moby/api
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/docker/go-connections v0.6.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/moby/docker-image-spec v1.3.1
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package container
|
||||
|
||||
import "github.com/docker/go-connections/nat"
|
||||
|
||||
// PortRangeProto is a string containing port number and protocol in the format "80/tcp",
|
||||
// or a port range and protocol in the format "80-83/tcp".
|
||||
//
|
||||
// It is currently an alias for [nat.Port] but may become a concrete type in a future release.
|
||||
type PortRangeProto = nat.Port
|
||||
|
||||
// PortSet is a collection of structs indexed by [HostPort].
|
||||
//
|
||||
// It is currently an alias for [nat.PortSet] but may become a concrete type in a future release.
|
||||
type PortSet = nat.PortSet
|
||||
|
||||
// PortBinding represents a binding between a Host IP address and a [HostPort].
|
||||
//
|
||||
// It is currently an alias for [nat.PortBinding] but may become a concrete type in a future release.
|
||||
type PortBinding = nat.PortBinding
|
||||
|
||||
// PortMap is a collection of [PortBinding] indexed by [HostPort].
|
||||
//
|
||||
// It is currently an alias for [nat.PortMap] but may become a concrete type in a future release.
|
||||
type PortMap = nat.PortMap
|
||||
345
api/types/container/network.go
Normal file
345
api/types/container/network.go
Normal file
@@ -0,0 +1,345 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"iter"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unique"
|
||||
)
|
||||
|
||||
// NetworkProtocol represents a network protocol for a port.
|
||||
type NetworkProtocol string
|
||||
|
||||
const (
|
||||
TCP NetworkProtocol = "tcp"
|
||||
UDP NetworkProtocol = "udp"
|
||||
SCTP NetworkProtocol = "sctp"
|
||||
)
|
||||
|
||||
// Sentinel port proto value for zero Port and PortRange values.
|
||||
var protoZero unique.Handle[NetworkProtocol]
|
||||
|
||||
// Port is a type representing a single port number and protocol in the format "<portnum>/[<proto>]".
|
||||
//
|
||||
// The zero port value, i.e. Port{}, is invalid; use [ParsePort] to create a valid Port value.
|
||||
type Port struct {
|
||||
num uint16
|
||||
proto unique.Handle[NetworkProtocol]
|
||||
}
|
||||
|
||||
// ParsePort parses s as a [Port].
|
||||
//
|
||||
// It normalizes the provided protocol such that "80/tcp", "80/TCP", and "80/tCp" are equivalent.
|
||||
// If a port number is provided, but no protocol, the default ("tcp") protocol is returned.
|
||||
func ParsePort(s string) (Port, error) {
|
||||
if s == "" {
|
||||
return Port{}, errors.New("invalid port: value is empty")
|
||||
}
|
||||
|
||||
port, proto, _ := strings.Cut(s, "/")
|
||||
|
||||
portNum, err := parsePortNumber(port)
|
||||
if err != nil {
|
||||
return Port{}, fmt.Errorf("invalid port '%s': %w", port, err)
|
||||
}
|
||||
|
||||
normalizedPortProto := normalizePortProto(proto)
|
||||
return Port{num: portNum, proto: normalizedPortProto}, nil
|
||||
}
|
||||
|
||||
// MustParsePort calls [ParsePort](s) and panics on error.
|
||||
//
|
||||
// It is intended for use in tests with hard-coded strings.
|
||||
func MustParsePort(s string) Port {
|
||||
p, err := ParsePort(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// PortFrom returns a [Port] with the given number and protocol.
|
||||
//
|
||||
// If no protocol is specified (i.e. proto == ""), then PortFrom returns Port{}, false.
|
||||
func PortFrom(num uint16, proto NetworkProtocol) (p Port, ok bool) {
|
||||
if proto == "" {
|
||||
return Port{}, false
|
||||
}
|
||||
normalized := normalizePortProto(string(proto))
|
||||
return Port{num: num, proto: normalized}, true
|
||||
}
|
||||
|
||||
// Num returns p's port number.
|
||||
func (p Port) Num() uint16 {
|
||||
return p.num
|
||||
}
|
||||
|
||||
// Proto returns p's network protocol.
|
||||
func (p Port) Proto() NetworkProtocol {
|
||||
return p.proto.Value()
|
||||
}
|
||||
|
||||
// IsZero reports whether p is the zero value.
|
||||
func (p Port) IsZero() bool {
|
||||
return p.proto == protoZero
|
||||
}
|
||||
|
||||
// IsValid reports whether p is an initialized valid port (not the zero value).
|
||||
func (p Port) IsValid() bool {
|
||||
return p.proto != protoZero
|
||||
}
|
||||
|
||||
// String returns a string representation of the port in the format "<portnum>/<proto>".
|
||||
// If the port is the zero value, it returns "invalid port".
|
||||
func (p Port) String() string {
|
||||
switch p.proto {
|
||||
case protoZero:
|
||||
return "invalid port"
|
||||
default:
|
||||
return string(p.AppendTo(nil))
|
||||
}
|
||||
}
|
||||
|
||||
// AppendText implements [encoding.TextAppender] interface.
|
||||
// It is the same as [Port.AppendTo] but returns an error to satisfy the interface.
|
||||
func (p Port) AppendText(b []byte) ([]byte, error) {
|
||||
return p.AppendTo(b), nil
|
||||
}
|
||||
|
||||
// AppendTo appends a text encoding of p to b and returns the extended buffer.
|
||||
func (p Port) AppendTo(b []byte) []byte {
|
||||
if p.IsZero() {
|
||||
return b
|
||||
}
|
||||
return fmt.Appendf(b, "%d/%s", p.num, p.proto.Value())
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler] interface.
|
||||
func (p Port) MarshalText() ([]byte, error) {
|
||||
return p.AppendText(nil)
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler] interface.
|
||||
func (p *Port) UnmarshalText(text []byte) error {
|
||||
if len(text) == 0 {
|
||||
*p = Port{}
|
||||
return nil
|
||||
}
|
||||
|
||||
port, err := ParsePort(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*p = port
|
||||
return nil
|
||||
}
|
||||
|
||||
// Range returns a [PortRange] representing the single port.
|
||||
func (p Port) Range() PortRange {
|
||||
return PortRange{start: p.num, end: p.num, proto: p.proto}
|
||||
}
|
||||
|
||||
// PortSet is a collection of structs indexed by [Port].
|
||||
type PortSet = map[Port]struct{}
|
||||
|
||||
// PortBinding represents a binding between a Host IP address and a Host Port.
|
||||
type PortBinding struct {
|
||||
// HostIP is the host IP Address
|
||||
HostIP string `json:"HostIp"`
|
||||
// HostPort is the host port number
|
||||
HostPort string `json:"HostPort"`
|
||||
}
|
||||
|
||||
// PortMap is a collection of [PortBinding] indexed by [Port].
|
||||
type PortMap = map[Port][]PortBinding
|
||||
|
||||
// PortRange represents a range of port numbers and a protocol in the format "8000-9000/tcp".
|
||||
//
|
||||
// The zero port range value, i.e. PortRange{}, is invalid; use [ParsePortRange] to create a valid PortRange value.
|
||||
type PortRange struct {
|
||||
start uint16
|
||||
end uint16
|
||||
proto unique.Handle[NetworkProtocol]
|
||||
}
|
||||
|
||||
// ParsePortRange parses s as a [PortRange].
|
||||
//
|
||||
// It normalizes the provided protocol such that "80-90/tcp", "80-90/TCP", and "80-90/tCp" are equivalent.
|
||||
// If a port number range is provided, but no protocol, the default ("tcp") protocol is returned.
|
||||
func ParsePortRange(s string) (PortRange, error) {
|
||||
if s == "" {
|
||||
return PortRange{}, errors.New("invalid port range: value is empty")
|
||||
}
|
||||
|
||||
portRange, proto, _ := strings.Cut(s, "/")
|
||||
|
||||
start, end, ok := strings.Cut(portRange, "-")
|
||||
startVal, err := parsePortNumber(start)
|
||||
if err != nil {
|
||||
return PortRange{}, fmt.Errorf("invalid start port '%s': %w", start, err)
|
||||
}
|
||||
|
||||
portProto := normalizePortProto(proto)
|
||||
|
||||
if !ok || start == end {
|
||||
return PortRange{start: startVal, end: startVal, proto: portProto}, nil
|
||||
}
|
||||
|
||||
endVal, err := parsePortNumber(end)
|
||||
if err != nil {
|
||||
return PortRange{}, fmt.Errorf("invalid end port '%s': %w", end, err)
|
||||
}
|
||||
if endVal < startVal {
|
||||
return PortRange{}, errors.New("invalid port range: " + s)
|
||||
}
|
||||
return PortRange{start: startVal, end: endVal, proto: portProto}, nil
|
||||
}
|
||||
|
||||
// MustParsePortRange calls [ParsePortRange](s) and panics on error.
|
||||
// It is intended for use in tests with hard-coded strings.
|
||||
func MustParsePortRange(s string) PortRange {
|
||||
pr, err := ParsePortRange(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return pr
|
||||
}
|
||||
|
||||
// PortRangeFrom returns a [PortRange] with the given start and end port numbers and protocol.
|
||||
//
|
||||
// If end < start or no protocol is specified (i.e. proto == ""), then PortRangeFrom returns PortRange{}, false.
|
||||
func PortRangeFrom(start, end uint16, proto NetworkProtocol) (pr PortRange, ok bool) {
|
||||
if end < start || proto == "" {
|
||||
return PortRange{}, false
|
||||
}
|
||||
normalized := normalizePortProto(string(proto))
|
||||
return PortRange{start: start, end: end, proto: normalized}, true
|
||||
}
|
||||
|
||||
// Start returns pr's start port number.
|
||||
func (pr PortRange) Start() uint16 {
|
||||
return pr.start
|
||||
}
|
||||
|
||||
// End returns pr's end port number.
|
||||
func (pr PortRange) End() uint16 {
|
||||
return pr.end
|
||||
}
|
||||
|
||||
// Proto returns pr's network protocol.
|
||||
func (pr PortRange) Proto() NetworkProtocol {
|
||||
return pr.proto.Value()
|
||||
}
|
||||
|
||||
// IsZero reports whether pr is the zero value.
|
||||
func (pr PortRange) IsZero() bool {
|
||||
return pr.proto == protoZero
|
||||
}
|
||||
|
||||
// IsValid reports whether pr is an initialized valid port range (not the zero value).
|
||||
func (pr PortRange) IsValid() bool {
|
||||
return pr.proto != protoZero
|
||||
}
|
||||
|
||||
// String returns a string representation of the port range in the format "<start>-<end>/<proto>" or "<portnum>/<proto>" if start == end.
|
||||
// If the port range is the zero value, it returns "invalid port range".
|
||||
func (pr PortRange) String() string {
|
||||
switch pr.proto {
|
||||
case protoZero:
|
||||
return "invalid port range"
|
||||
default:
|
||||
return string(pr.AppendTo(nil))
|
||||
}
|
||||
}
|
||||
|
||||
// AppendText implements [encoding.TextAppender] interface.
|
||||
// It is the same as [PortRange.AppendTo] but returns an error to satisfy the interface.
|
||||
func (pr PortRange) AppendText(b []byte) ([]byte, error) {
|
||||
return pr.AppendTo(b), nil
|
||||
}
|
||||
|
||||
// AppendTo appends a text encoding of pr to b and returns the extended buffer.
|
||||
func (pr PortRange) AppendTo(b []byte) []byte {
|
||||
if pr.IsZero() {
|
||||
return b
|
||||
}
|
||||
if pr.start == pr.end {
|
||||
return fmt.Appendf(b, "%d/%s", pr.start, pr.proto.Value())
|
||||
}
|
||||
return fmt.Appendf(b, "%d-%d/%s", pr.start, pr.end, pr.proto.Value())
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler] interface.
|
||||
func (pr PortRange) MarshalText() ([]byte, error) {
|
||||
return pr.AppendText(nil)
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler] interface.
|
||||
func (pr *PortRange) UnmarshalText(text []byte) error {
|
||||
if len(text) == 0 {
|
||||
*pr = PortRange{}
|
||||
return nil
|
||||
}
|
||||
|
||||
portRange, err := ParsePortRange(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*pr = portRange
|
||||
return nil
|
||||
}
|
||||
|
||||
// Range returns pr.
|
||||
func (pr PortRange) Range() PortRange {
|
||||
return pr
|
||||
}
|
||||
|
||||
// All returns an iterator over all the individual ports in the range.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// for port := range pr.All() {
|
||||
// // ...
|
||||
// }
|
||||
func (pr PortRange) All() iter.Seq[Port] {
|
||||
return func(yield func(Port) bool) {
|
||||
for i := uint32(pr.Start()); i <= uint32(pr.End()); i++ {
|
||||
if !yield(Port{num: uint16(i), proto: pr.proto}) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parsePortNumber parses rawPort into an int, unwrapping strconv errors
|
||||
// and returning a single "out of range" error for any value outside 0–65535.
|
||||
func parsePortNumber(rawPort string) (uint16, error) {
|
||||
if rawPort == "" {
|
||||
return 0, errors.New("value is empty")
|
||||
}
|
||||
port, err := strconv.ParseUint(rawPort, 10, 16)
|
||||
if err != nil {
|
||||
var numErr *strconv.NumError
|
||||
if errors.As(err, &numErr) {
|
||||
err = numErr.Err
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint16(port), nil
|
||||
}
|
||||
|
||||
// normalizePortProto normalizes the protocol string such that "tcp", "TCP", and "tCp" are equivalent.
|
||||
// If proto is not specified, it defaults to "tcp".
|
||||
func normalizePortProto(proto string) unique.Handle[NetworkProtocol] {
|
||||
if proto == "" {
|
||||
return unique.Make(TCP)
|
||||
}
|
||||
|
||||
proto = strings.ToLower(proto)
|
||||
|
||||
return unique.Make(NetworkProtocol(proto))
|
||||
}
|
||||
600
api/types/container/network_test.go
Normal file
600
api/types/container/network_test.go
Normal file
@@ -0,0 +1,600 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
type TestRanger interface {
|
||||
Range() PortRange
|
||||
}
|
||||
|
||||
var _ TestRanger = Port{}
|
||||
var _ TestRanger = PortRange{}
|
||||
|
||||
func TestPort(t *testing.T) {
|
||||
t.Run("Zero Value", func(t *testing.T) {
|
||||
var p Port
|
||||
assert.Check(t, p.IsZero())
|
||||
assert.Check(t, !p.IsValid())
|
||||
assert.Equal(t, p.String(), "invalid port")
|
||||
|
||||
t.Run("Marshal Unmarshal", func(t *testing.T) {
|
||||
var p Port
|
||||
bytes, err := p.MarshalText()
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, len(bytes) == 0)
|
||||
|
||||
err = p.UnmarshalText([]byte(""))
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, p, Port{})
|
||||
})
|
||||
|
||||
t.Run("JSON Marshal Unmarshal", func(t *testing.T) {
|
||||
var p Port
|
||||
bytes, err := json.Marshal(p)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, string(bytes), `""`)
|
||||
|
||||
err = json.Unmarshal([]byte(`""`), &p)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, p, Port{})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("PortFrom", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
num uint16
|
||||
proto NetworkProtocol
|
||||
}{
|
||||
{0, TCP},
|
||||
{80, TCP},
|
||||
{8080, TCP},
|
||||
{65535, TCP},
|
||||
{80, UDP},
|
||||
{8080, SCTP},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(fmt.Sprintf("%d_%s", tc.num, tc.proto), func(t *testing.T) {
|
||||
p, ok := PortFrom(tc.num, tc.proto)
|
||||
assert.Check(t, ok)
|
||||
assert.Equal(t, p.Num(), tc.num)
|
||||
assert.Equal(t, p.Proto(), tc.proto)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Normalize Protocol", func(t *testing.T) {
|
||||
pr1 := portFrom(1234, "tcp")
|
||||
pr2 := portFrom(1234, "TCP")
|
||||
pr3 := portFrom(1234, "tCp")
|
||||
assert.Equal(t, pr1, pr2)
|
||||
assert.Equal(t, pr2, pr3)
|
||||
})
|
||||
|
||||
negativeTests := []struct {
|
||||
num uint16
|
||||
proto NetworkProtocol
|
||||
}{
|
||||
{0, ""},
|
||||
{80, ""},
|
||||
}
|
||||
for _, tc := range negativeTests {
|
||||
t.Run(fmt.Sprintf("%d_%s", tc.num, tc.proto), func(t *testing.T) {
|
||||
p, ok := PortFrom(tc.num, tc.proto)
|
||||
assert.Check(t, !ok)
|
||||
assert.Check(t, p.IsZero())
|
||||
assert.Check(t, !p.IsValid())
|
||||
assert.Equal(t, p.String(), "invalid port")
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ParsePort", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
port Port // output of ParsePort()
|
||||
str string // output of String().
|
||||
portRange PortRange // output of Range()
|
||||
}{
|
||||
// Zero port
|
||||
{
|
||||
in: "0/tcp",
|
||||
port: portFrom(0, TCP),
|
||||
str: "0/tcp",
|
||||
portRange: portRangeFrom(0, 0, TCP),
|
||||
},
|
||||
// Max valid port
|
||||
{
|
||||
in: "65535/tcp",
|
||||
port: portFrom(65535, TCP),
|
||||
str: "65535/tcp",
|
||||
portRange: portRangeFrom(65535, 65535, TCP),
|
||||
},
|
||||
// Simple valid ports
|
||||
{
|
||||
in: "1234/tcp",
|
||||
port: portFrom(1234, TCP),
|
||||
str: "1234/tcp",
|
||||
portRange: portRangeFrom(1234, 1234, TCP),
|
||||
},
|
||||
{
|
||||
in: "1234/udp",
|
||||
port: portFrom(1234, UDP),
|
||||
str: "1234/udp",
|
||||
portRange: portRangeFrom(1234, 1234, UDP),
|
||||
},
|
||||
{
|
||||
in: "1234/sctp",
|
||||
port: portFrom(1234, SCTP),
|
||||
str: "1234/sctp",
|
||||
portRange: portRangeFrom(1234, 1234, SCTP),
|
||||
},
|
||||
// Default protocol is tcp
|
||||
{
|
||||
in: "1234",
|
||||
port: portFrom(1234, TCP),
|
||||
str: "1234/tcp",
|
||||
portRange: portRangeFrom(1234, 1234, TCP),
|
||||
},
|
||||
// Default protocol is tcp
|
||||
{
|
||||
in: "1234/",
|
||||
port: portFrom(1234, TCP),
|
||||
str: "1234/tcp",
|
||||
portRange: portRangeFrom(1234, 1234, TCP),
|
||||
},
|
||||
{
|
||||
in: "1234/tcp:ipv6only",
|
||||
port: portFrom(1234, "tcp:ipv6only"),
|
||||
str: "1234/tcp:ipv6only",
|
||||
portRange: portRangeFrom(1234, 1234, "tcp:ipv6only"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(strings.ReplaceAll(tc.in, "/", "_"), func(t *testing.T) {
|
||||
got, err := ParsePort(tc.in)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, got, tc.port)
|
||||
|
||||
MustParsePort(tc.in) // should not panic
|
||||
|
||||
assert.Check(t, !got.IsZero())
|
||||
assert.Check(t, got.IsValid())
|
||||
|
||||
// Check that ParsePort is a pure function.
|
||||
got2, err := ParsePort(tc.in)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, got2, got)
|
||||
|
||||
// Check that ParsePort(port.String()) is the identity function.
|
||||
got3, err := ParsePort(got.String())
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, got3, got)
|
||||
|
||||
// Check String() output
|
||||
s := got.String()
|
||||
wants := tc.str
|
||||
if wants == "" {
|
||||
wants = tc.in
|
||||
}
|
||||
assert.Equal(t, s, wants)
|
||||
|
||||
js := `"` + tc.in + `"`
|
||||
var jsgot Port
|
||||
err = json.Unmarshal([]byte(js), &jsgot)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, jsgot, got)
|
||||
|
||||
jsb, err := json.Marshal(jsgot)
|
||||
assert.NilError(t, err)
|
||||
|
||||
jswant := `"` + wants + `"`
|
||||
assert.Equal(t, string(jsb), jswant)
|
||||
|
||||
// Check Range() output
|
||||
r := got.Range()
|
||||
assert.Equal(t, r, tc.portRange)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Normalize Protocol", func(t *testing.T) {
|
||||
p1 := MustParsePort("1234/tcp")
|
||||
p2 := MustParsePort("1234/TCP")
|
||||
p3 := MustParsePort("1234/tCp")
|
||||
assert.Equal(t, p1, p2)
|
||||
assert.Equal(t, p2, p3)
|
||||
})
|
||||
|
||||
negativeTests := []string{
|
||||
// Empty string
|
||||
"",
|
||||
// Whitespace-only string
|
||||
" ",
|
||||
// No port number
|
||||
"/",
|
||||
// No port number (protocol only)
|
||||
"/tcp",
|
||||
// Negative port
|
||||
"-1",
|
||||
// Too large port
|
||||
"65536",
|
||||
// Non-numeric port
|
||||
"foo",
|
||||
// Port range instead of single port
|
||||
"1234-1240/udp",
|
||||
// Port range instead of single port without protocol
|
||||
"1234-1240",
|
||||
// Garbage port
|
||||
"asd1234/tcp",
|
||||
}
|
||||
|
||||
for _, s := range negativeTests {
|
||||
t.Run(strings.ReplaceAll(s, "/", "_"), func(t *testing.T) {
|
||||
got, err := ParsePort(s)
|
||||
assert.ErrorContains(t, err, "invalid port")
|
||||
assert.Check(t, got.IsZero())
|
||||
assert.Check(t, !got.IsValid())
|
||||
|
||||
// Skip JSON unmarshalling test for empty string as that should succeed.
|
||||
// See test "Zero Value" above.
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var jsgot Port
|
||||
js := []byte(`"` + s + `"`)
|
||||
err = json.Unmarshal(js, &jsgot)
|
||||
assert.ErrorContains(t, err, "invalid port")
|
||||
assert.Equal(t, jsgot, Port{})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPortRange(t *testing.T) {
|
||||
t.Run("Zero Value", func(t *testing.T) {
|
||||
var pr PortRange
|
||||
assert.Check(t, pr.IsZero())
|
||||
assert.Check(t, !pr.IsValid())
|
||||
assert.Equal(t, pr.String(), "invalid port range")
|
||||
|
||||
t.Run("Marshal Unmarshal", func(t *testing.T) {
|
||||
var pr PortRange
|
||||
bytes, err := pr.MarshalText()
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, len(bytes) == 0)
|
||||
|
||||
err = pr.UnmarshalText([]byte(""))
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, pr, PortRange{})
|
||||
})
|
||||
|
||||
t.Run("JSON Marshal Unmarshal", func(t *testing.T) {
|
||||
var pr PortRange
|
||||
bytes, err := json.Marshal(pr)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, string(bytes), `""`)
|
||||
|
||||
err = json.Unmarshal([]byte(`""`), &pr)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, pr, PortRange{})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("PortRangeFrom", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
start uint16
|
||||
end uint16
|
||||
proto NetworkProtocol
|
||||
}{
|
||||
{0, 0, TCP},
|
||||
{0, 1234, TCP},
|
||||
{80, 80, TCP},
|
||||
{80, 8080, TCP},
|
||||
{1234, 65535, TCP},
|
||||
{80, 80, UDP},
|
||||
{80, 8080, SCTP},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(fmt.Sprintf("%d_%d_%s", tc.start, tc.end, tc.proto), func(t *testing.T) {
|
||||
pr, ok := PortRangeFrom(tc.start, tc.end, tc.proto)
|
||||
assert.Check(t, ok)
|
||||
assert.Equal(t, pr.Start(), tc.start)
|
||||
assert.Equal(t, pr.End(), tc.end)
|
||||
assert.Equal(t, pr.Proto(), tc.proto)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Normalize Protocol", func(t *testing.T) {
|
||||
pr1, _ := PortRangeFrom(1234, 5678, "tcp")
|
||||
pr2, _ := PortRangeFrom(1234, 5678, "TCP")
|
||||
pr3, _ := PortRangeFrom(1234, 5678, "tCp")
|
||||
assert.Equal(t, pr1, pr2)
|
||||
assert.Equal(t, pr2, pr3)
|
||||
})
|
||||
|
||||
negativeTests := []struct {
|
||||
start uint16
|
||||
end uint16
|
||||
proto NetworkProtocol
|
||||
}{
|
||||
{1234, 80, TCP}, // end < start
|
||||
{0, 0, ""}, // empty protocol
|
||||
}
|
||||
for _, tc := range negativeTests {
|
||||
t.Run(fmt.Sprintf("%d_%d_%s", tc.start, tc.end, tc.proto), func(t *testing.T) {
|
||||
pr, ok := PortRangeFrom(tc.start, tc.end, tc.proto)
|
||||
assert.Check(t, !ok)
|
||||
assert.Check(t, pr.IsZero())
|
||||
assert.Check(t, !pr.IsValid())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ParsePortRange", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
portRange PortRange // output of ParsePortRange() and Range()
|
||||
str string // output of String(). If "", use in.
|
||||
|
||||
}{
|
||||
// Zero port
|
||||
{
|
||||
in: "0-1234/tcp",
|
||||
portRange: portRangeFrom(0, 1234, TCP),
|
||||
str: "0-1234/tcp",
|
||||
},
|
||||
// Max valid port
|
||||
{
|
||||
in: "1234-65535/tcp",
|
||||
portRange: portRangeFrom(1234, 65535, TCP),
|
||||
str: "1234-65535/tcp",
|
||||
},
|
||||
// Simple valid ports
|
||||
{
|
||||
in: "1234-4567/tcp",
|
||||
portRange: portRangeFrom(1234, 4567, TCP),
|
||||
str: "1234-4567/tcp",
|
||||
},
|
||||
{
|
||||
in: "1234-4567/udp",
|
||||
portRange: portRangeFrom(1234, 4567, UDP),
|
||||
str: "1234-4567/udp",
|
||||
},
|
||||
// Default protocol is tcp
|
||||
{
|
||||
in: "1234-4567",
|
||||
portRange: portRangeFrom(1234, 4567, TCP),
|
||||
str: "1234-4567/tcp",
|
||||
},
|
||||
// Default protocol is tcp
|
||||
{
|
||||
in: "1234-4567/",
|
||||
portRange: portRangeFrom(1234, 4567, TCP),
|
||||
str: "1234-4567/tcp",
|
||||
},
|
||||
{
|
||||
in: "1234/tcp",
|
||||
portRange: portRangeFrom(1234, 1234, TCP),
|
||||
str: "1234/tcp",
|
||||
},
|
||||
{
|
||||
in: "1234",
|
||||
portRange: portRangeFrom(1234, 1234, TCP),
|
||||
str: "1234/tcp",
|
||||
},
|
||||
{
|
||||
in: "1234-5678/tcp:ipv6only",
|
||||
portRange: portRangeFrom(1234, 5678, "tcp:ipv6only"),
|
||||
str: "1234-5678/tcp:ipv6only",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(strings.ReplaceAll(tc.in, "/", "_"), func(t *testing.T) {
|
||||
got, err := ParsePortRange(tc.in)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, got, tc.portRange)
|
||||
assert.Check(t, !got.IsZero())
|
||||
assert.Check(t, got.IsValid())
|
||||
|
||||
MustParsePortRange(tc.in) // should not panic
|
||||
|
||||
// Check that ParsePortRange is a pure function.
|
||||
got2, err := ParsePortRange(tc.in)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, got2, got)
|
||||
|
||||
// Check that ParsePortRange(port.String()) is the identity function.
|
||||
got3, err := ParsePortRange(got.String())
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, got3, got)
|
||||
|
||||
// Check String() output
|
||||
s := got.String()
|
||||
wants := tc.str
|
||||
if wants == "" {
|
||||
wants = tc.in
|
||||
}
|
||||
assert.Equal(t, s, wants)
|
||||
|
||||
js := `"` + tc.in + `"`
|
||||
var jsgot PortRange
|
||||
err = json.Unmarshal([]byte(js), &jsgot)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, jsgot, got)
|
||||
|
||||
jsb, err := json.Marshal(jsgot)
|
||||
assert.NilError(t, err)
|
||||
jswant := `"` + wants + `"`
|
||||
assert.Equal(t, string(jsb), jswant)
|
||||
|
||||
// Check Range() output
|
||||
r := got.Range()
|
||||
assert.Equal(t, r, tc.portRange)
|
||||
})
|
||||
|
||||
t.Run("Normalize Protocol", func(t *testing.T) {
|
||||
pr1 := MustParsePortRange("1234-5678/tcp")
|
||||
pr2 := MustParsePortRange("1234-5678/TCP")
|
||||
pr3 := MustParsePortRange("1234-5678/tCp")
|
||||
assert.Equal(t, pr1, pr2)
|
||||
assert.Equal(t, pr2, pr3)
|
||||
})
|
||||
|
||||
negativeTests := []string{
|
||||
// Empty string
|
||||
"",
|
||||
// Whitespace-only string
|
||||
" ",
|
||||
// No port number
|
||||
"/",
|
||||
// No port number (protocol only)
|
||||
"/tcp",
|
||||
// Negative start port
|
||||
"-1-1234",
|
||||
// Negative end port
|
||||
"1234--1",
|
||||
// Too large start port
|
||||
"65536-65537",
|
||||
// Too large end port
|
||||
"1234-65536",
|
||||
// Non-numeric start port
|
||||
"foo-1234",
|
||||
// Non-numeric end port
|
||||
"1234-bar",
|
||||
// Start port greater than end port
|
||||
"1234-1000",
|
||||
// Garbage port range
|
||||
"asd1234-5678/tcp",
|
||||
}
|
||||
|
||||
for _, s := range negativeTests {
|
||||
t.Run(strings.ReplaceAll(s, "/", "_"), func(t *testing.T) {
|
||||
got, err := ParsePortRange(s)
|
||||
assert.Check(t, err != nil)
|
||||
assert.Check(t, got.IsZero())
|
||||
assert.Check(t, !got.IsValid())
|
||||
|
||||
// Skip JSON unmarshalling test for empty string as that should succeed.
|
||||
// See test "Zero Value" above.
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var jsgot PortRange
|
||||
js := []byte(`"` + s + `"`)
|
||||
err = json.Unmarshal(js, &jsgot)
|
||||
assert.Check(t, err != nil)
|
||||
assert.Equal(t, jsgot, PortRange{})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("PortRange All()", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
want []Port
|
||||
}{
|
||||
{
|
||||
in: "1000-1000/tcp",
|
||||
want: []Port{portFrom(1000, TCP)},
|
||||
},
|
||||
{
|
||||
in: "1000-1002/tcp",
|
||||
want: []Port{portFrom(1000, TCP), portFrom(1001, TCP), portFrom(1002, TCP)},
|
||||
},
|
||||
{
|
||||
in: "0-0/tcp",
|
||||
want: []Port{portFrom(0, TCP)},
|
||||
},
|
||||
{
|
||||
in: "65535-65535/tcp",
|
||||
want: []Port{portFrom(65535, TCP)},
|
||||
},
|
||||
{
|
||||
in: "65530-65535/tcp",
|
||||
want: []Port{portFrom(65530, TCP), portFrom(65531, TCP), portFrom(65532, TCP), portFrom(65533, TCP), portFrom(65534, TCP), portFrom(65535, TCP)},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
pr := MustParsePortRange(tc.in)
|
||||
ports := slices.Collect(pr.All())
|
||||
if !reflect.DeepEqual(ports, tc.want) {
|
||||
t.Errorf("PortRange.All() = %#v, want %#v", ports, tc.want)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("All() stop early", func(t *testing.T) {
|
||||
want := []Port{portFrom(1000, TCP), portFrom(1001, TCP)}
|
||||
pr := MustParsePortRange("1000-2000/tcp")
|
||||
var ports []Port
|
||||
for p := range pr.All() {
|
||||
ports = append(ports, p)
|
||||
if len(ports) == 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(ports, want) {
|
||||
t.Errorf("PortRange.All() = %#v, want %#v", ports, want)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkPortRangeAll(b *testing.B) {
|
||||
b.Run("Single Port", func(b *testing.B) {
|
||||
pr := MustParsePortRange("1234/tcp")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var sink int64
|
||||
for p := range pr.All() {
|
||||
sink += int64(p.Num()) // prevent compiler optimization
|
||||
}
|
||||
if sink < 0 {
|
||||
b.Fatal("unreachable")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Range", func(b *testing.B) {
|
||||
pr := MustParsePortRange("0-65535/tcp")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var sink int64
|
||||
for p := range pr.All() {
|
||||
sink += int64(p.Num()) // prevent compiler optimization
|
||||
}
|
||||
if sink < 0 {
|
||||
b.Fatal("unreachable")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func portFrom(num uint16, proto NetworkProtocol) Port {
|
||||
p, ok := PortFrom(num, proto)
|
||||
if !ok {
|
||||
panic("invalid port")
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func portRangeFrom(start, end uint16, proto NetworkProtocol) PortRange {
|
||||
pr, ok := PortRangeFrom(start, end, proto)
|
||||
if !ok {
|
||||
panic("invalid port range")
|
||||
}
|
||||
return pr
|
||||
}
|
||||
@@ -11,12 +11,13 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/shell"
|
||||
@@ -525,7 +526,7 @@ func dispatchExpose(ctx context.Context, d dispatchRequest, c *instructions.Expo
|
||||
}
|
||||
c.Ports = ports
|
||||
|
||||
ps, _, err := nat.ParsePortSpecs(ports)
|
||||
ps, _, err := parsePortSpecs(ports)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -540,6 +541,154 @@ func dispatchExpose(ctx context.Context, d dispatchRequest, c *instructions.Expo
|
||||
return d.builder.commit(ctx, d.state, "EXPOSE "+strings.Join(c.Ports, " "))
|
||||
}
|
||||
|
||||
// Copied and modified from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat.go#L122-L144
|
||||
//
|
||||
// parsePortSpecs receives port specs in the format of ip:public:private/proto and parses
|
||||
// these in to the internal types
|
||||
func parsePortSpecs(ports []string) (map[container.Port]struct{}, map[container.Port][]container.PortBinding, error) {
|
||||
var (
|
||||
exposedPorts = make(map[container.Port]struct{}, len(ports))
|
||||
bindings = make(map[container.Port][]container.PortBinding)
|
||||
)
|
||||
for _, p := range ports {
|
||||
portMappings, err := parsePortSpec(p)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, pm := range portMappings {
|
||||
for port, portBindings := range pm {
|
||||
if _, ok := exposedPorts[port]; !ok {
|
||||
exposedPorts[port] = struct{}{}
|
||||
}
|
||||
bindings[port] = append(bindings[port], portBindings...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return exposedPorts, bindings, nil
|
||||
}
|
||||
|
||||
// Copied and modified from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat.go#L172-L237
|
||||
//
|
||||
// parsePortSpec parses a port specification string into a slice of [container.PortMap]
|
||||
func parsePortSpec(rawPort string) ([]container.PortMap, error) {
|
||||
ip, hostPort, containerPort := splitParts(rawPort)
|
||||
proto, containerPort := splitProtoPort(containerPort)
|
||||
if containerPort == "" {
|
||||
return nil, fmt.Errorf("no port specified: %s<empty>", rawPort)
|
||||
}
|
||||
|
||||
proto = strings.ToLower(proto)
|
||||
if err := validateProto(proto); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ip != "" && ip[0] == '[' {
|
||||
// Strip [] from IPV6 addresses
|
||||
rawIP, _, err := net.SplitHostPort(ip + ":")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid IP address %v: %w", ip, err)
|
||||
}
|
||||
ip = rawIP
|
||||
}
|
||||
if ip != "" && net.ParseIP(ip) == nil {
|
||||
return nil, errors.New("invalid IP address: " + ip)
|
||||
}
|
||||
|
||||
pr, err := container.ParsePortRange(containerPort)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid containerPort: " + containerPort)
|
||||
}
|
||||
|
||||
var (
|
||||
startPort = pr.Start()
|
||||
endPort = pr.End()
|
||||
)
|
||||
|
||||
var startHostPort, endHostPort uint16
|
||||
if hostPort != "" {
|
||||
hostPortRange, err := container.ParsePortRange(hostPort)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid hostPort: " + hostPort)
|
||||
}
|
||||
startHostPort = hostPortRange.Start()
|
||||
endHostPort = hostPortRange.End()
|
||||
if (endPort - startPort) != (endHostPort - startHostPort) {
|
||||
// Allow host port range iff containerPort is not a range.
|
||||
// In this case, use the host port range as the dynamic
|
||||
// host port range to allocate into.
|
||||
if endPort != startPort {
|
||||
return nil, fmt.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
count := endPort - startPort + 1
|
||||
ports := make([]container.PortMap, 0, count)
|
||||
|
||||
for i := uint16(0); i < count; i++ {
|
||||
hPort := ""
|
||||
if hostPort != "" {
|
||||
hPort = strconv.Itoa(int(startHostPort + i))
|
||||
// Set hostPort to a range only if there is a single container port
|
||||
// and a dynamic host port.
|
||||
if count == 1 && startHostPort != endHostPort {
|
||||
hPort += "-" + strconv.Itoa(int(endHostPort))
|
||||
}
|
||||
}
|
||||
ports = append(ports, container.PortMap{
|
||||
container.MustParsePort(fmt.Sprintf("%d/%s", startPort+i, proto)): []container.PortBinding{{HostIP: ip, HostPort: hPort}},
|
||||
})
|
||||
}
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
// Copied from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat.go#L156-170
|
||||
func splitParts(rawport string) (hostIP, hostPort, containerPort string) {
|
||||
parts := strings.Split(rawport, ":")
|
||||
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
return "", "", parts[0]
|
||||
case 2:
|
||||
return "", parts[0], parts[1]
|
||||
case 3:
|
||||
return parts[0], parts[1], parts[2]
|
||||
default:
|
||||
n := len(parts)
|
||||
return strings.Join(parts[:n-2], ":"), parts[n-2], parts[n-1]
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat.go#L95-L110
|
||||
// splitProtoPort splits a port(range) and protocol, formatted as "<portnum>/[<proto>]"
|
||||
// "<startport-endport>/[<proto>]". It returns an empty string for both if
|
||||
// no port(range) is provided. If a port(range) is provided, but no protocol,
|
||||
// the default ("tcp") protocol is returned.
|
||||
//
|
||||
// splitProtoPort does not validate or normalize the returned values.
|
||||
func splitProtoPort(rawPort string) (proto string, port string) {
|
||||
port, proto, _ = strings.Cut(rawPort, "/")
|
||||
if port == "" {
|
||||
return "", ""
|
||||
}
|
||||
if proto == "" {
|
||||
proto = "tcp"
|
||||
}
|
||||
return proto, port
|
||||
}
|
||||
|
||||
// Copied from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat.go#L112-L120
|
||||
func validateProto(proto string) error {
|
||||
switch proto {
|
||||
case "tcp", "udp", "sctp":
|
||||
// All good
|
||||
return nil
|
||||
default:
|
||||
return errors.New("invalid proto: " + proto)
|
||||
}
|
||||
}
|
||||
|
||||
// USER foo
|
||||
//
|
||||
// Set the user to 'foo' for future commands and when running the
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -335,7 +336,7 @@ func TestExpose(t *testing.T) {
|
||||
assert.Assert(t, sb.state.runConfig.ExposedPorts != nil)
|
||||
assert.Assert(t, is.Len(sb.state.runConfig.ExposedPorts, 1))
|
||||
|
||||
assert.Check(t, is.Contains(sb.state.runConfig.ExposedPorts, container.PortRangeProto("80/tcp")))
|
||||
assert.Check(t, is.Contains(sb.state.runConfig.ExposedPorts, container.MustParsePort("80/tcp")))
|
||||
}
|
||||
|
||||
func TestUser(t *testing.T) {
|
||||
@@ -626,3 +627,648 @@ func TestDispatchUnsupportedOptions(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Copied and modified from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat_test.go#L390-L499
|
||||
func TestParsePortSpecs(t *testing.T) {
|
||||
var (
|
||||
portMap map[container.Port]struct{}
|
||||
bindingMap map[container.Port][]container.PortBinding
|
||||
err error
|
||||
)
|
||||
|
||||
tcp1234 := container.MustParsePort("1234/tcp")
|
||||
udp2345 := container.MustParsePort("2345/udp")
|
||||
sctp3456 := container.MustParsePort("3456/sctp")
|
||||
|
||||
portMap, bindingMap, err = parsePortSpecs([]string{tcp1234.String(), udp2345.String(), sctp3456.String()})
|
||||
if err != nil {
|
||||
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
|
||||
}
|
||||
|
||||
if _, ok := portMap[tcp1234]; !ok {
|
||||
t.Fatal("1234/tcp was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[udp2345]; !ok {
|
||||
t.Fatal("2345/udp was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[sctp3456]; !ok {
|
||||
t.Fatal("3456/sctp was not parsed properly")
|
||||
}
|
||||
|
||||
for portSpec, bindings := range bindingMap {
|
||||
if len(bindings) != 1 {
|
||||
t.Fatalf("%s should have exactly one binding", portSpec)
|
||||
}
|
||||
|
||||
if bindings[0].HostIP != "" {
|
||||
t.Fatalf("HostIP should not be set for %s", portSpec)
|
||||
}
|
||||
|
||||
if bindings[0].HostPort != "" {
|
||||
t.Fatalf("HostPort should not be set for %s", portSpec)
|
||||
}
|
||||
}
|
||||
|
||||
portMap, bindingMap, err = parsePortSpecs([]string{"1234:1234/tcp", "2345:2345/udp", "3456:3456/sctp"})
|
||||
if err != nil {
|
||||
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
|
||||
}
|
||||
|
||||
if _, ok := portMap[tcp1234]; !ok {
|
||||
t.Fatal("1234/tcp was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[udp2345]; !ok {
|
||||
t.Fatal("2345/udp was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[sctp3456]; !ok {
|
||||
t.Fatal("3456/sctp was not parsed properly")
|
||||
}
|
||||
|
||||
for portSpec, bindings := range bindingMap {
|
||||
_, port := splitProtoPort(portSpec.String())
|
||||
|
||||
if len(bindings) != 1 {
|
||||
t.Fatalf("%s should have exactly one binding", portSpec)
|
||||
}
|
||||
|
||||
if bindings[0].HostIP != "" {
|
||||
t.Fatalf("HostIP should not be set for %s", portSpec)
|
||||
}
|
||||
|
||||
if bindings[0].HostPort != port {
|
||||
t.Fatalf("HostPort(%s) should be %s for %s", bindings[0].HostPort, port, portSpec)
|
||||
}
|
||||
}
|
||||
|
||||
portMap, bindingMap, err = parsePortSpecs([]string{"0.0.0.0:1234:1234/tcp", "0.0.0.0:2345:2345/udp", "0.0.0.0:3456:3456/sctp"})
|
||||
if err != nil {
|
||||
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
|
||||
}
|
||||
|
||||
if _, ok := portMap[tcp1234]; !ok {
|
||||
t.Fatal("1234/tcp was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[udp2345]; !ok {
|
||||
t.Fatal("2345/udp was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[sctp3456]; !ok {
|
||||
t.Fatal("3456/sctp was not parsed properly")
|
||||
}
|
||||
|
||||
for portSpec, bindings := range bindingMap {
|
||||
_, port := splitProtoPort(portSpec.String())
|
||||
|
||||
if len(bindings) != 1 {
|
||||
t.Fatalf("%s should have exactly one binding", portSpec)
|
||||
}
|
||||
|
||||
if bindings[0].HostIP != "0.0.0.0" {
|
||||
t.Fatalf("HostIP is not 0.0.0.0 for %s", portSpec)
|
||||
}
|
||||
|
||||
if bindings[0].HostPort != port {
|
||||
t.Fatalf("HostPort should be %s for %s", port, portSpec)
|
||||
}
|
||||
}
|
||||
|
||||
_, _, err = parsePortSpecs([]string{"localhost:1234:1234/tcp"})
|
||||
if err == nil {
|
||||
t.Fatal("Received no error while trying to parse a hostname instead of ip")
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat_test.go#L244-L274
|
||||
func TestParsePortSpecEmptyContainerPort(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
spec string
|
||||
expError string
|
||||
}{
|
||||
{
|
||||
name: "empty spec",
|
||||
spec: "",
|
||||
expError: `no port specified: <empty>`,
|
||||
},
|
||||
{
|
||||
name: "empty container port",
|
||||
spec: `0.0.0.0:1234-1235:/tcp`,
|
||||
expError: `no port specified: 0.0.0.0:1234-1235:/tcp<empty>`,
|
||||
},
|
||||
{
|
||||
name: "empty container port and proto",
|
||||
spec: `0.0.0.0:1234-1235:`,
|
||||
expError: `no port specified: 0.0.0.0:1234-1235:<empty>`,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := parsePortSpec(tc.spec)
|
||||
if err == nil || err.Error() != tc.expError {
|
||||
t.Fatalf("expected %v, got: %v", tc.expError, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Copied and modified from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat_test.go#L276-L302
|
||||
func TestParsePortSpecFull(t *testing.T) {
|
||||
portMappings, err := parsePortSpec("0.0.0.0:1234-1235:3333-3334/tcp")
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil error, got: %v", err)
|
||||
}
|
||||
|
||||
expected := []container.PortMap{
|
||||
{
|
||||
container.MustParsePort("3333/tcp"): []container.PortBinding{
|
||||
{
|
||||
HostIP: "0.0.0.0",
|
||||
HostPort: "1234",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
container.MustParsePort("3334/tcp"): []container.PortBinding{
|
||||
{
|
||||
HostIP: "0.0.0.0",
|
||||
HostPort: "1235",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expected, portMappings) {
|
||||
t.Fatalf("wrong port mappings: got=%v, want=%v", portMappings, expected)
|
||||
}
|
||||
}
|
||||
|
||||
// Copied and modified from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat_test.go#L304-L388
|
||||
func TestPartPortSpecIPV6(t *testing.T) {
|
||||
type test struct {
|
||||
name string
|
||||
spec string
|
||||
expected []container.PortMap
|
||||
}
|
||||
cases := []test{
|
||||
{
|
||||
name: "square angled IPV6 without host port",
|
||||
spec: "[2001:4860:0:2001::68]::333",
|
||||
expected: []container.PortMap{
|
||||
{
|
||||
container.MustParsePort("333/tcp"): []container.PortBinding{
|
||||
{
|
||||
HostIP: "2001:4860:0:2001::68",
|
||||
HostPort: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "square angled IPV6 with host port",
|
||||
spec: "[::1]:80:80",
|
||||
expected: []container.PortMap{
|
||||
{
|
||||
container.MustParsePort("80/tcp"): []container.PortBinding{
|
||||
{
|
||||
HostIP: "::1",
|
||||
HostPort: "80",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IPV6 without host port",
|
||||
spec: "2001:4860:0:2001::68::333",
|
||||
expected: []container.PortMap{
|
||||
{
|
||||
container.MustParsePort("333/tcp"): []container.PortBinding{
|
||||
{
|
||||
HostIP: "2001:4860:0:2001::68",
|
||||
HostPort: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IPV6 with host port",
|
||||
spec: "::1:80:80",
|
||||
expected: []container.PortMap{
|
||||
{
|
||||
container.MustParsePort("80/tcp"): []container.PortBinding{
|
||||
{
|
||||
HostIP: "::1",
|
||||
HostPort: "80",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ":: IPV6, without host port",
|
||||
spec: "::::80",
|
||||
expected: []container.PortMap{
|
||||
{
|
||||
container.MustParsePort("80/tcp"): []container.PortBinding{
|
||||
{
|
||||
HostIP: "::",
|
||||
HostPort: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
portMappings, err := parsePortSpec(c.spec)
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil error, got: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(c.expected, portMappings) {
|
||||
t.Fatalf("wrong port mappings: got=%v, want=%v", portMappings, c.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Copied and modified from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat_test.go#L501-L600
|
||||
func TestParsePortSpecsWithRange(t *testing.T) {
|
||||
var (
|
||||
portMap map[container.Port]struct{}
|
||||
bindingMap map[container.Port][]container.PortBinding
|
||||
err error
|
||||
)
|
||||
|
||||
portMap, bindingMap, err = parsePortSpecs([]string{"1234-1236/tcp", "2345-2347/udp", "3456-3458/sctp"})
|
||||
if err != nil {
|
||||
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
|
||||
}
|
||||
|
||||
if _, ok := portMap[container.MustParsePort("1235/tcp")]; !ok {
|
||||
t.Fatal("1234-1236/tcp was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[container.MustParsePort("2346/udp")]; !ok {
|
||||
t.Fatal("2345-2347/udp was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[container.MustParsePort("3456/sctp")]; !ok {
|
||||
t.Fatal("3456-3458/sctp was not parsed properly")
|
||||
}
|
||||
|
||||
for portSpec, bindings := range bindingMap {
|
||||
if len(bindings) != 1 {
|
||||
t.Fatalf("%s should have exactly one binding", portSpec)
|
||||
}
|
||||
|
||||
if bindings[0].HostIP != "" {
|
||||
t.Fatalf("HostIP should not be set for %s", portSpec)
|
||||
}
|
||||
|
||||
if bindings[0].HostPort != "" {
|
||||
t.Fatalf("HostPort should not be set for %s", portSpec)
|
||||
}
|
||||
}
|
||||
|
||||
portMap, bindingMap, err = parsePortSpecs([]string{"1234-1236:1234-1236/tcp", "2345-2347:2345-2347/udp", "3456-3458:3456-3458/sctp"})
|
||||
if err != nil {
|
||||
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
|
||||
}
|
||||
|
||||
if _, ok := portMap[container.MustParsePort("1235/tcp")]; !ok {
|
||||
t.Fatal("1234-1236 was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[container.MustParsePort("2346/udp")]; !ok {
|
||||
t.Fatal("2345-2347 was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[container.MustParsePort("3456/sctp")]; !ok {
|
||||
t.Fatal("3456-3458 was not parsed properly")
|
||||
}
|
||||
|
||||
for portSpec, bindings := range bindingMap {
|
||||
_, port := splitProtoPort(portSpec.String())
|
||||
if len(bindings) != 1 {
|
||||
t.Fatalf("%s should have exactly one binding", portSpec)
|
||||
}
|
||||
|
||||
if bindings[0].HostIP != "" {
|
||||
t.Fatalf("HostIP should not be set for %s", portSpec)
|
||||
}
|
||||
|
||||
if bindings[0].HostPort != port {
|
||||
t.Fatalf("HostPort should be %s for %s", port, portSpec)
|
||||
}
|
||||
}
|
||||
|
||||
portMap, bindingMap, err = parsePortSpecs([]string{"0.0.0.0:1234-1236:1234-1236/tcp", "0.0.0.0:2345-2347:2345-2347/udp", "0.0.0.0:3456-3458:3456-3458/sctp"})
|
||||
if err != nil {
|
||||
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
|
||||
}
|
||||
|
||||
if _, ok := portMap[container.MustParsePort("1235/tcp")]; !ok {
|
||||
t.Fatal("1234-1236 was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[container.MustParsePort("2346/udp")]; !ok {
|
||||
t.Fatal("2345-2347 was not parsed properly")
|
||||
}
|
||||
|
||||
if _, ok := portMap[container.MustParsePort("3456/sctp")]; !ok {
|
||||
t.Fatal("3456-3458 was not parsed properly")
|
||||
}
|
||||
|
||||
for portSpec, bindings := range bindingMap {
|
||||
_, port := splitProtoPort(portSpec.String())
|
||||
if len(bindings) != 1 || bindings[0].HostIP != "0.0.0.0" || bindings[0].HostPort != port {
|
||||
t.Fatalf("Expect single binding to port %s but found %s", port, bindings)
|
||||
}
|
||||
}
|
||||
|
||||
_, _, err = parsePortSpecs([]string{"localhost:1234-1236:1234-1236/tcp"})
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("Received no error while trying to parse a hostname instead of ip")
|
||||
}
|
||||
}
|
||||
|
||||
// Copied and modified from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat_test.go#L602-L642
|
||||
func TestParseNetworkOptsPrivateOnly(t *testing.T) {
|
||||
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::80"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ports) != 1 {
|
||||
t.Errorf("Expected 1 got %d", len(ports))
|
||||
}
|
||||
if len(bindings) != 1 {
|
||||
t.Errorf("Expected 1 got %d", len(bindings))
|
||||
}
|
||||
for k := range ports {
|
||||
if k.Proto() != "tcp" {
|
||||
t.Errorf("Expected tcp got %s", k.Proto())
|
||||
}
|
||||
if k.Num() != 80 {
|
||||
t.Errorf("Expected 80 got %d", k.Num())
|
||||
}
|
||||
b, exists := bindings[k]
|
||||
if !exists {
|
||||
t.Error("Binding does not exist")
|
||||
}
|
||||
if len(b) != 1 {
|
||||
t.Errorf("Expected 1 got %d", len(b))
|
||||
}
|
||||
s := b[0]
|
||||
if s.HostPort != "" {
|
||||
t.Errorf("Expected \"\" got %s", s.HostPort)
|
||||
}
|
||||
if s.HostIP != "192.168.1.100" {
|
||||
t.Errorf("Expected 192.168.1.100 got %s", s.HostIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copied and modified from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat_test.go#L644-L684
|
||||
func TestParseNetworkOptsPublic(t *testing.T) {
|
||||
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100:8080:80"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ports) != 1 {
|
||||
t.Errorf("Expected 1 got %d", len(ports))
|
||||
}
|
||||
if len(bindings) != 1 {
|
||||
t.Errorf("Expected 1 got %d", len(bindings))
|
||||
}
|
||||
for k := range ports {
|
||||
if k.Proto() != "tcp" {
|
||||
t.Errorf("Expected tcp got %s", k.Proto())
|
||||
}
|
||||
if k.Num() != 80 {
|
||||
t.Errorf("Expected 80 got %d", k.Num())
|
||||
}
|
||||
b, exists := bindings[k]
|
||||
if !exists {
|
||||
t.Error("Binding does not exist")
|
||||
}
|
||||
if len(b) != 1 {
|
||||
t.Errorf("Expected 1 got %d", len(b))
|
||||
}
|
||||
s := b[0]
|
||||
if s.HostPort != "8080" {
|
||||
t.Errorf("Expected 8080 got %s", s.HostPort)
|
||||
}
|
||||
if s.HostIP != "192.168.1.100" {
|
||||
t.Errorf("Expected 192.168.1.100 got %s", s.HostIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copied and modified from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat_test.go#L686-L701
|
||||
func TestParseNetworkOptsPublicNoPort(t *testing.T) {
|
||||
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100"})
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected error Invalid containerPort")
|
||||
}
|
||||
if ports != nil {
|
||||
t.Errorf("Expected nil got %s", ports)
|
||||
}
|
||||
if bindings != nil {
|
||||
t.Errorf("Expected nil got %s", bindings)
|
||||
}
|
||||
}
|
||||
|
||||
// Copied and modified from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat_test.go#L703-L717
|
||||
func TestParseNetworkOptsNegativePorts(t *testing.T) {
|
||||
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100:-1:-1"})
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected error Invalid containerPort")
|
||||
}
|
||||
if len(ports) != 0 {
|
||||
t.Errorf("Expected 0 got %d: %#v", len(ports), ports)
|
||||
}
|
||||
if len(bindings) != 0 {
|
||||
t.Errorf("Expected 0 got %d: %#v", len(bindings), bindings)
|
||||
}
|
||||
}
|
||||
|
||||
// Copied and modified from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat_test.go#L719-L759
|
||||
func TestParseNetworkOptsUdp(t *testing.T) {
|
||||
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::6000/udp"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ports) != 1 {
|
||||
t.Errorf("Expected 1 got %d: %#v", len(ports), ports)
|
||||
}
|
||||
if len(bindings) != 1 {
|
||||
t.Errorf("Expected 1 got %d", len(bindings))
|
||||
}
|
||||
for k := range ports {
|
||||
if k.Proto() != "udp" {
|
||||
t.Errorf("Expected udp got %s", k.Proto())
|
||||
}
|
||||
if k.Num() != 6000 {
|
||||
t.Errorf("Expected 6000 got %d", k.Num())
|
||||
}
|
||||
b, exists := bindings[k]
|
||||
if !exists {
|
||||
t.Error("Binding does not exist")
|
||||
}
|
||||
if len(b) != 1 {
|
||||
t.Errorf("Expected 1 got %d", len(b))
|
||||
}
|
||||
s := b[0]
|
||||
if s.HostPort != "" {
|
||||
t.Errorf("Expected \"\" got %s", s.HostPort)
|
||||
}
|
||||
if s.HostIP != "192.168.1.100" {
|
||||
t.Errorf("Expected 192.168.1.100 got %s", s.HostIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copied and modified from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat_test.go#L761-L801
|
||||
func TestParseNetworkOptsSctp(t *testing.T) {
|
||||
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::6000/sctp"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ports) != 1 {
|
||||
t.Errorf("Expected 1 got %d: %#v", len(ports), ports)
|
||||
}
|
||||
if len(bindings) != 1 {
|
||||
t.Errorf("Expected 1 got %d: %#v", len(bindings), bindings)
|
||||
}
|
||||
for k := range ports {
|
||||
if k.Proto() != "sctp" {
|
||||
t.Errorf("Expected sctp got %s", k.Proto())
|
||||
}
|
||||
if k.Num() != 6000 {
|
||||
t.Errorf("Expected 6000 got %d", k.Num())
|
||||
}
|
||||
b, exists := bindings[k]
|
||||
if !exists {
|
||||
t.Error("Binding does not exist")
|
||||
}
|
||||
if len(b) != 1 {
|
||||
t.Errorf("Expected 1 got %d", len(b))
|
||||
}
|
||||
s := b[0]
|
||||
if s.HostPort != "" {
|
||||
t.Errorf("Expected \"\" got %s", s.HostPort)
|
||||
}
|
||||
if s.HostIP != "192.168.1.100" {
|
||||
t.Errorf("Expected 192.168.1.100 got %s", s.HostIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from https://github.com/docker/go-connections/blob/c296721c0d56d3acad2973376ded214103a4fd2e/nat/nat_test.go#L146-L242
|
||||
func TestSplitProtoPort(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
input string
|
||||
expPort string
|
||||
expProto string
|
||||
}{
|
||||
{
|
||||
doc: "empty value",
|
||||
},
|
||||
{
|
||||
doc: "zero value",
|
||||
input: "0",
|
||||
expPort: "0",
|
||||
expProto: "tcp",
|
||||
},
|
||||
{
|
||||
doc: "empty port",
|
||||
input: "/udp",
|
||||
expPort: "",
|
||||
expProto: "",
|
||||
},
|
||||
{
|
||||
doc: "single port",
|
||||
input: "1234",
|
||||
expPort: "1234",
|
||||
expProto: "tcp",
|
||||
},
|
||||
{
|
||||
doc: "single port with empty protocol",
|
||||
input: "1234/",
|
||||
expPort: "1234",
|
||||
expProto: "tcp",
|
||||
},
|
||||
{
|
||||
doc: "single port with protocol",
|
||||
input: "1234/udp",
|
||||
expPort: "1234",
|
||||
expProto: "udp",
|
||||
},
|
||||
{
|
||||
doc: "port range",
|
||||
input: "80-8080",
|
||||
expPort: "80-8080",
|
||||
expProto: "tcp",
|
||||
},
|
||||
{
|
||||
doc: "port range with empty protocol",
|
||||
input: "80-8080/",
|
||||
expPort: "80-8080",
|
||||
expProto: "tcp",
|
||||
},
|
||||
{
|
||||
doc: "port range with protocol",
|
||||
input: "80-8080/udp",
|
||||
expPort: "80-8080",
|
||||
expProto: "udp",
|
||||
},
|
||||
// SplitProtoPort currently does not validate or normalize, so these are expected returns
|
||||
{
|
||||
doc: "negative value",
|
||||
input: "-1",
|
||||
expPort: "-1",
|
||||
expProto: "tcp",
|
||||
},
|
||||
{
|
||||
doc: "uppercase protocol",
|
||||
input: "1234/UDP",
|
||||
expPort: "1234",
|
||||
expProto: "UDP",
|
||||
},
|
||||
{
|
||||
doc: "any value",
|
||||
input: "any port value",
|
||||
expPort: "any port value",
|
||||
expProto: "tcp",
|
||||
},
|
||||
{
|
||||
doc: "any value with protocol",
|
||||
input: "any port value/any proto value",
|
||||
expPort: "any port value",
|
||||
expProto: "any proto value",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
proto, port := splitProtoPort(tc.input)
|
||||
if proto != tc.expProto {
|
||||
t.Errorf("expected proto %s, got %s", tc.expProto, proto)
|
||||
}
|
||||
if port != tc.expPort {
|
||||
t.Errorf("expected port %s, got %s", tc.expPort, port)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +137,8 @@ func fullMutableRunConfig() *container.Config {
|
||||
Cmd: []string{"command", "arg1"},
|
||||
Env: []string{"env1=foo", "env2=bar"},
|
||||
ExposedPorts: container.PortSet{
|
||||
"1000/tcp": {},
|
||||
"1001/tcp": {},
|
||||
container.MustParsePort("1000/tcp"): {},
|
||||
container.MustParsePort("1001/tcp"): {},
|
||||
},
|
||||
Volumes: map[string]struct{}{
|
||||
"one": {},
|
||||
@@ -161,7 +161,7 @@ func TestDeepCopyRunConfig(t *testing.T) {
|
||||
|
||||
ctrCfg.Cmd[1] = "arg2"
|
||||
ctrCfg.Env[1] = "env2=new"
|
||||
ctrCfg.ExposedPorts["10002"] = struct{}{}
|
||||
ctrCfg.ExposedPorts[container.MustParsePort("10002")] = struct{}{}
|
||||
ctrCfg.Volumes["three"] = struct{}{}
|
||||
ctrCfg.Entrypoint[1] = "arg2"
|
||||
ctrCfg.OnBuild[0] = "start"
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -150,7 +151,15 @@ func (c *containerConfig) portBindings() container.PortMap {
|
||||
continue
|
||||
}
|
||||
|
||||
port := container.PortRangeProto(fmt.Sprintf("%d/%s", portConfig.TargetPort, strings.ToLower(portConfig.Protocol.String())))
|
||||
if portConfig.TargetPort > math.MaxUint16 {
|
||||
continue
|
||||
}
|
||||
|
||||
port, ok := container.PortFrom(uint16(portConfig.TargetPort), container.NetworkProtocol(portConfig.Protocol.String()))
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
binding := []container.PortBinding{
|
||||
{},
|
||||
}
|
||||
@@ -176,8 +185,8 @@ func (c *containerConfig) init() *bool {
|
||||
return &init
|
||||
}
|
||||
|
||||
func (c *containerConfig) exposedPorts() map[container.PortRangeProto]struct{} {
|
||||
exposedPorts := make(map[container.PortRangeProto]struct{})
|
||||
func (c *containerConfig) exposedPorts() map[container.Port]struct{} {
|
||||
exposedPorts := make(map[container.Port]struct{})
|
||||
if c.task.Endpoint == nil {
|
||||
return exposedPorts
|
||||
}
|
||||
@@ -187,7 +196,15 @@ func (c *containerConfig) exposedPorts() map[container.PortRangeProto]struct{} {
|
||||
continue
|
||||
}
|
||||
|
||||
port := container.PortRangeProto(fmt.Sprintf("%d/%s", portConfig.TargetPort, strings.ToLower(portConfig.Protocol.String())))
|
||||
if portConfig.TargetPort > math.MaxUint16 {
|
||||
continue
|
||||
}
|
||||
|
||||
port, ok := container.PortFrom(uint16(portConfig.TargetPort), container.NetworkProtocol(portConfig.Protocol.String()))
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
exposedPorts[port] = struct{}{}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
@@ -643,27 +642,17 @@ func parsePortStatus(ctnr container.InspectResponse) (*api.PortStatus, error) {
|
||||
func parsePortMap(portMap container.PortMap) ([]*api.PortConfig, error) {
|
||||
exposedPorts := make([]*api.PortConfig, 0, len(portMap))
|
||||
|
||||
for portProtocol, mapping := range portMap {
|
||||
p, proto, ok := strings.Cut(string(portProtocol), "/")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid port mapping: %s", portProtocol)
|
||||
}
|
||||
|
||||
port, err := strconv.ParseUint(p, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for port, mapping := range portMap {
|
||||
var protocol api.PortConfig_Protocol
|
||||
switch strings.ToLower(proto) {
|
||||
case "tcp":
|
||||
switch port.Proto() {
|
||||
case container.TCP:
|
||||
protocol = api.ProtocolTCP
|
||||
case "udp":
|
||||
case container.UDP:
|
||||
protocol = api.ProtocolUDP
|
||||
case "sctp":
|
||||
case container.SCTP:
|
||||
protocol = api.ProtocolSCTP
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid protocol: %s", proto)
|
||||
return nil, fmt.Errorf("invalid protocol: %s", port.Proto())
|
||||
}
|
||||
|
||||
for _, binding := range mapping {
|
||||
@@ -677,7 +666,7 @@ func parsePortMap(portMap container.PortMap) ([]*api.PortConfig, error) {
|
||||
exposedPorts = append(exposedPorts, &api.PortConfig{
|
||||
PublishMode: api.PublishModeHost,
|
||||
Protocol: protocol,
|
||||
TargetPort: uint32(port),
|
||||
TargetPort: uint32(port.Num()),
|
||||
PublishedPort: uint32(hostPort),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/go-connections/nat"
|
||||
containertypes "github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/mount"
|
||||
networktypes "github.com/moby/moby/api/types/network"
|
||||
@@ -375,13 +374,17 @@ func validateHealthCheck(healthConfig *containertypes.HealthConfig) error {
|
||||
|
||||
func validatePortBindings(ports containertypes.PortMap) error {
|
||||
for port := range ports {
|
||||
_, portStr := nat.SplitProtoPort(string(port))
|
||||
if _, err := nat.ParsePort(portStr); err != nil {
|
||||
return errors.Errorf("invalid port specification: %q", portStr)
|
||||
if !port.IsValid() {
|
||||
return errors.Errorf("invalid port specification: %q", port.String())
|
||||
}
|
||||
|
||||
for _, pb := range ports[port] {
|
||||
_, err := nat.NewPort(nat.SplitProtoPort(pb.HostPort))
|
||||
if err != nil {
|
||||
if pb.HostPort == "" {
|
||||
// Empty HostPort means to map to an ephemeral port.
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := containertypes.ParsePortRange(pb.HostPort); err != nil {
|
||||
return errors.Errorf("invalid port specification: %q", pb.HostPort)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/go-connections/nat"
|
||||
memdb "github.com/hashicorp/go-memdb"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
@@ -391,29 +390,24 @@ func (v *View) transform(ctr *Container) *Snapshot {
|
||||
}
|
||||
}
|
||||
for p, bindings := range ctr.NetworkSettings.Ports {
|
||||
proto, port := nat.SplitProtoPort(string(p))
|
||||
p, err := nat.ParsePort(port)
|
||||
if err != nil {
|
||||
log.G(context.TODO()).WithError(err).Warn("invalid port map")
|
||||
continue
|
||||
}
|
||||
if len(bindings) == 0 {
|
||||
snapshot.Ports = append(snapshot.Ports, container.PortSummary{
|
||||
PrivatePort: uint16(p),
|
||||
Type: proto,
|
||||
PrivatePort: p.Num(),
|
||||
Type: string(p.Proto()),
|
||||
})
|
||||
continue
|
||||
}
|
||||
for _, binding := range bindings {
|
||||
h, err := nat.ParsePort(binding.HostPort)
|
||||
// TODO(thaJeztah): if this is always a port/proto (no range), we can simplify this to [container.ParsePort].
|
||||
h, err := container.ParsePortRange(binding.HostPort)
|
||||
if err != nil {
|
||||
log.G(context.TODO()).WithError(err).Warn("invalid host port map")
|
||||
continue
|
||||
}
|
||||
snapshot.Ports = append(snapshot.Ports, container.PortSummary{
|
||||
PrivatePort: uint16(p),
|
||||
PublicPort: uint16(h),
|
||||
Type: proto,
|
||||
PrivatePort: p.Num(),
|
||||
PublicPort: h.Start(),
|
||||
Type: string(p.Proto()),
|
||||
IP: binding.HostIP,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
@@ -15,7 +14,6 @@ import (
|
||||
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/go-connections/nat"
|
||||
containertypes "github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
networktypes "github.com/moby/moby/api/types/network"
|
||||
@@ -112,51 +110,56 @@ func buildSandboxOptions(cfg *config.Config, ctr *container.Container) ([]libnet
|
||||
}
|
||||
}
|
||||
|
||||
// Create a deep copy (as [nat.SortPortMap] mutates the map).
|
||||
// Not using a maps.Clone here, as that won't dereference the
|
||||
// slice (PortMap is a map[Port][]PortBinding).
|
||||
bindings := make(containertypes.PortMap)
|
||||
portBindings := make(containertypes.PortMap, len(ctr.HostConfig.PortBindings))
|
||||
for p, b := range ctr.HostConfig.PortBindings {
|
||||
bindings[p] = slices.Clone(b)
|
||||
portBindings[p] = slices.Clone(b)
|
||||
}
|
||||
|
||||
ports := slices.Collect(maps.Keys(ctr.Config.ExposedPorts))
|
||||
nat.SortPortMap(ports, bindings)
|
||||
for p := range ctr.Config.ExposedPorts {
|
||||
if _, ok := portBindings[p]; !ok {
|
||||
// Create nil entries for exposed but un-mapped ports.
|
||||
portBindings[p] = nil
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
publishedPorts []types.PortBinding
|
||||
exposedPorts []types.TransportPort
|
||||
)
|
||||
for _, port := range ports {
|
||||
portProto := types.ParseProtocol(port.Proto())
|
||||
portNum := uint16(port.Int())
|
||||
for port, bindings := range portBindings {
|
||||
protocol := types.ParseProtocol(string(port.Proto()))
|
||||
exposedPorts = append(exposedPorts, types.TransportPort{
|
||||
Proto: portProto,
|
||||
Port: portNum,
|
||||
Proto: protocol,
|
||||
Port: port.Num(),
|
||||
})
|
||||
|
||||
for _, binding := range bindings[port] {
|
||||
newP, err := nat.NewPort(nat.SplitProtoPort(binding.HostPort))
|
||||
var portStart, portEnd int
|
||||
if err == nil {
|
||||
portStart, portEnd, err = newP.Range()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing HostPort value(%s):%v", binding.HostPort, err)
|
||||
for _, binding := range bindings {
|
||||
var (
|
||||
portRange containertypes.PortRange
|
||||
err error
|
||||
)
|
||||
|
||||
// Empty HostPort means to map to an ephemeral port.
|
||||
if binding.HostPort != "" {
|
||||
portRange, err = containertypes.ParsePortRange(binding.HostPort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing HostPort value(%s):%v", binding.HostPort, err)
|
||||
}
|
||||
}
|
||||
|
||||
publishedPorts = append(publishedPorts, types.PortBinding{
|
||||
Proto: portProto,
|
||||
Port: portNum,
|
||||
Proto: protocol,
|
||||
Port: port.Num(),
|
||||
HostIP: net.ParseIP(binding.HostIP),
|
||||
HostPort: uint16(portStart),
|
||||
HostPortEnd: uint16(portEnd),
|
||||
HostPort: portRange.Start(),
|
||||
HostPortEnd: portRange.End(),
|
||||
})
|
||||
}
|
||||
|
||||
if ctr.HostConfig.PublishAllPorts && len(bindings[port]) == 0 {
|
||||
if ctr.HostConfig.PublishAllPorts && len(bindings) == 0 {
|
||||
publishedPorts = append(publishedPorts, types.PortBinding{
|
||||
Proto: portProto,
|
||||
Port: portNum,
|
||||
Proto: protocol,
|
||||
Port: port.Num(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestContainerWarningHostAndPublishPorts(t *testing.T) {
|
||||
}{
|
||||
{ports: containertypes.PortMap{}},
|
||||
{ports: containertypes.PortMap{
|
||||
"8080": []containertypes.PortBinding{{HostPort: "8989"}},
|
||||
containertypes.MustParsePort("8080"): []containertypes.PortBinding{{HostPort: "8989"}},
|
||||
}, warnings: []string{"Published ports are discarded when using host network mode"}},
|
||||
}
|
||||
muteLogs(t)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func TestContainerConfigToDockerImageConfig(t *testing.T) {
|
||||
ociCFG := containerConfigToDockerOCIImageConfig(&container.Config{
|
||||
ExposedPorts: container.PortSet{
|
||||
"80/tcp": struct{}{},
|
||||
container.MustParsePort("80/tcp"): struct{}{},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ func containerConfigToDockerOCIImageConfig(cfg *container.Config) imagespec.Dock
|
||||
if len(cfg.ExposedPorts) > 0 {
|
||||
ociCfg.ExposedPorts = map[string]struct{}{}
|
||||
for k := range cfg.ExposedPorts {
|
||||
ociCfg.ExposedPorts[string(k)] = struct{}{}
|
||||
ociCfg.ExposedPorts[k.String()] = struct{}{}
|
||||
}
|
||||
}
|
||||
ext.Healthcheck = cfg.Healthcheck
|
||||
@@ -97,7 +97,9 @@ func containerConfigToDockerOCIImageConfig(cfg *container.Config) imagespec.Dock
|
||||
func dockerOCIImageConfigToContainerConfig(cfg imagespec.DockerOCIImageConfig) *container.Config {
|
||||
exposedPorts := make(container.PortSet, len(cfg.ExposedPorts))
|
||||
for k := range cfg.ExposedPorts {
|
||||
exposedPorts[container.PortRangeProto(k)] = struct{}{}
|
||||
if p, err := container.ParsePort(k); err == nil {
|
||||
exposedPorts[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return &container.Config{
|
||||
|
||||
@@ -221,8 +221,8 @@ func TestContainerInitDNS(t *testing.T) {
|
||||
func TestMerge(t *testing.T) {
|
||||
configImage := &containertypes.Config{
|
||||
ExposedPorts: containertypes.PortSet{
|
||||
"1111/tcp": struct{}{},
|
||||
"2222/tcp": struct{}{},
|
||||
containertypes.MustParsePort("1111/tcp"): struct{}{},
|
||||
containertypes.MustParsePort("2222/tcp"): struct{}{},
|
||||
},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
Volumes: map[string]struct{}{
|
||||
@@ -233,8 +233,8 @@ func TestMerge(t *testing.T) {
|
||||
|
||||
configUser := &containertypes.Config{
|
||||
ExposedPorts: containertypes.PortSet{
|
||||
"2222/tcp": struct{}{},
|
||||
"3333/tcp": struct{}{},
|
||||
containertypes.MustParsePort("2222/tcp"): struct{}{},
|
||||
containertypes.MustParsePort("3333/tcp"): struct{}{},
|
||||
},
|
||||
Env: []string{"VAR2=3", "VAR3=3"},
|
||||
Volumes: map[string]struct{}{
|
||||
@@ -250,7 +250,7 @@ func TestMerge(t *testing.T) {
|
||||
t.Fatalf("Expected 3 ExposedPorts, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
|
||||
}
|
||||
for portSpecs := range configUser.ExposedPorts {
|
||||
if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
|
||||
if portSpecs.Num() != 1111 && portSpecs.Num() != 2222 && portSpecs.Num() != 3333 {
|
||||
t.Fatalf("Expected 1111 or 2222 or 3333, found %s", portSpecs)
|
||||
}
|
||||
}
|
||||
@@ -273,7 +273,9 @@ func TestMerge(t *testing.T) {
|
||||
}
|
||||
|
||||
configImage2 := &containertypes.Config{
|
||||
ExposedPorts: map[containertypes.PortRangeProto]struct{}{"0/tcp": {}},
|
||||
ExposedPorts: map[containertypes.Port]struct{}{
|
||||
containertypes.MustParsePort("0/tcp"): {},
|
||||
},
|
||||
}
|
||||
|
||||
if err := merge(configUser, configImage2); err != nil {
|
||||
@@ -284,7 +286,7 @@ func TestMerge(t *testing.T) {
|
||||
t.Fatalf("Expected 4 ExposedPorts, 0000, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
|
||||
}
|
||||
for portSpecs := range configUser.ExposedPorts {
|
||||
if portSpecs.Port() != "0" && portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
|
||||
if portSpecs.Num() != 0 && portSpecs.Num() != 1111 && portSpecs.Num() != 2222 && portSpecs.Num() != 3333 {
|
||||
t.Fatalf("Expected %q or %q or %q or %q, found %s", 0, 1111, 2222, 3333, portSpecs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func containerConfigToDockerOCIImageConfig(cfg *container.Config) imagespec.Dock
|
||||
if len(cfg.ExposedPorts) > 0 {
|
||||
ociCfg.ExposedPorts = map[string]struct{}{}
|
||||
for k, v := range cfg.ExposedPorts {
|
||||
ociCfg.ExposedPorts[string(k)] = v
|
||||
ociCfg.ExposedPorts[k.String()] = v
|
||||
}
|
||||
}
|
||||
ext.Healthcheck = cfg.Healthcheck
|
||||
|
||||
14
daemon/internal/image/cache/compare_test.go
vendored
14
daemon/internal/image/cache/compare_test.go
vendored
@@ -12,17 +12,17 @@ import (
|
||||
|
||||
func TestCompare(t *testing.T) {
|
||||
ports1 := container.PortSet{
|
||||
"1111/tcp": struct{}{},
|
||||
"2222/tcp": struct{}{},
|
||||
container.MustParsePort("1111/tcp"): struct{}{},
|
||||
container.MustParsePort("2222/tcp"): struct{}{},
|
||||
}
|
||||
ports2 := container.PortSet{
|
||||
"3333/tcp": struct{}{},
|
||||
"4444/tcp": struct{}{},
|
||||
container.MustParsePort("3333/tcp"): struct{}{},
|
||||
container.MustParsePort("4444/tcp"): struct{}{},
|
||||
}
|
||||
ports3 := container.PortSet{
|
||||
"1111/tcp": struct{}{},
|
||||
"2222/tcp": struct{}{},
|
||||
"5555/tcp": struct{}{},
|
||||
container.MustParsePort("1111/tcp"): struct{}{},
|
||||
container.MustParsePort("2222/tcp"): struct{}{},
|
||||
container.MustParsePort("5555/tcp"): struct{}{},
|
||||
}
|
||||
volumes1 := map[string]struct{}{
|
||||
"/test1": {},
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package links
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"maps"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
)
|
||||
|
||||
@@ -20,21 +22,18 @@ type Link struct {
|
||||
// Child environments variables
|
||||
ChildEnvironment []string
|
||||
// Child exposed ports
|
||||
Ports []container.PortRangeProto // TODO(thaJeztah): can we use []string here, or do we need the features of nat.Port?
|
||||
Ports []container.Port
|
||||
}
|
||||
|
||||
// EnvVars generates environment variables for the linked container
|
||||
// for the Link with the given options.
|
||||
func EnvVars(parentIP, childIP, name string, env []string, exposedPorts map[container.PortRangeProto]struct{}) []string {
|
||||
func EnvVars(parentIP, childIP, name string, env []string, exposedPorts map[container.Port]struct{}) []string {
|
||||
return NewLink(parentIP, childIP, name, env, exposedPorts).ToEnv()
|
||||
}
|
||||
|
||||
// NewLink initializes a new Link struct with the provided options.
|
||||
func NewLink(parentIP, childIP, name string, env []string, exposedPorts map[container.PortRangeProto]struct{}) *Link {
|
||||
ports := make([]container.PortRangeProto, 0, len(exposedPorts))
|
||||
for p := range exposedPorts {
|
||||
ports = append(ports, p)
|
||||
}
|
||||
func NewLink(parentIP, childIP, name string, env []string, exposedPorts map[container.Port]struct{}) *Link {
|
||||
ports := slices.Collect(maps.Keys(exposedPorts))
|
||||
|
||||
return &Link{
|
||||
Name: name,
|
||||
@@ -53,27 +52,26 @@ func (l *Link) ToEnv() []string {
|
||||
alias := strings.ReplaceAll(strings.ToUpper(n), "-", "_")
|
||||
|
||||
// sort the ports so that we can bulk the continuous ports together
|
||||
nat.Sort(l.Ports, withTCPPriority)
|
||||
slices.SortFunc(l.Ports, withTCPPriority)
|
||||
|
||||
var pStart, pEnd container.PortRangeProto
|
||||
env := make([]string, 0, 1+len(l.Ports)*4)
|
||||
var pStart, pEnd container.Port
|
||||
|
||||
for i, p := range l.Ports {
|
||||
if i == 0 {
|
||||
pStart, pEnd = p, p
|
||||
env = append(env, fmt.Sprintf("%s_PORT=%s://%s:%s", alias, p.Proto(), l.ChildIP, p.Port()))
|
||||
env = append(env, fmt.Sprintf("%s_PORT=%s://%s:%d", alias, p.Proto(), l.ChildIP, p.Num()))
|
||||
}
|
||||
|
||||
// These env-vars are produced for every port, regardless if they're
|
||||
// part of a port-range.
|
||||
prefix := fmt.Sprintf("%s_PORT_%s_%s", alias, p.Port(), strings.ToUpper(p.Proto()))
|
||||
env = append(env, fmt.Sprintf("%s=%s://%s:%s", prefix, p.Proto(), l.ChildIP, p.Port()))
|
||||
// These env-vars are produced for every port, regardless if they're part of a port-range.
|
||||
prefix := fmt.Sprintf("%s_PORT_%d_%s", alias, p.Num(), strings.ToUpper(string(p.Proto())))
|
||||
env = append(env, fmt.Sprintf("%s=%s://%s:%d", prefix, p.Proto(), l.ChildIP, p.Num()))
|
||||
env = append(env, fmt.Sprintf("%s_ADDR=%s", prefix, l.ChildIP))
|
||||
env = append(env, fmt.Sprintf("%s_PORT=%s", prefix, p.Port()))
|
||||
env = append(env, fmt.Sprintf("%s_PORT=%d", prefix, p.Num()))
|
||||
env = append(env, fmt.Sprintf("%s_PROTO=%s", prefix, p.Proto()))
|
||||
|
||||
// Detect whether this port is part of a range (consecutive port number
|
||||
// and same protocol).
|
||||
if p.Int() == pEnd.Int()+1 && strings.EqualFold(p.Proto(), pStart.Proto()) {
|
||||
// Detect whether this port is part of a range (consecutive port number and same protocol).
|
||||
if p.Num() == pEnd.Num()+1 && p.Proto() == pEnd.Proto() {
|
||||
pEnd = p
|
||||
if i < len(l.Ports)-1 {
|
||||
continue
|
||||
@@ -81,11 +79,11 @@ func (l *Link) ToEnv() []string {
|
||||
}
|
||||
|
||||
if pEnd != pStart {
|
||||
prefix = fmt.Sprintf("%s_PORT_%s_%s", alias, pStart.Port(), strings.ToUpper(pStart.Proto()))
|
||||
env = append(env, fmt.Sprintf("%s_START=%s://%s:%s", prefix, pStart.Proto(), l.ChildIP, pStart.Port()))
|
||||
env = append(env, fmt.Sprintf("%s_PORT_START=%s", prefix, pStart.Port()))
|
||||
env = append(env, fmt.Sprintf("%s_END=%s://%s:%s", prefix, pEnd.Proto(), l.ChildIP, pEnd.Port()))
|
||||
env = append(env, fmt.Sprintf("%s_PORT_END=%s", prefix, pEnd.Port()))
|
||||
prefix = fmt.Sprintf("%s_PORT_%d_%s", alias, pStart.Num(), strings.ToUpper(string(pStart.Proto())))
|
||||
env = append(env, fmt.Sprintf("%s_START=%s://%s:%d", prefix, pStart.Proto(), l.ChildIP, pStart.Num()))
|
||||
env = append(env, fmt.Sprintf("%s_PORT_START=%d", prefix, pStart.Num()))
|
||||
env = append(env, fmt.Sprintf("%s_END=%s://%s:%d", prefix, pEnd.Proto(), l.ChildIP, pEnd.Num()))
|
||||
env = append(env, fmt.Sprintf("%s_PORT_END=%d", prefix, pEnd.Num()))
|
||||
}
|
||||
|
||||
// Reset for next range (if any)
|
||||
@@ -113,17 +111,15 @@ func (l *Link) ToEnv() []string {
|
||||
|
||||
// withTCPPriority prioritizes ports using TCP over other protocols before
|
||||
// comparing port-number and protocol.
|
||||
func withTCPPriority(ip, jp container.PortRangeProto) bool {
|
||||
if strings.EqualFold(ip.Proto(), jp.Proto()) {
|
||||
return ip.Int() < jp.Int()
|
||||
func withTCPPriority(ip, jp container.Port) int {
|
||||
if ip.Proto() == jp.Proto() {
|
||||
return cmp.Compare(ip.Num(), jp.Num())
|
||||
}
|
||||
|
||||
if strings.EqualFold(ip.Proto(), "tcp") {
|
||||
return true
|
||||
if ip.Proto() == container.TCP {
|
||||
return -1
|
||||
}
|
||||
if strings.EqualFold(jp.Proto(), "tcp") {
|
||||
return false
|
||||
if jp.Proto() == container.TCP {
|
||||
return 1
|
||||
}
|
||||
|
||||
return strings.ToLower(ip.Proto()) < strings.ToLower(jp.Proto())
|
||||
return cmp.Compare(ip.Proto(), jp.Proto())
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package links
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestLinkNaming(t *testing.T) {
|
||||
actual := EnvVars("172.0.17.3", "172.0.17.2", "/db/docker-1", nil, container.PortSet{
|
||||
"6379/tcp": struct{}{},
|
||||
container.MustParsePort("6379/tcp"): struct{}{},
|
||||
})
|
||||
|
||||
expectedEnv := []string{
|
||||
@@ -28,23 +29,25 @@ func TestLinkNaming(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLinkNew(t *testing.T) {
|
||||
tcp6379 := container.MustParsePort("6379/tcp")
|
||||
link := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", nil, container.PortSet{
|
||||
"6379/tcp": struct{}{},
|
||||
tcp6379: struct{}{},
|
||||
})
|
||||
|
||||
expected := &Link{
|
||||
Name: "/db/docker",
|
||||
ParentIP: "172.0.17.3",
|
||||
ChildIP: "172.0.17.2",
|
||||
Ports: []container.PortRangeProto{"6379/tcp"},
|
||||
Ports: []container.Port{tcp6379},
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, expected, link)
|
||||
assert.DeepEqual(t, expected, link, cmpopts.EquateComparable(container.Port{}))
|
||||
}
|
||||
|
||||
func TestLinkEnv(t *testing.T) {
|
||||
tcp6379 := container.MustParsePort("6379/tcp")
|
||||
actual := EnvVars("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, container.PortSet{
|
||||
"6379/tcp": struct{}{},
|
||||
tcp6379: struct{}{},
|
||||
})
|
||||
|
||||
expectedEnv := []string{
|
||||
@@ -64,39 +67,39 @@ func TestLinkEnv(t *testing.T) {
|
||||
// TestSortPorts verifies that ports are sorted with TCP taking priority,
|
||||
// and ports with the same protocol to be sorted by port.
|
||||
func TestSortPorts(t *testing.T) {
|
||||
ports := []container.PortRangeProto{
|
||||
"6379/tcp",
|
||||
"6376/udp",
|
||||
"6380/tcp",
|
||||
"6376/sctp",
|
||||
"6381/tcp",
|
||||
"6381/udp",
|
||||
"6375/udp",
|
||||
"6375/sctp",
|
||||
ports := []container.Port{
|
||||
container.MustParsePort("6379/tcp"),
|
||||
container.MustParsePort("6376/udp"),
|
||||
container.MustParsePort("6380/tcp"),
|
||||
container.MustParsePort("6376/sctp"),
|
||||
container.MustParsePort("6381/tcp"),
|
||||
container.MustParsePort("6381/udp"),
|
||||
container.MustParsePort("6375/udp"),
|
||||
container.MustParsePort("6375/sctp"),
|
||||
}
|
||||
|
||||
expected := []container.PortRangeProto{
|
||||
"6379/tcp",
|
||||
"6380/tcp",
|
||||
"6381/tcp",
|
||||
"6375/sctp",
|
||||
"6376/sctp",
|
||||
"6375/udp",
|
||||
"6376/udp",
|
||||
"6381/udp",
|
||||
expected := []container.Port{
|
||||
container.MustParsePort("6379/tcp"),
|
||||
container.MustParsePort("6380/tcp"),
|
||||
container.MustParsePort("6381/tcp"),
|
||||
container.MustParsePort("6375/sctp"),
|
||||
container.MustParsePort("6376/sctp"),
|
||||
container.MustParsePort("6375/udp"),
|
||||
container.MustParsePort("6376/udp"),
|
||||
container.MustParsePort("6381/udp"),
|
||||
}
|
||||
|
||||
nat.Sort(ports, withTCPPriority)
|
||||
assert.DeepEqual(t, expected, ports)
|
||||
slices.SortFunc(ports, withTCPPriority)
|
||||
assert.DeepEqual(t, expected, ports, cmpopts.EquateComparable(container.Port{}))
|
||||
}
|
||||
|
||||
func TestLinkMultipleEnv(t *testing.T) {
|
||||
actual := EnvVars("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, container.PortSet{
|
||||
"6300/udp": struct{}{},
|
||||
"6379/tcp": struct{}{},
|
||||
"6380/tcp": struct{}{},
|
||||
"6381/tcp": struct{}{},
|
||||
"6382/udp": struct{}{},
|
||||
container.MustParsePort("6300/udp"): struct{}{},
|
||||
container.MustParsePort("6379/tcp"): struct{}{},
|
||||
container.MustParsePort("6380/tcp"): struct{}{},
|
||||
container.MustParsePort("6381/tcp"): struct{}{},
|
||||
container.MustParsePort("6382/udp"): struct{}{},
|
||||
})
|
||||
|
||||
expectedEnv := []string{
|
||||
@@ -143,11 +146,11 @@ func BenchmarkLinkMultipleEnv(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = EnvVars("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, container.PortSet{
|
||||
"6300/udp": struct{}{},
|
||||
"6379/tcp": struct{}{},
|
||||
"6380/tcp": struct{}{},
|
||||
"6381/tcp": struct{}{},
|
||||
"6382/udp": struct{}{},
|
||||
container.MustParsePort("6300/udp"): struct{}{},
|
||||
container.MustParsePort("6379/tcp"): struct{}{},
|
||||
container.MustParsePort("6380/tcp"): struct{}{},
|
||||
container.MustParsePort("6381/tcp"): struct{}{},
|
||||
container.MustParsePort("6382/udp"): struct{}{},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/go-connections/nat"
|
||||
containertypes "github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
"github.com/moby/moby/v2/daemon/container"
|
||||
@@ -404,13 +403,12 @@ func portOp(key string, filter map[string]bool) func(value string) error {
|
||||
return fmt.Errorf("filter for '%s' should not contain ':': %s", key, value)
|
||||
}
|
||||
// support two formats, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
|
||||
proto, portRange := nat.SplitProtoPort(value)
|
||||
start, end, err := nat.ParsePortRange(portRange)
|
||||
portRange, err := containertypes.ParsePortRange(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while looking up for %s %s: %s", key, value, err)
|
||||
}
|
||||
for portNum := start; portNum <= end; portNum++ {
|
||||
filter[fmt.Sprintf("%d/%s", portNum, proto)] = true
|
||||
for p := range portRange.All() {
|
||||
filter[p.String()] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,17 +4,14 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/go-connections/nat"
|
||||
containertypes "github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/events"
|
||||
networktypes "github.com/moby/moby/api/types/network"
|
||||
@@ -960,51 +957,44 @@ func buildPortsRelatedCreateEndpointOptions(c *container.Container, n *libnetwor
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Create a deep copy (as [nat.SortPortMap] mutates the map).
|
||||
// Not using a maps.Clone here, as that won't dereference the
|
||||
// slice (PortMap is a map[Port][]PortBinding).
|
||||
bindings := make(containertypes.PortMap)
|
||||
for p, b := range c.HostConfig.PortBindings {
|
||||
bindings[p] = slices.Clone(b)
|
||||
}
|
||||
|
||||
ports := slices.Collect(maps.Keys(bindings))
|
||||
nat.SortPortMap(ports, bindings)
|
||||
|
||||
var (
|
||||
exposedPorts []lntypes.TransportPort
|
||||
publishedPorts []lntypes.PortBinding
|
||||
)
|
||||
for _, port := range ports {
|
||||
portProto := lntypes.ParseProtocol(port.Proto())
|
||||
portNum := uint16(port.Int())
|
||||
for p, bindings := range c.HostConfig.PortBindings {
|
||||
protocol := lntypes.ParseProtocol(string(p.Proto()))
|
||||
exposedPorts = append(exposedPorts, lntypes.TransportPort{
|
||||
Proto: portProto,
|
||||
Port: portNum,
|
||||
Proto: protocol,
|
||||
Port: p.Num(),
|
||||
})
|
||||
|
||||
for _, binding := range bindings[port] {
|
||||
newP, err := nat.NewPort(nat.SplitProtoPort(binding.HostPort))
|
||||
var portStart, portEnd int
|
||||
if err == nil {
|
||||
portStart, portEnd, err = newP.Range()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing HostPort value (%s): %w", binding.HostPort, err)
|
||||
for _, binding := range bindings {
|
||||
var (
|
||||
portRange containertypes.PortRange
|
||||
err error
|
||||
)
|
||||
|
||||
// Empty HostPort means to map to an ephemeral port.
|
||||
if binding.HostPort != "" {
|
||||
portRange, err = containertypes.ParsePortRange(binding.HostPort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing HostPort value(%s):%v", binding.HostPort, err)
|
||||
}
|
||||
}
|
||||
|
||||
publishedPorts = append(publishedPorts, lntypes.PortBinding{
|
||||
Proto: portProto,
|
||||
Port: portNum,
|
||||
Proto: protocol,
|
||||
Port: p.Num(),
|
||||
HostIP: net.ParseIP(binding.HostIP),
|
||||
HostPort: uint16(portStart),
|
||||
HostPortEnd: uint16(portEnd),
|
||||
HostPort: portRange.Start(),
|
||||
HostPortEnd: portRange.End(),
|
||||
})
|
||||
}
|
||||
|
||||
if c.HostConfig.PublishAllPorts && len(bindings[port]) == 0 {
|
||||
if c.HostConfig.PublishAllPorts && len(bindings) == 0 {
|
||||
publishedPorts = append(publishedPorts, lntypes.PortBinding{
|
||||
Proto: portProto,
|
||||
Port: portNum,
|
||||
Proto: protocol,
|
||||
Port: p.Num(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1038,9 +1028,9 @@ func getEndpointPortMapInfo(pm containertypes.PortMap, ep *libnetwork.Endpoint)
|
||||
if expData, ok := driverInfo[netlabel.ExposedPorts]; ok {
|
||||
if exposedPorts, ok := expData.([]lntypes.TransportPort); ok {
|
||||
for _, tp := range exposedPorts {
|
||||
natPort, err := nat.NewPort(tp.Proto.String(), strconv.Itoa(int(tp.Port)))
|
||||
if err != nil {
|
||||
log.G(context.TODO()).Errorf("invalid exposed port %s: %v", tp.String(), err)
|
||||
natPort, ok := containertypes.PortFrom(tp.Port, containertypes.NetworkProtocol(tp.Proto.String()))
|
||||
if !ok {
|
||||
log.G(context.TODO()).Errorf("Invalid exposed port: %s", tp.String())
|
||||
continue
|
||||
}
|
||||
if _, ok := pm[natPort]; !ok {
|
||||
@@ -1057,12 +1047,13 @@ func getEndpointPortMapInfo(pm containertypes.PortMap, ep *libnetwork.Endpoint)
|
||||
|
||||
if portMapping, ok := mapData.([]lntypes.PortBinding); ok {
|
||||
for _, pp := range portMapping {
|
||||
// Use an empty string for the host port if there's no port assigned.
|
||||
natPort, err := nat.NewPort(pp.Proto.String(), strconv.Itoa(int(pp.Port)))
|
||||
if err != nil {
|
||||
log.G(context.TODO()).Errorf("invalid port binding %s: %v", pp, err)
|
||||
// Use an empty string for the host natPort if there's no natPort assigned.
|
||||
natPort, ok := containertypes.PortFrom(pp.Port, containertypes.NetworkProtocol(pp.Proto.String()))
|
||||
if !ok {
|
||||
log.G(context.TODO()).Errorf("Invalid port binding: %s", pp.String())
|
||||
continue
|
||||
}
|
||||
|
||||
var hp string
|
||||
if pp.HostPort > 0 {
|
||||
hp = strconv.Itoa(int(pp.HostPort))
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/moby/moby/api/types"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/filters"
|
||||
@@ -891,7 +890,7 @@ func handleSysctlBC(
|
||||
func handlePortBindingsBC(hostConfig *container.HostConfig, version string) string {
|
||||
var emptyPBs []string
|
||||
|
||||
for portProto, bindings := range hostConfig.PortBindings {
|
||||
for port, bindings := range hostConfig.PortBindings {
|
||||
if len(bindings) > 0 {
|
||||
continue
|
||||
}
|
||||
@@ -902,15 +901,15 @@ func handlePortBindingsBC(hostConfig *container.HostConfig, version string) stri
|
||||
// on-disk state for containers created by older versions of the
|
||||
// Engine. Drop the PortBindings entry to ensure that no backfilling
|
||||
// will happen when restarting the daemon.
|
||||
delete(hostConfig.PortBindings, portProto)
|
||||
delete(hostConfig.PortBindings, port)
|
||||
continue
|
||||
}
|
||||
|
||||
if versions.Equal(version, "1.52") {
|
||||
emptyPBs = append(emptyPBs, string(portProto))
|
||||
emptyPBs = append(emptyPBs, port.String())
|
||||
}
|
||||
|
||||
hostConfig.PortBindings[portProto] = []nat.PortBinding{{}}
|
||||
hostConfig.PortBindings[port] = []container.PortBinding{{}}
|
||||
}
|
||||
|
||||
if len(emptyPBs) > 0 {
|
||||
|
||||
@@ -505,7 +505,7 @@ func (s *DockerAPISuite) TestContainerAPIBadPort(c *testing.T) {
|
||||
|
||||
hostConfig := container.HostConfig{
|
||||
PortBindings: container.PortMap{
|
||||
"8080/tcp": []container.PortBinding{
|
||||
container.MustParsePort("8080/tcp"): []container.PortBinding{
|
||||
{
|
||||
HostIP: "",
|
||||
HostPort: "aa80",
|
||||
|
||||
@@ -92,7 +92,7 @@ func (s *DockerCLICreateSuite) TestCreateWithPortRange(c *testing.T) {
|
||||
|
||||
var containers []struct {
|
||||
HostConfig *struct {
|
||||
PortBindings map[containertypes.PortRangeProto][]containertypes.PortBinding
|
||||
PortBindings map[containertypes.Port][]containertypes.PortBinding
|
||||
}
|
||||
}
|
||||
err := json.Unmarshal([]byte(out), &containers)
|
||||
@@ -105,8 +105,8 @@ func (s *DockerCLICreateSuite) TestCreateWithPortRange(c *testing.T) {
|
||||
assert.Equal(c, len(cont.HostConfig.PortBindings), 4, fmt.Sprintf("Expected 4 ports bindings, got %d", len(cont.HostConfig.PortBindings)))
|
||||
|
||||
for k, v := range cont.HostConfig.PortBindings {
|
||||
assert.Equal(c, len(v), 1, fmt.Sprintf("Expected 1 ports binding, for the port %s but found %s", k, v))
|
||||
assert.Equal(c, k.Port(), v[0].HostPort, fmt.Sprintf("Expected host port %s to match published port %s", k.Port(), v[0].HostPort))
|
||||
assert.Equal(c, len(v), 1, fmt.Sprintf("Expected 1 ports binding, for the port %s but found %s", k, v))
|
||||
assert.Equal(c, fmt.Sprintf("%d", k.Num()), v[0].HostPort, fmt.Sprintf("Expected host port %d to match published port %s", k.Num(), v[0].HostPort))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ func (s *DockerCLICreateSuite) TestCreateWithLargePortRange(c *testing.T) {
|
||||
|
||||
var containers []struct {
|
||||
HostConfig *struct {
|
||||
PortBindings map[containertypes.PortRangeProto][]containertypes.PortBinding
|
||||
PortBindings map[containertypes.Port][]containertypes.PortBinding
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ func (s *DockerCLICreateSuite) TestCreateWithLargePortRange(c *testing.T) {
|
||||
|
||||
for k, v := range cont.HostConfig.PortBindings {
|
||||
assert.Equal(c, len(v), 1)
|
||||
assert.Equal(c, k.Port(), v[0].HostPort, fmt.Sprintf("Expected host port %s to match published port %s", k.Port(), v[0].HostPort))
|
||||
assert.Equal(c, fmt.Sprintf("%d", k.Num()), v[0].HostPort, fmt.Sprintf("Expected host port %d to match published port %s", k.Num(), v[0].HostPort))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -197,11 +197,11 @@ func assertPortRange(ctx context.Context, id string, expectedTCP, expectedUDP []
|
||||
}
|
||||
|
||||
var validTCP, validUDP bool
|
||||
for portAndProto, binding := range inspect.NetworkSettings.Ports {
|
||||
if portAndProto.Proto() == "tcp" && len(expectedTCP) == 0 {
|
||||
for port, binding := range inspect.NetworkSettings.Ports {
|
||||
if port.Proto() == "tcp" && len(expectedTCP) == 0 {
|
||||
continue
|
||||
}
|
||||
if portAndProto.Proto() == "udp" && len(expectedTCP) == 0 {
|
||||
if port.Proto() == "udp" && len(expectedTCP) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -2173,9 +2173,8 @@ func (s *DockerCLIRunSuite) TestRunAllowPortRangeThroughExpose(c *testing.T) {
|
||||
c.Fatal(err)
|
||||
}
|
||||
for port, binding := range ports {
|
||||
portnum, _ := strconv.Atoi(strings.Split(string(port), "/")[0])
|
||||
if portnum < 3000 || portnum > 3003 {
|
||||
c.Fatalf("Port %d is out of range ", portnum)
|
||||
if port.Num() < 3000 || port.Num() > 3003 {
|
||||
c.Fatalf("Port %d is out of range", port.Num())
|
||||
}
|
||||
if len(binding) == 0 || binding[0].HostPort == "" {
|
||||
c.Fatalf("Port is not mapped for the port %s", port)
|
||||
@@ -2510,12 +2509,11 @@ func (s *DockerCLIRunSuite) TestRunAllowPortRangeThroughPublish(c *testing.T) {
|
||||
err := json.Unmarshal([]byte(portStr), &ports)
|
||||
assert.NilError(c, err, "failed to unmarshal: %v", portStr)
|
||||
for port, binding := range ports {
|
||||
portnum, _ := strconv.Atoi(strings.Split(string(port), "/")[0])
|
||||
if portnum < 3000 || portnum > 3003 {
|
||||
c.Fatalf("Port %d is out of range ", portnum)
|
||||
if port.Num() < 3000 || port.Num() > 3003 {
|
||||
c.Fatalf("Port %d is out of range", port.Num())
|
||||
}
|
||||
if len(binding) == 0 || binding[0].HostPort == "" {
|
||||
c.Fatal("Port is not mapped for the port "+port, id)
|
||||
c.Fatalf("Port is not mapped for the port %s", port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,12 +71,13 @@ func TestNetworkStateCleanupOnDaemonStart(t *testing.T) {
|
||||
defer d.Stop(t)
|
||||
|
||||
apiClient := d.NewClientT(t)
|
||||
mappedPort := containertypes.MustParsePort("80/tcp")
|
||||
|
||||
// The intention of this container is to ignore stop signals.
|
||||
// Sadly this means the test will take longer, but at least this test can be parallelized.
|
||||
cid := container.Run(ctx, t, apiClient,
|
||||
container.WithExposedPorts("80/tcp"),
|
||||
container.WithPortMap(containertypes.PortMap{"80/tcp": {{}}}),
|
||||
container.WithPortMap(containertypes.PortMap{mappedPort: {{}}}),
|
||||
container.WithCmd("/bin/sh", "-c", "while true; do echo hello; sleep 1; done"))
|
||||
defer func() {
|
||||
err := apiClient.ContainerRemove(ctx, cid, client.ContainerRemoveOptions{Force: true})
|
||||
@@ -87,7 +88,7 @@ func TestNetworkStateCleanupOnDaemonStart(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, inspect.NetworkSettings.SandboxID != "")
|
||||
assert.Assert(t, inspect.NetworkSettings.SandboxKey != "")
|
||||
assert.Assert(t, inspect.NetworkSettings.Ports["80/tcp"] != nil)
|
||||
assert.Assert(t, inspect.NetworkSettings.Ports[mappedPort] != nil)
|
||||
|
||||
assert.NilError(t, d.Kill())
|
||||
d.Start(t)
|
||||
@@ -96,5 +97,5 @@ func TestNetworkStateCleanupOnDaemonStart(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, inspect.NetworkSettings.SandboxID == "")
|
||||
assert.Assert(t, inspect.NetworkSettings.SandboxKey == "")
|
||||
assert.Assert(t, is.Nil(inspect.NetworkSettings.Ports["80/tcp"]))
|
||||
assert.Assert(t, is.Nil(inspect.NetworkSettings.Ports[mappedPort]))
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ func TestNetworkLoopbackNat(t *testing.T) {
|
||||
assert.Check(t, is.Equal(msg, strings.TrimSpace(b.String())))
|
||||
}
|
||||
|
||||
func startServerContainer(ctx context.Context, t *testing.T, msg string, port int) string {
|
||||
func startServerContainer(ctx context.Context, t *testing.T, msg string, port uint16) string {
|
||||
t.Helper()
|
||||
apiClient := testEnv.APIClient()
|
||||
|
||||
@@ -123,7 +123,7 @@ func startServerContainer(ctx context.Context, t *testing.T, msg string, port in
|
||||
container.WithExposedPorts(fmt.Sprintf("%d/tcp", port)),
|
||||
func(c *container.TestContainerConfig) {
|
||||
c.HostConfig.PortBindings = containertypes.PortMap{
|
||||
containertypes.PortRangeProto(fmt.Sprintf("%d/tcp", port)): []containertypes.PortBinding{
|
||||
containertypes.MustParsePort(fmt.Sprintf("%d/tcp", port)): []containertypes.PortBinding{
|
||||
{
|
||||
HostPort: fmt.Sprintf("%d", port),
|
||||
},
|
||||
|
||||
@@ -73,9 +73,10 @@ func WithSysctls(sysctls map[string]string) func(*TestContainerConfig) {
|
||||
// WithExposedPorts sets the exposed ports of the container
|
||||
func WithExposedPorts(ports ...string) func(*TestContainerConfig) {
|
||||
return func(c *TestContainerConfig) {
|
||||
c.Config.ExposedPorts = map[container.PortRangeProto]struct{}{}
|
||||
c.Config.ExposedPorts = map[container.Port]struct{}{}
|
||||
for _, port := range ports {
|
||||
c.Config.ExposedPorts[container.PortRangeProto(port)] = struct{}{}
|
||||
p, _ := container.ParsePort(port)
|
||||
c.Config.ExposedPorts[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
containertypes "github.com/moby/moby/api/types/container"
|
||||
networktypes "github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/api/types/versions"
|
||||
@@ -518,18 +517,19 @@ func TestEndpointWithCustomIfname(t *testing.T) {
|
||||
func TestPublishedPortAlreadyInUse(t *testing.T) {
|
||||
ctx := setupTest(t)
|
||||
apiClient := testEnv.APIClient()
|
||||
mappedPort := containertypes.MustParsePort("80/tcp")
|
||||
|
||||
ctr1 := ctr.Run(ctx, t, apiClient,
|
||||
ctr.WithCmd("top"),
|
||||
ctr.WithExposedPorts("80/tcp"),
|
||||
ctr.WithPortMap(containertypes.PortMap{"80/tcp": {{HostPort: "8000"}}}))
|
||||
ctr.WithPortMap(containertypes.PortMap{mappedPort: {{HostPort: "8000"}}}))
|
||||
defer ctr.Remove(ctx, t, apiClient, ctr1, client.ContainerRemoveOptions{Force: true})
|
||||
|
||||
ctr2 := ctr.Create(ctx, t, apiClient,
|
||||
ctr.WithCmd("top"),
|
||||
ctr.WithRestartPolicy(containertypes.RestartPolicyAlways),
|
||||
ctr.WithExposedPorts("80/tcp"),
|
||||
ctr.WithPortMap(containertypes.PortMap{"80/tcp": {{HostPort: "8000"}}}))
|
||||
ctr.WithPortMap(containertypes.PortMap{mappedPort: {{HostPort: "8000"}}}))
|
||||
defer ctr.Remove(ctx, t, apiClient, ctr2, client.ContainerRemoveOptions{Force: true})
|
||||
|
||||
err := apiClient.ContainerStart(ctx, ctr2, client.ContainerStartOptions{})
|
||||
@@ -564,18 +564,18 @@ func TestAllPortMappingsAreReturned(t *testing.T) {
|
||||
|
||||
ctrID := ctr.Run(ctx, t, apiClient,
|
||||
ctr.WithExposedPorts("80/tcp", "81/tcp"),
|
||||
ctr.WithPortMap(containertypes.PortMap{"80/tcp": {{HostPort: "8000"}}}),
|
||||
ctr.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: "8000"}}}),
|
||||
ctr.WithEndpointSettings("testnetv4", &networktypes.EndpointSettings{}),
|
||||
ctr.WithEndpointSettings("testnetv6", &networktypes.EndpointSettings{}))
|
||||
defer ctr.Remove(ctx, t, apiClient, ctrID, client.ContainerRemoveOptions{Force: true})
|
||||
|
||||
inspect := ctr.Inspect(ctx, t, apiClient, ctrID)
|
||||
assert.DeepEqual(t, inspect.NetworkSettings.Ports, containertypes.PortMap{
|
||||
"80/tcp": []containertypes.PortBinding{
|
||||
containertypes.MustParsePort("80/tcp"): []containertypes.PortBinding{
|
||||
{HostIP: "0.0.0.0", HostPort: "8000"},
|
||||
{HostIP: "::", HostPort: "8000"},
|
||||
},
|
||||
"81/tcp": nil,
|
||||
containertypes.MustParsePort("81/tcp"): nil,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -603,7 +603,7 @@ func TestFirewalldReloadNoZombies(t *testing.T) {
|
||||
|
||||
cid := ctr.Run(ctx, t, c,
|
||||
ctr.WithExposedPorts("80/tcp", "81/tcp"),
|
||||
ctr.WithPortMap(containertypes.PortMap{"80/tcp": {{HostPort: "8000"}}}))
|
||||
ctr.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: "8000"}}}))
|
||||
defer func() {
|
||||
if !removed {
|
||||
ctr.Remove(ctx, t, c, cid, client.ContainerRemoveOptions{Force: true})
|
||||
@@ -793,7 +793,7 @@ func TestPortMappingRestore(t *testing.T) {
|
||||
const svrName = "svr"
|
||||
cid := ctr.Run(ctx, t, c,
|
||||
ctr.WithExposedPorts("80/tcp"),
|
||||
ctr.WithPortMap(containertypes.PortMap{"80/tcp": {}}),
|
||||
ctr.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {}}),
|
||||
ctr.WithName(svrName),
|
||||
ctr.WithRestartPolicy(containertypes.RestartPolicyUnlessStopped),
|
||||
ctr.WithCmd("httpd", "-f"),
|
||||
@@ -804,9 +804,9 @@ func TestPortMappingRestore(t *testing.T) {
|
||||
t.Helper()
|
||||
insp := ctr.Inspect(ctx, t, c, cid)
|
||||
assert.Check(t, is.Equal(insp.State.Running, true))
|
||||
if assert.Check(t, is.Contains(insp.NetworkSettings.Ports, containertypes.PortRangeProto("80/tcp"))) &&
|
||||
assert.Check(t, is.Len(insp.NetworkSettings.Ports["80/tcp"], 2)) {
|
||||
hostPort := insp.NetworkSettings.Ports["80/tcp"][0].HostPort
|
||||
if assert.Check(t, is.Contains(insp.NetworkSettings.Ports, containertypes.MustParsePort("80/tcp"))) &&
|
||||
assert.Check(t, is.Len(insp.NetworkSettings.Ports[containertypes.MustParsePort("80/tcp")], 2)) {
|
||||
hostPort := insp.NetworkSettings.Ports[containertypes.MustParsePort("80/tcp")][0].HostPort
|
||||
res := ctr.RunAttach(ctx, t, c,
|
||||
ctr.WithExtraHost("thehost:host-gateway"),
|
||||
ctr.WithCmd("wget", "-T3", "http://"+net.JoinHostPort("thehost", hostPort)),
|
||||
@@ -951,7 +951,7 @@ func TestEmptyPortBindingsBC(t *testing.T) {
|
||||
d.StartWithBusybox(ctx, t)
|
||||
defer d.Stop(t)
|
||||
|
||||
createInspect := func(t *testing.T, version string, pbs []nat.PortBinding) (containertypes.PortMap, []string) {
|
||||
createInspect := func(t *testing.T, version string, pbs []containertypes.PortBinding) (containertypes.PortMap, []string) {
|
||||
apiClient := d.NewClientT(t, client.WithVersion(version))
|
||||
defer apiClient.Close()
|
||||
|
||||
@@ -966,7 +966,7 @@ func TestEmptyPortBindingsBC(t *testing.T) {
|
||||
// Create a container with an empty list of port bindings for container port 80/tcp.
|
||||
config := ctr.NewTestConfig(ctr.WithCmd("top"),
|
||||
ctr.WithExposedPorts("80/tcp"),
|
||||
ctr.WithPortMap(containertypes.PortMap{"80/tcp": pbs}))
|
||||
ctr.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80/tcp"): pbs}))
|
||||
c, err := apiClient.ContainerCreate(ctx, config.Config, config.HostConfig, config.NetworkingConfig, config.Platform, config.Name)
|
||||
assert.NilError(t, err)
|
||||
defer apiClient.ContainerRemove(ctx, c.ID, client.ContainerRemoveOptions{Force: true})
|
||||
@@ -979,25 +979,25 @@ func TestEmptyPortBindingsBC(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("backfilling on old client version", func(t *testing.T) {
|
||||
expMappings := containertypes.PortMap{"80/tcp": {
|
||||
expMappings := containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {
|
||||
{}, // An empty PortBinding is backfilled
|
||||
}}
|
||||
expWarnings := make([]string, 0)
|
||||
|
||||
mappings, warnings := createInspect(t, "1.51", []nat.PortBinding{})
|
||||
mappings, warnings := createInspect(t, "1.51", []containertypes.PortBinding{})
|
||||
assert.DeepEqual(t, expMappings, mappings)
|
||||
assert.DeepEqual(t, expWarnings, warnings)
|
||||
})
|
||||
|
||||
t.Run("backfilling on API 1.52, with a warning", func(t *testing.T) {
|
||||
expMappings := containertypes.PortMap{"80/tcp": {
|
||||
expMappings := containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {
|
||||
{}, // An empty PortBinding is backfilled
|
||||
}}
|
||||
expWarnings := []string{
|
||||
"Following container port(s) have an empty list of port-bindings: 80/tcp. Starting with API 1.53, such bindings will be discarded.",
|
||||
}
|
||||
|
||||
mappings, warnings := createInspect(t, "1.52", []nat.PortBinding{})
|
||||
mappings, warnings := createInspect(t, "1.52", []containertypes.PortBinding{})
|
||||
assert.DeepEqual(t, expMappings, mappings)
|
||||
assert.DeepEqual(t, expWarnings, warnings)
|
||||
})
|
||||
@@ -1006,19 +1006,19 @@ func TestEmptyPortBindingsBC(t *testing.T) {
|
||||
expMappings := containertypes.PortMap{}
|
||||
expWarnings := make([]string, 0)
|
||||
|
||||
mappings, warnings := createInspect(t, "1.53", []nat.PortBinding{})
|
||||
mappings, warnings := createInspect(t, "1.53", []containertypes.PortBinding{})
|
||||
assert.DeepEqual(t, expMappings, mappings)
|
||||
assert.DeepEqual(t, expWarnings, warnings)
|
||||
})
|
||||
|
||||
for _, apiVersion := range []string{"1.51", "1.52", "1.53"} {
|
||||
t.Run("no backfilling on API "+apiVersion+" with non-empty bindings", func(t *testing.T) {
|
||||
expMappings := containertypes.PortMap{"80/tcp": {
|
||||
expMappings := containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {
|
||||
{HostPort: "8080"},
|
||||
}}
|
||||
expWarnings := make([]string, 0)
|
||||
|
||||
mappings, warnings := createInspect(t, apiVersion, []nat.PortBinding{{HostPort: "8080"}})
|
||||
mappings, warnings := createInspect(t, apiVersion, []containertypes.PortBinding{{HostPort: "8080"}})
|
||||
assert.DeepEqual(t, expMappings, mappings)
|
||||
assert.DeepEqual(t, expWarnings, warnings)
|
||||
})
|
||||
@@ -1043,7 +1043,7 @@ func TestPortBindingBackfillingForOlderContainers(t *testing.T) {
|
||||
|
||||
cid := ctr.Create(ctx, t, c,
|
||||
ctr.WithExposedPorts("80/tcp"),
|
||||
ctr.WithPortMap(containertypes.PortMap{"80/tcp": {}}))
|
||||
ctr.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {}}))
|
||||
defer c.ContainerRemove(ctx, cid, client.ContainerRemoveOptions{Force: true})
|
||||
|
||||
// Stop the daemon to safely tamper with the on-disk state.
|
||||
@@ -1052,7 +1052,7 @@ func TestPortBindingBackfillingForOlderContainers(t *testing.T) {
|
||||
d.TamperWithContainerConfig(t, cid, func(container *container.Container) {
|
||||
// Simulate a container created with an older version of the Engine
|
||||
// by setting an empty list of port bindings.
|
||||
container.HostConfig.PortBindings = containertypes.PortMap{"80/tcp": {}}
|
||||
container.HostConfig.PortBindings = containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {}}
|
||||
})
|
||||
|
||||
// Restart the daemon — it should backfill the empty port binding slice.
|
||||
@@ -1060,7 +1060,7 @@ func TestPortBindingBackfillingForOlderContainers(t *testing.T) {
|
||||
|
||||
inspect := ctr.Inspect(ctx, t, c, cid)
|
||||
|
||||
expMappings := containertypes.PortMap{"80/tcp": {
|
||||
expMappings := containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {
|
||||
{}, // An empty PortBinding is backfilled
|
||||
}}
|
||||
assert.DeepEqual(t, expMappings, inspect.HostConfig.PortBindings)
|
||||
|
||||
@@ -82,7 +82,7 @@ var index = []section{
|
||||
containers: []ctrDesc{
|
||||
{
|
||||
name: "c1",
|
||||
portMappings: containertypes.PortMap{"80/tcp": {{HostPort: "8080"}}},
|
||||
portMappings: containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: "8080"}}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -95,7 +95,7 @@ var index = []section{
|
||||
containers: []ctrDesc{
|
||||
{
|
||||
name: "c1",
|
||||
portMappings: containertypes.PortMap{"80/tcp": {{HostPort: "8080"}}},
|
||||
portMappings: containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: "8080"}}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -108,7 +108,7 @@ var index = []section{
|
||||
containers: []ctrDesc{
|
||||
{
|
||||
name: "c1",
|
||||
portMappings: containertypes.PortMap{"80/tcp": {{HostPort: "8080"}}},
|
||||
portMappings: containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: "8080"}}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -142,7 +142,7 @@ var index = []section{
|
||||
containers: []ctrDesc{
|
||||
{
|
||||
name: "c1",
|
||||
portMappings: containertypes.PortMap{"80/tcp": {{HostPort: "8080"}}},
|
||||
portMappings: containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: "8080"}}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -155,7 +155,7 @@ var index = []section{
|
||||
containers: []ctrDesc{
|
||||
{
|
||||
name: "c1",
|
||||
portMappings: containertypes.PortMap{"80/tcp": {{HostPort: "8080"}}},
|
||||
portMappings: containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: "8080"}}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -167,7 +167,7 @@ var index = []section{
|
||||
containers: []ctrDesc{
|
||||
{
|
||||
name: "c1",
|
||||
portMappings: containertypes.PortMap{"80/tcp": {{HostPort: "8080"}}},
|
||||
portMappings: containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: "8080"}}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -179,7 +179,7 @@ var index = []section{
|
||||
containers: []ctrDesc{
|
||||
{
|
||||
name: "c1",
|
||||
portMappings: containertypes.PortMap{"80/tcp": {{HostIP: "127.0.0.1", HostPort: "8080"}}},
|
||||
portMappings: containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostIP: "127.0.0.1", HostPort: "8080"}}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -327,7 +327,7 @@ func createBridgeNetworks(ctx context.Context, t *testing.T, d *daemon.Daemon, s
|
||||
for _, ctr := range nw.containers {
|
||||
var exposedPorts []string
|
||||
for ep := range ctr.portMappings {
|
||||
exposedPorts = append(exposedPorts, ep.Port()+"/"+ep.Proto())
|
||||
exposedPorts = append(exposedPorts, ep.String())
|
||||
}
|
||||
id := container.Run(ctx, t, c,
|
||||
container.WithNetworkMode(nw.name),
|
||||
@@ -356,7 +356,7 @@ func createServices(ctx context.Context, t *testing.T, d *daemon.Daemon, section
|
||||
portConfig = append(portConfig, swarmtypes.PortConfig{
|
||||
Protocol: swarmtypes.PortConfigProtocol(ctrPP.Proto()),
|
||||
PublishedPort: uint32(hp),
|
||||
TargetPort: uint32(ctrPP.Int()),
|
||||
TargetPort: uint32(ctrPP.Num()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ var index = []section{
|
||||
containers: []ctrDesc{
|
||||
{
|
||||
name: "c1",
|
||||
portMappings: containertypes.PortMap{"80/tcp": {{HostPort: "8080"}}},
|
||||
portMappings: containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: "8080"}}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -92,7 +92,7 @@ var index = []section{
|
||||
containers: []ctrDesc{
|
||||
{
|
||||
name: "c1",
|
||||
portMappings: containertypes.PortMap{"80/tcp": {{HostPort: "8080"}}},
|
||||
portMappings: containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: "8080"}}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -105,7 +105,7 @@ var index = []section{
|
||||
containers: []ctrDesc{
|
||||
{
|
||||
name: "c1",
|
||||
portMappings: containertypes.PortMap{"80/tcp": {{HostPort: "8080"}}},
|
||||
portMappings: containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: "8080"}}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -139,7 +139,7 @@ var index = []section{
|
||||
containers: []ctrDesc{
|
||||
{
|
||||
name: "c1",
|
||||
portMappings: containertypes.PortMap{"80/tcp": {{HostPort: "8080"}}},
|
||||
portMappings: containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: "8080"}}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -152,7 +152,7 @@ var index = []section{
|
||||
containers: []ctrDesc{
|
||||
{
|
||||
name: "c1",
|
||||
portMappings: containertypes.PortMap{"80/tcp": {{HostPort: "8080"}}},
|
||||
portMappings: containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: "8080"}}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -178,7 +178,7 @@ var index = []section{
|
||||
containers: []ctrDesc{
|
||||
{
|
||||
name: "c1",
|
||||
portMappings: containertypes.PortMap{"80/tcp": {{HostIP: "127.0.0.1", HostPort: "8080"}}},
|
||||
portMappings: containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostIP: "127.0.0.1", HostPort: "8080"}}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -300,7 +300,7 @@ func createBridgeNetworks(ctx context.Context, t *testing.T, d *daemon.Daemon, s
|
||||
for _, ctr := range nw.containers {
|
||||
var exposedPorts []string
|
||||
for ep := range ctr.portMappings {
|
||||
exposedPorts = append(exposedPorts, ep.Port()+"/"+ep.Proto())
|
||||
exposedPorts = append(exposedPorts, ep.String())
|
||||
}
|
||||
id := container.Run(ctx, t, c,
|
||||
container.WithNetworkMode(nw.name),
|
||||
|
||||
@@ -388,7 +388,7 @@ func TestBridgeINCRouted(t *testing.T) {
|
||||
container.WithNetworkMode(netName),
|
||||
container.WithName("ctr-"+gwMode),
|
||||
container.WithExposedPorts("80/tcp"),
|
||||
container.WithPortMap(containertypes.PortMap{"80/tcp": {}}),
|
||||
container.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {}}),
|
||||
)
|
||||
t.Cleanup(func() {
|
||||
c.ContainerRemove(ctx, ctrId, client.ContainerRemoveOptions{Force: true})
|
||||
@@ -565,7 +565,7 @@ func TestAccessToPublishedPort(t *testing.T) {
|
||||
container.WithNetworkMode(serverNetName),
|
||||
container.WithName("ctr-server"),
|
||||
container.WithExposedPorts("80/tcp"),
|
||||
container.WithPortMap(containertypes.PortMap{"80/tcp": {containertypes.PortBinding{HostPort: "8080"}}}),
|
||||
container.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {containertypes.PortBinding{HostPort: "8080"}}}),
|
||||
container.WithCmd("httpd", "-f"),
|
||||
)
|
||||
defer c.ContainerRemove(ctx, ctrId, client.ContainerRemoveOptions{Force: true})
|
||||
@@ -688,7 +688,7 @@ func TestInterNetworkDirectRouting(t *testing.T) {
|
||||
container.WithNetworkMode(serverNetName),
|
||||
container.WithName("ctr-pub"),
|
||||
container.WithExposedPorts("80/tcp"),
|
||||
container.WithPortMap(containertypes.PortMap{"80/tcp": {containertypes.PortBinding{HostPort: "8080"}}}),
|
||||
container.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {containertypes.PortBinding{HostPort: "8080"}}}),
|
||||
container.WithCmd("httpd", "-f"),
|
||||
)
|
||||
defer c.ContainerRemove(ctx, ctrPubId, client.ContainerRemoveOptions{Force: true})
|
||||
@@ -1063,7 +1063,7 @@ func TestDisableIPv6OnInterface(t *testing.T) {
|
||||
container.WithName(ctrName),
|
||||
container.WithNetworkMode(tc.netName),
|
||||
container.WithExposedPorts("80/tcp"),
|
||||
container.WithPortMap(containertypes.PortMap{"80/tcp": {{HostPort: "8080"}}}),
|
||||
container.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: "8080"}}}),
|
||||
container.WithEndpointSettings(tc.netName, &networktypes.EndpointSettings{
|
||||
DriverOpts: map[string]string{
|
||||
netlabel.EndpointSysctls: "net.ipv6.conf.IFNAME.disable_ipv6=1",
|
||||
@@ -1504,7 +1504,7 @@ func TestGatewaySelection(t *testing.T) {
|
||||
container.WithName(ctrName),
|
||||
container.WithNetworkMode(netName4),
|
||||
container.WithExposedPorts("80"),
|
||||
container.WithPortMap(containertypes.PortMap{"80": {{HostPort: "8080"}}}),
|
||||
container.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80"): {{HostPort: "8080"}}}),
|
||||
container.WithCmd("httpd", "-f"),
|
||||
)
|
||||
defer c.ContainerRemove(ctx, ctrId, client.ContainerRemoveOptions{Force: true})
|
||||
@@ -1956,7 +1956,7 @@ func TestDropInForwardChain(t *testing.T) {
|
||||
ctrId := container.Run(ctx, t, c,
|
||||
container.WithNetworkMode(netName46),
|
||||
container.WithExposedPorts("80"),
|
||||
container.WithPortMap(containertypes.PortMap{"80": {{HostPort: hostPort}}}),
|
||||
container.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80"): {{HostPort: hostPort}}}),
|
||||
container.WithCmd("httpd", "-f"),
|
||||
)
|
||||
defer c.ContainerRemove(ctx, ctrId, client.ContainerRemoveOptions{Force: true})
|
||||
|
||||
@@ -97,13 +97,13 @@ func TestFlakyPortMappedHairpinWindows(t *testing.T) {
|
||||
serverId := container.Run(ctx, t, c,
|
||||
container.WithNetworkMode(serverNetName),
|
||||
container.WithExposedPorts("80"),
|
||||
container.WithPortMap(containertypes.PortMap{"80": {{HostIP: "0.0.0.0"}}}),
|
||||
container.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80"): {{HostIP: "0.0.0.0"}}}),
|
||||
container.WithCmd("httpd", "-f"),
|
||||
)
|
||||
defer c.ContainerRemove(ctx, serverId, client.ContainerRemoveOptions{Force: true})
|
||||
|
||||
inspect := container.Inspect(ctx, t, c, serverId)
|
||||
hostPort := inspect.NetworkSettings.Ports["80/tcp"][0].HostPort
|
||||
hostPort := inspect.NetworkSettings.Ports[containertypes.MustParsePort("80/tcp")][0].HostPort
|
||||
|
||||
attachCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -73,7 +73,7 @@ func TestDisableNAT(t *testing.T) {
|
||||
{
|
||||
name: "defaults",
|
||||
expPortMap: containertypes.PortMap{
|
||||
"80/tcp": []containertypes.PortBinding{
|
||||
containertypes.MustParsePort("80/tcp"): []containertypes.PortBinding{
|
||||
{HostIP: "0.0.0.0", HostPort: "8080"},
|
||||
{HostIP: "::", HostPort: "8080"},
|
||||
},
|
||||
@@ -84,7 +84,7 @@ func TestDisableNAT(t *testing.T) {
|
||||
gwMode4: "nat",
|
||||
gwMode6: "routed",
|
||||
expPortMap: containertypes.PortMap{
|
||||
"80/tcp": []containertypes.PortBinding{
|
||||
containertypes.MustParsePort("80/tcp"): []containertypes.PortBinding{
|
||||
{HostIP: "0.0.0.0", HostPort: "8080"},
|
||||
{HostIP: "::", HostPort: ""},
|
||||
},
|
||||
@@ -95,7 +95,7 @@ func TestDisableNAT(t *testing.T) {
|
||||
gwMode4: "routed",
|
||||
gwMode6: "nat",
|
||||
expPortMap: containertypes.PortMap{
|
||||
"80/tcp": []containertypes.PortBinding{
|
||||
containertypes.MustParsePort("80/tcp"): []containertypes.PortBinding{
|
||||
{HostIP: "::", HostPort: "8080"},
|
||||
{HostIP: "0.0.0.0", HostPort: ""},
|
||||
},
|
||||
@@ -124,7 +124,7 @@ func TestDisableNAT(t *testing.T) {
|
||||
id := container.Run(ctx, t, c,
|
||||
container.WithNetworkMode(netName),
|
||||
container.WithExposedPorts("80/tcp"),
|
||||
container.WithPortMap(containertypes.PortMap{"80/tcp": {{HostPort: "8080"}}}),
|
||||
container.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: "8080"}}}),
|
||||
)
|
||||
defer c.ContainerRemove(ctx, id, client.ContainerRemoveOptions{Force: true})
|
||||
|
||||
@@ -162,13 +162,13 @@ func TestPortMappedHairpinTCP(t *testing.T) {
|
||||
serverId := container.Run(ctx, t, c,
|
||||
container.WithNetworkMode(serverNetName),
|
||||
container.WithExposedPorts("80"),
|
||||
container.WithPortMap(containertypes.PortMap{"80": {{HostIP: "0.0.0.0"}}}),
|
||||
container.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80"): {{HostIP: "0.0.0.0"}}}),
|
||||
container.WithCmd("httpd", "-f"),
|
||||
)
|
||||
defer c.ContainerRemove(ctx, serverId, client.ContainerRemoveOptions{Force: true})
|
||||
|
||||
inspect := container.Inspect(ctx, t, c, serverId)
|
||||
hostPort := inspect.NetworkSettings.Ports["80/tcp"][0].HostPort
|
||||
hostPort := inspect.NetworkSettings.Ports[containertypes.MustParsePort("80/tcp")][0].HostPort
|
||||
|
||||
clientCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
@@ -209,13 +209,13 @@ func TestPortMappedHairpinUDP(t *testing.T) {
|
||||
serverId := container.Run(ctx, t, c,
|
||||
container.WithNetworkMode(serverNetName),
|
||||
container.WithExposedPorts("54/udp"),
|
||||
container.WithPortMap(containertypes.PortMap{"54/udp": {{HostIP: "0.0.0.0"}}}),
|
||||
container.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("54/udp"): {{HostIP: "0.0.0.0"}}}),
|
||||
container.WithCmd("/bin/sh", "-c", "echo 'foobar.internal 192.168.155.23' | dnsd -c - -p 54"),
|
||||
)
|
||||
defer c.ContainerRemove(ctx, serverId, client.ContainerRemoveOptions{Force: true})
|
||||
|
||||
inspect := container.Inspect(ctx, t, c, serverId)
|
||||
hostPort := inspect.NetworkSettings.Ports["54/udp"][0].HostPort
|
||||
hostPort := inspect.NetworkSettings.Ports[containertypes.MustParsePort("54/udp")][0].HostPort
|
||||
|
||||
// nslookup gets an answer quickly from the dns server, but then tries to
|
||||
// query another DNS server (for some unknown reasons) and times out. Hence,
|
||||
@@ -251,13 +251,13 @@ func TestProxy4To6(t *testing.T) {
|
||||
serverId := container.Run(ctx, t, c,
|
||||
container.WithNetworkMode(netName),
|
||||
container.WithExposedPorts("80"),
|
||||
container.WithPortMap(containertypes.PortMap{"80": {{HostIP: "::1"}}}),
|
||||
container.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80"): {{HostIP: "::1"}}}),
|
||||
container.WithCmd("httpd", "-f"),
|
||||
)
|
||||
defer c.ContainerRemove(ctx, serverId, client.ContainerRemoveOptions{Force: true})
|
||||
|
||||
inspect := container.Inspect(ctx, t, c, serverId)
|
||||
hostPort := inspect.NetworkSettings.Ports["80/tcp"][0].HostPort
|
||||
hostPort := inspect.NetworkSettings.Ports[containertypes.MustParsePort("80/tcp")][0].HostPort
|
||||
|
||||
var resp *http.Response
|
||||
addr := "http://[::1]:" + hostPort
|
||||
@@ -375,7 +375,7 @@ func TestAccessPublishedPortFromHost(t *testing.T) {
|
||||
serverID := container.Run(ctx, t, c,
|
||||
container.WithName(sanitizeCtrName(t.Name()+"-server")),
|
||||
container.WithExposedPorts("80/tcp"),
|
||||
container.WithPortMap(containertypes.PortMap{"80/tcp": {{HostPort: hostPort}}}),
|
||||
container.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: hostPort}}}),
|
||||
container.WithCmd("httpd", "-f"),
|
||||
container.WithNetworkMode(bridgeName))
|
||||
defer c.ContainerRemove(ctx, serverID, client.ContainerRemoveOptions{Force: true})
|
||||
@@ -455,7 +455,7 @@ func TestAccessPublishedPortFromRemoteHost(t *testing.T) {
|
||||
serverID := container.Run(ctx, t, c,
|
||||
container.WithName(sanitizeCtrName(t.Name()+"-server")),
|
||||
container.WithExposedPorts("80/tcp"),
|
||||
container.WithPortMap(containertypes.PortMap{"80/tcp": {{HostPort: hostPort}}}),
|
||||
container.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostPort: hostPort}}}),
|
||||
container.WithCmd("httpd", "-f"),
|
||||
container.WithNetworkMode(bridgeName))
|
||||
defer c.ContainerRemove(ctx, serverID, client.ContainerRemoveOptions{Force: true})
|
||||
@@ -553,13 +553,13 @@ func TestAccessPublishedPortFromCtr(t *testing.T) {
|
||||
serverId := container.Run(ctx, t, c,
|
||||
container.WithNetworkMode(netName),
|
||||
container.WithExposedPorts("80"),
|
||||
container.WithPortMap(containertypes.PortMap{"80": {{HostIP: "0.0.0.0"}}}),
|
||||
container.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {{HostIP: "0.0.0.0"}}}),
|
||||
container.WithCmd("httpd", "-f"),
|
||||
)
|
||||
defer c.ContainerRemove(ctx, serverId, client.ContainerRemoveOptions{Force: true})
|
||||
|
||||
inspect := container.Inspect(ctx, t, c, serverId)
|
||||
hostPort := inspect.NetworkSettings.Ports["80/tcp"][0].HostPort
|
||||
hostPort := inspect.NetworkSettings.Ports[containertypes.MustParsePort("80/tcp")][0].HostPort
|
||||
|
||||
clientCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
@@ -603,7 +603,9 @@ func TestRestartUserlandProxyUnder2MSL(t *testing.T) {
|
||||
ctrOpts := []func(*container.TestContainerConfig){
|
||||
container.WithName(ctrName),
|
||||
container.WithExposedPorts("80/tcp"),
|
||||
container.WithPortMap(containertypes.PortMap{"80/tcp": {{HostPort: "1780"}}}),
|
||||
container.WithPortMap(containertypes.PortMap{
|
||||
containertypes.MustParsePort("80/tcp"): {{HostPort: "1780"}},
|
||||
}),
|
||||
container.WithCmd("httpd", "-f"),
|
||||
container.WithNetworkMode(netName),
|
||||
}
|
||||
@@ -700,7 +702,9 @@ func TestDirectRoutingOpenPorts(t *testing.T) {
|
||||
container.WithNetworkMode(netName),
|
||||
container.WithName("ctr-"+gwMode),
|
||||
container.WithExposedPorts("80/tcp"),
|
||||
container.WithPortMap(containertypes.PortMap{"80/tcp": {}}),
|
||||
container.WithPortMap(containertypes.PortMap{
|
||||
containertypes.MustParsePort("80/tcp"): {},
|
||||
}),
|
||||
)
|
||||
t.Cleanup(func() {
|
||||
c.ContainerRemove(ctx, ctrId, client.ContainerRemoveOptions{Force: true})
|
||||
@@ -979,7 +983,9 @@ func TestRoutedNonGateway(t *testing.T) {
|
||||
ctrId := container.Run(ctx, t, c,
|
||||
container.WithCmd("httpd", "-f"),
|
||||
container.WithExposedPorts("80/tcp"),
|
||||
container.WithPortMap(containertypes.PortMap{"80/tcp": {{HostPort: "8080"}}}),
|
||||
container.WithPortMap(containertypes.PortMap{
|
||||
containertypes.MustParsePort("80/tcp"): {{HostPort: "8080"}},
|
||||
}),
|
||||
container.WithNetworkMode(natNetName),
|
||||
container.WithNetworkMode(routedNetName),
|
||||
container.WithEndpointSettings(natNetName, &networktypes.EndpointSettings{GwPriority: 1}),
|
||||
@@ -1133,7 +1139,9 @@ func TestAccessPublishedPortFromAnotherNetwork(t *testing.T) {
|
||||
container.WithName("server"),
|
||||
container.WithCmd("nc", "-lp", "5000"),
|
||||
container.WithExposedPorts("5000/tcp"),
|
||||
container.WithPortMap(containertypes.PortMap{"5000/tcp": {{HostPort: "5000"}}}),
|
||||
container.WithPortMap(containertypes.PortMap{
|
||||
containertypes.MustParsePort("5000/tcp"): {{HostPort: "5000"}},
|
||||
}),
|
||||
container.WithNetworkMode(servnet))
|
||||
defer c.ContainerRemove(ctx, serverID, client.ContainerRemoveOptions{Force: true})
|
||||
|
||||
@@ -1329,7 +1337,9 @@ func testDirectRemoteAccessOnExposedPort(t *testing.T, ctx context.Context, d *d
|
||||
container.WithName(sanitizeCtrName(t.Name()+"-server")),
|
||||
container.WithCmd("nc", "-lup", "5000"),
|
||||
container.WithExposedPorts("5000/udp"),
|
||||
container.WithPortMap(containertypes.PortMap{"5000/udp": {{HostPort: hostPort}}}),
|
||||
container.WithPortMap(containertypes.PortMap{
|
||||
containertypes.MustParsePort("5000/udp"): {{HostPort: hostPort}},
|
||||
}),
|
||||
container.WithNetworkMode(bridgeName),
|
||||
container.WithEndpointSettings(bridgeName, &networktypes.EndpointSettings{
|
||||
IPAddress: ctrIP.String(),
|
||||
@@ -1415,7 +1425,9 @@ func TestAccessPortPublishedOnLoopbackAddress(t *testing.T) {
|
||||
container.WithCmd("nc", "-lup", "5000"),
|
||||
container.WithExposedPorts("5000/udp"),
|
||||
// This port is mapped on 127.0.0.2, so it should not be remotely accessible.
|
||||
container.WithPortMap(containertypes.PortMap{"5000/udp": {{HostIP: loIP, HostPort: hostPort}}}),
|
||||
container.WithPortMap(containertypes.PortMap{
|
||||
containertypes.MustParsePort("5000/udp"): {{HostIP: loIP, HostPort: hostPort}},
|
||||
}),
|
||||
container.WithNetworkMode(bridgeName))
|
||||
defer c.ContainerRemove(ctx, serverID, client.ContainerRemoveOptions{Force: true})
|
||||
|
||||
@@ -1526,7 +1538,7 @@ func TestSkipRawRules(t *testing.T) {
|
||||
|
||||
ctrId := container.Run(ctx, t, c,
|
||||
container.WithExposedPorts("80/tcp"),
|
||||
container.WithPortMap(containertypes.PortMap{"80/tcp": {
|
||||
container.WithPortMap(containertypes.PortMap{containertypes.MustParsePort("80/tcp"): {
|
||||
{HostIP: "127.0.0.1", HostPort: "8080"},
|
||||
{HostPort: "8081"},
|
||||
}}),
|
||||
@@ -1559,9 +1571,9 @@ func TestMixAnyWithSpecificHostAddrs(t *testing.T) {
|
||||
ctrId := container.Run(ctx, t, c,
|
||||
container.WithExposedPorts("80/"+proto, "81/"+proto, "82/"+proto),
|
||||
container.WithPortMap(containertypes.PortMap{
|
||||
containertypes.PortRangeProto("81/" + proto): {{}},
|
||||
containertypes.PortRangeProto("82/" + proto): {{}},
|
||||
containertypes.PortRangeProto("80/" + proto): {{HostIP: "127.0.0.1"}},
|
||||
containertypes.MustParsePort("81/" + proto): {{}},
|
||||
containertypes.MustParsePort("82/" + proto): {{}},
|
||||
containertypes.MustParsePort("80/" + proto): {{HostIP: "127.0.0.1"}},
|
||||
}),
|
||||
)
|
||||
defer c.ContainerRemove(ctx, ctrId, client.ContainerRemoveOptions{Force: true})
|
||||
|
||||
@@ -164,7 +164,7 @@ COPY . /static`); err != nil {
|
||||
// Find out the system assigned port
|
||||
i, err := c.ContainerInspect(context.Background(), b.ID)
|
||||
assert.NilError(t, err)
|
||||
ports, exists := i.NetworkSettings.Ports["80/tcp"]
|
||||
ports, exists := i.NetworkSettings.Ports[containertypes.MustParsePort("80/tcp")]
|
||||
assert.Assert(t, exists, "unable to find port 80/tcp for %s", ctrName)
|
||||
if len(ports) == 0 {
|
||||
t.Fatalf("no ports mapped for 80/tcp for %s: %#v", ctrName, i.NetworkSettings.Ports)
|
||||
|
||||
237
vendor/github.com/docker/go-connections/nat/nat.go
generated
vendored
237
vendor/github.com/docker/go-connections/nat/nat.go
generated
vendored
@@ -1,237 +0,0 @@
|
||||
// Package nat is a convenience package for manipulation of strings describing network ports.
|
||||
package nat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PortBinding represents a binding between a Host IP address and a Host Port
|
||||
type PortBinding struct {
|
||||
// HostIP is the host IP Address
|
||||
HostIP string `json:"HostIp"`
|
||||
// HostPort is the host port number
|
||||
HostPort string
|
||||
}
|
||||
|
||||
// PortMap is a collection of PortBinding indexed by Port
|
||||
type PortMap map[Port][]PortBinding
|
||||
|
||||
// PortSet is a collection of structs indexed by Port
|
||||
type PortSet map[Port]struct{}
|
||||
|
||||
// Port is a string containing port number and protocol in the format "80/tcp"
|
||||
type Port string
|
||||
|
||||
// NewPort creates a new instance of a Port given a protocol and port number or port range
|
||||
func NewPort(proto, port string) (Port, error) {
|
||||
// Check for parsing issues on "port" now so we can avoid having
|
||||
// to check it later on.
|
||||
|
||||
portStartInt, portEndInt, err := ParsePortRangeToInt(port)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if portStartInt == portEndInt {
|
||||
return Port(fmt.Sprintf("%d/%s", portStartInt, proto)), nil
|
||||
}
|
||||
return Port(fmt.Sprintf("%d-%d/%s", portStartInt, portEndInt, proto)), nil
|
||||
}
|
||||
|
||||
// ParsePort parses the port number string and returns an int
|
||||
func ParsePort(rawPort string) (int, error) {
|
||||
if rawPort == "" {
|
||||
return 0, nil
|
||||
}
|
||||
port, err := strconv.ParseUint(rawPort, 10, 16)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid port '%s': %w", rawPort, errors.Unwrap(err))
|
||||
}
|
||||
return int(port), nil
|
||||
}
|
||||
|
||||
// ParsePortRangeToInt parses the port range string and returns start/end ints
|
||||
func ParsePortRangeToInt(rawPort string) (int, int, error) {
|
||||
if rawPort == "" {
|
||||
return 0, 0, nil
|
||||
}
|
||||
start, end, err := ParsePortRange(rawPort)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return int(start), int(end), nil
|
||||
}
|
||||
|
||||
// Proto returns the protocol of a Port
|
||||
func (p Port) Proto() string {
|
||||
proto, _ := SplitProtoPort(string(p))
|
||||
return proto
|
||||
}
|
||||
|
||||
// Port returns the port number of a Port
|
||||
func (p Port) Port() string {
|
||||
_, port := SplitProtoPort(string(p))
|
||||
return port
|
||||
}
|
||||
|
||||
// Int returns the port number of a Port as an int
|
||||
func (p Port) Int() int {
|
||||
portStr := p.Port()
|
||||
// We don't need to check for an error because we're going to
|
||||
// assume that any error would have been found, and reported, in NewPort()
|
||||
port, _ := ParsePort(portStr)
|
||||
return port
|
||||
}
|
||||
|
||||
// Range returns the start/end port numbers of a Port range as ints
|
||||
func (p Port) Range() (int, int, error) {
|
||||
return ParsePortRangeToInt(p.Port())
|
||||
}
|
||||
|
||||
// SplitProtoPort splits a port(range) and protocol, formatted as "<portnum>/[<proto>]"
|
||||
// "<startport-endport>/[<proto>]". It returns an empty string for both if
|
||||
// no port(range) is provided. If a port(range) is provided, but no protocol,
|
||||
// the default ("tcp") protocol is returned.
|
||||
//
|
||||
// SplitProtoPort does not validate or normalize the returned values.
|
||||
func SplitProtoPort(rawPort string) (proto string, port string) {
|
||||
port, proto, _ = strings.Cut(rawPort, "/")
|
||||
if port == "" {
|
||||
return "", ""
|
||||
}
|
||||
if proto == "" {
|
||||
proto = "tcp"
|
||||
}
|
||||
return proto, port
|
||||
}
|
||||
|
||||
func validateProto(proto string) error {
|
||||
switch proto {
|
||||
case "tcp", "udp", "sctp":
|
||||
// All good
|
||||
return nil
|
||||
default:
|
||||
return errors.New("invalid proto: " + proto)
|
||||
}
|
||||
}
|
||||
|
||||
// ParsePortSpecs receives port specs in the format of ip:public:private/proto and parses
|
||||
// these in to the internal types
|
||||
func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) {
|
||||
var (
|
||||
exposedPorts = make(map[Port]struct{}, len(ports))
|
||||
bindings = make(map[Port][]PortBinding)
|
||||
)
|
||||
for _, p := range ports {
|
||||
portMappings, err := ParsePortSpec(p)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, pm := range portMappings {
|
||||
port := pm.Port
|
||||
if _, ok := exposedPorts[port]; !ok {
|
||||
exposedPorts[port] = struct{}{}
|
||||
}
|
||||
bindings[port] = append(bindings[port], pm.Binding)
|
||||
}
|
||||
}
|
||||
return exposedPorts, bindings, nil
|
||||
}
|
||||
|
||||
// PortMapping is a data object mapping a Port to a PortBinding
|
||||
type PortMapping struct {
|
||||
Port Port
|
||||
Binding PortBinding
|
||||
}
|
||||
|
||||
func (p *PortMapping) String() string {
|
||||
return net.JoinHostPort(p.Binding.HostIP, p.Binding.HostPort+":"+string(p.Port))
|
||||
}
|
||||
|
||||
func splitParts(rawport string) (hostIP, hostPort, containerPort string) {
|
||||
parts := strings.Split(rawport, ":")
|
||||
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
return "", "", parts[0]
|
||||
case 2:
|
||||
return "", parts[0], parts[1]
|
||||
case 3:
|
||||
return parts[0], parts[1], parts[2]
|
||||
default:
|
||||
n := len(parts)
|
||||
return strings.Join(parts[:n-2], ":"), parts[n-2], parts[n-1]
|
||||
}
|
||||
}
|
||||
|
||||
// ParsePortSpec parses a port specification string into a slice of PortMappings
|
||||
func ParsePortSpec(rawPort string) ([]PortMapping, error) {
|
||||
ip, hostPort, containerPort := splitParts(rawPort)
|
||||
proto, containerPort := SplitProtoPort(containerPort)
|
||||
proto = strings.ToLower(proto)
|
||||
if err := validateProto(proto); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ip != "" && ip[0] == '[' {
|
||||
// Strip [] from IPV6 addresses
|
||||
rawIP, _, err := net.SplitHostPort(ip + ":")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid IP address %v: %w", ip, err)
|
||||
}
|
||||
ip = rawIP
|
||||
}
|
||||
if ip != "" && net.ParseIP(ip) == nil {
|
||||
return nil, errors.New("invalid IP address: " + ip)
|
||||
}
|
||||
if containerPort == "" {
|
||||
return nil, fmt.Errorf("no port specified: %s<empty>", rawPort)
|
||||
}
|
||||
|
||||
startPort, endPort, err := ParsePortRange(containerPort)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid containerPort: " + containerPort)
|
||||
}
|
||||
|
||||
var startHostPort, endHostPort uint64
|
||||
if hostPort != "" {
|
||||
startHostPort, endHostPort, err = ParsePortRange(hostPort)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid hostPort: " + hostPort)
|
||||
}
|
||||
if (endPort - startPort) != (endHostPort - startHostPort) {
|
||||
// Allow host port range iff containerPort is not a range.
|
||||
// In this case, use the host port range as the dynamic
|
||||
// host port range to allocate into.
|
||||
if endPort != startPort {
|
||||
return nil, fmt.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
count := endPort - startPort + 1
|
||||
ports := make([]PortMapping, 0, count)
|
||||
|
||||
for i := uint64(0); i < count; i++ {
|
||||
cPort := Port(strconv.FormatUint(startPort+i, 10) + "/" + proto)
|
||||
hPort := ""
|
||||
if hostPort != "" {
|
||||
hPort = strconv.FormatUint(startHostPort+i, 10)
|
||||
// Set hostPort to a range only if there is a single container port
|
||||
// and a dynamic host port.
|
||||
if count == 1 && startHostPort != endHostPort {
|
||||
hPort += "-" + strconv.FormatUint(endHostPort, 10)
|
||||
}
|
||||
}
|
||||
ports = append(ports, PortMapping{
|
||||
Port: cPort,
|
||||
Binding: PortBinding{HostIP: ip, HostPort: hPort},
|
||||
})
|
||||
}
|
||||
return ports, nil
|
||||
}
|
||||
33
vendor/github.com/docker/go-connections/nat/parse.go
generated
vendored
33
vendor/github.com/docker/go-connections/nat/parse.go
generated
vendored
@@ -1,33 +0,0 @@
|
||||
package nat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParsePortRange parses and validates the specified string as a port-range (8000-9000)
|
||||
func ParsePortRange(ports string) (uint64, uint64, error) {
|
||||
if ports == "" {
|
||||
return 0, 0, errors.New("empty string specified for ports")
|
||||
}
|
||||
if !strings.Contains(ports, "-") {
|
||||
start, err := strconv.ParseUint(ports, 10, 16)
|
||||
end := start
|
||||
return start, end, err
|
||||
}
|
||||
|
||||
parts := strings.Split(ports, "-")
|
||||
start, err := strconv.ParseUint(parts[0], 10, 16)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
end, err := strconv.ParseUint(parts[1], 10, 16)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if end < start {
|
||||
return 0, 0, errors.New("invalid range specified for port: " + ports)
|
||||
}
|
||||
return start, end, nil
|
||||
}
|
||||
96
vendor/github.com/docker/go-connections/nat/sort.go
generated
vendored
96
vendor/github.com/docker/go-connections/nat/sort.go
generated
vendored
@@ -1,96 +0,0 @@
|
||||
package nat
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type portSorter struct {
|
||||
ports []Port
|
||||
by func(i, j Port) bool
|
||||
}
|
||||
|
||||
func (s *portSorter) Len() int {
|
||||
return len(s.ports)
|
||||
}
|
||||
|
||||
func (s *portSorter) Swap(i, j int) {
|
||||
s.ports[i], s.ports[j] = s.ports[j], s.ports[i]
|
||||
}
|
||||
|
||||
func (s *portSorter) Less(i, j int) bool {
|
||||
ip := s.ports[i]
|
||||
jp := s.ports[j]
|
||||
|
||||
return s.by(ip, jp)
|
||||
}
|
||||
|
||||
// Sort sorts a list of ports using the provided predicate
|
||||
// This function should compare `i` and `j`, returning true if `i` is
|
||||
// considered to be less than `j`
|
||||
func Sort(ports []Port, predicate func(i, j Port) bool) {
|
||||
s := &portSorter{ports, predicate}
|
||||
sort.Sort(s)
|
||||
}
|
||||
|
||||
type portMapEntry struct {
|
||||
port Port
|
||||
binding PortBinding
|
||||
}
|
||||
|
||||
type portMapSorter []portMapEntry
|
||||
|
||||
func (s portMapSorter) Len() int { return len(s) }
|
||||
func (s portMapSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// Less sorts the port so that the order is:
|
||||
// 1. port with larger specified bindings
|
||||
// 2. larger port
|
||||
// 3. port with tcp protocol
|
||||
func (s portMapSorter) Less(i, j int) bool {
|
||||
pi, pj := s[i].port, s[j].port
|
||||
hpi, hpj := toInt(s[i].binding.HostPort), toInt(s[j].binding.HostPort)
|
||||
return hpi > hpj || pi.Int() > pj.Int() || (pi.Int() == pj.Int() && strings.ToLower(pi.Proto()) == "tcp")
|
||||
}
|
||||
|
||||
// SortPortMap sorts the list of ports and their respected mapping. The ports
|
||||
// will explicit HostPort will be placed first.
|
||||
func SortPortMap(ports []Port, bindings PortMap) {
|
||||
s := portMapSorter{}
|
||||
for _, p := range ports {
|
||||
if binding, ok := bindings[p]; ok && len(binding) > 0 {
|
||||
for _, b := range binding {
|
||||
s = append(s, portMapEntry{port: p, binding: b})
|
||||
}
|
||||
bindings[p] = []PortBinding{}
|
||||
} else {
|
||||
s = append(s, portMapEntry{port: p})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(s)
|
||||
var (
|
||||
i int
|
||||
pm = make(map[Port]struct{})
|
||||
)
|
||||
// reorder ports
|
||||
for _, entry := range s {
|
||||
if _, ok := pm[entry.port]; !ok {
|
||||
ports[i] = entry.port
|
||||
pm[entry.port] = struct{}{}
|
||||
i++
|
||||
}
|
||||
// reorder bindings for this port
|
||||
if _, ok := bindings[entry.port]; ok {
|
||||
bindings[entry.port] = append(bindings[entry.port], entry.binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toInt(s string) uint64 {
|
||||
i, _, err := ParsePortRange(s)
|
||||
if err != nil {
|
||||
i = 0
|
||||
}
|
||||
return i
|
||||
}
|
||||
24
vendor/github.com/moby/moby/api/types/container/nat_aliases.go
generated
vendored
24
vendor/github.com/moby/moby/api/types/container/nat_aliases.go
generated
vendored
@@ -1,24 +0,0 @@
|
||||
package container
|
||||
|
||||
import "github.com/docker/go-connections/nat"
|
||||
|
||||
// PortRangeProto is a string containing port number and protocol in the format "80/tcp",
|
||||
// or a port range and protocol in the format "80-83/tcp".
|
||||
//
|
||||
// It is currently an alias for [nat.Port] but may become a concrete type in a future release.
|
||||
type PortRangeProto = nat.Port
|
||||
|
||||
// PortSet is a collection of structs indexed by [HostPort].
|
||||
//
|
||||
// It is currently an alias for [nat.PortSet] but may become a concrete type in a future release.
|
||||
type PortSet = nat.PortSet
|
||||
|
||||
// PortBinding represents a binding between a Host IP address and a [HostPort].
|
||||
//
|
||||
// It is currently an alias for [nat.PortBinding] but may become a concrete type in a future release.
|
||||
type PortBinding = nat.PortBinding
|
||||
|
||||
// PortMap is a collection of [PortBinding] indexed by [HostPort].
|
||||
//
|
||||
// It is currently an alias for [nat.PortMap] but may become a concrete type in a future release.
|
||||
type PortMap = nat.PortMap
|
||||
345
vendor/github.com/moby/moby/api/types/container/network.go
generated
vendored
Normal file
345
vendor/github.com/moby/moby/api/types/container/network.go
generated
vendored
Normal file
@@ -0,0 +1,345 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"iter"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unique"
|
||||
)
|
||||
|
||||
// NetworkProtocol represents a network protocol for a port.
|
||||
type NetworkProtocol string
|
||||
|
||||
const (
|
||||
TCP NetworkProtocol = "tcp"
|
||||
UDP NetworkProtocol = "udp"
|
||||
SCTP NetworkProtocol = "sctp"
|
||||
)
|
||||
|
||||
// Sentinel port proto value for zero Port and PortRange values.
|
||||
var protoZero unique.Handle[NetworkProtocol]
|
||||
|
||||
// Port is a type representing a single port number and protocol in the format "<portnum>/[<proto>]".
|
||||
//
|
||||
// The zero port value, i.e. Port{}, is invalid; use [ParsePort] to create a valid Port value.
|
||||
type Port struct {
|
||||
num uint16
|
||||
proto unique.Handle[NetworkProtocol]
|
||||
}
|
||||
|
||||
// ParsePort parses s as a [Port].
|
||||
//
|
||||
// It normalizes the provided protocol such that "80/tcp", "80/TCP", and "80/tCp" are equivalent.
|
||||
// If a port number is provided, but no protocol, the default ("tcp") protocol is returned.
|
||||
func ParsePort(s string) (Port, error) {
|
||||
if s == "" {
|
||||
return Port{}, errors.New("invalid port: value is empty")
|
||||
}
|
||||
|
||||
port, proto, _ := strings.Cut(s, "/")
|
||||
|
||||
portNum, err := parsePortNumber(port)
|
||||
if err != nil {
|
||||
return Port{}, fmt.Errorf("invalid port '%s': %w", port, err)
|
||||
}
|
||||
|
||||
normalizedPortProto := normalizePortProto(proto)
|
||||
return Port{num: portNum, proto: normalizedPortProto}, nil
|
||||
}
|
||||
|
||||
// MustParsePort calls [ParsePort](s) and panics on error.
|
||||
//
|
||||
// It is intended for use in tests with hard-coded strings.
|
||||
func MustParsePort(s string) Port {
|
||||
p, err := ParsePort(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// PortFrom returns a [Port] with the given number and protocol.
|
||||
//
|
||||
// If no protocol is specified (i.e. proto == ""), then PortFrom returns Port{}, false.
|
||||
func PortFrom(num uint16, proto NetworkProtocol) (p Port, ok bool) {
|
||||
if proto == "" {
|
||||
return Port{}, false
|
||||
}
|
||||
normalized := normalizePortProto(string(proto))
|
||||
return Port{num: num, proto: normalized}, true
|
||||
}
|
||||
|
||||
// Num returns p's port number.
|
||||
func (p Port) Num() uint16 {
|
||||
return p.num
|
||||
}
|
||||
|
||||
// Proto returns p's network protocol.
|
||||
func (p Port) Proto() NetworkProtocol {
|
||||
return p.proto.Value()
|
||||
}
|
||||
|
||||
// IsZero reports whether p is the zero value.
|
||||
func (p Port) IsZero() bool {
|
||||
return p.proto == protoZero
|
||||
}
|
||||
|
||||
// IsValid reports whether p is an initialized valid port (not the zero value).
|
||||
func (p Port) IsValid() bool {
|
||||
return p.proto != protoZero
|
||||
}
|
||||
|
||||
// String returns a string representation of the port in the format "<portnum>/<proto>".
|
||||
// If the port is the zero value, it returns "invalid port".
|
||||
func (p Port) String() string {
|
||||
switch p.proto {
|
||||
case protoZero:
|
||||
return "invalid port"
|
||||
default:
|
||||
return string(p.AppendTo(nil))
|
||||
}
|
||||
}
|
||||
|
||||
// AppendText implements [encoding.TextAppender] interface.
|
||||
// It is the same as [Port.AppendTo] but returns an error to satisfy the interface.
|
||||
func (p Port) AppendText(b []byte) ([]byte, error) {
|
||||
return p.AppendTo(b), nil
|
||||
}
|
||||
|
||||
// AppendTo appends a text encoding of p to b and returns the extended buffer.
|
||||
func (p Port) AppendTo(b []byte) []byte {
|
||||
if p.IsZero() {
|
||||
return b
|
||||
}
|
||||
return fmt.Appendf(b, "%d/%s", p.num, p.proto.Value())
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler] interface.
|
||||
func (p Port) MarshalText() ([]byte, error) {
|
||||
return p.AppendText(nil)
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler] interface.
|
||||
func (p *Port) UnmarshalText(text []byte) error {
|
||||
if len(text) == 0 {
|
||||
*p = Port{}
|
||||
return nil
|
||||
}
|
||||
|
||||
port, err := ParsePort(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*p = port
|
||||
return nil
|
||||
}
|
||||
|
||||
// Range returns a [PortRange] representing the single port.
|
||||
func (p Port) Range() PortRange {
|
||||
return PortRange{start: p.num, end: p.num, proto: p.proto}
|
||||
}
|
||||
|
||||
// PortSet is a collection of structs indexed by [Port].
|
||||
type PortSet = map[Port]struct{}
|
||||
|
||||
// PortBinding represents a binding between a Host IP address and a Host Port.
|
||||
type PortBinding struct {
|
||||
// HostIP is the host IP Address
|
||||
HostIP string `json:"HostIp"`
|
||||
// HostPort is the host port number
|
||||
HostPort string `json:"HostPort"`
|
||||
}
|
||||
|
||||
// PortMap is a collection of [PortBinding] indexed by [Port].
|
||||
type PortMap = map[Port][]PortBinding
|
||||
|
||||
// PortRange represents a range of port numbers and a protocol in the format "8000-9000/tcp".
|
||||
//
|
||||
// The zero port range value, i.e. PortRange{}, is invalid; use [ParsePortRange] to create a valid PortRange value.
|
||||
type PortRange struct {
|
||||
start uint16
|
||||
end uint16
|
||||
proto unique.Handle[NetworkProtocol]
|
||||
}
|
||||
|
||||
// ParsePortRange parses s as a [PortRange].
|
||||
//
|
||||
// It normalizes the provided protocol such that "80-90/tcp", "80-90/TCP", and "80-90/tCp" are equivalent.
|
||||
// If a port number range is provided, but no protocol, the default ("tcp") protocol is returned.
|
||||
func ParsePortRange(s string) (PortRange, error) {
|
||||
if s == "" {
|
||||
return PortRange{}, errors.New("invalid port range: value is empty")
|
||||
}
|
||||
|
||||
portRange, proto, _ := strings.Cut(s, "/")
|
||||
|
||||
start, end, ok := strings.Cut(portRange, "-")
|
||||
startVal, err := parsePortNumber(start)
|
||||
if err != nil {
|
||||
return PortRange{}, fmt.Errorf("invalid start port '%s': %w", start, err)
|
||||
}
|
||||
|
||||
portProto := normalizePortProto(proto)
|
||||
|
||||
if !ok || start == end {
|
||||
return PortRange{start: startVal, end: startVal, proto: portProto}, nil
|
||||
}
|
||||
|
||||
endVal, err := parsePortNumber(end)
|
||||
if err != nil {
|
||||
return PortRange{}, fmt.Errorf("invalid end port '%s': %w", end, err)
|
||||
}
|
||||
if endVal < startVal {
|
||||
return PortRange{}, errors.New("invalid port range: " + s)
|
||||
}
|
||||
return PortRange{start: startVal, end: endVal, proto: portProto}, nil
|
||||
}
|
||||
|
||||
// MustParsePortRange calls [ParsePortRange](s) and panics on error.
|
||||
// It is intended for use in tests with hard-coded strings.
|
||||
func MustParsePortRange(s string) PortRange {
|
||||
pr, err := ParsePortRange(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return pr
|
||||
}
|
||||
|
||||
// PortRangeFrom returns a [PortRange] with the given start and end port numbers and protocol.
|
||||
//
|
||||
// If end < start or no protocol is specified (i.e. proto == ""), then PortRangeFrom returns PortRange{}, false.
|
||||
func PortRangeFrom(start, end uint16, proto NetworkProtocol) (pr PortRange, ok bool) {
|
||||
if end < start || proto == "" {
|
||||
return PortRange{}, false
|
||||
}
|
||||
normalized := normalizePortProto(string(proto))
|
||||
return PortRange{start: start, end: end, proto: normalized}, true
|
||||
}
|
||||
|
||||
// Start returns pr's start port number.
|
||||
func (pr PortRange) Start() uint16 {
|
||||
return pr.start
|
||||
}
|
||||
|
||||
// End returns pr's end port number.
|
||||
func (pr PortRange) End() uint16 {
|
||||
return pr.end
|
||||
}
|
||||
|
||||
// Proto returns pr's network protocol.
|
||||
func (pr PortRange) Proto() NetworkProtocol {
|
||||
return pr.proto.Value()
|
||||
}
|
||||
|
||||
// IsZero reports whether pr is the zero value.
|
||||
func (pr PortRange) IsZero() bool {
|
||||
return pr.proto == protoZero
|
||||
}
|
||||
|
||||
// IsValid reports whether pr is an initialized valid port range (not the zero value).
|
||||
func (pr PortRange) IsValid() bool {
|
||||
return pr.proto != protoZero
|
||||
}
|
||||
|
||||
// String returns a string representation of the port range in the format "<start>-<end>/<proto>" or "<portnum>/<proto>" if start == end.
|
||||
// If the port range is the zero value, it returns "invalid port range".
|
||||
func (pr PortRange) String() string {
|
||||
switch pr.proto {
|
||||
case protoZero:
|
||||
return "invalid port range"
|
||||
default:
|
||||
return string(pr.AppendTo(nil))
|
||||
}
|
||||
}
|
||||
|
||||
// AppendText implements [encoding.TextAppender] interface.
|
||||
// It is the same as [PortRange.AppendTo] but returns an error to satisfy the interface.
|
||||
func (pr PortRange) AppendText(b []byte) ([]byte, error) {
|
||||
return pr.AppendTo(b), nil
|
||||
}
|
||||
|
||||
// AppendTo appends a text encoding of pr to b and returns the extended buffer.
|
||||
func (pr PortRange) AppendTo(b []byte) []byte {
|
||||
if pr.IsZero() {
|
||||
return b
|
||||
}
|
||||
if pr.start == pr.end {
|
||||
return fmt.Appendf(b, "%d/%s", pr.start, pr.proto.Value())
|
||||
}
|
||||
return fmt.Appendf(b, "%d-%d/%s", pr.start, pr.end, pr.proto.Value())
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler] interface.
|
||||
func (pr PortRange) MarshalText() ([]byte, error) {
|
||||
return pr.AppendText(nil)
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler] interface.
|
||||
func (pr *PortRange) UnmarshalText(text []byte) error {
|
||||
if len(text) == 0 {
|
||||
*pr = PortRange{}
|
||||
return nil
|
||||
}
|
||||
|
||||
portRange, err := ParsePortRange(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*pr = portRange
|
||||
return nil
|
||||
}
|
||||
|
||||
// Range returns pr.
|
||||
func (pr PortRange) Range() PortRange {
|
||||
return pr
|
||||
}
|
||||
|
||||
// All returns an iterator over all the individual ports in the range.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// for port := range pr.All() {
|
||||
// // ...
|
||||
// }
|
||||
func (pr PortRange) All() iter.Seq[Port] {
|
||||
return func(yield func(Port) bool) {
|
||||
for i := uint32(pr.Start()); i <= uint32(pr.End()); i++ {
|
||||
if !yield(Port{num: uint16(i), proto: pr.proto}) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parsePortNumber parses rawPort into an int, unwrapping strconv errors
|
||||
// and returning a single "out of range" error for any value outside 0–65535.
|
||||
func parsePortNumber(rawPort string) (uint16, error) {
|
||||
if rawPort == "" {
|
||||
return 0, errors.New("value is empty")
|
||||
}
|
||||
port, err := strconv.ParseUint(rawPort, 10, 16)
|
||||
if err != nil {
|
||||
var numErr *strconv.NumError
|
||||
if errors.As(err, &numErr) {
|
||||
err = numErr.Err
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint16(port), nil
|
||||
}
|
||||
|
||||
// normalizePortProto normalizes the protocol string such that "tcp", "TCP", and "tCp" are equivalent.
|
||||
// If proto is not specified, it defaults to "tcp".
|
||||
func normalizePortProto(proto string) unique.Handle[NetworkProtocol] {
|
||||
if proto == "" {
|
||||
return unique.Make(TCP)
|
||||
}
|
||||
|
||||
proto = strings.ToLower(proto)
|
||||
|
||||
return unique.Make(NetworkProtocol(proto))
|
||||
}
|
||||
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
@@ -506,7 +506,6 @@ github.com/docker/distribution/registry/storage/cache
|
||||
github.com/docker/distribution/registry/storage/cache/memory
|
||||
# github.com/docker/go-connections v0.6.0
|
||||
## explicit; go 1.18
|
||||
github.com/docker/go-connections/nat
|
||||
github.com/docker/go-connections/sockets
|
||||
github.com/docker/go-connections/tlsconfig
|
||||
# github.com/docker/go-events v0.0.0-20250808211157-605354379745
|
||||
|
||||
Reference in New Issue
Block a user