From 3ceff6a8b1af1ebb7c614beb0eec389c2b271d27 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 5 Jan 2023 20:18:14 -0500 Subject: [PATCH] make ReloadableListener lock-free Also stop attaching the *tls.Config to the wrapped connection, since this forces it to be retained beyond its natural lifetime. --- irc/client.go | 10 +++++----- irc/listeners.go | 4 ++-- irc/utils/proxy.go | 42 ++++++++++++++++++++++-------------------- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/irc/client.go b/irc/client.go index b98a8501..146abeab 100644 --- a/irc/client.go +++ b/irc/client.go @@ -295,7 +295,7 @@ func (server *Server) RunClient(conn IRCConn) { var banMsg string realIP := utils.AddrToIP(wConn.RemoteAddr()) var proxiedIP net.IP - if wConn.Config.Tor { + if wConn.Tor { // cover up details of the tor proxying infrastructure (not a user privacy concern, // but a hardening measure): proxiedIP = utils.IPv4LoopbackAddress @@ -329,7 +329,7 @@ func (server *Server) RunClient(conn IRCConn) { lastActive: now, channels: make(ChannelSet), ctime: now, - isSTSOnly: wConn.Config.STSOnly, + isSTSOnly: wConn.STSOnly, languages: server.Languages().Default(), loginThrottle: connection_limits.GenericThrottle{ Duration: config.Accounts.LoginThrottling.Duration, @@ -358,8 +358,8 @@ func (server *Server) RunClient(conn IRCConn) { lastActive: now, realIP: realIP, proxiedIP: proxiedIP, - isTor: wConn.Config.Tor, - hideSTS: wConn.Config.Tor || wConn.Config.HideSTS, + isTor: wConn.Tor, + hideSTS: wConn.Tor || wConn.HideSTS, } client.sessions = []*Session{session} @@ -369,7 +369,7 @@ func (server *Server) RunClient(conn IRCConn) { client.SetMode(modes.TLS, true) } - if wConn.Config.TLSConfig != nil { + if wConn.TLS { // error is not useful to us here anyways so we can ignore it session.certfp, session.peerCerts, _ = utils.GetCertFP(wConn.Conn, RegisterTimeout) } diff --git a/irc/listeners.go b/irc/listeners.go index 9e4592d2..6cc91298 100644 --- a/irc/listeners.go +++ b/irc/listeners.go @@ -204,10 +204,10 @@ func confirmProxyData(conn *utils.WrappedConn, remoteAddr, xForwardedFor, xForwa } } - if conn.Config.TLSConfig != nil || conn.Config.Tor { + if conn.TLS || conn.Tor { // we terminated our own encryption: conn.Secure = true - } else if !conn.Config.WebSocket { + } else if !conn.WebSocket { // plaintext normal connection: loopback and secureNets are secure realIP := utils.AddrToIP(conn.RemoteAddr()) conn.Secure = realIP.IsLoopback() || utils.IPInNets(realIP, config.Server.secureNets) diff --git a/irc/utils/proxy.go b/irc/utils/proxy.go index 1cef6122..d31a4ff8 100644 --- a/irc/utils/proxy.go +++ b/irc/utils/proxy.go @@ -9,7 +9,7 @@ import ( "io" "net" "strings" - "sync" + "sync/atomic" "time" ) @@ -209,7 +209,11 @@ func parseProxyLineV2(line []byte) (ip net.IP, err error) { type WrappedConn struct { net.Conn ProxiedIP net.IP - Config ListenerConfig + TLS bool + Tor bool + STSOnly bool + WebSocket bool + HideSTS bool // Secure indicates whether we believe the connection between us and the client // was secure against interception and modification (including all proxies): Secure bool @@ -218,35 +222,30 @@ type WrappedConn struct { // ReloadableListener is a wrapper for net.Listener that allows reloading // of config data for postprocessing connections (TLS, PROXY protocol, etc.) type ReloadableListener struct { - // TODO: make this lock-free - sync.Mutex realListener net.Listener - config ListenerConfig - isClosed bool + // nil means the listener is closed: + config atomic.Pointer[ListenerConfig] } func NewReloadableListener(realListener net.Listener, config ListenerConfig) *ReloadableListener { - return &ReloadableListener{ + result := &ReloadableListener{ realListener: realListener, - config: config, } + result.config.Store(&config) // heap escape + return result } func (rl *ReloadableListener) Reload(config ListenerConfig) { - rl.Lock() - rl.config = config - rl.Unlock() + rl.config.Store(&config) } func (rl *ReloadableListener) Accept() (conn net.Conn, err error) { conn, err = rl.realListener.Accept() - rl.Lock() - config := rl.config - isClosed := rl.isClosed - rl.Unlock() + config := rl.config.Load() - if isClosed { + if config == nil { + // Close() was called if err == nil { conn.Close() } @@ -279,14 +278,17 @@ func (rl *ReloadableListener) Accept() (conn net.Conn, err error) { return &WrappedConn{ Conn: conn, ProxiedIP: proxiedIP, - Config: config, + TLS: config.TLSConfig != nil, + Tor: config.Tor, + STSOnly: config.STSOnly, + WebSocket: config.WebSocket, + HideSTS: config.HideSTS, + // Secure will be set later by client code }, nil } func (rl *ReloadableListener) Close() error { - rl.Lock() - rl.isClosed = true - rl.Unlock() + rl.config.Store(nil) return rl.realListener.Close() }