mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-11 22:14:02 +01:00
208 lines
6.6 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|