Files
akvorado/outlet/routing/provider/bmp/serve.go
2025-08-17 15:57:46 +02:00

208 lines
6.6 KiB
Go

// SPDX-FileCopyrightText: 2022 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only
package bmp
import (
"fmt"
"io"
"net"
"net/netip"
"time"
"github.com/osrg/gobgp/v3/pkg/packet/bgp"
"github.com/osrg/gobgp/v3/pkg/packet/bmp"
)
// serveConnection handle the connection from an exporter.
func (p *Provider) serveConnection(conn *net.TCPConn, exporter netip.AddrPort, exporterStr string) error {
p.metrics.openedConnections.WithLabelValues(exporterStr).Inc()
logger := p.r.With().Str("exporter", exporterStr).Logger()
conn.SetLinger(0)
// Stop the connection when exiting this method or when dying
stop := make(chan struct{})
p.t.Go(func() error {
select {
case <-stop:
logger.Info().Msgf("connection down for %s", exporterStr)
p.handleConnectionDown(exporter)
case <-p.t.Dying():
// No need to clean up
}
conn.Close()
p.metrics.closedConnections.WithLabelValues(exporterStr).Inc()
return nil
})
defer close(stop)
// Setup TCP keepalive
if err := conn.SetKeepAlive(true); err != nil {
p.r.Err(err).Msg("unable to enable keepalive")
return nil
}
if err := conn.SetKeepAlivePeriod(time.Minute); err != nil {
p.r.Err(err).Msg("unable to set keepalive period")
return nil
}
// Handle panics
defer func() {
if r := recover(); r != nil {
logger.Panic().Str("panic", fmt.Sprintf("%+v", r)).Msg("fatal error while processing BMP messages")
p.metrics.panics.WithLabelValues(exporterStr).Inc()
}
}()
// Reading from connection
p.handleConnectionUp(exporter)
init := false
header := make([]byte, bmp.BMP_HEADER_SIZE)
for {
_, err := io.ReadFull(conn, header)
if err != nil {
if p.t.Alive() && err != io.EOF {
logger.Err(err).Msg("cannot read BMP header")
p.metrics.errors.WithLabelValues(exporterStr, "cannot read BMP header").Inc()
}
return nil
}
msg := bmp.BMPMessage{}
if err := msg.Header.DecodeFromBytes(header); err != nil {
logger.Err(err).Msg("cannot decode BMP header")
p.metrics.errors.WithLabelValues(exporterStr, "cannot decode BMP header").Inc()
return nil
}
switch msg.Header.Type {
case bmp.BMP_MSG_ROUTE_MONITORING:
msg.Body = &bmp.BMPRouteMonitoring{}
p.metrics.messages.WithLabelValues(exporterStr, "route-monitoring").Inc()
case bmp.BMP_MSG_STATISTICS_REPORT:
// Ignore
p.metrics.messages.WithLabelValues(exporterStr, "statistics-report").Inc()
case bmp.BMP_MSG_PEER_DOWN_NOTIFICATION:
msg.Body = &bmp.BMPPeerDownNotification{}
p.metrics.messages.WithLabelValues(exporterStr, "peer-down-notification").Inc()
case bmp.BMP_MSG_PEER_UP_NOTIFICATION:
msg.Body = &bmp.BMPPeerUpNotification{}
p.metrics.messages.WithLabelValues(exporterStr, "peer-up-notification").Inc()
case bmp.BMP_MSG_INITIATION:
msg.Body = &bmp.BMPInitiation{}
p.metrics.messages.WithLabelValues(exporterStr, "initiation").Inc()
init = true
case bmp.BMP_MSG_TERMINATION:
msg.Body = &bmp.BMPTermination{}
p.metrics.messages.WithLabelValues(exporterStr, "termination").Inc()
case bmp.BMP_MSG_ROUTE_MIRRORING:
// Ignore
p.metrics.messages.WithLabelValues(exporterStr, "route-mirroring").Inc()
default:
logger.Info().Msgf("unknown BMP message type %d", msg.Header.Type)
p.metrics.messages.WithLabelValues(exporterStr, "unknown").Inc()
}
// First message should be BMP_MSG_INITIATION
if !init {
logger.Error().Msg("first message is not `initiation'")
p.metrics.errors.WithLabelValues(exporterStr, "first message not initiation").Inc()
return nil
}
body := make([]byte, msg.Header.Length-bmp.BMP_HEADER_SIZE)
_, err = io.ReadFull(conn, body)
if err != nil {
if p.t.Alive() {
logger.Err(err).Msg("cannot read BMP body")
p.metrics.errors.WithLabelValues(exporterStr, "cannot read BMP body").Inc()
}
return nil
}
if msg.Body == nil || msg.Header.Type == bmp.BMP_MSG_ROUTE_MIRRORING {
// We do not want to parse route mirroring messages as they can
// contain malformed BGP messages.
continue
}
var marshallingOptions []*bgp.MarshallingOption
var pkey peerKey
if msg.Header.Type != bmp.BMP_MSG_INITIATION && msg.Header.Type != bmp.BMP_MSG_TERMINATION {
if err := msg.PeerHeader.DecodeFromBytes(body); err != nil {
logger.Err(err).Msg("cannot parse BMP peer header")
p.metrics.errors.WithLabelValues(exporterStr, "cannot parse BMP peer header").Inc()
return nil
}
body = body[bmp.BMP_PEER_HEADER_SIZE:]
pkey = peerKeyFromBMPPeerHeader(exporter, &msg.PeerHeader)
p.mu.RLock()
if pinfo, ok := p.peers[pkey]; ok {
marshallingOptions = pinfo.marshallingOptions
}
p.mu.RUnlock()
}
if err := msg.Body.ParseBody(&msg, body, marshallingOptions...); err != nil {
msgError, ok := err.(*bgp.MessageError)
if ok {
switch msgError.ErrorHandling {
case bgp.ERROR_HANDLING_SESSION_RESET:
p.metrics.ignored.WithLabelValues(exporterStr, "session-reset").Inc()
continue
case bgp.ERROR_HANDLING_AFISAFI_DISABLE:
p.metrics.ignored.WithLabelValues(exporterStr, "afi-safi").Inc()
continue
case bgp.ERROR_HANDLING_TREAT_AS_WITHDRAW:
// This is a pickle. This can be an essential attribute (eg.
// AS path) that's malformed or something quite minor for
// our own usage (eg. a non-optional attribute), let's skip for now.
p.metrics.ignored.WithLabelValues(exporterStr, "treat-as-withdraw").Inc()
continue
case bgp.ERROR_HANDLING_ATTRIBUTE_DISCARD:
// Optional attribute, let's handle it
case bgp.ERROR_HANDLING_NONE:
// Odd?
p.metrics.ignored.WithLabelValues(exporterStr, "none").Inc()
continue
}
} else {
logger.Err(err).Msg("cannot parse BMP body")
p.metrics.errors.WithLabelValues(exporterStr, "cannot parse BMP body").Inc()
return nil
}
}
// Handle different messages
switch body := msg.Body.(type) {
case *bmp.BMPInitiation:
found := false
for _, info := range body.Info {
switch tlv := info.(type) {
case *bmp.BMPInfoTLVString:
if tlv.Type == bmp.BMP_INIT_TLV_TYPE_SYS_NAME {
logger.Info().Str("sysname", tlv.Value).Msg("new connection")
found = true
}
}
}
if !found {
logger.Info().Msg("new connection")
}
case *bmp.BMPTermination:
for _, info := range body.Info {
switch tlv := info.(type) {
case *bmp.BMPInfoTLVString:
logger.Info().Str("reason", tlv.Value).Msg("termination message received")
return nil
}
}
logger.Info().Msg("termination message received")
return nil
case *bmp.BMPPeerUpNotification:
p.handlePeerUpNotification(pkey, body)
case *bmp.BMPPeerDownNotification:
p.handlePeerDownNotification(pkey)
case *bmp.BMPRouteMonitoring:
p.handleRouteMonitoring(pkey, body)
}
}
}