@ -86,6 +86,7 @@ type Client struct {
addrFamSelAtomic syncs . AtomicValue [ AddressFamilySelector ]
addrFamSelAtomic syncs . AtomicValue [ AddressFamilySelector ]
mu sync . Mutex
mu sync . Mutex
atomicState syncs . AtomicValue [ ConnectedState ] // hold mu to write
started bool // true upon first connect, never transitions to false
started bool // true upon first connect, never transitions to false
preferred bool
preferred bool
canAckPings bool
canAckPings bool
@ -99,6 +100,14 @@ type Client struct {
clock tstime . Clock
clock tstime . Clock
}
}
// ConnectedState describes the state of a derphttp Client.
type ConnectedState struct {
Connected bool
Connecting bool
Closed bool
LocalAddr netip . AddrPort // if Connected
}
func ( c * Client ) String ( ) string {
func ( c * Client ) String ( ) string {
return fmt . Sprintf ( "<derphttp_client.Client %s url=%s>" , c . ServerPublicKey ( ) . ShortString ( ) , c . url )
return fmt . Sprintf ( "<derphttp_client.Client %s url=%s>" , c . ServerPublicKey ( ) . ShortString ( ) , c . url )
}
}
@ -307,6 +316,12 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
if c . client != nil {
if c . client != nil {
return c . client , c . connGen , nil
return c . client , c . connGen , nil
}
}
c . atomicState . Store ( ConnectedState { Connecting : true } )
defer func ( ) {
if err != nil {
c . atomicState . Store ( ConnectedState { Connecting : false } )
}
} ( )
// timeout is the fallback maximum time (if ctx doesn't limit
// timeout is the fallback maximum time (if ctx doesn't limit
// it further) to do all of: DNS + TCP + TLS + HTTP Upgrade +
// it further) to do all of: DNS + TCP + TLS + HTTP Upgrade +
@ -524,6 +539,12 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
c . netConn = tcpConn
c . netConn = tcpConn
c . tlsState = tlsState
c . tlsState = tlsState
c . connGen ++
c . connGen ++
localAddr , _ := c . client . LocalAddr ( )
c . atomicState . Store ( ConnectedState {
Connected : true ,
LocalAddr : localAddr ,
} )
return c . client , c . connGen , nil
return c . client , c . connGen , nil
}
}
@ -906,16 +927,15 @@ func (c *Client) SendPing(data [8]byte) error {
// LocalAddr reports c's local TCP address, without any implicit
// LocalAddr reports c's local TCP address, without any implicit
// connect or reconnect.
// connect or reconnect.
func ( c * Client ) LocalAddr ( ) ( netip . AddrPort , error ) {
func ( c * Client ) LocalAddr ( ) ( netip . AddrPort , error ) {
c . mu . Lock ( )
st := c . atomicState . Load ( )
closed , client := c . closed , c . client
if st . Closed {
c . mu . Unlock ( )
if closed {
return netip . AddrPort { } , ErrClientClosed
return netip . AddrPort { } , ErrClientClosed
}
}
if client == nil {
la := st . LocalAddr
if ! st . Connected && ! la . IsValid ( ) {
return netip . AddrPort { } , errors . New ( "client not connected" )
return netip . AddrPort { } , errors . New ( "client not connected" )
}
}
return client. LocalAddr ( )
return la, nil
}
}
func ( c * Client ) ForwardPacket ( from , to key . NodePublic , b [ ] byte ) error {
func ( c * Client ) ForwardPacket ( from , to key . NodePublic , b [ ] byte ) error {
@ -1049,6 +1069,7 @@ func (c *Client) Close() error {
if c . netConn != nil {
if c . netConn != nil {
c . netConn . Close ( )
c . netConn . Close ( )
}
}
c . atomicState . Store ( ConnectedState { Closed : true } )
return nil
return nil
}
}