diff options
Diffstat (limited to 'internal/net')
| -rw-r--r-- | internal/net/remote_host_test.go | 34 | ||||
| -rw-r--r-- | internal/net/server.go | 8 |
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 |
