Skip to content

nat: introduce String method on PortMapping#111

Merged
thaJeztah merged 1 commit intodocker:masterfrom
ndeloof:PortMapping_String
Apr 24, 2025
Merged

nat: introduce String method on PortMapping#111
thaJeztah merged 1 commit intodocker:masterfrom
ndeloof:PortMapping_String

Conversation

@ndeloof
Copy link
Contributor

@ndeloof ndeloof commented Jan 16, 2024

This introduce PortMapping.String() for (some) symmetry with ParsePortSpec

nat/nat.go Outdated
}

func (p *PortMapping) String() string {
return fmt.Sprintf("%s:%s:%s", p.Binding.HostIP, p.Binding.HostPort, p.Port)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use net.JoinHostPort() here, to account for IPv6 addresses; https://pkg.go.dev/net#JoinHostPort

e.g.;

Suggested change
return fmt.Sprintf("%s:%s:%s", p.Binding.HostIP, p.Binding.HostPort, p.Port)
return net.JoinHostPort(p.Binding.HostIP, p.Binding.HostPort+":"+p.Port.Port())

But I need to have a closer look, as there's many possible formats (ports mapped here can also have the protocol (e.g. 80/tcp), which may have to be taken into account here.

And of course we also have PortMap (which is a list of mapped ports) 😞

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I guess HostPort doesn't have the scheme, so maybe that part is not an issue

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know if HostIP defaults to 0.0.0.0 (or :: IPv6)? 🤔

@ndeloof ndeloof force-pushed the PortMapping_String branch from 008e7cc to a314a9d Compare January 22, 2024 11:27
@thaJeztah thaJeztah force-pushed the PortMapping_String branch 2 times, most recently from 7f68fd5 to a2b6795 Compare April 24, 2025 09:45
@thaJeztah
Copy link
Member

I rebased, and pushed some changes;

  • updated the test to use a test-table to test various permutations
  • swapped the fmt.Sprintf to string-concat the values (ISTR some of these may be run in a loop, so probably doesn't hurt to slightly optimize)
diff --git a/nat/nat.go b/nat/nat.go
index 5b53382..6c80825 100644
--- a/nat/nat.go
+++ b/nat/nat.go
@@ -151,7 +151,7 @@ type PortMapping struct {
 }
 
 func (p *PortMapping) String() string {
-	return net.JoinHostPort(p.Binding.HostIP, fmt.Sprintf("%s:%s", p.Binding.HostPort, p.Port))
+	return net.JoinHostPort(p.Binding.HostIP, p.Binding.HostPort+":"+string(p.Port))
 }
 
 func splitParts(rawport string) (string, string, string) {
diff --git a/nat/nat_test.go b/nat/nat_test.go
index 3497643..571bf9e 100644
--- a/nat/nat_test.go
+++ b/nat/nat_test.go
@@ -699,14 +699,50 @@ func TestParseNetworkOptsSctp(t *testing.T) {
 }
 
 func TestStringer(t *testing.T) {
-	spec := "192.168.1.100:8080:6000/udp"
-	mappings, err := ParsePortSpec(spec)
-	if err != nil {
-		t.Fatal(err)
+	tests := []struct {
+		doc      string
+		in       string
+		expected string
+	}{
+		{
+			doc:      "no host mapping",
+			in:       ":8080:6000/tcp",
+			expected: ":8080:6000/tcp",
+		},
+		{
+			doc:      "no proto",
+			in:       "192.168.1.100:8080:6000",
+			expected: "192.168.1.100:8080:6000/tcp",
+		},
+		{
+			doc:      "ipv4 mapping",
+			in:       "192.168.1.100:8080:6000/udp",
+			expected: "192.168.1.100:8080:6000/udp",
+		},
+		{
+			doc:      "ipv6 mapping",
+			in:       "[::1]:8080:6000/udp",
+			expected: "[::1]:8080:6000/udp",
+		},
+		{
+			doc:      "ipv6 legacy mapping",
+			in:       "::1:8080:6000/udp",
+			expected: "[::1]:8080:6000/udp",
+		},
 	}
-	for _, m := range mappings {
-		if m.String() != spec {
-			t.Fail()
-		}
+	for _, tc := range tests {
+		t.Run(tc.doc, func(t *testing.T) {
+			mappings, err := ParsePortSpec(tc.in)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if len(mappings) != 1 {
+				// All tests produce a single mapping
+				t.Fatalf("Expected 1 got %d", len(mappings))
+			}
+			if actual := mappings[0].String(); actual != tc.expected {
+				t.Errorf("Expected %s got %s", tc.expected, actual)
+			}
+		})
 	}
 }
-- 
2.44.0

}
}

func TestStringer(t *testing.T) {
Copy link
Contributor

@robmry robmry Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth adding an in with a missing port, just to check it doesn't come out as a zero or something.

For maximum evil, perhaps ::::80 -> [::]::80/tcp?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK; added some evil permutations 👍

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
@thaJeztah thaJeztah force-pushed the PortMapping_String branch from a2b6795 to d630407 Compare April 24, 2025 10:45
@thaJeztah thaJeztah merged commit 6c377ea into docker:master Apr 24, 2025
13 checks passed
@thaJeztah thaJeztah changed the title introduce String method on PortMapping nat: introduce String method on PortMapping Dec 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants