Jelajahi Sumber

Add IPv6 support to StripIPAddresses

Rod Hynes 5 tahun lalu
induk
melakukan
9e5ea16255
2 mengubah file dengan 138 tambahan dan 10 penghapusan
  1. 35 10
      psiphon/utils.go
  2. 103 0
      psiphon/utils_test.go

+ 35 - 10
psiphon/utils.go

@@ -118,25 +118,50 @@ func IsAddressInUseError(err error) bool {
 	return false
 	return false
 }
 }
 
 
-var stripIPv4AddressRegex = regexp.MustCompile(
-	`(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}(:(6553[0-5]|655[0-2][0-9]\d|65[0-4](\d){2}|6[0-4](\d){3}|[1-5](\d){4}|[1-9](\d){0,3}))?`)
-
-// StripIPAddresses returns a copy of the input with all IP addresses [and
-// optional ports] replaced  by "[address]". This is intended to be used to
+var stripIPAddressAndPortRegex = regexp.MustCompile(
+	// IP address
+	`(` +
+		// IPv4
+		`\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|` +
+
+		// IPv6
+		//
+		// Optional brackets for IPv6 with port
+		`\[?` +
+		`(` +
+		// Uncompressed IPv6; ensure there are 8 segments to avoid matching, e.g., a
+		// timestamp
+		`(([a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4})|` +
+		// Compressed IPv6
+		`([a-fA-F0-9:]*::[a-fA-F0-9:]+)|([a-fA-F0-9:]+::[a-fA-F0-9:]*)` +
+		`)` +
+		// Optional mapped/translated/embeded IPv4 suffix
+		`(.\d{1,3}\.\d{1,3}\.\d{1,3})?` +
+		`\]?` +
+		`)` +
+
+		// Optional port number
+		`(:\d+)?`)
+
+// StripIPAddresses returns a copy of the input with all IP addresses (and
+// optional ports) replaced by "[redacted]". This is intended to be used to
 // strip addresses from "net" package I/O error messages and otherwise avoid
 // strip addresses from "net" package I/O error messages and otherwise avoid
 // inadvertently recording direct server IPs via error message logs; and, in
 // inadvertently recording direct server IPs via error message logs; and, in
 // metrics, to reduce the error space due to superfluous source port data.
 // metrics, to reduce the error space due to superfluous source port data.
 //
 //
-// Limitation: only strips IPv4 addresses.
+// StripIPAddresses uses a simple regex match which liberally matches IP
+// address-like patterns and will match invalid addresses; for example, it
+// will match port numbers greater than 65535. We err on the side of redaction
+// and are not as concerned, in this context, with false positive matches. If
+// a user configures an upstream proxy address with an invalid IP or port
+// value, we prefer to redact it.
 func StripIPAddresses(b []byte) []byte {
 func StripIPAddresses(b []byte) []byte {
-	// TODO: IPv6 support
-	return stripIPv4AddressRegex.ReplaceAll(b, []byte("[redacted]"))
+	return stripIPAddressAndPortRegex.ReplaceAll(b, []byte("[redacted]"))
 }
 }
 
 
 // StripIPAddressesString is StripIPAddresses for strings.
 // StripIPAddressesString is StripIPAddresses for strings.
 func StripIPAddressesString(s string) string {
 func StripIPAddressesString(s string) string {
-	// TODO: IPv6 support
-	return stripIPv4AddressRegex.ReplaceAllString(s, "[redacted]")
+	return stripIPAddressAndPortRegex.ReplaceAllString(s, "[redacted]")
 }
 }
 
 
 // RedactNetError removes network address information from a "net" package
 // RedactNetError removes network address information from a "net" package

+ 103 - 0
psiphon/utils_test.go

@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2020, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package psiphon
+
+import (
+	"testing"
+)
+
+func TestStripIPAddresses(t *testing.T) {
+
+	testCases := []struct {
+		description    string
+		input          string
+		expectedOutput string
+	}{
+		{
+			"IPv4 address",
+			"prefix 192.168.0.1 suffix",
+			"prefix [redacted] suffix",
+		},
+		{
+			"IPv6 address",
+			"prefix 2001:0db8:0000:0000:0000:ff00:0042:8329 suffix",
+			"prefix [redacted] suffix",
+		},
+		{
+			"Remove leading zeros IPv6 address",
+			"prefix 2001:db8:0:0:0:ff00:42:8329 suffix",
+			"prefix [redacted] suffix",
+		},
+		{
+			"Omit consecutive zeros sections IPv6 address",
+			"prefix 2001:db8::ff00:42:8329 suffix",
+			"prefix [redacted] suffix",
+		},
+		{
+			"IPv4 mapped/translated/embedded address",
+			"prefix 0::ffff:192.168.0.1, 0::ffff:0:192.168.0.1, 64:ff9b::192.168.0.1 suffix",
+			"prefix [redacted], [redacted], [redacted] suffix",
+		},
+		{
+			"IPv4 address and port",
+			"read tcp 127.0.0.1:1025->127.0.0.1:8000: use of closed network connection",
+			"read tcp [redacted]->[redacted]: use of closed network connection",
+		},
+		{
+			"IPv6 address and port",
+			"read tcp [2001:db8::ff00:42:8329]:1025->[2001:db8::ff00:42:8329]:8000: use of closed network connection",
+			"read tcp [redacted]->[redacted]: use of closed network connection",
+		},
+		{
+			"Loopback IPv6 address and invalid port number",
+			"dial tcp [::1]:88888: network is unreachable",
+			"dial tcp [redacted]: network is unreachable",
+		},
+		{
+			"Numbers and periods",
+			"prefix 192. 168. 0. 1 suffix",
+			"prefix 192. 168. 0. 1 suffix",
+		},
+		{
+			"Hex string and colon",
+			"prefix 0123456789abcdef: suffix",
+			"prefix 0123456789abcdef: suffix",
+		},
+		{
+			"Colons",
+			"prefix :: suffix",
+			"prefix :: suffix",
+		},
+		{
+			"Notice",
+			`{"data":{"SSHClientVersion":"SSH-2.0-C","candidateNumber":0,"diagnosticID":"se0XVQ/4","dialPortNumber":"4000","establishedTunnelsCount":0,"isReplay":false,"networkLatencyMultiplier":2.8284780852763953,"networkType":"WIFI","protocol":"OSSH","region":"US","upstream_ossh_padding":7077},"noticeType":"ConnectedServer","timestamp":"2020-12-16T14:07:02.030Z"}`,
+			`{"data":{"SSHClientVersion":"SSH-2.0-C","candidateNumber":0,"diagnosticID":"se0XVQ/4","dialPortNumber":"4000","establishedTunnelsCount":0,"isReplay":false,"networkLatencyMultiplier":2.8284780852763953,"networkType":"WIFI","protocol":"OSSH","region":"US","upstream_ossh_padding":7077},"noticeType":"ConnectedServer","timestamp":"2020-12-16T14:07:02.030Z"}`,
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.description, func(t *testing.T) {
+			output := StripIPAddressesString(testCase.input)
+			if output != testCase.expectedOutput {
+				t.Errorf("unexpected output: %s", output)
+			}
+		})
+	}
+}