Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion internal/pkg/netutils/sockopt.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func SetReuseAddrSockopt(sc syscall.RawConn) error {
return SetReuseAddrSockoptImpl(sc)
}

func DialerControl(logger *slog.Logger, network, address string, c syscall.RawConn, ttl, minTtl uint8, mss uint16, password string, bindInterface string, tos uint8) error {
func DialerControl(logger *slog.Logger, network, address string, c syscall.RawConn, ttl, minTtl uint8, mss uint16, password string, bindInterface string, tos uint8, tcpAO *TCPAOConfig) error {
if password != "" {
logger.Warn("setting md5 for active connection is not supported",
slog.String("Topic", "Peer"),
Expand Down Expand Up @@ -87,5 +87,8 @@ func DialerControl(logger *slog.Logger, network, address string, c syscall.RawCo
slog.String("Key", address),
)
}
if tcpAO != nil {
return ErrTCPAONotSupported
}
return nil
}
13 changes: 12 additions & 1 deletion internal/pkg/netutils/sockopt_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func SetReuseAddrSockopt(sc syscall.RawConn) error {
return setSockOptInt(sc, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
}

func DialerControl(logger *slog.Logger, network, address string, c syscall.RawConn, ttl, minTtl uint8, mss uint16, password string, bindInterface string, tos uint8) error {
func DialerControl(logger *slog.Logger, network, address string, c syscall.RawConn, ttl, minTtl uint8, mss uint16, password string, bindInterface string, tos uint8, tcpAO *TCPAOConfig) error {
family := syscall.AF_INET
raddr, _ := net.ResolveTCPAddr("tcp", address)
if raddr.IP.To4() == nil {
Expand Down Expand Up @@ -280,5 +280,16 @@ func DialerControl(logger *slog.Logger, network, address string, c syscall.RawCo
return sockerr
}
}

if tcpAO != nil {
addrPort, err := netip.ParseAddrPort(address)
if err != nil {
return fmt.Errorf("invalid TCP-AO peer address %q: %w", address, err)
}
addr := addrPort.Addr().Unmap()
if err := AddTCPAOKeysSockopt(c, netip.PrefixFrom(addr, addr.BitLen()), bindInterface, *tcpAO); err != nil {
return fmt.Errorf("failed to configure TCP-AO for peer %s: %w", addr, err)
}
}
return nil
}
5 changes: 4 additions & 1 deletion internal/pkg/netutils/sockopt_openbsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ func SetReuseAddrSockopt(sc syscall.RawConn) error {
return setSockOptInt(sc, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
}

func DialerControl(logger *slog.Logger, network, address string, c syscall.RawConn, ttl, minTtl uint8, mss uint16, password string, bindInterface string, tos uint8) error {
func DialerControl(logger *slog.Logger, network, address string, c syscall.RawConn, ttl, minTtl uint8, mss uint16, password string, bindInterface string, tos uint8, tcpAO *TCPAOConfig) error {
family := syscall.AF_INET
raddr, _ := net.ResolveTCPAddr("tcp", address)
if raddr.IP.To4() == nil {
Expand Down Expand Up @@ -472,5 +472,8 @@ func DialerControl(logger *slog.Logger, network, address string, c syscall.RawCo
return sockerr
}
}
if tcpAO != nil {
return ErrTCPAONotSupported
}
return nil
}
5 changes: 4 additions & 1 deletion internal/pkg/netutils/sockopt_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func SetReuseAddrSockopt(sc syscall.RawConn) error {
return setSockOptInt(sc, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
}

func DialerControl(logger *slog.Logger, network, address string, c syscall.RawConn, ttl, ttlMin uint8, mss uint16, password string, bindInterface string, tos uint8) error {
func DialerControl(logger *slog.Logger, network, address string, c syscall.RawConn, ttl, ttlMin uint8, mss uint16, password string, bindInterface string, tos uint8, tcpAO *TCPAOConfig) error {
if password != "" {
logger.Warn("setting md5 for active connection is not supported",
slog.String("Topic", "Peer"),
Expand All @@ -120,5 +120,8 @@ func DialerControl(logger *slog.Logger, network, address string, c syscall.RawCo
slog.String("Topic", "Peer"),
slog.String("Key", address))
}
if tcpAO != nil {
return ErrTCPAONotSupported
}
return nil
}
6 changes: 3 additions & 3 deletions internal/pkg/netutils/tcp_ao_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package netutils
import (
"context"
"errors"
"log/slog"
"net"
"net/netip"
"syscall"
Expand Down Expand Up @@ -173,9 +174,8 @@ func TestTCPAOKeySelection(t *testing.T) {
PreferredSendID: &current,
}
dialer := net.Dialer{Timeout: time.Second}
dialer.SetMultipathTCP(false)
dialer.Control = func(_, _ string, raw syscall.RawConn) error {
return AddTCPAOKeysSockopt(raw, peer, "", clientConfig)
dialer.Control = func(network, address string, raw syscall.RawConn) error {
return DialerControl(slog.Default(), network, address, raw, 0, 0, 0, "", "", 0, &clientConfig)
}
clientConn, err := dialer.DialContext(context.Background(), "tcp4", listener.Addr().String())
require.NoError(t, err)
Expand Down
3 changes: 2 additions & 1 deletion pkg/config/oc/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,8 @@ func (n *Neighbor) NeedsResendOpenMessage(new *Neighbor) bool {
!n.GracefulRestart.Config.Equal(&new.GracefulRestart.Config) ||
isAfiSafiChanged(n.AfiSafis, new.AfiSafis) ||
!n.EbgpMultihop.Config.Equal(&new.EbgpMultihop.Config) ||
!n.TtlSecurity.Config.Equal(&new.TtlSecurity.Config)
!n.TtlSecurity.Config.Equal(&new.TtlSecurity.Config) ||
n.TcpAo.Config.Keychain != new.TcpAo.Config.Keychain
}

// TODO: these regexp are duplicated in api
Expand Down
45 changes: 40 additions & 5 deletions pkg/server/fsm.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,20 @@ func (p *pConfAccess) Update(conf *oc.Neighbor) {
p.conf.Store(conf)
}

// tcpAoKeyBinding is a peer's resolved TCP-AO configuration:
// a reference to a shared keychain plus the peer's preferred send ID.
//
// Keychain's key additions and deletions mutate the shared keychain under its lock,
// socketKeys() can be used to take live key snapshots for socket operations.
type tcpAoKeyBinding struct {
keychain *tcpAoKeychain
preferredSendID uint8
}

func (b *tcpAoKeyBinding) socketKeys() (*tcpAoSocketKeys, error) {
return b.keychain.socketKeys(b.preferredSendID)
}

type fsm struct {
counterStats oc.Messages
timerStats oc.Timers
Expand Down Expand Up @@ -450,6 +464,16 @@ type fsm struct {
adminStateCh chan adminStateOperation
outgoingConnCh chan outgoingConn

// tcpAoKeyBinding holds resolved TCP-AO configuration for the peer:
// - Passive peers have all configured keys installed on matching listening sockets;
// preferred key is not set on the listener until an inbound connection is accepted and matched to the peer.
// - Active peers install all keys in net.Dialer.Control in connectLoop() and mark the preferred key during installation.
// - Async keychain update causes update of keys directly on the listening sockets and fsm conn sockets of the affected peers.
// This is handled on best effort; keychain update is not rolled back upon individual socket operation errors.
// - Attaching, removing, or changing a keychain of a peer always uses peer delete-and-add path.
// A preferred key change within the same keychain is handled live by updating RNext on the current connection.
tcpAoKeyBinding atomic.Pointer[tcpAoKeyBinding]

// only loop goroutine accesses; no lock required
outgoingConnMgr *outgoingConnManager
idleHoldTime float64
Expand Down Expand Up @@ -911,7 +935,7 @@ func (h *fsmHandler) idle(ctx context.Context) (bgp.FSMState, *fsmStateReason) {
func (h *fsmHandler) connectLoop(ctx context.Context) net.Conn {
fsm := h.fsm

retryInterval, addr, port, password, ttl, ttlMin, mss, localAddress, localPort, bindInterface, tos := func() (int, string, int, string, uint8, uint8, uint16, string, int, string, uint8) {
retryInterval, addr, port, password, ttl, ttlMin, mss, localAddress, localPort, bindInterface, tos := func() (int, netip.Addr, int, string, uint8, uint8, uint16, string, int, string, uint8) {
conf := fsm.pConf.ReadOnly()
tick := max(int(conf.Timers.Config.ConnectRetry), minConnectRetryInterval)

Expand All @@ -937,7 +961,7 @@ func (h *fsmHandler) connectLoop(ctx context.Context) net.Conn {
ttl = conf.EbgpMultihop.Config.MultihopTtl
}
}
return tick, addr.String(), port, password, ttl, ttlMin, conf.Transport.Config.TcpMss, conf.Transport.Config.LocalAddress.String(), int(conf.Transport.Config.LocalPort), conf.Transport.Config.BindInterface, tos
return tick, addr, port, password, ttl, ttlMin, conf.Transport.Config.TcpMss, conf.Transport.Config.LocalAddress.String(), int(conf.Transport.Config.LocalPort), conf.Transport.Config.BindInterface, tos
}()

tick := minConnectRetryInterval
Expand All @@ -963,11 +987,22 @@ func (h *fsmHandler) connectLoop(ctx context.Context) net.Conn {
Timeout: time.Duration(max(retryInterval-1, minConnectRetryInterval)) * time.Second,
KeepAlive: -1,
Control: func(network, address string, c syscall.RawConn) error {
return netutils.DialerControl(fsm.logger, network, address, c, ttl, ttlMin, mss, password, bindInterface, tos)
var tcpAO *netutils.TCPAOConfig
if keyBinding := fsm.tcpAoKeyBinding.Load(); keyBinding != nil {
tcpAoKeys, err := keyBinding.socketKeys()
if err != nil {
return fmt.Errorf("failed to load TCP-AO keychain for peer %s: %w", addr, err)
}
config, err := tcpAoKeys.netutilsConfig(true)
if err != nil {
return fmt.Errorf("failed to configure TCP-AO for peer %s: %w", addr, err)
}
tcpAO = &config
}
return netutils.DialerControl(fsm.logger, network, address, c, ttl, ttlMin, mss, password, bindInterface, tos, tcpAO)
},
}

conn, err := d.DialContext(ctx, "tcp", net.JoinHostPort(addr, strconv.Itoa(port)))
conn, err := d.DialContext(ctx, "tcp", net.JoinHostPort(addr.String(), strconv.Itoa(port)))
select {
case <-ctx.Done():
fsm.logger.Debug("stop connect loop")
Expand Down
2 changes: 1 addition & 1 deletion pkg/server/fsm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ func TestFsmPeerConfigAccess(t *testing.T) {
},
}

peer := newPeer(nil, &a, bgp.BGP_FSM_ESTABLISHED, nil, nil, slog.Default())
peer := newPeer(nil, &a, bgp.BGP_FSM_ESTABLISHED, nil, nil, nil, slog.Default())
b := peer.fsm.pConf.ReadCopy()

assert.True(t, a.Equal(&b))
Expand Down
11 changes: 9 additions & 2 deletions pkg/server/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ func (pg *peerGroup) DeleteDynamicNeighbor(prefix string) {
}

func newDynamicPeer(g *oc.Global, neighborAddress string, pg *oc.PeerGroup, loc *table.TableManager, policy *table.RoutingPolicy, logger *slog.Logger) *peer {
if pg.TcpAo.Config.Keychain != "" {
logger.Debug("TCP-AO dynamic neighbors are not supported",
slog.String("Topic", "Peer"),
slog.String("Key", neighborAddress))
return nil
}
conf := oc.Neighbor{
Config: oc.NeighborConfig{
PeerGroup: pg.Config.PeerGroupName,
Expand Down Expand Up @@ -93,7 +99,7 @@ func newDynamicPeer(g *oc.Global, neighborAddress string, pg *oc.PeerGroup, loc
return nil

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If oc.PeerGroup has AO configurations, newDynamicPeer should fail?

}

return newPeer(g, &conf, bgp.BGP_FSM_ACTIVE, loc, policy, logger)
return newPeer(g, &conf, bgp.BGP_FSM_ACTIVE, loc, policy, nil, logger)
}

// pathIDSet is the set of add-path local identifiers advertised for a destination.
Expand Down Expand Up @@ -125,7 +131,7 @@ type peer struct {
routeRefreshInProgress sync.RWMutex
}

func newPeer(g *oc.Global, conf *oc.Neighbor, state bgp.FSMState, loc *table.TableManager, policy *table.RoutingPolicy, logger *slog.Logger) *peer {
func newPeer(g *oc.Global, conf *oc.Neighbor, state bgp.FSMState, loc *table.TableManager, policy *table.RoutingPolicy, tcpAo *tcpAoKeyBinding, logger *slog.Logger) *peer {
peer := &peer{
localRib: loc,
policy: policy,
Expand All @@ -140,6 +146,7 @@ func newPeer(g *oc.Global, conf *oc.Neighbor, state bgp.FSMState, loc *table.Tab
rfs, _ := oc.AfiSafis(conf.AfiSafis).ToRfList()
peer.adjRibIn = table.NewAdjRib(logger, rfs)
peer.rtmHandler = table.NewRouteTargetMembershipHandler()
peer.fsm.tcpAoKeyBinding.Store(tcpAo)
return peer
}

Expand Down
Loading