vppn/peer/connstate.go
2024-12-12 21:11:17 +01:00

394 lines
8.8 KiB
Go

package peer
import (
"log"
"net/netip"
"time"
"vppn/m"
)
func logState(s connState, msg string, args ...any) {
log.Printf("["+s.Name()+"] "+msg, args...)
}
// ----------------------------------------------------------------------------
// The connection state corresponds to what we're connected TO.
type connState interface {
Name() string
HandleMediatorUpdate(ip byte) connState
HandleSendPing()
HandlePing(wrapper[Ping]) connState
HandlePong(wrapper[Pong]) connState
HandleTimeout() connState
}
// Helper function.
func newConnStateFromPeer(update peerUpdate, data *connData) connState {
peer := update.Peer
if peer == nil {
return newConnNull(data)
}
if _, isPublic := netip.AddrFromSlice(peer.PublicIP); isPublic {
return newConnUnconnectedServer(data, peer)
} else if data.server {
return newConnUnconnectedClient(data, peer)
} else {
return newConnUnconnectedMediator(data, peer)
}
}
/////////////////////
// Null Connection //
/////////////////////
type connNull struct {
*connData
}
func newConnNull(data *connData) connState {
c := connNull{data}
c.peer = nil
c.encSharedKey = nil
c.publicAddr = netip.AddrPort{}
c.pingTimer.Stop()
c.timeoutTimer.Stop()
c.addr = c.publicAddr
c.viaIP = 0
c.up = false
c.route.Store(nil)
return c
}
func (c connNull) Name() string {
return "NoPeer"
}
func (c connNull) HandleMediatorUpdate(ip byte) connState {
c.mediatorIP = ip
return c
}
func (c connNull) HandlePing(w wrapper[Ping]) connState {
logState(c, "Ignoring ping.")
return c
}
func (c connNull) HandlePong(w wrapper[Pong]) connState {
logState(c, "Ignoring pong.")
return c
}
func (c connNull) HandleTimeout() connState {
logState(c, "Unexpected timeout.")
return c
}
////////////////////////
// Unconnected Server //
////////////////////////
type connUnconnectedServer struct {
*connData
}
func newConnUnconnectedServer(data *connData, peer *m.Peer) connState {
addr, _ := netip.AddrFromSlice(peer.PublicIP)
pubAddr := netip.AddrPortFrom(addr, peer.Port)
c := connUnconnectedServer{data}
c.peer = peer
c.encSharedKey = computeSharedKey(peer.EncPubKey, c.encPrivKey)
c.publicAddr = pubAddr
c.pingTimer.Reset(time.Millisecond) // Ping right away to bring up.
c.timeoutTimer.Stop() // No timeouts yet.
c.addr = c.publicAddr
c.viaIP = 0
c.up = false
c.route.Store(c.Route())
return c
}
func (c connUnconnectedServer) Name() string {
return "ServerUnconnected"
}
func (c connUnconnectedServer) HandleMediatorUpdate(ip byte) connState {
// Server connection doesn't use a mediator.
c.mediatorIP = ip
return c
}
func (c connUnconnectedServer) HandlePing(w wrapper[Ping]) connState {
logState(c, "Ignoring ping.")
return c
}
func (c connUnconnectedServer) HandlePong(w wrapper[Pong]) connState {
return newConnConnectedServer(c.connData, w)
}
func (c connUnconnectedServer) HandleTimeout() connState {
logState(c, "Unexpected timeout.")
return c
}
//////////////////////
// Connected Server //
//////////////////////
type connConnectedServer struct {
*connData
}
func newConnConnectedServer(data *connData, w wrapper[Pong]) connState {
c := connConnectedServer{data}
c.pingTimer.Reset(pingInterval)
c.timeoutTimer.Reset(timeoutInterval)
c.addr = w.SrcAddr
c.viaIP = 0
c.up = true
c.route.Store(c.Route())
return c
}
func (c connConnectedServer) Name() string {
return "ServerConnected"
}
func (c connConnectedServer) HandleMediatorUpdate(ip byte) connState {
// Server connection doesn't use a mediator.
c.mediatorIP = ip
return c
}
func (c connConnectedServer) HandlePing(w wrapper[Ping]) connState {
logState(c, "Ignoring ping.")
return c
}
func (c connConnectedServer) HandlePong(w wrapper[Pong]) connState {
c.timeoutTimer.Reset(timeoutInterval)
return c
}
func (c connConnectedServer) HandleTimeout() connState {
return newConnUnconnectedServer(c.connData, c.peer)
}
////////////////////////
// Unconnected Client //
////////////////////////
type connUnconnectedClient struct {
*connData
}
func newConnUnconnectedClient(data *connData, peer *m.Peer) connState {
addr, _ := netip.AddrFromSlice(peer.PublicIP)
pubAddr := netip.AddrPortFrom(addr, peer.Port)
c := connUnconnectedClient{data}
c.peer = peer
c.publicAddr = pubAddr
c.encPrivKey = data.encPrivKey
c.encSharedKey = computeSharedKey(peer.EncPubKey, c.encPrivKey)
c.addr = c.publicAddr
c.viaIP = 0
c.up = false
c.route.Store(c.Route())
c.pingTimer.Stop() // Conncection is from client => pings incoming.
c.timeoutTimer.Stop() // No timeouts yet.
return c
}
func (c connUnconnectedClient) Name() string {
return "ClientUnconnected"
}
func (c connUnconnectedClient) HandleMediatorUpdate(ip byte) connState {
// Client connection doesn't use a mediator.
c.mediatorIP = ip
return c
}
func (c connUnconnectedClient) HandlePing(w wrapper[Ping]) connState {
next := newConnConnectedClient(c.connData, w)
c.sendPong(w) // Have to send after transitionsing so route is ok.
return next
}
func (c connUnconnectedClient) HandlePong(w wrapper[Pong]) connState {
logState(c, "Ignorning pong.")
return c
}
func (c connUnconnectedClient) HandleTimeout() connState {
logState(c, "Unexpected timeout.")
return c
}
//////////////////////
// Connected Client //
//////////////////////
type connConnectedClient struct {
*connData
}
func newConnConnectedClient(data *connData, w wrapper[Ping]) connState {
c := connConnectedClient{data}
c.addr = w.SrcAddr
c.viaIP = 0
c.up = true
c.route.Store(c.Route())
c.pingTimer.Stop() // Conncection is from client => pings incoming.
c.timeoutTimer.Reset(timeoutInterval)
return c
}
func (c connConnectedClient) Name() string {
return "ClientConnected"
}
func (c connConnectedClient) HandleMediatorUpdate(ip byte) connState {
// Client connection doesn't use a mediator.
c.mediatorIP = ip
return c
}
func (c connConnectedClient) HandlePing(w wrapper[Ping]) connState {
// The connection is from a client. If the client's address changes, we
// should follow that change.
if c.addr != w.SrcAddr {
c.addr = w.SrcAddr
c.route.Store(c.Route())
}
c.sendPong(w)
c.timeoutTimer.Reset(timeoutInterval)
return c
}
func (c connConnectedClient) HandlePong(w wrapper[Pong]) connState {
logState(c, "Ignoring pong.")
return c
}
func (c connConnectedClient) HandleTimeout() connState {
return newConnUnconnectedClient(c.connData, c.peer)
}
//////////////////////////
// Unconnected Mediator //
//////////////////////////
type connUnconnectedMediator struct {
*connData
}
func newConnUnconnectedMediator(data *connData, peer *m.Peer) connState {
addr, _ := netip.AddrFromSlice(peer.PublicIP)
pubAddr := netip.AddrPortFrom(addr, peer.Port)
c := connUnconnectedMediator{data}
c.peer = peer
c.publicAddr = pubAddr
c.encPrivKey = data.encPrivKey
c.encSharedKey = computeSharedKey(peer.EncPubKey, c.encPrivKey)
c.addr = c.publicAddr
c.viaIP = 0
c.up = false
c.route.Store(c.Route())
c.pingTimer.Stop() // No pings for mediators.
c.timeoutTimer.Stop() // No timeouts yet.
// If we have a mediator route, we can connect.
if mRoute := c.routes[c.mediatorIP].Load(); mRoute != nil {
return newConnConnectedMediator(data, mRoute)
}
return c
}
func (c connUnconnectedMediator) Name() string {
return "MediatorUnconnected"
}
func (c connUnconnectedMediator) HandleMediatorUpdate(ip byte) connState {
c.mediatorIP = ip
if mRoute := c.routes[c.mediatorIP].Load(); mRoute != nil {
return newConnConnectedMediator(c.connData, mRoute)
}
return c
}
func (c connUnconnectedMediator) HandlePing(w wrapper[Ping]) connState {
logState(c, "Ignorning ping.")
return c
}
func (c connUnconnectedMediator) HandlePong(w wrapper[Pong]) connState {
logState(c, "Ignorning pong.")
return c
}
func (c connUnconnectedMediator) HandleTimeout() connState {
logState(c, "Unexpected timeout.")
return c
}
////////////////////////
// Connected Mediator //
////////////////////////
type connConnectedMediator struct {
*connData
}
func newConnConnectedMediator(data *connData, route *route) connState {
c := connConnectedMediator{data}
c.addr = route.Addr
c.viaIP = route.PeerIP
c.up = true
c.route.Store(c.Route())
// No pings for mediated routes.
c.pingTimer.Stop()
c.timeoutTimer.Stop()
return c
}
func (c connConnectedMediator) Name() string {
return "MediatorConnected"
}
func (c connConnectedMediator) HandleMediatorUpdate(ip byte) connState {
c.mediatorIP = ip
if mRoute := c.routes[c.mediatorIP].Load(); mRoute != nil {
return newConnConnectedMediator(c.connData, mRoute)
}
return newConnUnconnectedMediator(c.connData, c.peer)
}
func (c connConnectedMediator) HandlePing(w wrapper[Ping]) connState {
logState(c, "Ignoring ping.")
return c
}
func (c connConnectedMediator) HandlePong(w wrapper[Pong]) connState {
logState(c, "Ignoring pong.")
return c
}
func (c connConnectedMediator) HandleTimeout() connState {
return newConnUnconnectedMediator(c.connData, c.peer)
}