Skip to content

Commit 6dcee4d

Browse files
speruriclaude
andcommitted
Fix PROXY protocol v2 cache lookup by client IP instead of connection addresses
Store SSL state keyed by upstream client IP from PROXY header, not by the full connection address pair. This fixes cache misses when there's a mismatch between actual TCP connection addresses (IPv6 on dualstack listener) and the original client IP from the PROXY protocol header. Changes: - Cache now stores by client host IP as primary key - Fallback lookup by full address pair for actual connections - Extract client host from remoteAddr in GetProxyProtoSSL - Add logging to identify cache hits/misses Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6641166 commit 6dcee4d

1 file changed

Lines changed: 45 additions & 8 deletions

File tree

proxylistener/proxylistener.go

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,17 @@ import (
1313
)
1414

1515
// proxySSLCache is a global, thread-safe cache of SSL information from PROXY protocol v2 TLVs.
16-
// Keys are strings in the format "remoteAddr|localAddr", values are bools indicating SSL/TLS presence.
16+
// Keys are strings in the format "sourceIP:port" (upstream/original client), values are bools indicating SSL/TLS presence.
1717
var proxySSLCache sync.Map
1818

19+
// Extract host from address string (handles both "host:port" and "[::1]:port" formats)
20+
func extractHost(addr string) string {
21+
if host, _, err := net.SplitHostPort(addr); err == nil {
22+
return host
23+
}
24+
return addr
25+
}
26+
1927
const (
2028
defaultReadHeaderTimeout = time.Second // 10s seems too long https://github.com/pires/go-proxyproto/blob/5c8010d2392f09ce18169631c024aceae758335a/protocol.go#L28
2129
defaultReadBufferSize = 256 // https://github.com/pires/go-proxyproto/blob/5c8010d2392f09ce18169631c024aceae758335a/protocol.go#L21
@@ -119,8 +127,18 @@ func (tl *tlvExtractorListener) Accept() (net.Conn, error) {
119127
log.Printf("[tlvExtractorListener] TLV[%d]: Type=%d, Len=%d", i, tlv.Type, len(tlv.Value))
120128
}
121129
ssl := hasTLVSSL(tlvs)
130+
131+
// Get the upstream client address from PROXY protocol header
132+
// This is the original client IP that we want to preserve
133+
if header.SourceAddr != nil {
134+
upstreamClientKey := header.SourceAddr.String()
135+
log.Printf("[tlvExtractorListener] Storing SSL=%v for upstream client %s", ssl, upstreamClientKey)
136+
proxySSLCache.Store(upstreamClientKey, ssl)
137+
}
138+
139+
// Also store using actual connection addresses as fallback
122140
key := conn.RemoteAddr().String() + "|" + conn.LocalAddr().String()
123-
log.Printf("[tlvExtractorListener] Storing SSL=%v for key=%s", ssl, key)
141+
log.Printf("[tlvExtractorListener] Storing SSL=%v for actual conn key=%s (fallback)", ssl, key)
124142
proxySSLCache.Store(key, ssl)
125143
}
126144
} else {
@@ -155,6 +173,9 @@ func (c *tlvCacheCleanupConn) Write(b []byte) (int, error) {
155173
}
156174

157175
func (c *tlvCacheCleanupConn) Close() error {
176+
// Clean up both cache keys if they were stored
177+
// Note: We don't clean up by host-only key because multiple connections
178+
// from the same client may be active simultaneously
158179
key := c.conn.RemoteAddr().String() + "|" + c.conn.LocalAddr().String()
159180
proxySSLCache.Delete(key)
160181
return c.conn.Close()
@@ -193,15 +214,31 @@ func hasTLVSSL(tlvs []proxyproto.TLV) bool {
193214
return false
194215
}
195216

196-
// GetProxyProtoSSL retrieves the SSL/TLS state for a given connection
217+
// GetProxyProtoSSL retrieves the SSL/TLS state for a given upstream client address
197218
// Returns (ssl, ok) where ssl is true if the connection had SSL TLV data
198219
// and ok is true if the lookup was successful
199220
func GetProxyProtoSSL(remoteAddr, localAddr string) (bool, bool) {
221+
// First try to look up by upstream client IP from X-Forwarded-For or PROXY protocol
222+
// Extract just the host part (without port)
223+
clientHost := extractHost(remoteAddr)
224+
if val, ok := proxySSLCache.Load(clientHost); ok {
225+
ssl, ok := val.(bool)
226+
if ok {
227+
log.Printf("[GetProxyProtoSSL] Found SSL=%v for client %s (by host)", ssl, clientHost)
228+
return ssl, ok
229+
}
230+
}
231+
232+
// Fallback: try the full address pair (for actual connection matches)
200233
key := remoteAddr + "|" + localAddr
201-
val, ok := proxySSLCache.Load(key)
202-
if !ok {
203-
return false, false
234+
if val, ok := proxySSLCache.Load(key); ok {
235+
ssl, ok := val.(bool)
236+
if ok {
237+
log.Printf("[GetProxyProtoSSL] Found SSL=%v for key %s (fallback)", ssl, key)
238+
return ssl, ok
239+
}
204240
}
205-
ssl, ok := val.(bool)
206-
return ssl, ok
241+
242+
log.Printf("[GetProxyProtoSSL] NOT found: client=%s remoteAddr=%s localAddr=%s", clientHost, remoteAddr, localAddr)
243+
return false, false
207244
}

0 commit comments

Comments
 (0)