diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0e1fb6bf..14cb27ab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: strategy: fail-fast: false matrix: - go: ["1.18.x", "oldstable", "stable"] + go: ["oldstable", "stable"] platform: [ubuntu-24.04] runs-on: ${{ matrix.platform }} steps: @@ -48,7 +48,7 @@ jobs: strategy: fail-fast: false matrix: - go: ["1.18.x", "oldstable", "stable"] + go: ["oldstable", "stable"] platform: [windows-latest, macos-latest] runs-on: ${{ matrix.platform }} steps: diff --git a/go.mod b/go.mod index 37f870a0..7a1e247c 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,10 @@ module github.com/docker/go-connections -go 1.18 +go 1.23.0 -require github.com/Microsoft/go-winio v0.4.21 +require ( + github.com/Microsoft/go-winio v0.4.21 + github.com/moby/moby/api v1.52.0-beta.1.0.20251007000938-19e498ea6522 +) require golang.org/x/sys v0.1.0 // indirect diff --git a/go.sum b/go.sum index 2aa99bc3..a434678f 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,10 @@ github.com/Microsoft/go-winio v0.4.21 h1:+6mVbXh4wPzUrl1COX9A+ZCvEpYsOBZ6/+kwDnvLyro= github.com/Microsoft/go-winio v0.4.21/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/moby/moby/api v1.52.0-beta.1.0.20251007000938-19e498ea6522 h1:bLoNdDBj+gro5RVsCOg5rQQ5MZQrrtxUuYMllhCtkaU= +github.com/moby/moby/api v1.52.0-beta.1.0.20251007000938-19e498ea6522/go.mod h1:/ou52HkRydg4+odrUR3vFsGgjIyHvprrpEQEkweL10s= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -9,3 +13,5 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= diff --git a/nat/nat.go b/nat/nat.go index 9a366fbf..f5d31658 100644 --- a/nat/nat.go +++ b/nat/nat.go @@ -5,37 +5,42 @@ import ( "errors" "fmt" "net" + "net/netip" "strconv" "strings" + + "github.com/moby/moby/api/types/network" ) // 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 -} +// +// Deprecated: Use [network.PortBinding] instead. +type PortBinding = network.PortBinding // PortMap is a collection of PortBinding indexed by Port -type PortMap map[Port][]PortBinding +// +// Deprecated: Use [network.PortMap] instead. +type PortMap = network.PortMap // PortSet is a collection of structs indexed by Port -type PortSet map[Port]struct{} +// +// Deprecated: Use [network.PortSet] instead. +type PortSet = network.PortSet // Port is a string containing port number and protocol in the format "80/tcp" -type Port string +// +// Deprecated: Use [network.Port] or [network.PortRange] accordingly instead. +type Port struct { + network.PortRange +} // NewPort creates a new instance of a Port given a protocol and port number or port range func NewPort(proto, portOrRange string) (Port, error) { - start, end, err := parsePortRange(portOrRange) + pr, err := network.ParsePortRange(portOrRange + "/" + proto) if err != nil { - return "", err + return Port{}, err } - if start == end { - return Port(fmt.Sprintf("%d/%s", start, proto)), nil - } - return Port(fmt.Sprintf("%d-%d/%s", start, end, proto)), nil + return Port{pr}, nil } // ParsePort parses the port number string and returns an int @@ -59,37 +64,20 @@ func ParsePortRangeToInt(rawPort string) (startPort, endPort int, _ error) { return parsePortRange(rawPort) } -// Proto returns the protocol of a Port -func (p Port) Proto() string { - _, proto, _ := strings.Cut(string(p), "/") - if proto == "" { - proto = "tcp" - } - return proto -} - // Port returns the port number of a Port func (p Port) Port() string { - port, _, _ := strings.Cut(string(p), "/") - return port + return fmt.Sprintf("%d", p.Start()) } // Int returns the port number of a Port as an int. It assumes [Port] // is valid, and returns 0 otherwise. func (p Port) Int() int { - // 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, _ := parsePortNumber(p.Port()) - return port + return int(p.Start()) } // Range returns the start/end port numbers of a Port range as ints func (p Port) Range() (int, int, error) { - portRange := p.Port() - if portRange == "" { - return 0, 0, nil - } - return parsePortRange(portRange) + return int(p.Start()), int(p.End()), nil } // SplitProtoPort splits a port(range) and protocol, formatted as "/[]" @@ -144,13 +132,21 @@ func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, } // PortMapping is a data object mapping a Port to a PortBinding +// +// Deprecated: Use [network.PortMap] instead. type PortMapping struct { Port Port Binding PortBinding } func (p *PortMapping) String() string { - return net.JoinHostPort(p.Binding.HostIP, p.Binding.HostPort+":"+string(p.Port)) + var host string + + if p.Binding.HostIP != netip.IPv4Unspecified() { + host = p.Binding.HostIP.String() + } + + return net.JoinHostPort(host, p.Binding.HostPort+":"+p.Port.String()) } func splitParts(rawport string) (hostIP, hostPort, containerPort string) { @@ -228,9 +224,28 @@ func ParsePortSpec(rawPort string) ([]PortMapping, error) { hPort += "-" + strconv.Itoa(endHostPort) } } + port, err := network.ParsePortRange(fmt.Sprintf("%d/%s", startPort+i, proto)) + if err != nil { + return nil, err + } + + var addr netip.Addr + if strings.Count(ip, ":") >= 2 { + addr = netip.IPv6Unspecified() + } else { + addr = netip.IPv4Unspecified() + } + + if ip != "" { + addr, err = netip.ParseAddr(ip) + if err != nil { + return nil, err + } + } + ports = append(ports, PortMapping{ - Port: Port(strconv.Itoa(startPort+i) + "/" + proto), - Binding: PortBinding{HostIP: ip, HostPort: hPort}, + Port: Port{port}, + Binding: PortBinding{HostIP: addr, HostPort: hPort}, }) } return ports, nil diff --git a/nat/nat_test.go b/nat/nat_test.go index ae8b4877..c0e2ac78 100644 --- a/nat/nat_test.go +++ b/nat/nat_test.go @@ -1,10 +1,19 @@ package nat import ( + "net/netip" "reflect" "testing" + + "github.com/moby/moby/api/types/network" ) +// MustParsePort is like ParsePort but panics if the port cannot be parsed. +func MustParsePort(port string) Port { + pr := network.MustParsePortRange(port) + return Port{pr} +} + func TestParsePort(t *testing.T) { tests := []struct { doc string @@ -107,7 +116,7 @@ func TestPort(t *testing.T) { t.Fatalf("tcp, 1234 had a parsing issue: %v", err) } - if string(p) != "1234/tcp" { + if p.String() != "1234/tcp" { t.Fatal("tcp, 1234 did not result in the string 1234/tcp") } @@ -138,7 +147,7 @@ func TestPort(t *testing.T) { t.Fatalf("tcp, 1234-1242 had a parsing issue: %v", err) } - if string(p) != "1234-1242/tcp" { + if p.String() != "1234-1242/tcp" { t.Fatal("tcp, 1234-1242 did not result in the string 1234-1242/tcp") } } @@ -281,16 +290,16 @@ func TestParsePortSpecFull(t *testing.T) { expected := []PortMapping{ { - Port: "3333/tcp", + Port: MustParsePort("3333/tcp"), Binding: PortBinding{ - HostIP: "0.0.0.0", + HostIP: netip.MustParseAddr("0.0.0.0"), HostPort: "1234", }, }, { - Port: "3334/tcp", + Port: MustParsePort("3334/tcp"), Binding: PortBinding{ - HostIP: "0.0.0.0", + HostIP: netip.MustParseAddr("0.0.0.0"), HostPort: "1235", }, }, @@ -313,9 +322,9 @@ func TestPartPortSpecIPV6(t *testing.T) { spec: "[2001:4860:0:2001::68]::333", expected: []PortMapping{ { - Port: "333/tcp", + Port: MustParsePort("333/tcp"), Binding: PortBinding{ - HostIP: "2001:4860:0:2001::68", + HostIP: netip.MustParseAddr("2001:4860:0:2001::68"), HostPort: "", }, }, @@ -326,9 +335,9 @@ func TestPartPortSpecIPV6(t *testing.T) { spec: "[::1]:80:80", expected: []PortMapping{ { - Port: "80/tcp", + Port: MustParsePort("80/tcp"), Binding: PortBinding{ - HostIP: "::1", + HostIP: netip.MustParseAddr("::1"), HostPort: "80", }, }, @@ -339,9 +348,9 @@ func TestPartPortSpecIPV6(t *testing.T) { spec: "2001:4860:0:2001::68::333", expected: []PortMapping{ { - Port: "333/tcp", + Port: MustParsePort("333/tcp"), Binding: PortBinding{ - HostIP: "2001:4860:0:2001::68", + HostIP: netip.MustParseAddr("2001:4860:0:2001::68"), HostPort: "", }, }, @@ -352,9 +361,9 @@ func TestPartPortSpecIPV6(t *testing.T) { spec: "::1:80:80", expected: []PortMapping{ { - Port: "80/tcp", + Port: MustParsePort("80/tcp"), Binding: PortBinding{ - HostIP: "::1", + HostIP: netip.MustParseAddr("::1"), HostPort: "80", }, }, @@ -365,9 +374,9 @@ func TestPartPortSpecIPV6(t *testing.T) { spec: "::::80", expected: []PortMapping{ { - Port: "80/tcp", + Port: MustParsePort("80/tcp"), Binding: PortBinding{ - HostIP: "::", + HostIP: netip.MustParseAddr("::"), HostPort: "", }, }, @@ -399,15 +408,15 @@ func TestParsePortSpecs(t *testing.T) { t.Fatalf("Error while processing ParsePortSpecs: %s", err) } - if _, ok := portMap["1234/tcp"]; !ok { + if _, ok := portMap[MustParsePort("1234/tcp")]; !ok { t.Fatal("1234/tcp was not parsed properly") } - if _, ok := portMap["2345/udp"]; !ok { + if _, ok := portMap[MustParsePort("2345/udp")]; !ok { t.Fatal("2345/udp was not parsed properly") } - if _, ok := portMap["3456/sctp"]; !ok { + if _, ok := portMap[MustParsePort("3456/sctp")]; !ok { t.Fatal("3456/sctp was not parsed properly") } @@ -416,8 +425,8 @@ func TestParsePortSpecs(t *testing.T) { 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].HostIP.IsUnspecified() { + t.Fatalf("HostIP should not be set for %s, got %s", portSpec, bindings[0].HostIP.String()) } if bindings[0].HostPort != "" { @@ -430,27 +439,27 @@ func TestParsePortSpecs(t *testing.T) { t.Fatalf("Error while processing ParsePortSpecs: %s", err) } - if _, ok := portMap["1234/tcp"]; !ok { + if _, ok := portMap[MustParsePort("1234/tcp")]; !ok { t.Fatal("1234/tcp was not parsed properly") } - if _, ok := portMap["2345/udp"]; !ok { + if _, ok := portMap[MustParsePort("2345/udp")]; !ok { t.Fatal("2345/udp was not parsed properly") } - if _, ok := portMap["3456/sctp"]; !ok { + if _, ok := portMap[MustParsePort("3456/sctp")]; !ok { t.Fatal("3456/sctp was not parsed properly") } for portSpec, bindings := range bindingMap { - _, port := SplitProtoPort(string(portSpec)) + _, 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].HostIP.IsUnspecified() { + t.Fatalf("HostIP should not be set for %s, got %s", portSpec, bindings[0].HostIP.String()) } if bindings[0].HostPort != port { @@ -463,26 +472,26 @@ func TestParsePortSpecs(t *testing.T) { t.Fatalf("Error while processing ParsePortSpecs: %s", err) } - if _, ok := portMap["1234/tcp"]; !ok { + if _, ok := portMap[MustParsePort("1234/tcp")]; !ok { t.Fatal("1234/tcp was not parsed properly") } - if _, ok := portMap["2345/udp"]; !ok { + if _, ok := portMap[MustParsePort("2345/udp")]; !ok { t.Fatal("2345/udp was not parsed properly") } - if _, ok := portMap["3456/sctp"]; !ok { + if _, ok := portMap[MustParsePort("3456/sctp")]; !ok { t.Fatal("3456/sctp was not parsed properly") } for portSpec, bindings := range bindingMap { - _, port := SplitProtoPort(string(portSpec)) + _, 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" { + if bindings[0].HostIP.String() != "0.0.0.0" { t.Fatalf("HostIP is not 0.0.0.0 for %s", portSpec) } @@ -510,15 +519,15 @@ func TestParsePortSpecsWithRange(t *testing.T) { t.Fatalf("Error while processing ParsePortSpecs: %s", err) } - if _, ok := portMap["1235/tcp"]; !ok { + if _, ok := portMap[MustParsePort("1235/tcp")]; !ok { t.Fatal("1234/tcp was not parsed properly") } - if _, ok := portMap["2346/udp"]; !ok { + if _, ok := portMap[MustParsePort("2346/udp")]; !ok { t.Fatal("2345/udp was not parsed properly") } - if _, ok := portMap["3456/sctp"]; !ok { + if _, ok := portMap[MustParsePort("3456/sctp")]; !ok { t.Fatal("3456/sctp was not parsed properly") } @@ -527,7 +536,7 @@ func TestParsePortSpecsWithRange(t *testing.T) { t.Fatalf("%s should have exactly one binding", portSpec) } - if bindings[0].HostIP != "" { + if !bindings[0].HostIP.IsUnspecified() { t.Fatalf("HostIP should not be set for %s", portSpec) } @@ -541,26 +550,26 @@ func TestParsePortSpecsWithRange(t *testing.T) { t.Fatalf("Error while processing ParsePortSpecs: %s", err) } - if _, ok := portMap["1235/tcp"]; !ok { + if _, ok := portMap[MustParsePort("1235/tcp")]; !ok { t.Fatal("1234/tcp was not parsed properly") } - if _, ok := portMap["2346/udp"]; !ok { + if _, ok := portMap[MustParsePort("2346/udp")]; !ok { t.Fatal("2345/udp was not parsed properly") } - if _, ok := portMap["3456/sctp"]; !ok { + if _, ok := portMap[MustParsePort("3456/sctp")]; !ok { t.Fatal("3456/sctp was not parsed properly") } for portSpec, bindings := range bindingMap { - _, port := SplitProtoPort(string(portSpec)) + _, 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].HostIP.IsUnspecified() { + t.Fatalf("HostIP should not be set for %s, got %s", portSpec, bindings[0].HostIP.String()) } if bindings[0].HostPort != port { @@ -573,21 +582,21 @@ func TestParsePortSpecsWithRange(t *testing.T) { t.Fatalf("Error while processing ParsePortSpecs: %s", err) } - if _, ok := portMap["1235/tcp"]; !ok { + if _, ok := portMap[MustParsePort("1235/tcp")]; !ok { t.Fatal("1234/tcp was not parsed properly") } - if _, ok := portMap["2346/udp"]; !ok { + if _, ok := portMap[MustParsePort("2346/udp")]; !ok { t.Fatal("2345/udp was not parsed properly") } - if _, ok := portMap["3456/sctp"]; !ok { + if _, ok := portMap[MustParsePort("3456/sctp")]; !ok { t.Fatal("3456/sctp was not parsed properly") } for portSpec, bindings := range bindingMap { - _, port := SplitProtoPort(string(portSpec)) - if len(bindings) != 1 || bindings[0].HostIP != "0.0.0.0" || bindings[0].HostPort != port { + _, port := SplitProtoPort(portSpec.String()) + if len(bindings) != 1 || bindings[0].HostIP.String() != "0.0.0.0" || bindings[0].HostPort != port { t.Fatalf("Expect single binding to port %s but found %s", port, bindings) } } @@ -635,7 +644,7 @@ func TestParseNetworkOptsPrivateOnly(t *testing.T) { t.Logf("Expected \"\" got %s", s.HostPort) t.Fail() } - if s.HostIP != "192.168.1.100" { + if s.HostIP.String() != "192.168.1.100" { t.Fail() } } @@ -677,7 +686,7 @@ func TestParseNetworkOptsPublic(t *testing.T) { t.Logf("Expected 8080 got %s", s.HostPort) t.Fail() } - if s.HostIP != "192.168.1.100" { + if s.HostIP.String() != "192.168.1.100" { t.Fail() } } @@ -752,7 +761,7 @@ func TestParseNetworkOptsUdp(t *testing.T) { t.Logf("Expected \"\" got %s", s.HostPort) t.Fail() } - if s.HostIP != "192.168.1.100" { + if s.HostIP.String() != "192.168.1.100" { t.Fail() } } @@ -794,7 +803,7 @@ func TestParseNetworkOptsSctp(t *testing.T) { t.Logf("Expected \"\" got %s", s.HostPort) t.Fail() } - if s.HostIP != "192.168.1.100" { + if s.HostIP.String() != "192.168.1.100" { t.Fail() } } diff --git a/nat/sort.go b/nat/sort.go index 96880304..45ba991f 100644 --- a/nat/sort.go +++ b/nat/sort.go @@ -72,14 +72,14 @@ func SortPortMap(ports []Port, bindings map[Port][]PortBinding) { b := b // capture loop variable for go < 1.22 s = append(s, portMapEntry{ port: p, binding: &b, - portInt: portInt, portProto: portProto, + portInt: portInt, portProto: string(portProto), }) } bindings[p] = []PortBinding{} } else { s = append(s, portMapEntry{ port: p, - portInt: portInt, portProto: portProto, + portInt: portInt, portProto: string(portProto), }) } } diff --git a/nat/sort_test.go b/nat/sort_test.go index 4af89487..b7672b92 100644 --- a/nat/sort_test.go +++ b/nat/sort_test.go @@ -2,14 +2,15 @@ package nat import ( "fmt" + "net/netip" "reflect" "testing" ) func TestSortUniquePorts(t *testing.T) { ports := []Port{ - "6379/tcp", - "22/tcp", + MustParsePort("6379/tcp"), + MustParsePort("22/tcp"), } Sort(ports, func(ip, jp Port) bool { @@ -17,7 +18,7 @@ func TestSortUniquePorts(t *testing.T) { }) first := ports[0] - if string(first) != "22/tcp" { + if first.String() != "22/tcp" { t.Log(first) t.Fail() } @@ -25,10 +26,10 @@ func TestSortUniquePorts(t *testing.T) { func TestSortSamePortWithDifferentProto(t *testing.T) { ports := []Port{ - "8888/tcp", - "8888/udp", - "6379/tcp", - "6379/udp", + MustParsePort("8888/tcp"), + MustParsePort("8888/udp"), + MustParsePort("6379/tcp"), + MustParsePort("6379/udp"), } Sort(ports, func(ip, jp Port) bool { @@ -36,42 +37,42 @@ func TestSortSamePortWithDifferentProto(t *testing.T) { }) first := ports[0] - if string(first) != "6379/tcp" { + if first.String() != "6379/tcp" { t.Fail() } } func TestSortPortMap(t *testing.T) { ports := []Port{ - "22/tcp", - "22/udp", - "8000/tcp", - "8443/tcp", - "6379/tcp", - "9999/tcp", + MustParsePort("22/tcp"), + MustParsePort("22/udp"), + MustParsePort("8000/tcp"), + MustParsePort("8443/tcp"), + MustParsePort("6379/tcp"), + MustParsePort("9999/tcp"), } portMap := map[Port][]PortBinding{ - "22/tcp": {{}}, - "8000/tcp": {{}}, - "8443/tcp": {}, - "6379/tcp": {{}, {HostIP: "0.0.0.0", HostPort: "32749"}}, - "9999/tcp": {{HostIP: "0.0.0.0", HostPort: "40000"}}, + MustParsePort("22/tcp"): {{}}, + MustParsePort("8000/tcp"): {{}}, + MustParsePort("8443/tcp"): {}, + MustParsePort("6379/tcp"): {{}, {HostIP: netip.MustParseAddr("0.0.0.0"), HostPort: "32749"}}, + MustParsePort("9999/tcp"): {{HostIP: netip.MustParseAddr("0.0.0.0"), HostPort: "40000"}}, } SortPortMap(ports, portMap) if !reflect.DeepEqual(ports, []Port{ - "9999/tcp", - "6379/tcp", - "8443/tcp", - "8000/tcp", - "22/tcp", - "22/udp", + MustParsePort("9999/tcp"), + MustParsePort("6379/tcp"), + MustParsePort("8443/tcp"), + MustParsePort("8000/tcp"), + MustParsePort("22/tcp"), + MustParsePort("22/udp"), }) { t.Errorf("failed to prioritize port with explicit mappings, got %v", ports) } - if pm := portMap["6379/tcp"]; !reflect.DeepEqual(pm, []PortBinding{ - {HostIP: "0.0.0.0", HostPort: "32749"}, + if pm := portMap[MustParsePort("6379/tcp")]; !reflect.DeepEqual(pm, []PortBinding{ + {HostIP: netip.MustParseAddr("0.0.0.0"), HostPort: "32749"}, {}, }) { t.Errorf("failed to prioritize bindings with explicit mappings, got %v", pm) @@ -85,18 +86,18 @@ func BenchmarkSortPortMap(b *testing.B) { for i := 0; i < n; i++ { portNum := 30000 + (i % 50) // force duplicate port numbers - tcp := Port(fmt.Sprintf("%d/tcp", portNum)) - udp := Port(fmt.Sprintf("%d/udp", portNum)) + tcp := MustParsePort(fmt.Sprintf("%d/tcp", portNum)) + udp := MustParsePort(fmt.Sprintf("%d/udp", portNum)) ports = append(ports, tcp, udp) portMap[tcp] = []PortBinding{ - {HostIP: "127.0.0.2", HostPort: fmt.Sprint(40000 + i)}, - {HostIP: "127.0.0.1", HostPort: fmt.Sprint(40000 + i)}, + {HostIP: netip.MustParseAddr("127.0.0.2"), HostPort: fmt.Sprint(40000 + i)}, + {HostIP: netip.MustParseAddr("127.0.0.1"), HostPort: fmt.Sprint(40000 + i)}, } portMap[udp] = []PortBinding{ - {HostIP: "127.0.0.2", HostPort: fmt.Sprint(40000 + i)}, - {HostIP: "127.0.0.1", HostPort: fmt.Sprint(40000 + i)}, + {HostIP: netip.MustParseAddr("127.0.0.2"), HostPort: fmt.Sprint(40000 + i)}, + {HostIP: netip.MustParseAddr("127.0.0.1"), HostPort: fmt.Sprint(40000 + i)}, } }