aboutsummaryrefslogtreecommitdiff
path: root/internal/net
diff options
context:
space:
mode:
Diffstat (limited to 'internal/net')
-rw-r--r--internal/net/remote_host_test.go34
-rw-r--r--internal/net/server.go8
2 files changed, 42 insertions, 0 deletions
diff --git a/internal/net/remote_host_test.go b/internal/net/remote_host_test.go
new file mode 100644
index 0000000..690e758
--- /dev/null
+++ b/internal/net/remote_host_test.go
@@ -0,0 +1,34 @@
+package net
+
+import (
+ "testing"
+ "time"
+)
+
+// remoteAddrConn is a minimal Conn that also reports a peer address, so the
+// Session.RemoteHost port-stripping contract can be tested without a socket.
+type remoteAddrConn struct{ addr string }
+
+func (c remoteAddrConn) ReadMessage() (string, error) { return "", nil }
+func (c remoteAddrConn) Write(b []byte) (int, error) { return len(b), nil }
+func (c remoteAddrConn) Close() error { return nil }
+func (c remoteAddrConn) SetWriteDeadline(time.Time) error { return nil }
+func (c remoteAddrConn) SetEcho(bool) error { return nil }
+func (c remoteAddrConn) RemoteAddr() string { return c.addr }
+
+// TestSessionRemoteHostStripsPort: login throttling keys on the client host,
+// so RemoteHost must drop the ephemeral source port (RemoteAddr keeps it).
+// Keying on the raw address would let a reconnect reset the limiter.
+func TestSessionRemoteHostStripsPort(t *testing.T) {
+ for _, tc := range []struct{ in, want string }{
+ {"203.0.113.7:54321", "203.0.113.7"},
+ {"[2001:db8::1]:443", "2001:db8::1"},
+ {"203.0.113.7", "203.0.113.7"},
+ {"", ""},
+ } {
+ sess := &Session{Conn: remoteAddrConn{addr: tc.in}}
+ if got := sess.RemoteHost(); got != tc.want {
+ t.Fatalf("RemoteHost(%q) = %q, want %q", tc.in, got, tc.want)
+ }
+ }
+}
diff --git a/internal/net/server.go b/internal/net/server.go
index c818eef..daf3e47 100644
--- a/internal/net/server.go
+++ b/internal/net/server.go
@@ -151,6 +151,14 @@ func (sess *Session) RemoteAddr() string {
return ""
}
+// RemoteHost returns the client's IP/host without the ephemeral source port.
+// Rate-limit keys must use this: RemoteAddr includes a fresh port on every
+// reconnect, so keying on it lets a reconnecting attacker reset the limiter.
+// Synthetic/test connections without a transport address return "".
+func (sess *Session) RemoteHost() string {
+ return remoteHost(sess.RemoteAddr())
+}
+
// sessionSeq is the source of Session.seq values. It is incremented under the
// adding Hub's mutex, so no extra atomicity is needed for correctness; atomic
// access keeps the package-level default counter safe if Hub.Add is ever