bgp

package module
v0.0.0-...-3641ee5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 2, 2026 License: MIT Imports: 24 Imported by: 0

README

bgp Test Status Go Reference

Package bgp implements the Border Gateway Protocol version 4 (BGP-4), as described in RFC 4271 and related RFCs: the wire format, the finite state machine, and the peering lifecycle. MIT Licensed.

The package is built in layers. Each layer is usable without the ones above it:

  • The Message types, such as Open, Update, and Notification, with their binary encoding.
  • Conn frames messages over a connection.
  • FSM runs the RFC 4271 finite state machine over a Conn: one session attempt for each Connect call, delivering zero-copy borrowed values to its handlers.
  • Peer wraps an FSM with a retry loop and handlers whose values are fully owned.
  • Server coordinates many Peers, accepting connections on shared listeners.

Most callers want Peer or Server. FSM is the expert layer for callers who need zero-copy delivery or their own retry policy.

Example

A speaker which announces one route to a neighbor and logs the routes it receives. Run owns the connection lifecycle: dialing, the OPEN exchange, keepalives, and retrying dead sessions until the context is canceled.

attrs, err := bgp.MarshalAttributes(
	bgp.OriginIGP,
	bgp.ASPath{{ASNs: []uint32{64496}}},
	bgp.NextHop(netip.MustParseAddr("192.0.2.10")),
)
if err != nil {
	log.Fatalf("failed to marshal attributes: %v", err)
}

p, err := bgp.NewPeer(netip.MustParseAddr("192.0.2.1"), bgp.PeerConfig{
	LocalASN: 64496,
	LocalID:  bgp.MustParseIdentifier("192.0.2.10"),
	PeerASN:  64497,

	OnEstablished: func(ctx context.Context, p *bgp.Peer, s bgp.Session) error {
		err := p.SendUpdate(ctx, &bgp.Update{
			Attributes: attrs,
			NLRI:       []netip.Prefix{netip.MustParsePrefix("198.51.100.0/24")},
		})
		if err != nil {
			return err
		}

		return p.SendUpdate(ctx, bgp.NewEndOfRIB(bgp.Family{
			AFI:  bgp.AFIIPv4,
			SAFI: bgp.SAFIUnicast,
		}))
	},

	OnUpdate: func(_ context.Context, _ *bgp.Peer, u *bgp.Update) error {
		log.Printf("update: reachable %v, withdrawn %v", u.Prefixes, u.Withdrawn)
		return nil
	},
})
if err != nil {
	log.Fatalf("failed to create peer: %v", err)
}

// Canceling ctx sends the peer Cease / Administrative Shutdown and returns.
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

if err := p.Run(ctx); err != nil {
	log.Fatalf("failed to run peer: %v", err)
}

See the package examples for multiprotocol IPv6, dual stack, passive peers, a hardened internet-facing configuration, and more.

Scope

There is no routing table and no policy engine, permanently. An established session hands received UPDATE messages to the caller, who owns all routing decisions. The package is designed around that boundary: a RIB plugs in at the Peer layer through its handlers, wiring its methods into each PeerConfig and draining its Adj-RIB-Out through the send path. Companion projects, such as BMP (RFC 7854) monitoring and BFD (RFC 5880) liveness for peerings, build on the same seams and live in their own repositories.

Platform support

The message, Conn, FSM, Peer, and Server layers are portable Go. The TCP socket options a production BGP speaker needs, such as TCP-MD5, GTSM, and DSCP, are only supported on Linux; elsewhere, setting them fails with an error which wraps errors.ErrUnsupported.

Testing

The package is tested against real internet routing data and real routers:

  • Corpus tests parse every message of route collector archives and every route of a full internet table, requiring a byte-for-byte marshal round trip.
  • Fuzz targets cover message parsing, attribute parsing, and connection framing, seeded from the corpus.
  • An interop harness runs live sessions against FRRouting, covering establishment, capability negotiation, route exchange, TCP-MD5, and GTSM.

Documentation

Overview

Package bgp implements the Border Gateway Protocol version 4 (BGP-4), as described in RFC 4271 and related RFCs.

The package is built in layers. Each layer is usable without the ones above it:

  • The Message types, such as Open, Update, and Notification, with their binary encoding.
  • Conn frames messages over a connection.
  • FSM runs the RFC 4271 finite state machine over a Conn: one session attempt for each Connect call, delivering zero-copy borrowed values to its handlers.
  • Peer wraps an FSM with a retry loop and handlers whose values are fully owned.
  • Server coordinates many Peers, accepting connections on shared listeners.

Most callers want Peer or Server. FSM is the expert layer for callers who need zero-copy delivery or their own retry policy. There is no routing table and no policy: an established session hands received UPDATE messages to the caller, who owns any routing decisions.

Multiprotocol BGP (RFC 4760) is a first-class concern: the MPReachNLRI and MPUnreachNLRI attributes carry routes for any address family, including IPv4, and IPv4 routes may use an IPv6 next hop (RFC 8950). The IPv4-only fields of an Update exist for compatibility with the original RFC 4271 wire format.

Example

A classic IPv4 unicast speaker: dial one remote peer, announce one route when the session is established, and log routes received in return.

package main

import (
	"context"
	"log"
	"net/netip"
	"os"
	"os/signal"

	"github.com/mdlayher/bgp"
)

func main() {
	// The route to announce, prepared up front: path attributes are carried
	// in wire form, so parsed attributes are marshaled once and reused for
	// every session.
	attrs, err := bgp.MarshalAttributes(
		bgp.OriginIGP,
		bgp.ASPath{{ASNs: []uint32{64496}}},
		bgp.NextHop(netip.MustParseAddr("192.0.2.10")),
	)
	if err != nil {
		log.Fatalf("failed to marshal attributes: %v", err)
	}

	// The peering: the remote speaker's address to dial, then this
	// speaker's identity. An empty Families list advertises no
	// multiprotocol capabilities: the classic IPv4 unicast speaker.
	p, err := bgp.NewPeer(netip.MustParseAddr("192.0.2.1"), bgp.PeerConfig{
		LocalASN: 64496,
		LocalID:  bgp.MustParseIdentifier("192.0.2.10"),
		PeerASN:  64497,

		OnEstablished: func(ctx context.Context, p *bgp.Peer, s bgp.Session) error {
			// One announcement and End-of-RIB fit comfortably in a handler; a
			// bulk table push belongs on a goroutine bound to ctx instead. See
			// PeerConfig.
			announce := &bgp.Update{
				Attributes: attrs,
				NLRI:       []netip.Prefix{netip.MustParsePrefix("198.51.100.0/24")},
			}

			if err := p.SendUpdate(ctx, announce); err != nil {
				return err
			}

			return p.SendUpdate(ctx, bgp.NewEndOfRIB(bgp.Family{
				AFI:  bgp.AFIIPv4,
				SAFI: bgp.SAFIUnicast,
			}))
		},

		OnUpdate: func(_ context.Context, _ *bgp.Peer, u *bgp.Update) error {
			// u is fully owned: it may be retained or handed to another
			// goroutine freely.
			log.Printf("update: reachable %v, withdrawn %v", u.NLRI, u.Withdrawn)
			return nil
		},

		OnClose: func(_ *bgp.Peer, c bgp.Close) {
			// The session is already down and Run will retry; observe why.
			log.Printf("closed: notification=%+v err=%v", c.Notification, c.Err)
		},
	})
	if err != nil {
		log.Fatalf("failed to create peer: %v", err)
	}

	// Canceling ctx sends the peer Cease / Administrative Shutdown and returns.
	// Run blocks the caller and owns the connection lifecycle: dialing,
	// retrying with backoff, and replacing dead sessions until ctx is canceled.
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
	defer stop()

	if err := p.Run(ctx); err != nil {
		log.Fatalf("failed to run peer: %v", err)
	}
}
Example (DualStack)

A dual-stack speaker: one session negotiates IPv4 and IPv6 unicast, each family announced in its own MP_REACH_NLRI and ended with its own End-of-RIB. Received routes are found by attribute type with Lookup.

package main

import (
	"context"
	"log"
	"net/netip"

	"github.com/mdlayher/bgp"
)

func main() {
	v4 := bgp.Family{AFI: bgp.AFIIPv4, SAFI: bgp.SAFIUnicast}
	v6 := bgp.Family{AFI: bgp.AFIIPv6, SAFI: bgp.SAFIUnicast}

	// One announcement per family. Sessions which negotiate multiprotocol
	// support carry IPv4 in MP_REACH_NLRI too, leaving the UPDATE's classic
	// IPv4 fields empty.
	announce := map[bgp.Family]*bgp.Update{}
	for _, r := range []struct {
		family  bgp.Family
		nextHop netip.Addr
		prefix  netip.Prefix
	}{
		{v4, netip.MustParseAddr("192.0.2.10"), netip.MustParsePrefix("198.51.100.0/24")},
		{v6, netip.MustParseAddr("2001:db8::10"), netip.MustParsePrefix("2001:db8:100::/48")},
	} {
		attrs, err := bgp.MarshalAttributes(
			bgp.OriginIGP,
			bgp.ASPath{{ASNs: []uint32{64496}}},
			bgp.MPReachNLRI{Family: r.family, NextHop: r.nextHop, NLRI: bgp.Prefixes{r.prefix}},
		)
		if err != nil {
			log.Fatalf("failed to marshal attributes: %v", err)
		}

		announce[r.family] = &bgp.Update{Attributes: attrs}
	}

	p, err := bgp.NewPeer(netip.MustParseAddr("192.0.2.1"), bgp.PeerConfig{
		LocalASN: 64496,
		LocalID:  bgp.MustParseIdentifier("192.0.2.10"),
		PeerASN:  64497,
		Families: []bgp.Family{v4, v6},

		OnEstablished: func(ctx context.Context, p *bgp.Peer, s bgp.Session) error {
			// Announce into each family the peer agreed to; s.Families is
			// the intersection of both speakers' offers.
			for _, f := range s.Families {
				if err := p.SendUpdate(ctx, announce[f]); err != nil {
					return err
				}

				if err := p.SendUpdate(ctx, bgp.NewEndOfRIB(f)); err != nil {
					return err
				}
			}

			return nil
		},

		OnUpdate: func(_ context.Context, _ *bgp.Peer, u *bgp.Update) error {
			// Parse only the attributes of interest; the rest stay raw.
			if reach, ok, err := bgp.Lookup[bgp.MPReachNLRI](u.Attributes); err != nil {
				return err
			} else if ok {
				log.Printf("%s reachable via %s: %v", reach.Family, reach.NextHop, reach.NLRI)
			}

			if unreach, ok, err := bgp.Lookup[bgp.MPUnreachNLRI](u.Attributes); err != nil {
				return err
			} else if ok {
				log.Printf("%s withdrawn: %v", unreach.Family, unreach.NLRI)
			}

			return nil
		},
	})
	if err != nil {
		log.Fatalf("failed to create peer: %v", err)
	}

	if err := p.Run(context.Background()); err != nil {
		log.Fatalf("failed to run peer: %v", err)
	}
}
Example (ExtendedNextHop)

IPv4 routes over an IPv6-only link (RFC 8950): the peering runs over IPv6, the extended next hop capability is advertised for IPv4 unicast, and IPv4 reachability is announced with an IPv6 next hop. This is the shape of BGP unnumbered fabrics.

package main

import (
	"context"
	"errors"
	"log"
	"net/netip"
	"slices"

	"github.com/mdlayher/bgp"
)

func main() {
	v4 := bgp.Family{AFI: bgp.AFIIPv4, SAFI: bgp.SAFIUnicast}

	attrs, err := bgp.MarshalAttributes(
		bgp.OriginIGP,
		bgp.ASPath{{ASNs: []uint32{64496}}},
		bgp.MPReachNLRI{
			Family:  v4,
			NextHop: netip.MustParseAddr("2001:db8::10"),
			NLRI:    bgp.Prefixes{netip.MustParsePrefix("198.51.100.0/24")},
		},
	)
	if err != nil {
		log.Fatalf("failed to marshal attributes: %v", err)
	}

	p, err := bgp.NewPeer(netip.MustParseAddr("2001:db8::1"), bgp.PeerConfig{
		LocalASN: 64496,
		LocalID:  bgp.MustParseIdentifier("192.0.2.10"),
		PeerASN:  64497,
		Families: []bgp.Family{v4},
		// Capabilities beyond the ones Identity's fields express are
		// advertised raw; the Session reports what the peer agreed to.
		Capabilities: []bgp.Capability{bgp.ExtendedNextHopCapability(v4)},

		OnEstablished: func(ctx context.Context, p *bgp.Peer, s bgp.Session) error {
			// Without the peer's agreement an IPv6 next hop for IPv4 is a
			// malformed attribute on its side; end the session rather than
			// announce one. The Cease is this handler's error.
			if !slices.Contains(s.ExtendedNextHop, v4) {
				return errors.New("peer does not accept an IPv6 next hop for IPv4 unicast")
			}

			if err := p.SendUpdate(ctx, &bgp.Update{Attributes: attrs}); err != nil {
				return err
			}

			return p.SendUpdate(ctx, bgp.NewEndOfRIB(v4))
		},
	})
	if err != nil {
		log.Fatalf("failed to create peer: %v", err)
	}

	if err := p.Run(context.Background()); err != nil {
		log.Fatalf("failed to run peer: %v", err)
	}
}
Example (Hardened)

A hardened external peering: TCP-MD5 (RFC 2385) authenticates the TCP session and GTSM (RFC 5082) rejects packets from beyond the directly connected peer. Both are transport properties of the peering, so the key is peering config and the TTL floor is dialer config.

package main

import (
	"context"
	"log"
	"net/netip"

	"github.com/mdlayher/bgp"
)

func main() {
	p, err := bgp.NewPeer(netip.MustParseAddr("192.0.2.1"), bgp.PeerConfig{
		LocalASN: 64496,
		LocalID:  bgp.MustParseIdentifier("192.0.2.10"),
		PeerASN:  64497,

		// The key covers both directions: the Peer signs the connections it
		// dials, and a Server installs it on its listeners for the ones it
		// accepts. Without a Server, a passive caller installs it with
		// Listener.SetMD5 before the remote SYN can arrive.
		//
		// A key mismatch never produces a connection: the kernel drops the
		// missigned segments, so the symptom is a dial which times out and
		// is retried, visible in the Logger's retry activity. OnClose does
		// not fire for an attempt with no connection to report.
		MD5Password: "correct horse battery staple",

		// GTSM sends with TTL 255 and drops anything arriving lower: a
		// packet which crossed a router cannot reach the session. The
		// remote must run GTSM too, or its packets are dropped here.
		Dialer: bgp.Dialer{TCPOptions: bgp.TCPOptions{GTSM: true}},
	})
	if err != nil {
		log.Fatalf("failed to create peer: %v", err)
	}

	if err := p.Run(context.Background()); err != nil {
		log.Fatalf("failed to run peer: %v", err)
	}
}
Example (Multiprotocol)

A multiprotocol IPv6 unicast speaker: the family is negotiated via the multiprotocol capability (RFC 4760), and reachability travels in an MP_REACH_NLRI attribute rather than the UPDATE's IPv4 NLRI field.

package main

import (
	"context"
	"log"
	"net/netip"

	"github.com/mdlayher/bgp"
)

func main() {
	v6 := bgp.Family{AFI: bgp.AFIIPv6, SAFI: bgp.SAFIUnicast}

	attrs, err := bgp.MarshalAttributes(
		bgp.OriginIGP,
		bgp.ASPath{{ASNs: []uint32{64496}}},
		bgp.MPReachNLRI{
			Family:  v6,
			NextHop: netip.MustParseAddr("2001:db8::10"),
			NLRI:    bgp.Prefixes{netip.MustParsePrefix("2001:db8:100::/48")},
		},
	)
	if err != nil {
		log.Fatalf("failed to marshal attributes: %v", err)
	}

	p, err := bgp.NewPeer(netip.MustParseAddr("2001:db8::1"), bgp.PeerConfig{
		LocalASN: 64496,
		LocalID:  bgp.MustParseIdentifier("192.0.2.10"),
		PeerASN:  64497,
		Families: []bgp.Family{v6},

		OnEstablished: func(ctx context.Context, p *bgp.Peer, _ bgp.Session) error {
			if err := p.SendUpdate(ctx, &bgp.Update{Attributes: attrs}); err != nil {
				return err
			}

			return p.SendUpdate(ctx, bgp.NewEndOfRIB(v6))
		},
	})
	if err != nil {
		log.Fatalf("failed to create peer: %v", err)
	}

	if err := p.Run(context.Background()); err != nil {
		log.Fatalf("failed to run peer: %v", err)
	}
}
Example (RouteRefresh)

Route refresh (RFC 2918) in both directions: advertising the capability promises to re-send the table when the peer asks, and asking the peer re-fetches its table when local policy changes — without a session reset.

package main

import (
	"context"
	"log"
	"net/netip"
	"os"
	"os/signal"
	"syscall"

	"github.com/mdlayher/bgp"
)

func main() {
	v4 := bgp.Family{AFI: bgp.AFIIPv4, SAFI: bgp.SAFIUnicast}

	attrs, err := bgp.MarshalAttributes(
		bgp.OriginIGP,
		bgp.ASPath{{ASNs: []uint32{64496}}},
		bgp.NextHop(netip.MustParseAddr("192.0.2.10")),
	)
	if err != nil {
		log.Fatalf("failed to marshal attributes: %v", err)
	}

	// The whole table, sent at establishment and again on every refresh
	// request. A real RIB snapshots under its lock and sends outside it.
	advertise := func(ctx context.Context, p *bgp.Peer) error {
		announce := &bgp.Update{
			Attributes: attrs,
			NLRI:       []netip.Prefix{netip.MustParsePrefix("198.51.100.0/24")},
		}

		if err := p.SendUpdate(ctx, announce); err != nil {
			return err
		}

		return p.SendUpdate(ctx, bgp.NewEndOfRIB(v4))
	}

	p, err := bgp.NewPeer(netip.MustParseAddr("192.0.2.1"), bgp.PeerConfig{
		LocalASN: 64496,
		LocalID:  bgp.MustParseIdentifier("192.0.2.10"),
		PeerASN:  64497,
		// Advertising the capability requires OnRouteRefresh: the promise
		// must be kept.
		RouteRefresh: true,

		OnEstablished: func(ctx context.Context, p *bgp.Peer, _ bgp.Session) error {
			return advertise(ctx, p)
		},
		OnRouteRefresh: func(ctx context.Context, p *bgp.Peer, r *bgp.RouteRefresh) error {
			// The peer asked for the table again: its inbound policy
			// changed, or it lost state.
			if r.Family != v4 {
				return nil
			}

			return advertise(ctx, p)
		},
		OnUpdate: func(_ context.Context, _ *bgp.Peer, u *bgp.Update) error {
			log.Printf("update: reachable %v, withdrawn %v", u.NLRI, u.Withdrawn)
			return nil
		},
	})
	if err != nil {
		log.Fatalf("failed to create peer: %v", err)
	}

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
	defer stop()

	// A reload of local inbound policy re-fetches the peer's routes so the
	// new policy applies to all of them. SendRouteRefresh is safe from any
	// goroutine, and reports an error when the peer did not advertise the
	// capability or no session is established.
	go func() {
		reload := make(chan os.Signal, 1)
		signal.Notify(reload, syscall.SIGHUP)
		for range reload {
			if err := p.SendRouteRefresh(ctx, v4); err != nil {
				log.Printf("failed to request route refresh: %v", err)
			}
		}
	}()

	if err := p.Run(ctx); err != nil {
		log.Fatalf("failed to run peer: %v", err)
	}
}

Index

Examples

Constants

View Source
const (
	SubcodeConnectionNotSynchronized uint8 = 1
	SubcodeBadMessageLength          uint8 = 2
	SubcodeBadMessageType            uint8 = 3
)

Message Header Error subcodes, as described in RFC 4271, section 6.1. These values are carried by a Notification with Code NotificationMessageHeaderError.

View Source
const (
	SubcodeUnsupportedVersionNumber     uint8 = 1
	SubcodeBadPeerAS                    uint8 = 2
	SubcodeBadBGPIdentifier             uint8 = 3
	SubcodeUnsupportedOptionalParameter uint8 = 4
	SubcodeUnacceptableHoldTime         uint8 = 6
	SubcodeUnsupportedCapability        uint8 = 7
)

OPEN Message Error subcodes, as described in RFC 4271, section 6.2, and RFC 5492, section 5. These values are carried by a Notification with Code NotificationOpenMessageError.

View Source
const (
	SubcodeMalformedAttributeList         uint8 = 1
	SubcodeUnrecognizedWellKnownAttribute uint8 = 2
	SubcodeMissingWellKnownAttribute      uint8 = 3
	SubcodeAttributeFlagsError            uint8 = 4
	SubcodeAttributeLengthError           uint8 = 5
	SubcodeInvalidOriginAttribute         uint8 = 6
	SubcodeInvalidNextHopAttribute        uint8 = 8
	SubcodeOptionalAttributeError         uint8 = 9
	SubcodeInvalidNetworkField            uint8 = 10
	SubcodeMalformedASPath                uint8 = 11
)

UPDATE Message Error subcodes, as described in RFC 4271, section 6.3. These values are carried by a Notification with Code NotificationUpdateMessageError.

View Source
const (
	SubcodeUnexpectedMessageOpenSent    uint8 = 1
	SubcodeUnexpectedMessageOpenConfirm uint8 = 2
	SubcodeUnexpectedMessageEstablished uint8 = 3
)

Finite State Machine Error subcodes, as described in RFC 6608. These values are carried by a Notification with Code NotificationFSMError, and report the state a session was in when an unexpected message arrived.

View Source
const (
	SubcodeCeaseMaximumPrefixesReached        uint8 = 1
	SubcodeCeaseAdministrativeShutdown        uint8 = 2
	SubcodeCeasePeerDeconfigured              uint8 = 3
	SubcodeCeaseAdministrativeReset           uint8 = 4
	SubcodeCeaseConnectionRejected            uint8 = 5
	SubcodeCeaseOtherConfigurationChange      uint8 = 6
	SubcodeCeaseConnectionCollisionResolution uint8 = 7
	SubcodeCeaseOutOfResources                uint8 = 8
	SubcodeCeaseHardReset                     uint8 = 9
	SubcodeCeaseBFDDown                       uint8 = 10
)

Cease subcodes, as described in RFC 4486, plus Hard Reset (RFC 8538) and BFD Down (RFC 9384). These values are carried by a Notification with Code NotificationCease. Hard Reset instructs a graceful restart helper to flush immediately; BFD Down reports that the peering's BFD session (RFC 5880) went down.

View Source
const DSCPCS6 = 48

DSCPCS6 is the Class Selector 6 Differentiated Services Code Point (RFC 2474). RFC 4594 assigns it to network control traffic, and routers conventionally apply it to BGP. It is the usual value for TCPOptions.DSCP.

View Source
const (
	// MaxMessageSize is the maximum size in bytes of an encoded BGP message,
	// as described in RFC 4271, section 4.1.
	MaxMessageSize = 4096
)
View Source
const Port = 179

Port is the well-known TCP port for BGP, as assigned by IANA. A Dialer dials it when its own Port field is zero.

Variables

View Source
var ErrNotEstablished = errors.New("bgp: session is not established")

ErrNotEstablished is returned by SendUpdate and SendRouteRefresh when there is no established session, and wrapped (per errors.Is) in the error of a send whose connection write failed, since that failure ends the session. It is the only session state a caller observes directly: a route pusher winds down on it, and the next session's OnEstablished starts a fresh one. A message which fails to marshal returns its error alone, without ErrNotEstablished: the session is unaffected.

Functions

func Lookup

func Lookup[T Attribute](as RawAttributes) (T, bool, error)

Lookup finds the first attribute of as whose type is T's and parses it, reporting false when as carries none: the typed accessor for one attribute of an Update, as in Lookup[ASPath](u.Attributes). A malformed attribute produces the *MessageError of RawAttribute.Parse.

T must be one of the typed Attribute implementations, which each know their own wire type. The Attribute interface itself is rejected with an error, and a RawAttribute has no type of its own to look up, so Lookup[RawAttribute] finds nothing; RawAttributes.Find serves both.

Types

type AFI

type AFI uint16

An AFI is an IANA Address Family Identifier.

const (
	AFIIPv4  AFI = 1
	AFIIPv6  AFI = 2
	AFIL2VPN AFI = 25
)

AFI values named by this package. Any AFI may be negotiated and carried; these are the ones whose reachability information this package knows the shape of.

func (AFI) String

func (a AFI) String() string

String returns the name of an AFI, or its number when unnamed.

type ASPath

type ASPath []ASSegment

An ASPath is the AS_PATH attribute: the autonomous systems through which routing information in an Update has passed.

ASNs are always encoded in four-octet form, per RFC 6793. Sessions with speakers which do not support the Four-Octet AS Number capability are not supported by this package.

func (ASPath) Origin

func (p ASPath) Origin() OriginAS

Origin returns the origin autonomous system the path names, per RFC 6811, section 2. Validation of the origin against RPKI data is the caller's; see ValidationState for carrying its result.

type ASSegment

type ASSegment struct {
	// Set indicates that the segment is an unordered AS_SET, rather than an
	// ordered AS_SEQUENCE.
	Set bool

	// ASNs lists the autonomous systems within the segment.
	ASNs []uint32
}

An ASSegment is a single segment of an ASPath.

type Aggregator

type Aggregator struct {
	// ASN is the autonomous system number of the aggregating speaker.
	ASN uint32

	// ID is the BGP identifier of the aggregating speaker.
	ID Identifier
}

An Aggregator is the AGGREGATOR attribute, identifying the autonomous system and router which aggregated a route. The ASN is always encoded in four-octet form, per RFC 6793.

type AtomicAggregate

type AtomicAggregate struct{}

An AtomicAggregate is the ATOMIC_AGGREGATE attribute, indicating that a route was selected over a more specific route which it covers.

type AttrFlags

type AttrFlags uint8

AttrFlags describe a BGP path attribute, as described in RFC 4271, section 4.3.

const (
	AttrFlagOptional   AttrFlags = 0x80
	AttrFlagTransitive AttrFlags = 0x40
	AttrFlagPartial    AttrFlags = 0x20
)

AttrFlags values, as described in RFC 4271. The extended length flag is a wire encoding detail managed by this package: it is cleared when parsing and computed as needed when marshaling.

type AttrType

type AttrType uint8

An AttrType is the type of a BGP path attribute.

const (
	AttrOrigin              AttrType = 1
	AttrASPath              AttrType = 2
	AttrNextHop             AttrType = 3
	AttrMED                 AttrType = 4
	AttrLocalPref           AttrType = 5
	AttrAtomicAggregate     AttrType = 6
	AttrAggregator          AttrType = 7
	AttrCommunities         AttrType = 8
	AttrOriginatorID        AttrType = 9
	AttrClusterList         AttrType = 10
	AttrMPReachNLRI         AttrType = 14
	AttrMPUnreachNLRI       AttrType = 15
	AttrExtendedCommunities AttrType = 16
	AttrLargeCommunities    AttrType = 32
	AttrOTC                 AttrType = 35
)

AttrType values, as assigned by IANA.

type Attribute

type Attribute interface {
	// contains filtered or unexported methods
}

An Attribute is a BGP path attribute in parsed form. Attribute is implemented by Origin, ASPath, NextHop, MED, LocalPref, AtomicAggregate, Aggregator, Communities, OriginatorID, ClusterList, ExtendedCommunities, MPReachNLRI, MPUnreachNLRI, LargeCommunities, and OTC, and by RawAttribute itself, so that an attribute this package does not interpret can ride alongside typed ones.

type Capability

type Capability struct {
	Code CapabilityCode
	Data []byte
}

A Capability is a BGP capability in raw binary form, advertised in an Open message, as described in RFC 5492.

When produced by ParseMessage, Data references the input buffer rather than copying it; see ParseMessage.

func ExtendedNextHopCapability

func ExtendedNextHopCapability(fs ...Family) Capability

ExtendedNextHopCapability produces a Capability which advertises the ability to receive routes for each address family in fs with an IPv6 next hop, as described in RFC 8950.

func FQDNCapability

func FQDNCapability(hostname, domain string) (Capability, error)

FQDNCapability produces a Capability which advertises the speaker's hostname and domain name, as described in draft-walton-bgp-hostname-capability-02. The wire encoding is two length-prefixed UTF-8 strings, with lengths in bytes. Either string may be empty. FQDNCapability fails when a string cannot fit its one-byte length.

The capability is cosmetic: the draft says it SHOULD only be used to display a speaker's name when troubleshooting, and nothing in this package reads it beyond the codec.

func GracefulRestartCapability

func GracefulRestartCapability(gr GracefulRestart) (Capability, error)

GracefulRestartCapability produces a Capability which advertises graceful restart. RestartTime must lie within [0, 4095s], the 12 bit whole-seconds wire field, and is truncated to whole seconds.

A Peer or FSM advertises graceful restart through its configuration's GracefulRestart field, not by placing this Capability in Capabilities: the Restart State bit varies per session attempt, so the FSM owns the encoding.

func MultiprotocolCapability

func MultiprotocolCapability(f Family) Capability

MultiprotocolCapability produces a Capability which advertises support for the address family f, as described in RFC 4760.

func (*Capability) Clone

func (src *Capability) Clone() *Capability

Clone makes a deep copy of Capability. The result aliases no memory with the original.

func (Capability) ExtendedNextHop

func (c Capability) ExtendedNextHop() ([]Family, error)

ExtendedNextHop parses the address families for which a CapabilityExtendedNextHop Capability advertises IPv6 next hop support, as described in RFC 8950. The result is the families ExtendedNextHopCapability was given, after a wire round trip.

A nil result is a well-formed capability advertising no families. Entries naming a next hop AFI other than IPv6 are skipped: RFC 8950 defines no such entries.

func (Capability) FQDN

func (c Capability) FQDN() (hostname, domain string, err error)

FQDN parses the hostname and domain name a CapabilityFQDN Capability carries. The returned strings never reference Data, so they remain valid after the buffer Data references is reused.

func (Capability) GracefulRestart

func (c Capability) GracefulRestart() (GracefulRestart, error)

GracefulRestart parses the content of a CapabilityGracefulRestart Capability. The returned value never references Data, so it remains valid after the buffer Data references is reused.

func (Capability) Multiprotocol

func (c Capability) Multiprotocol() (Family, error)

Multiprotocol parses the address family advertised by a CapabilityMultiprotocol Capability.

type CapabilityCode

type CapabilityCode uint8

A CapabilityCode identifies the type of a Capability.

const (
	CapabilityMultiprotocol   CapabilityCode = 1
	CapabilityRouteRefresh    CapabilityCode = 2
	CapabilityExtendedNextHop CapabilityCode = 5
	CapabilityGracefulRestart CapabilityCode = 64
	CapabilityFourOctetAS     CapabilityCode = 65
	CapabilityFQDN            CapabilityCode = 73
)

CapabilityCode values, as assigned by IANA.

type Close

type Close struct {
	// Notification is the NOTIFICATION which ended the session, fully
	// owned, or nil if the transport died without one.
	Notification *Notification

	// Local reports which speaker ended the session or attempt. True when
	// this speaker did: it sent Notification, or gave up on a transport
	// whose write failed — including the hold-time write deadline. False
	// when the peer did: it sent Notification, or its connection ended,
	// which is any read error that is not a parse error. Notification is
	// nil exactly when the transport failed, so a nil Notification with
	// Local set is RFC 4271's TcpConnectionFails event on this side of
	// the connection.
	Local bool

	// Err is the transport, parse, or handler error which caused the
	// close, if any.
	Err error

	// Established reports whether this close ends an established session,
	// one whose OnEstablished has fired, rather than a failed session
	// attempt. The distinction is load-bearing for graceful restart
	// helpers: a failed reconnect attempt while stale routes are retained
	// must not be mistaken for the session ending again.
	Established bool
}

A Close reports why a session or session attempt ended, passed to OnClose at both layers. Every field is fully owned.

type ClusterList

type ClusterList []Identifier

A ClusterList is the CLUSTER_LIST attribute: the sequence of route reflection clusters a route has passed through, as described in RFC 4456. A cluster's identifier is by default its reflector's BGP identifier.

type Communities

type Communities []Community

Communities is the COMMUNITIES attribute: the community values applied to a route.

type Community

type Community uint32

A Community is a BGP community value, as described in RFC 1997, conventionally written as "ASN:value".

func NewCommunity

func NewCommunity(asn, value uint16) Community

NewCommunity produces a Community from an ASN and a value.

func (Community) String

func (c Community) String() string

String returns the conventional "ASN:value" form of a Community.

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

A Conn sends and receives BGP messages over an underlying connection.

Conn only frames messages on and off the connection. It implements no protocol logic: no timers, no session state, no replies on the caller's behalf.

As with net.Conn, one goroutine may read and one may write, concurrently.

func NewConn

func NewConn(c net.Conn) *Conn

NewConn creates a Conn which sends and receives BGP messages over c, which may be any net.Conn. For the TCP socket options a BGP speaker typically needs, such as TCP-MD5 or GTSM, use Dialer or ListenConfig instead.

func (*Conn) Close

func (c *Conn) Close() error

Close closes the connection. Any blocked ReadMessage or WriteMessage call is unblocked and returns an error.

func (*Conn) LocalAddr

func (c *Conn) LocalAddr() net.Addr

LocalAddr returns the local network address of the connection.

func (*Conn) ReadMessage

func (c *Conn) ReadMessage() (Message, error)

ReadMessage reads the next Message, blocking until a complete message arrives, the read deadline expires, or an error occurs.

Like bufio.Scanner, ReadMessage returns values which reference an internal buffer: the Message, and every byte slice reachable from it, are valid only until the next call. To retain data longer, copy it; see ParseMessage.

A malformed message produces a *MessageError describing the Notification RFC 4271 requires in response, and is not consumed: the Conn is no longer synchronized with its peer and must be closed.

A connection closed by the peer between messages produces io.EOF; one closed mid-message produces io.ErrUnexpectedEOF.

func (*Conn) RemoteAddr

func (c *Conn) RemoteAddr() net.Addr

RemoteAddr returns the remote network address of the connection.

func (*Conn) SetDeadline

func (c *Conn) SetDeadline(t time.Time) error

SetDeadline implements the net.Conn method of the same name.

func (*Conn) SetReadDeadline

func (c *Conn) SetReadDeadline(t time.Time) error

SetReadDeadline implements the net.Conn method of the same name. It bounds the time spent in ReadMessage.

func (*Conn) SetWriteDeadline

func (c *Conn) SetWriteDeadline(t time.Time) error

SetWriteDeadline implements the net.Conn method of the same name. It bounds the time spent in WriteMessage.

func (*Conn) WriteMessage

func (c *Conn) WriteMessage(m Message) error

WriteMessage writes m in a single write, blocking until the write completes, the write deadline expires, or an error occurs.

A marshal failure is returned wrapped, so errors.Is and errors.As reach the original error; no byte reaches the connection.

type Dialer

type Dialer struct {
	// TCPOptions are the socket options applied to each dialed connection.
	TCPOptions

	// LocalAddr optionally binds the local side of the connection to a
	// specific address, port, or both. The zero value binds nothing, the
	// normal posture for an active open: BGP speakers connect from an
	// ephemeral port.
	LocalAddr netip.AddrPort

	// Port is the TCP port the peer is dialed on. The zero value is Port,
	// the well-known 179.
	Port uint16
}

A Dialer creates BGP connections by performing an active open. It applies the TCP socket options a BGP speaker typically needs. The zero value is usable and produces a plain TCP connection.

func (*Dialer) Dial

func (d *Dialer) Dial(ctx context.Context, addr netip.Addr) (*Conn, error)

Dial performs an active open to the BGP speaker at addr, on the Dialer's Port, and returns a Conn over the resulting connection.

type Direction

type Direction int

A Direction is the direction a message traveled on a connection, as seen by this speaker.

const (
	// DirectionReceived: the peer sent the message and this speaker read it.
	DirectionReceived Direction = iota

	// DirectionSent: this speaker wrote the message to the peer.
	DirectionSent
)

func (Direction) String

func (d Direction) String() string

String returns the name of the Direction.

type EVPNRoute

type EVPNRoute struct {
	Type  EVPNRouteType
	Value []byte
}

An EVPNRoute is one record of EVPN reachability information, as described in RFC 7432, section 7: a route type and the type specific value it frames.

Value is deliberately opaque. Its interpretation (route distinguishers, Ethernet segment identifiers, MAC addresses, Ethernet tags, VNIs, MPLS labels) is the vocabulary of a layer 2 control plane, which is the caller's side of this package's boundary. What this package owns is the framing: a route type, a length, and a value of exactly that length.

type EVPNRouteType

type EVPNRouteType uint8

An EVPNRouteType is the type of one EVPN NLRI record, as assigned by IANA.

const (
	EVPNRouteEthernetAutoDiscovery         EVPNRouteType = 1
	EVPNRouteMACIPAdvertisement            EVPNRouteType = 2
	EVPNRouteInclusiveMulticastEthernetTag EVPNRouteType = 3
	EVPNRouteEthernetSegment               EVPNRouteType = 4
	EVPNRouteIPPrefix                      EVPNRouteType = 5
)

EVPNRouteType values for the route types of RFC 7432, section 7 and RFC 9136, section 3.

func (EVPNRouteType) String

func (t EVPNRouteType) String() string

String returns the name of an EVPNRouteType.

type EVPNRoutes

type EVPNRoutes []EVPNRoute

EVPNRoutes is the NLRI of the L2VPN EVPN family (AFI 25, SAFI 70), a list of typed records rather than of prefixes, as described in RFC 7432, section 7.

type ExtendedCommunities

type ExtendedCommunities []ExtendedCommunity

ExtendedCommunities is the EXTENDED_COMMUNITIES attribute: the extended community values applied to a route.

type ExtendedCommunity

type ExtendedCommunity [8]byte

An ExtendedCommunity is a BGP extended community value, as described in RFC 4360: 8 wire bytes, carried verbatim. The type and subtype registries are large, so the value is deliberately opaque; only the common route target and route origin forms (NewRouteTarget, NewRouteOrigin) and the origin validation state (NewValidationState, ValidationState) are interpreted, by their constructors, accessors, and String.

func NewRouteOrigin

func NewRouteOrigin(asn, value uint32) (ExtendedCommunity, error)

NewRouteOrigin produces a route origin (site of origin) ExtendedCommunity from an ASN and a value, choosing the two-octet or four-octet AS specific encoding to fit the ASN. The four-octet encoding only has room for a 2 byte value.

func NewRouteTarget

func NewRouteTarget(asn, value uint32) (ExtendedCommunity, error)

NewRouteTarget produces a route target ExtendedCommunity from an ASN and a value, choosing the two-octet or four-octet AS specific encoding to fit the ASN. The four-octet encoding only has room for a 2 byte value.

func NewValidationState

func NewValidationState(s ValidationState) ExtendedCommunity

NewValidationState produces the origin validation state ExtendedCommunity (RFC 8097) carrying s: a non-transitive opaque community, so it does not cross an autonomous system boundary.

func (ExtendedCommunity) String

func (c ExtendedCommunity) String() string

String returns the conventional form of common route target ("RT:") and route origin ("SoO:") communities, and of the origin validation state ("OVS:", RFC 8097). Other values render as "UNK:type:subtype:0xvalue"; unlike the similar FRR form, the value bytes are preserved.

func (ExtendedCommunity) ValidationState

func (c ExtendedCommunity) ValidationState() (ValidationState, bool)

ValidationState returns the origin validation state the community carries (RFC 8097), reporting false when it is a community of some other kind. A state outside the three RFC 8097 defines is returned as is, per the RFC's instruction to treat unknown values as Not Found being a policy decision this package leaves to the caller.

type FSM

type FSM struct {
	// contains filtered or unexported fields
}

An FSM runs the RFC 4271 finite state machine for one peering. It owns the whole connection lifecycle:

  • Dialing and accepting connections.
  • Resolving simultaneous-open collisions.
  • The OPEN exchange.
  • The established session, with keepalives and the hold timer.

Received UPDATEs go to the caller's handlers; the FSM stores no routes.

The FSM is the expert, zero-copy layer. Values passed to its handlers borrow the connection's read buffer under the contract described on FSMConfig. One Connect executes one session attempt and returns when the machine comes back to Idle. Most callers want Peer, which wraps an FSM, hands its handlers fully owned values, and supplies the retry loop.

Example

The expert layer: an FSM delivers zero-copy borrowed values to its handlers and runs exactly one session attempt per Connect, leaving the retry policy to the caller. Most callers want Peer instead.

package main

import (
	"context"
	"log"
	"net/netip"
	"os"
	"os/signal"
	"time"

	"github.com/mdlayher/bgp"
)

func main() {
	// The FSM carries no addressing: its transport is a DialFunc, here a
	// Dialer closed over the remote address.
	var d bgp.Dialer
	peer := netip.MustParseAddr("192.0.2.1")

	// Retained attributes must be detached from the read buffer before the
	// handler returns; the prefixes are consumed in place.
	retained := map[netip.Prefix]bgp.RawAttributes{}

	f, err := bgp.NewFSM(bgp.FSMConfig{
		LocalASN: 64496,
		LocalID:  bgp.MustParseIdentifier("192.0.2.10"),
		PeerASN:  64497,
		DialFunc: func(ctx context.Context) (*bgp.Conn, error) {
			return d.Dial(ctx, peer)
		},

		OnUpdate: func(_ context.Context, _ *bgp.FSM, u *bgp.Update) error {
			// u borrows the connection's read buffer and is valid only for
			// this call: Clone what outlives it, and nothing else.
			for _, prefix := range u.Withdrawn {
				delete(retained, prefix)
			}

			if len(u.NLRI) == 0 {
				return nil
			}

			attrs := u.Attributes.Clone()
			for _, prefix := range u.NLRI {
				retained[prefix] = attrs
			}

			return nil
		},
		OnClose: func(_ *bgp.FSM, c bgp.Close) {
			log.Printf("closed: established=%t notification=%+v err=%v", c.Established, c.Notification, c.Err)
		},
	})
	if err != nil {
		log.Fatalf("failed to create FSM: %v", err)
	}

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
	defer stop()

	// Connect returns when the attempt ends, in Idle, and the FSM never
	// pauses there: the idle hold between attempts is the caller's retry
	// policy. Peer.Run is this loop with a jittered hold; a caller who
	// wants, say, to give up after a number of failures writes its own.
	for {
		if err := f.Connect(ctx); err != nil {
			// Only ctx cancellation returns an error; a failed attempt
			// returned nil after reporting its Close.
			log.Printf("stopped: %v", err)
			return
		}

		select {
		case <-time.After(5 * time.Second):
		case <-ctx.Done():
			return
		}
	}
}

func NewFSM

func NewFSM(c FSMConfig) (*FSM, error)

NewFSM validates the configuration and produces an FSM, in Idle. Nothing runs until Connect.

func (*FSM) Connect

func (f *FSM) Connect(ctx context.Context) error

Connect starts the state machine from Idle (RFC 4271's Start event) and blocks until it returns to Idle: one session attempt. The attempt spans dialing on the connect retry cadence, accepting, and resolving collisions, through the established session until it ends. The dial cadence lives inside the attempt, so Connect against an unreachable peer cycles the Connect and Active states indefinitely, exactly as RFC 4271's machine does. ctx bounds it.

Every path back to Idle returns, under one contract:

  • A failed attempt or an ended session returns nil. OnClose has reported its Close, exactly once. A caller's retry loop needs no other signal, and per-close-reason policy belongs in OnClose.
  • When ctx ends (the ManualStop event), Connect returns ctx's error instead. A Close is reported only when something observable was in flight: a connection which had begun the OPEN exchange.

The FSM never pauses in Idle. An idle hold before the next Connect is the caller's retry policy, and Peer.Run is the standard loop.

An FSM is sequentially reusable: Connect may be called again after it returns, never concurrently, and the *FSM identity is stable across attempts.

Cancellation sends any established session Cease / Administrative Shutdown. A *MessageError cancellation cause is sent verbatim instead (context.WithCancelCause, matched with errors.AsType). The cause is a complete, caller-owned farewell, which is how a caller shuts down with a Hard Reset (RFC 8538) or a dynamic communication (NewShutdownError).

func (*FSM) DeliverConn

func (f *FSM) DeliverConn(c *Conn) error

DeliverConn hands an inbound connection to the FSM: the passive open. The FSM must not be idle (a Connect must be in progress) and must be able to take a connection; on error, the caller retains ownership and should close the connection. On success the FSM owns it, though it may still refuse it, such as when a session is already established.

DeliverConn does not wait for Connect: a caller which starts Connect concurrently may see an error until it is active. Closing the refused connection is always sound, because a live remote speaker retries its open.

The FSM checks no address: it carries no addressing, so the caller's choice of FSM is the admission decision, and the PeerASN and PeerID pins negotiation enforces are the identity checks. A caller which needs to authenticate the remote beyond that must do so before it delivers the connection; Peer layers a TCP remote-address check for the peering it addresses.

The connection must behave like a net.Conn in the three respects the FSM depends on; see FSMConfig.DialFunc, which documents them.

func (*FSM) ResetSession

func (f *FSM) ResetSession(ctx context.Context, cause *MessageError) error

ResetSession ends the established session with a NOTIFICATION, leaving the FSM free to attempt a new one: the session reset behind a bounce, and the entry point for liveness signals such as a BFD session (RFC 5880) reporting the forwarding path down.

cause picks the NOTIFICATION, like a cancellation cause:

  • A nil cause sends Cease / Administrative Reset.
  • A non-nil cause is sent verbatim. A BFD-driven caller sends SubcodeCeaseBFDDown (RFC 9384), or wraps it in a Hard Reset (NewHardResetError) when the session negotiated graceful restart notification support, so the peer does not retain routes over broken forwarding.

ResetSession is synchronous: a nil return means the session is down and its OnClose has fired. ctx is honored until the FSM accepts the reset. After that the call is committed until the teardown, itself bounded, completes. ResetSession returns ErrNotEstablished when no session is established, so a reset racing the session's own death is visible but harmless.

ResetSession must not be called from the FSM's handlers: the teardown it waits for joins the handlers' own goroutine, so the call stalls the bounded reader join and the Close reports a stuck handler. A handler ends its session by returning an error instead. That is the stronger contract, since no handler fires after an error return. A handler reacting to something other than the message in hand starts a goroutine (go f.ResetSession).

func (*FSM) SendRouteRefresh

func (f *FSM) SendRouteRefresh(ctx context.Context, fam Family) error

SendRouteRefresh sends a ROUTE-REFRESH message (RFC 2918) on the established session, requesting that the peer re-advertise its routes for the given family. The peer must have advertised the route refresh capability, reported by Session.RouteRefresh; SendRouteRefresh returns an error rather than send an unnegotiated message. Its blocking and ordering behavior is SendUpdate's.

func (*FSM) SendUpdate

func (f *FSM) SendUpdate(ctx context.Context, u *Update) error

SendUpdate sends an UPDATE message on the established session, blocking until the session's writer accepts the message and the write completes. There is no queue, so a bulk push is throttled by the peer's receive rate. SendUpdate may be called from any goroutine, and messages sent by one goroutine are written in order.

ctx is honored until the message is accepted for writing. After that the call is committed until the write completes or the session ends. The commitment is bounded: the write runs under a deadline of the negotiated hold time, so a peer which stops reading fails the write and ends the session rather than parking the caller indefinitely.

The error reports the send's outcome:

  • ErrNotEstablished: no session is up.
  • An error wrapping ErrNotEstablished: the session died during the write.
  • The marshal error alone: the UPDATE failed to marshal, and the session stays healthy.

SendUpdate does not police the UPDATE's contents: the families advertised, and the attributes attached, are the caller's protocol business.

type FSMConfig

type FSMConfig struct {
	// Identity is the peering's protocol identity and negotiation surface,
	// shared verbatim with [PeerConfig]; see [Identity].
	Identity

	// DialFunc performs the active open, and is required unless Passive:
	// the FSM owns when to dial, and this function owns how and where,
	// since the FSM carries no addressing. For plain TCP it is a
	// one-line closure around [Dialer.Dial]; TCP-MD5 is a Peer-level option
	// (PeerConfig.MD5Password).
	//
	// DialFunc must honor ctx: the FSM cancels an abandoned dial and
	// closes the connection a late one produces.
	//
	// The connection DialFunc returns must behave like a net.Conn in three
	// respects the FSM depends on: Close unblocks a blocked read, so a
	// hold timer can tear the connection down; SetWriteDeadline is
	// honored, so no write can stall teardown or a send indefinitely; and
	// one goroutine may read while another writes.
	DialFunc func(ctx context.Context) (*Conn, error)

	// Passive suppresses the active open: connections are only accepted
	// via DeliverConn, and the attempt waits in Active for one.
	Passive bool

	// OnEstablished, if set, is called when a session reaches
	// Established, with its negotiated parameters. It is the first
	// handler of each session.
	//
	// Every handler runs on the session's receive path, and a nil
	// handler ignores its event. ctx is session-scoped: a child of
	// Connect's ctx, canceled when the session leaves Established. The
	// contract below applies to every handler:
	//
	//   - Values passed to handlers reference the connection's read
	//     buffer and are only valid for the duration of the call. Callers
	//     which retain data must copy it; the Clone methods detach a
	//     whole message at once. See [ParseMessage].
	//   - Blocking in a handler pauses receipt, so TCP backpressure
	//     reaches the peer. It never pauses transmission or timers.
	//   - All handler invocations for an FSM are serialized, across
	//     consecutive sessions too. Per session, OnEstablished is first
	//     and OnClose is last. OnClose fires only after the receive path
	//     has quiesced, so an Adj-RIB-In flush can never race a late
	//     OnUpdate.
	//   - A single invocation must return within the negotiated hold time
	//     ([Session.HoldTime]): the peer cannot be answered with keepalives
	//     forever while the receive path is stalled, so a longer stall
	//     tears the session down with Cease / Out of Resources. Bulk
	//     transmission must therefore not run synchronously inside
	//     OnEstablished; start a goroutine bound to ctx instead, and
	//     return.
	//   - A handler must watch ctx: teardown cancels it and waits a
	//     bounded time for the handler to return. A handler which ignores
	//     the cancellation is abandoned on its goroutine so the FSM can
	//     continue, Close.Err reports the abandonment, and the stuck
	//     invocation forfeits the ordering guarantees above: it may
	//     overlap OnClose and a later session's handlers.
	//   - A non-nil handler error terminates the session. If the error is
	//     a *MessageError (per errors.AsType), its code, subcode, and data
	//     become the NOTIFICATION sent to the peer; any other error sends
	//     Cease.
	OnEstablished func(ctx context.Context, f *FSM, s Session) error

	// OnUpdate, if set, is called for each UPDATE received while the
	// session is Established: the feed for an Adj-RIB-In.
	//
	// See [FSMConfig.OnEstablished] for the full handler contract.
	OnUpdate func(ctx context.Context, f *FSM, u *Update) error

	// OnRouteRefresh, if set, is called for each ROUTE-REFRESH message
	// (RFC 2918) received while the session is Established: the peer
	// asks this speaker to re-advertise a family's routes. Advertising
	// the route refresh capability requires this handler; see
	// Identity.RouteRefresh.
	//
	// See [FSMConfig.OnEstablished] for the full handler contract.
	OnRouteRefresh func(ctx context.Context, f *FSM, r *RouteRefresh) error

	// OnKeepalive, if set, is called for each KEEPALIVE received while
	// the session is Established; the KEEPALIVE which confirms the OPEN
	// exchange is OnEstablished's moment instead. A KEEPALIVE carries no
	// content, so the handler receives none. Most callers leave it nil:
	// the FSM already maintains the hold timer, and this hook only
	// observes peer liveness.
	//
	// See [FSMConfig.OnEstablished] for the full handler contract.
	OnKeepalive func(ctx context.Context, f *FSM) error

	// OnClose, if set, is called when a session ends, including when
	// Connect's ctx is canceled. It is also called when a session attempt
	// ends with something to report: a connection had begun the OPEN
	// exchange, or sending an OPEN failed. An attempt with nothing
	// observable in flight, such as one whose dial never produced a
	// connection, ends without OnClose. [Close.Established] distinguishes a
	// session end from a failed attempt. Connect returns nil exactly when
	// OnClose has reported the attempt's Close; see [FSM.Connect].
	OnClose func(f *FSM, c Close)

	// OnStateChange, if set, observes every transition of the state
	// machine, for metrics and diagnostics: from and to are RFC 4271,
	// section 8 states, aggregated across a collision's two connections
	// (see [State], including when the aggregate may regress). Every Connect
	// call emits a bookended stream. The first transition leaves Idle. The
	// last returns to it, emitted after any Close is reported, as the
	// attempt's final hook. The transition to Established precedes
	// OnEstablished.
	//
	// The hook observes only: it returns no error, and intermediate states
	// are diagnostics, not program inputs. Session logic belongs on
	// OnEstablished, OnClose, and [ErrNotEstablished]. It runs on the FSM
	// goroutine and must return promptly.
	OnStateChange func(f *FSM, from, to State)

	// OnMessage, if set, observes every message the peering exchanges on
	// the wire, in both directions, on every connection the FSM owns: the
	// tap for a monitoring protocol, a route collector's message log, or
	// a capture. See [MessageEvent] for exactly which frames fire.
	//
	// The tap observes only: it returns no error and can steer nothing.
	// The event's Raw and Message are lent under the handler contract
	// above, valid for the duration of the call; a Peer's tap receives
	// owned copies instead. A received message's event precedes that
	// message's handler.
	//
	// The tap runs on the goroutine which read or wrote the message: the
	// connection's reader for a received message, and for a sent one the
	// FSM goroutine before Established or the session's writer within it.
	// Invocations are serialized per connection per origin, and
	// concurrent otherwise: a collision's two readers, or a reader
	// against a writer, may overlap. The tap must therefore be safe for
	// concurrent use. It must also return promptly: it stalls receipt,
	// the FSM's timers, or the session's sends while it runs.
	OnMessage func(f *FSM, e MessageEvent)

	// Logger, if set, records state transitions and retry activity.
	Logger *slog.Logger
}

An FSMConfig configures an FSM. The embedded Identity's LocalASN and LocalID are required, and an FSM which is not Passive requires a DialFunc; the zero value of every other field is usable. The conveniences of a TCP speaker, such as a built-in Dialer, TCP-MD5, and a standing shutdown communication, are PeerConfig's.

type Family

type Family struct {
	AFI  AFI
	SAFI SAFI
}

A Family identifies a BGP address family by the combination of an AFI and SAFI, as described in RFC 4760.

func (Family) String

func (f Family) String() string

String returns the name of a Family.

type GracefulRestart

type GracefulRestart struct {
	// Restarting is the Restart State (R) bit: the speaker has restarted,
	// and this session is its first since.
	Restarting bool

	// NotificationSupport is the N bit (RFC 8538): the speaker supports
	// graceful restart procedures across sessions ended by a NOTIFICATION,
	// Hard Reset excepted.
	NotificationSupport bool

	// RestartTime is how long the peer should retain this speaker's routes
	// while it is away: whole seconds, at most 4095, per the 12 bit wire
	// field.
	RestartTime time.Duration

	// Families lists the families the speaker claims forwarding state for.
	Families []GracefulRestartFamily
}

A GracefulRestart is the decoded content of a graceful restart capability (RFC 4724, with the RFC 8538 N bit): a speaker's restart claims and the families whose forwarding state it preserves. This package carries the negotiation surface only. The behavior the capability negotiates, such as retaining a restarting peer's routes as stale, running the restart timer, and sweeping on End-of-RIB, belongs to the caller's RIB.

type GracefulRestartConfig

type GracefulRestartConfig struct {
	// RestartTime is the retention deadline advertised to the peer: how
	// long it should keep this speaker's routes while this speaker is
	// away. At most 4095 seconds, truncated to whole seconds.
	RestartTime time.Duration

	// NotificationSupport advertises the RFC 8538 N bit: this speaker
	// supports graceful restart procedures across sessions ended by a
	// NOTIFICATION, Hard Reset excepted.
	NotificationSupport bool

	// Families lists the families carried in the capability, each with its
	// forwarding-preserved claim. The claim is the caller's assertion; the
	// package does not verify forwarding state.
	Families []GracefulRestartFamily

	// Restarting, if set, decides the OPEN's Restart State (R) bit: true
	// while this session attempt is the recovery from a restart, false
	// once recovery is complete. It is consulted once per session attempt,
	// on the FSM goroutine, and must return promptly. A nil Restarting
	// never sets the bit.
	Restarting func() bool
}

A GracefulRestartConfig configures the graceful restart capability an FSM or Peer advertises (RFC 4724, with the RFC 8538 N bit). The static fields are validated at construction; only the Restart State bit varies per session attempt, through Restarting.

func (*GracefulRestartConfig) Clone

Clone makes a deep copy of GracefulRestartConfig. The result aliases no memory with the original.

type GracefulRestartFamily

type GracefulRestartFamily struct {
	Family              Family
	ForwardingPreserved bool
}

A GracefulRestartFamily is one family entry of a graceful restart capability: an address family, and the speaker's claim that its forwarding state for the family survived the restart (the F bit).

type Identifier

type Identifier uint32

An Identifier is a BGP identifier: a 4 byte number which identifies a speaker within an autonomous system, or a route reflection cluster. Per RFC 6286, an identifier is conventionally derived from one of a router's IPv4 addresses and is rendered in dotted quad form, but it is a number, not an address: it need not be routable, and IPv6-only speakers may use any value.

func MustParseIdentifier

func MustParseIdentifier(s string) Identifier

MustParseIdentifier parses an Identifier from its conventional dotted quad form, panicking on error: for tests with hard-coded strings.

func ParseIdentifier

func ParseIdentifier(s string) (Identifier, error)

ParseIdentifier parses an Identifier from its conventional dotted quad form, such as "192.0.2.1".

func (Identifier) String

func (id Identifier) String() string

String returns the conventional dotted quad form of an Identifier.

type Identity

type Identity struct {
	// LocalASN is the local autonomous system number, which must be nonzero
	// (RFC 7607). Four byte ASNs are handled natively; see [Open.ASN].
	LocalASN uint32

	// LocalID is the local BGP identifier, which must be nonzero.
	LocalID Identifier

	// HoldTime is the hold time proposed in the local OPEN message. The zero
	// value proposes the default of 90 seconds; nonzero values must be at
	// least 3 seconds, and are truncated to whole seconds, the wire
	// encoding's precision. The negotiated hold time is the minimum of the two
	// speakers' proposals, reported by [Session.HoldTime]. Proposing or
	// accepting a hold time of zero, which RFC 4271 permits to disable
	// keepalives entirely, is unsupported: without a hold timer a dead
	// connection is never detected.
	HoldTime time.Duration

	// PeerASN is the remote autonomous system number, or zero to accept
	// any. A peer whose OPEN carries a different ASN is rejected with Bad
	// Peer AS. With PeerID it pins who may answer. Where the peer is, is
	// addressing (NewPeer's addr, or an FSM's DialFunc), not identity.
	PeerASN uint32

	// PeerID is the remote BGP identifier, or zero to accept any. A peer
	// whose OPEN carries a different identifier is rejected with Bad BGP
	// Identifier (RFC 4271, section 6.2). Pinning both PeerASN and PeerID
	// to the local values is rejected at construction: an internal peer
	// bearing the local identifier can never establish (RFC 6286).
	PeerID Identifier

	// Families lists the address families to advertise via multiprotocol
	// capabilities (RFC 4760). An empty list advertises no multiprotocol
	// capabilities at all: the classic IPv4 unicast speaker.
	Families []Family

	// Capabilities carries any further capabilities verbatim: extended
	// next hop, FQDN, and anything this package does not model. The
	// capabilities this package encodes itself (multiprotocol, route
	// refresh, graceful restart, and the automatic four-octet AS) have
	// their own fields and are rejected here.
	Capabilities []Capability

	// RouteRefresh advertises the route refresh capability (RFC 2918): a
	// promise that this speaker will re-advertise a family's routes when
	// the peer asks. Keeping it is the caller's RIB's job, so RouteRefresh
	// requires OnRouteRefresh. Session.RouteRefresh reports the peer's
	// advertisement, which gates SendRouteRefresh.
	RouteRefresh bool

	// GracefulRestart, if set, advertises the graceful restart capability
	// (RFC 4724, with the RFC 8538 N bit). It must not also appear in
	// Capabilities: the Restart State bit varies per session attempt, so
	// the FSM owns the encoding. Only the negotiation surface lives in
	// this package; see [GracefulRestart] for what remains the caller's.
	GracefulRestart *GracefulRestartConfig
}

An Identity carries the protocol identity and negotiation surface of one peering: who the two speakers are, the hold time proposal, and the capabilities the local OPEN advertises. LocalASN and LocalID are required; the zero value of every other field is usable.

FSMConfig and PeerConfig embed an Identity rather than naming it, so its fields are set inline in either config's literal and an Identity assembled once can be assigned wholesale to both.

type Keepalive

type Keepalive struct{}

A Keepalive is a BGP KEEPALIVE message, exchanged to maintain an established session, as described in RFC 4271, section 4.4.

func (*Keepalive) AppendBinary

func (*Keepalive) AppendBinary(b []byte) ([]byte, error)

AppendBinary implements encoding.BinaryAppender.

type LargeCommunities

type LargeCommunities []LargeCommunity

LargeCommunities is the LARGE_COMMUNITY attribute: the large community values applied to a route.

type LargeCommunity

type LargeCommunity struct {
	// Global is the global administrator: the ASN of the autonomous system
	// which defined the community's meaning.
	Global uint32

	// Local1 and Local2 carry data whose meaning is defined by the global
	// administrator.
	Local1, Local2 uint32
}

A LargeCommunity is a BGP large community value, as described in RFC 8092, conventionally written as "global:local1:local2".

func (LargeCommunity) String

func (c LargeCommunity) String() string

String returns the conventional "global:local1:local2" form of a LargeCommunity.

type ListenConfig

type ListenConfig struct {
	// TCPOptions are the socket options applied to the listening socket,
	// which every accepted connection inherits.
	TCPOptions
}

A ListenConfig contains options for a Listener. The zero value is usable and produces a plain TCP listener.

func (*ListenConfig) Listen

func (lc *ListenConfig) Listen(ctx context.Context, laddr netip.AddrPort) (*Listener, error)

Listen begins listening for BGP connections on laddr, which must carry a valid address. A Listener is always bound to exactly one address family, so that socket options such as GTSM apply to every connection it accepts. To listen on every address of one family, use 0.0.0.0 or ::. A port of zero selects an ephemeral port, which Listener.Addr reports.

type Listener

type Listener struct {
	// contains filtered or unexported fields
}

A Listener accepts BGP connections opened by a peer: the passive open.

func (*Listener) Accept

func (l *Listener) Accept() (*Conn, error)

Accept waits for and returns the next connection to the Listener, mirroring the net.Listener method of the same name. Close unblocks a pending Accept.

func (*Listener) Addr

func (l *Listener) Addr() net.Addr

Addr returns the Listener's network address.

func (*Listener) Close

func (l *Listener) Close() error

Close closes the Listener, unblocking any pending Accept. Connections already returned by Accept are unaffected.

func (*Listener) RemoveMD5

func (l *Listener) RemoveMD5(peer netip.Addr) error

RemoveMD5 removes the TCP-MD5 key installed for peer by SetMD5, restoring plain TCP for the speaker at peer. Removing a key which was never installed is not an error.

RemoveMD5 is only supported on Linux, and elsewhere returns an error which wraps errors.ErrUnsupported.

func (*Listener) SetMD5

func (l *Listener) SetMD5(peer netip.Addr, password string) error

SetMD5 installs a TCP-MD5 (RFC 2385) key for the speaker at peer on the listening socket, authenticating the connections accepted from peer. The password must not be empty, and installing a key for a peer which already has one replaces it. The key must be installed before the peer's SYN arrives.

SetMD5 covers accepted connections only. On the Peer path, set PeerConfig.MD5Password instead: it signs the peer's dialed connections, and a Server installs it on its listeners via this method.

TCP-MD5 is only supported on Linux. Elsewhere, SetMD5 returns an error which wraps errors.ErrUnsupported.

type LocalPref

type LocalPref uint32

A LocalPref is the LOCAL_PREF attribute: a speaker's degree of preference for a route, exchanged between peers within an autonomous system.

type MED

type MED uint32

A MED is the MULTI_EXIT_DISC (Multi-Exit Discriminator) attribute, used to discriminate among multiple entry points to a neighboring autonomous system.

type MPReachNLRI

type MPReachNLRI struct {
	// Family is the address family of the advertised routes.
	Family Family

	// NextHop is the address of the router to be used as the next hop to
	// NLRI. Its address family need not match Family: per RFC 8950, IPv4
	// routes may be advertised with an IPv6 next hop when negotiated using
	// ExtendedNextHopCapability. The zero netip.Addr is an absent next
	// hop, a real wire shape: a flowspec UPDATE (RFC 8955) carries none.
	// The route distinguishers a VPN family wraps around its next hop on
	// the wire are managed by this package and never appear here.
	NextHop netip.Addr

	// LinkLocal optionally carries an IPv6 link-local next hop alongside
	// NextHop, as described in RFC 2545. It is the zero netip.Addr when
	// not present.
	LinkLocal netip.Addr

	// NLRI is the advertised reachability information, in the shape Family
	// determines: Prefixes for a prefix shaped family, EVPNRoutes for L2VPN
	// EVPN, RawNLRI for a family this package does not model.
	NLRI NLRI
}

An MPReachNLRI is the MP_REACH_NLRI attribute, advertising routes and a next hop for an arbitrary address family, as described in RFC 4760.

type MPUnreachNLRI

type MPUnreachNLRI struct {
	// Family is the address family of the withdrawn routes.
	Family Family

	// NLRI is the withdrawn reachability information, in the shape Family
	// determines; see [MPReachNLRI.NLRI]. A nil NLRI withdraws nothing, which
	// is the End-of-RIB marker of RFC 4724; see [NewEndOfRIB].
	NLRI NLRI
}

An MPUnreachNLRI is the MP_UNREACH_NLRI attribute, withdrawing routes for an arbitrary address family, as described in RFC 4760.

type Message

type Message interface {
	encoding.BinaryAppender
	// contains filtered or unexported methods
}

A Message is a BGP message which can append its binary form to a buffer. *Open, *Update, *Notification, *Keepalive, and *RouteRefresh implement Message. Call AppendBinary with a nil buffer for a standalone encoding.

func ParseMessage

func ParseMessage(b []byte) (Message, error)

ParseMessage parses a Message from b, which must contain exactly one BGP message.

To avoid copies, a parsed Message references b: do not modify or reuse b while the Message or data taken from it remains in use. To retain a Message longer, copy the referenced data (RawAttribute.Data, Capability.Data, and Notification.Data): the Clone methods on Update, Open, and Notification detach a whole message at once, RawAttributes.Clone detaches an attribute list alone, and RawAttribute.Parse returns Attributes which never reference b.

A malformed message produces a *MessageError describing the Notification RFC 4271 requires in response.

type MessageError

type MessageError struct {
	// Code and Subcode identify the error condition, and correspond to the
	// Notification fields of the same names.
	Code    NotificationCode
	Subcode uint8

	// Data carries the diagnostic information RFC 4271 requires for this
	// error condition, such as the erroneous length field of a message
	// header. It is nil when the condition requires no data.
	Data []byte
	// contains filtered or unexported fields
}

A MessageError is an error produced by a malformed BGP message, carrying the NOTIFICATION code and subcode RFC 4271 requires in response. A session implementation recognizes it with errors.AsType and answers the peer with Notification.

Unlike a Message produced by ParseMessage, a MessageError owns its Data and remains valid after the buffer it was parsed from is reused.

func NewHardResetError

func NewHardResetError(code NotificationCode, subcode uint8, communication string) (*MessageError, error)

NewHardResetError produces the *MessageError for a Hard Reset (RFC 8538, section 3): a Cease which tells an RFC 8538 helper not to retain this speaker's routes, where plain graceful restart would. Use it anywhere a *MessageError ends a session: as a context cancellation cause, as the reason passed to Server.RemovePeer, or as an error returned from a handler.

code and subcode name the underlying reason: the NOTIFICATION which would have been sent were Hard Reset not in effect. The reason rides encapsulated in the Hard Reset's data, as RFC 8538 describes.

communication optionally attaches an RFC 9003 shutdown communication to the reason. It is only valid when the reason is a Cease whose subcode is Administrative Shutdown or Administrative Reset. For any other reason it must be empty.

func NewShutdownError

func NewShutdownError(subcode uint8, communication string) (*MessageError, error)

NewShutdownError produces the *MessageError for an operator-initiated session end carrying an RFC 9003 shutdown communication. Use it anywhere a *MessageError ends a session: as a context cancellation cause, as the reason passed to Server.RemovePeer, or as an error returned from a handler. It serves a dynamic farewell composed at shutdown time. PeerConfig.ShutdownCommunication is the static counterpart.

subcode must be SubcodeCeaseAdministrativeShutdown or SubcodeCeaseAdministrativeReset. These are the only Cease subcodes RFC 9003 permits to carry a communication. communication must be valid UTF-8 of at most 255 bytes. It may be empty for a plain Cease.

Note that signal.NotifyContext cannot carry a cancellation cause. A caller which wants a farewell on a signal-driven shutdown watches the signal itself and cancels a context.WithCancelCause context with this error.

func (*MessageError) Error

func (e *MessageError) Error() string

Error implements error.

func (*MessageError) Notification

func (e *MessageError) Notification() *Notification

Notification produces the Notification to send to the peer in response to e, as described in RFC 4271, section 6. The Notification owns its Data: mutating it does not reach back into e.

type MessageEvent

type MessageEvent struct {
	// Direction is the direction the message traveled.
	Direction Direction

	// Raw is the message exactly as framed on the wire, header included.
	// When a received header's length field is invalid, Raw holds only the
	// header: framing is lost beyond it. When a write failed, some of Raw
	// may have crossed the wire.
	Raw []byte

	// Message is the parsed (received) or written (sent) message, or nil
	// when a received frame could not be parsed.
	Message Message

	// Err is nil for a message which parsed or wrote cleanly. On a
	// received event it is otherwise the *MessageError describing the
	// parse failure; on a sent event, the transport's write error.
	Err error

	// LocalAddr and RemoteAddr are the connection's endpoints, which
	// distinguish a collision's two connections. Their concrete types are
	// the transport's; a TCP connection reports *net.TCPAddr.
	LocalAddr, RemoteAddr net.Addr
}

A MessageEvent is one message crossing one connection, as reported to the OnMessage tap of an FSM or Peer. A tap observes every message a peering exchanges, in both directions and on every connection the state machine owns. That includes the OPEN exchange, a collision's losing connection, keepalives, and NOTIFICATIONs.

An event fires only when a frame was delimited, a complete header at minimum. A connection which dies mid-read fires nothing, and a message which failed to marshal never fires: no byte reached the connection.

type MessageType

type MessageType uint8

A MessageType is the type of a BGP Message.

const (
	MessageTypeOpen         MessageType = 1
	MessageTypeUpdate       MessageType = 2
	MessageTypeNotification MessageType = 3
	MessageTypeKeepalive    MessageType = 4
	MessageTypeRouteRefresh MessageType = 5
)

MessageType values, as assigned by IANA.

func (MessageType) String

func (t MessageType) String() string

String returns the name of a MessageType.

type NLRI

type NLRI interface {
	// contains filtered or unexported methods
}

An NLRI is the Network Layer Reachability Information of one address family: the payload of an MPReachNLRI or MPUnreachNLRI attribute, whose shape the family determines. NLRI is implemented by Prefixes, EVPNRoutes, and RawNLRI.

A nil NLRI carries no reachability information: it is what an End-of-RIB marker's MPUnreachNLRI holds, and what parse produces for any family whose NLRI is empty.

Reachability information is not universally prefix shaped: the families of RFC 4271 and RFC 4760 carry prefixes, EVPN carries typed records (RFC 7432), and others carry route distinguishers, labels, or flow specifications. A family this package does not model decodes to RawNLRI rather than to an error, so it survives parse and re-marshal byte for byte, as an unknown attribute type does.

type NextHop

type NextHop netip.Addr

A NextHop is the NEXT_HOP attribute: the IPv4 address of the router to be used as the next hop to the routes advertised in an Update. Next hops for other address families are carried by MPReachNLRI.

func (NextHop) String

func (n NextHop) String() string

String returns the string form of a NextHop's address.

type Notification

type Notification struct {
	// Code and Subcode indicate the type of error condition. Subcode values
	// are defined relative to a given Code.
	Code    NotificationCode
	Subcode uint8

	// Data carries diagnostic information whose contents depend on Code and
	// Subcode. When produced by ParseMessage, Data references the input
	// buffer rather than copying it; see [ParseMessage].
	Data []byte
}

A Notification is a BGP NOTIFICATION message, sent when an error condition is detected, as described in RFC 4271, section 4.5. The connection is closed immediately after a NOTIFICATION is sent or received.

func (*Notification) AppendBinary

func (n *Notification) AppendBinary(b []byte) ([]byte, error)

AppendBinary implements encoding.BinaryAppender.

func (*Notification) Clone

func (src *Notification) Clone() *Notification

Clone makes a deep copy of Notification. The result aliases no memory with the original.

func (*Notification) HardReset

func (n *Notification) HardReset() (*Notification, bool)

HardReset returns the reason encapsulated in a Hard Reset NOTIFICATION (RFC 8538, section 3): the NOTIFICATION which would have been sent were Hard Reset not in effect. It is NewHardResetError's decoding counterpart.

HardReset reports false when n carries no encapsulated reason. A nil n also reports false, so it may be called directly on Close.Notification. So does a Hard Reset with empty data, which some speakers send: detecting a Hard Reset at all needs only n's own Code and Subcode, not this method.

The returned Notification's Data references n.Data rather than copying it.

func (*Notification) ShutdownCommunication

func (n *Notification) ShutdownCommunication() (string, bool)

ShutdownCommunication returns the RFC 9003 shutdown communication carried by a NOTIFICATION: a human-readable UTF-8 message telling the remote operator why the session ended. Only a Cease whose subcode is Administrative Shutdown or Administrative Reset can carry one.

ShutdownCommunication reports false when n carries no valid communication. A nil n also reports false, so it may be called directly on Close.Notification.

type NotificationCode

type NotificationCode uint8

A NotificationCode is the broad category of error condition conveyed by a Notification.

const (
	NotificationMessageHeaderError NotificationCode = 1
	NotificationOpenMessageError   NotificationCode = 2
	NotificationUpdateMessageError NotificationCode = 3
	NotificationHoldTimerExpired   NotificationCode = 4
	NotificationFSMError           NotificationCode = 5
	NotificationCease              NotificationCode = 6
)

NotificationCode values, as assigned by IANA.

func (NotificationCode) String

func (c NotificationCode) String() string

String returns the name of a NotificationCode.

type OTC

type OTC uint32

An OTC is the OTC (Only to Customer) attribute: the autonomous system beyond which a route must only propagate toward customers, used to detect and prevent route leaks, as described in RFC 9234. The role negotiation half of RFC 9234 (an OPEN capability) is out of scope for this package; the attribute is meaningful standalone.

type Open

type Open struct {
	// ASN is the speaker's autonomous system number. Four byte ASNs (RFC
	// 6793) are handled natively: marshaling generates the Four-Octet AS
	// Number capability and parsing consumes it, so it must not appear in
	// Capabilities.
	ASN uint32

	// HoldTime proposes the session's hold time. It must be zero or at
	// least 3 seconds, and is truncated to a whole number of seconds on
	// the wire.
	HoldTime time.Duration

	// ID is the speaker's BGP identifier, unique within an autonomous
	// system; see [Identifier].
	ID Identifier

	// Capabilities advertises the speaker's optional capabilities, as
	// described in RFC 5492.
	Capabilities []Capability
	// contains filtered or unexported fields
}

An Open is a BGP OPEN message, the first message sent by each peer after a connection is established, as described in RFC 4271, section 4.2.

func (*Open) AppendBinary

func (o *Open) AppendBinary(b []byte) ([]byte, error)

AppendBinary implements encoding.BinaryAppender.

func (*Open) Clone

func (src *Open) Clone() *Open

Clone makes a deep copy of Open. The result aliases no memory with the original.

type Origin

type Origin uint8

An Origin is the ORIGIN attribute, describing how the routes in an Update were originally learned.

const (
	OriginIGP        Origin = 0
	OriginEGP        Origin = 1
	OriginIncomplete Origin = 2
)

Origin values, as described in RFC 4271.

func (Origin) String

func (o Origin) String() string

String returns the name of an Origin.

type OriginAS

type OriginAS struct {
	// ASN is the rightmost autonomous system of the path's final
	// AS_SEQUENCE, or zero when Set or Empty.
	ASN uint32

	// Set reports that the path's final segment is an AS_SET.
	Set bool

	// Empty reports a path with no autonomous system at all.
	Empty bool
}

An OriginAS is the origin of a route as its AS_PATH names it, derived per RFC 6811, section 2, for route origin validation. Exactly one of the three readings applies: ASN names the origin; Set reports that the path ends in an AS_SET, whose origin is RFC 6811's "NONE" and matches no authorization; Empty reports a path with no autonomous system at all, whose origin is the receiving speaker's own AS, which the path cannot know.

type OriginatorID

type OriginatorID Identifier

An OriginatorID is the ORIGINATOR_ID attribute: the BGP identifier of the route's originator in the local autonomous system, added by a route reflector, as described in RFC 4456.

func (OriginatorID) String

func (o OriginatorID) String() string

String returns the conventional dotted quad form of an OriginatorID.

type Peer

type Peer struct {
	// contains filtered or unexported fields
}

A Peer runs one BGP peering with one remote speaker, forever. It wraps an FSM, which handles dialing, collision resolution, the OPEN exchange, keepalives, and the hold timer, as described in RFC 4271. Session attempts are retried with a short jittered idle hold between them until Run's ctx is canceled. Received UPDATEs go to the caller's handlers as fully owned deep copies. The Peer stores no routes.

Peer is the mainstream layer, and a RIB plugs in here: its handlers carry no buffer-lifetime rules, at the cost of one Update.Clone per received message. A caller which needs zero-copy delivery, or its own retry policy, builds on FSM directly.

func NewPeer

func NewPeer(addr netip.Addr, c PeerConfig) (*Peer, error)

NewPeer validates the configuration and produces a Peer for the peering with the remote speaker at addr. Nothing runs until Run.

addr is addressing, not identity. It serves three roles:

  • The dial target of the active open.
  • The remote address a TCP connection handed to DeliverConn must match.
  • The key a Server files the peering under.

Who may answer there is pinned by PeerASN and PeerID, and the port dialed is Dialer.Port. An active peer using the built-in Dialer requires addr. A Passive peer, or a DialFunc transport, may leave it zero, and DeliverConn then checks no address.

func (*Peer) Addr

func (p *Peer) Addr() netip.Addr

Addr returns the remote address of the peering: NewPeer's addr, normalized, or the zero Addr when none was given. It is the peering's stable address for callers which hold only a Peer, such as a RIB breaking ties on lowest peer address, and it does not vary with the transport the way Session.RemoteAddr does. A peering whose transport does not address peers by IP, such as a DialFunc transport, may still set addr so the peering carries a stable address here.

func (*Peer) DeliverConn

func (p *Peer) DeliverConn(c *Conn) error

DeliverConn hands an inbound connection to the peer: the passive open. The Peer must be running. On error, the caller retains ownership and should close the connection; on success the Peer owns it. A connection delivered during the idle hold between session attempts ends the hold early and seeds the next attempt. Only in the narrow window where no hold is ready to take it is it answered with Cease / Connection Rejected and closed.

A TCP connection's remote address must match NewPeer's addr when addr is set. Any other address type, and any connection when addr is unset, is accepted as it stands. The PeerASN and PeerID pins negotiation enforces are then the identity checks. A caller which needs to authenticate the remote beyond that must do so before it delivers the connection.

The transport requirements are the FSM's; see FSM.DeliverConn. A Peer managed by a Server returns an error: the Server's listeners deliver its connections.

Example

A passive speaker: the peer never dials, and connections arrive from a Listener via DeliverConn.

package main

import (
	"context"
	"log"
	"net/netip"
	"os"
	"os/signal"

	"github.com/mdlayher/bgp"
)

func main() {
	// The peering's port is only used for dialing, so a passive peer may
	// leave it zero, but every delivered connection's remote address must
	// match the peering's address.
	p, err := bgp.NewPeer(netip.MustParseAddr("192.0.2.1"), bgp.PeerConfig{
		LocalASN: 64496,
		LocalID:  bgp.MustParseIdentifier("192.0.2.10"),
		PeerASN:  64497,
		Passive:  true,

		OnUpdate: func(_ context.Context, _ *bgp.Peer, u *bgp.Update) error {
			log.Printf("update: reachable %v, withdrawn %v", u.NLRI, u.Withdrawn)
			return nil
		},
	})
	if err != nil {
		log.Fatalf("failed to create peer: %v", err)
	}

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
	defer stop()

	// With TCP-MD5 in play, install keys via Listener.SetMD5 before the
	// remote speaker's SYN arrives.
	var lc bgp.ListenConfig
	l, err := lc.Listen(ctx, netip.MustParseAddrPort("192.0.2.10:179"))
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	defer l.Close()

	go func() {
		for {
			c, err := l.Accept()
			if err != nil {
				return
			}

			// Closing a refused connection is always sound: a live remote
			// speaker retries its open.
			if err := p.DeliverConn(c); err != nil {
				_ = c.Close()
			}
		}
	}()

	if err := p.Run(ctx); err != nil {
		log.Fatalf("failed to run peer: %v", err)
	}
}

func (*Peer) ResetSession

func (p *Peer) ResetSession(ctx context.Context, cause *MessageError) error

ResetSession ends the established session with a NOTIFICATION: the bounce. The cause and blocking contract are FSM.ResetSession's. The peering then continues, and the retry loop establishes a fresh session after the idle hold. Server.RemovePeer, by contrast, ends the peering. Like the send methods, ResetSession works on a Server-managed Peer.

func (*Peer) Run

func (p *Peer) Run(ctx context.Context) error

Run drives the peering until ctx is canceled: one FSM Connect after another, forever, with a short jittered idle hold after each failure. A session failure goes to OnClose and is retried. Cancellation is the only exit. "Give up after N failures" is caller policy via cancellation, and any other retry policy is a caller loop over FSM.Connect instead.

Run always returns a non-nil error:

  • ctx's error, once canceled.
  • An error when the Peer is already running, or is managed by a Server, which runs its peers itself.

func (*Peer) SendRouteRefresh

func (p *Peer) SendRouteRefresh(ctx context.Context, f Family) error

SendRouteRefresh sends a ROUTE-REFRESH message (RFC 2918) on the established session. The contract is FSM.SendRouteRefresh's.

func (*Peer) SendUpdate

func (p *Peer) SendUpdate(ctx context.Context, u *Update) error

SendUpdate sends an UPDATE message on the established session. The blocking, ordering, and error contract is FSM.SendUpdate's. The message is the caller's own and is written as given, never copied: ownership wrapping applies to received values, not sent ones.

type PeerConfig

type PeerConfig struct {
	// Identity is the peering's protocol identity and negotiation surface,
	// shared verbatim with [FSMConfig]; see [Identity].
	Identity

	// MD5Password optionally enables TCP-MD5 (RFC 2385) on the peering with
	// the given key, which must match the key configured by the peer. An
	// empty password disables TCP-MD5.
	//
	// The key is a property of the peering and covers both directions of
	// connection: the Peer signs the connections it dials, and a Server
	// installs the key on its listening sockets before the peer runs. A
	// caller accepting connections without a Server must install the key on
	// its own Listener via [Listener.SetMD5], before the remote SYN arrives.
	//
	// Keys are plain strings by design: BGP MD5 keys are cleartext
	// operational strings in every router configuration, not secrets in the
	// cryptographic sense.
	//
	// TCP-MD5 is only supported on Linux. Elsewhere, connections fail with
	// an error which wraps [errors.ErrUnsupported].
	MD5Password string

	// Dialer supplies the transport of the active open: its TCPOptions,
	// the local bind address, and the port the peer is dialed on. It is
	// ignored when DialFunc is set, and on a Passive peer.
	Dialer Dialer

	// DialFunc, when non-nil, performs the active open in place of Dialer:
	// the seam for a transport which is not TCP, passed through to the
	// Peer's FSM. The transport addresses its own target, so NewPeer's
	// addr may be zero. DialFunc must honor ctx, and the connection it
	// returns must behave like a net.Conn in the three respects the FSM
	// depends on; see [FSMConfig.DialFunc]. Setting DialFunc together with
	// MD5Password, or on a Passive peer, is an error.
	DialFunc func(ctx context.Context) (*Conn, error)

	// Passive suppresses the active open: the peering only accepts
	// connections, through DeliverConn or a Server's listeners.
	Passive bool

	// OnEstablished, if set, is called when a session reaches
	// Established, with its negotiated parameters. It is the first
	// handler of each session.
	//
	// Every handler runs on the session's receive path, and a nil
	// handler ignores its event. ctx is session-scoped: a child of Run's
	// ctx, canceled when the session leaves Established. The contract
	// below applies to every handler:
	//
	//   - Every value a handler receives is fully owned: unlike the FSM's
	//     zero-copy handlers, an Update or RouteRefresh here is a deep
	//     copy the caller may retain indefinitely and hand to other
	//     goroutines freely.
	//   - Blocking in a handler pauses receipt, so TCP backpressure
	//     reaches the peer. It never pauses transmission or timers. A
	//     slow consumer which must not stall the session hands its owned
	//     values to its own goroutine through a queue of its choosing:
	//     the handler blocking on a full queue is exactly how
	//     backpressure is meant to reach the wire.
	//   - All handler invocations for a Peer are serialized, across
	//     consecutive sessions too. Per session, OnEstablished is first
	//     and OnClose is last. OnClose fires only after the receive path
	//     has quiesced, so an Adj-RIB-In flush can never race a late
	//     OnUpdate.
	//   - A single invocation must return within the negotiated hold time
	//     ([Session.HoldTime]): the peer cannot be answered with keepalives
	//     forever while the receive path is stalled, so a longer stall
	//     tears the session down with Cease / Out of Resources. Bulk
	//     transmission must therefore not run synchronously inside
	//     OnEstablished; start a goroutine bound to ctx instead, and
	//     return.
	//   - A handler must watch ctx: teardown cancels it and waits a
	//     bounded time for the handler to return. A handler which ignores
	//     the cancellation is abandoned on its goroutine so the Peer can
	//     continue, Close.Err reports the abandonment, and the stuck
	//     invocation forfeits the ordering guarantees above: it may
	//     overlap OnClose and a later session's handlers.
	//   - A non-nil handler error terminates the session. If the error is
	//     a *MessageError (per errors.AsType), its code, subcode, and data
	//     become the NOTIFICATION sent to the peer; any other error sends
	//     Cease.
	OnEstablished func(ctx context.Context, p *Peer, s Session) error

	// OnUpdate, if set, is called for each UPDATE received while the
	// session is Established: the feed for an Adj-RIB-In. The Update is
	// a fully owned deep copy.
	//
	// See [PeerConfig.OnEstablished] for the full handler contract.
	OnUpdate func(ctx context.Context, p *Peer, u *Update) error

	// OnRouteRefresh, if set, is called for each ROUTE-REFRESH message
	// (RFC 2918) received while the session is Established: the peer
	// asks this speaker to re-advertise a family's routes. Advertising
	// the route refresh capability requires this handler; see
	// Identity.RouteRefresh.
	//
	// See [PeerConfig.OnEstablished] for the full handler contract.
	OnRouteRefresh func(ctx context.Context, p *Peer, r *RouteRefresh) error

	// OnKeepalive, if set, is called for each KEEPALIVE received while
	// the session is Established; the KEEPALIVE which confirms the OPEN
	// exchange is OnEstablished's moment instead. A KEEPALIVE carries no
	// content, so the handler receives none. Most callers leave it nil:
	// the hold timer is maintained regardless, and this hook only
	// observes peer liveness.
	//
	// See [PeerConfig.OnEstablished] for the full handler contract.
	OnKeepalive func(ctx context.Context, p *Peer) error

	// OnClose, if set, is called when a session ends, including when
	// Run's ctx is canceled. It is also called when a session attempt
	// ends with something to report: a connection had begun the OPEN
	// exchange, or sending an OPEN failed. An attempt with nothing
	// observable in flight, such as one whose dial never produced a
	// connection, ends without OnClose. [Close.Established] distinguishes a
	// session end from a failed attempt. The session is already down and
	// will be retried by Run, so OnClose only observes.
	//
	// OnClose runs on the peer's own goroutine, and a Server's removal
	// verbs wait for that goroutine: calling them from OnClose deadlocks,
	// the classic self-join. A handler which notices its own peering died
	// removes it from a new goroutine (go s.RemovePeer(addr, nil)).
	OnClose func(p *Peer, c Close)

	// ShutdownCommunication optionally attaches a standing farewell to the
	// Cease with subcode Administrative Shutdown sent when Run's ctx is
	// canceled, whether or not a session is established: an RFC 9003
	// shutdown communication for the remote operator to read. It must be
	// valid UTF-8 of at most 255 bytes, validated by NewPeer, never
	// truncated. The remote speaker's own communication, if any, can be
	// decoded from [Close.Notification] via
	// [Notification.ShutdownCommunication].
	//
	// A cancellation cause overrides it: when Run's ctx was canceled with
	// a *MessageError cause (context.WithCancelCause, per errors.AsType),
	// that NOTIFICATION is sent verbatim instead. That is how a caller
	// shuts down with a Hard Reset (RFC 8538) or a dynamic communication
	// (NewShutdownError), and how a Server ends a removed peering.
	ShutdownCommunication string

	// OnStateChange, if set, observes every transition of the underlying
	// state machine, forwarded verbatim from the FSM; see
	// FSMConfig.OnStateChange for the contract. The Peer's idle hold
	// between attempts happens while the state is Idle.
	OnStateChange func(p *Peer, from, to State)

	// OnMessage, if set, observes every message the peering exchanges on
	// the wire, in both directions, forwarded from the FSM with the
	// event's Raw and Message fully owned; see FSMConfig.OnMessage for
	// the contract, in particular that the tap must be safe for
	// concurrent use and return promptly.
	OnMessage func(p *Peer, e MessageEvent)

	// Logger, if set, records state transitions and retry activity.
	Logger *slog.Logger
}

A PeerConfig configures a Peer. The embedded Identity's LocalASN and LocalID are required; the zero value of every other field is usable. The remote address is NewPeer's addr parameter, not configuration.

type Prefixes

type Prefixes []netip.Prefix

Prefixes is the NLRI of a prefix shaped address family: IPv4 or IPv6, unicast or multicast. It is the shape of all reachability information in RFC 4271 and of the address families RFC 4760 was written for.

A Prefixes may only belong to one of those four families. Reachability information which happens to contain a prefix but is not a bare list of them, such as an RFC 4364 labeled VPN route or an RFC 9136 EVPN IP prefix route, is not a Prefixes.

type RawAttribute

type RawAttribute struct {
	Flags AttrFlags
	Type  AttrType
	Data  []byte
}

A RawAttribute is a BGP path attribute in raw binary form, as carried in an Update. Parse decodes a RawAttribute into one of this package's Attribute types, at the cost of additional allocations; callers which do not inspect attribute contents may skip parsing entirely.

When produced by ParseMessage, Data references the input buffer rather than copying it; see ParseMessage.

func (*RawAttribute) Clone

func (src *RawAttribute) Clone() *RawAttribute

Clone makes a deep copy of RawAttribute. The result aliases no memory with the original.

func (RawAttribute) Parse

func (a RawAttribute) Parse() (Attribute, error)

Parse decodes a RawAttribute into a typed Attribute. Attributes of a type unknown to this package cannot be parsed, and remain available in raw form. Parsed Attributes never reference Data, and remain valid after the buffer Data references is reused.

A malformed attribute of a known type produces a *MessageError which carries the erroneous attribute as its diagnostic data, per RFC 4271, section 6.3. An attribute of an unknown type produces a plain error: it is not a protocol error, and RFC 4271 requires unrecognized optional transitive attributes be passed along unmodified.

type RawAttributes

type RawAttributes []RawAttribute

RawAttributes is the path attribute list of an Update, in raw binary form.

Its Clone method is the retention primitive for callers which store attributes past the lifetime of the buffer they reference, most typically a RIB keeping a route's attributes, without cloning the whole Update.

func MarshalAttributes

func MarshalAttributes(attrs ...Attribute) (RawAttributes, error)

MarshalAttributes converts typed Attributes into raw form, for use in an Update message.

func (RawAttributes) Clone

func (as RawAttributes) Clone() RawAttributes

Clone returns a deep copy of the attribute list which shares no memory with the original: each attribute's Data survives reuse of the buffer a parsed message references. A nil list clones to nil.

func (RawAttributes) Find

func (as RawAttributes) Find(t AttrType) (RawAttribute, bool)

Find returns the first attribute of type t in as, in raw form: the lookup for a caller which only needs to test presence, or which needs an attribute this package does not interpret. Lookup is the typed form.

func (RawAttributes) Parse

func (as RawAttributes) Parse() ([]Attribute, error)

Parse decodes every attribute in as into typed form, in list order. An attribute of a type this package does not interpret is returned as the RawAttribute itself, which implements Attribute: nothing is dropped, and MarshalAttributes passes such an attribute along unmodified, as RFC 4271, section 5 requires of unrecognized optional transitive attributes.

A malformed attribute of a known type fails the whole parse with its *MessageError; see RawAttribute.Parse. Typed values never reference the raw list's Data, but a RawAttribute returned as-is does, so a caller retaining the result past the lifetime of the buffer the list references clones the list first.

type RawNLRI

type RawNLRI []byte

A RawNLRI is reachability information in raw binary form: the shape of an address family this package does not model, and the escape hatch for sending one. It is the NLRI counterpart of an unparsed RawAttribute.

Unlike Prefixes and EVPNRoutes, a RawNLRI belongs to no particular family, which is what makes it an escape hatch: a caller may use it to send a family whose shape this package models differently, at the cost of parsing that family's NLRI themselves. Parse never produces a RawNLRI for a family this package does model.

type RouteRefresh

type RouteRefresh struct {
	// Family is the address family of the routes to be refreshed.
	Family Family
}

A RouteRefresh is a BGP ROUTE-REFRESH message: a request that a peer re-advertise its routes for a given address family, as described in RFC 2918. Support is negotiated using CapabilityRouteRefresh.

func (*RouteRefresh) AppendBinary

func (r *RouteRefresh) AppendBinary(b []byte) ([]byte, error)

AppendBinary implements encoding.BinaryAppender.

func (*RouteRefresh) Clone

func (src *RouteRefresh) Clone() *RouteRefresh

Clone makes a deep copy of RouteRefresh. The result aliases no memory with the original.

type SAFI

type SAFI uint8

A SAFI is a BGP Subsequent Address Family Identifier, as described in RFC 4760.

const (
	SAFIUnicast   SAFI = 1
	SAFIMulticast SAFI = 2
	SAFIVPLS      SAFI = 65
	SAFIEVPN      SAFI = 70
	SAFIMPLSVPN   SAFI = 128
)

SAFI values named by this package, as assigned by IANA. As with AFI values, naming is not a precondition for carrying a family.

func (SAFI) String

func (s SAFI) String() string

String returns the name of a SAFI, or its number when unnamed.

type Server

type Server struct {
	// contains filtered or unexported fields
}

A Server coordinates any number of Peers with one remote speaker each:

  • It accepts connections on the listeners handed to Run.
  • It hands each accepted connection to the Peer configured for its remote address, and rejects connections from unconfigured peers.
  • It runs every peer's connection lifecycle.

Like a Peer, a Server stores no routes and makes no routing decisions.

Each peering's TCP-MD5 key is the Server's to install: on every listener of the peering's address family, before the peer runs and before any connection is accepted. See Server.Run for the listen backlog's caveat.

Example

A Server coordinating multiple peerings: one listener demultiplexes inbound connections by remote address, unconfigured peers are observed and rejected, and shutdown delivers an operator farewell to every session.

package main

import (
	"context"
	"log"
	"net/netip"
	"os"
	"os/signal"

	"github.com/mdlayher/bgp"
)

func main() {
	srv := bgp.NewServer(bgp.ServerConfig{
		// Observe connections from addresses with no configured Peer;
		// paired with AddPeer, this hook is the building block for dynamic
		// neighbors.
		OnUnconfiguredPeer: func(_ context.Context, raddr netip.AddrPort, o *bgp.Open) {
			if o != nil {
				log.Printf("unconfigured peer %s claims AS%d", raddr, o.ASN)
			}
		},
	})

	// Each peering stands alone: its own remote speaker, TCP-MD5 key, and
	// standing shutdown farewell. The Server files each peering under its
	// remote address and installs each key on its listeners as Run starts,
	// before any peer runs.
	for _, peering := range []struct {
		addr netip.Addr
		cfg  bgp.PeerConfig
	}{
		{
			addr: netip.MustParseAddr("192.0.2.1"),
			cfg: bgp.PeerConfig{
				LocalASN:              64496,
				LocalID:               bgp.MustParseIdentifier("192.0.2.10"),
				PeerASN:               64497,
				MD5Password:           "correct horse battery staple",
				ShutdownCommunication: "transit-a maintenance, back soon",
			},
		},
		{
			addr: netip.MustParseAddr("192.0.2.2"),
			cfg: bgp.PeerConfig{
				LocalASN: 64496,
				LocalID:  bgp.MustParseIdentifier("192.0.2.10"),
				PeerASN:  64498,
				Passive:  true,
			},
		},
	} {
		if _, err := srv.AddPeer(peering.addr, peering.cfg); err != nil {
			log.Fatalf("failed to add peer: %v", err)
		}
	}

	// A dynamic farewell overrides each peer's static default. Note
	// signal.NotifyContext cannot carry a cancellation cause, so watch the
	// signal directly and cancel with the cause instead.
	drain, err := bgp.NewShutdownError(bgp.SubcodeCeaseAdministrativeShutdown, "emergency drain INC-77")
	if err != nil {
		log.Fatalf("failed to create shutdown error: %v", err)
	}

	ctx, cancel := context.WithCancelCause(context.Background())
	go func() {
		sig := make(chan os.Signal, 1)
		signal.Notify(sig, os.Interrupt)
		<-sig
		cancel(drain)
	}()

	// The Server accepts on listeners the caller binds, and closes them
	// when Run returns. Bind immediately before Run: a handshake the kernel
	// completes in between meets no key.
	l, err := (&bgp.ListenConfig{}).Listen(ctx, netip.MustParseAddrPort("192.0.2.10:179"))
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	if err := srv.Run(ctx, l); err != nil {
		log.Fatalf("failed to run server: %v", err)
	}
}

func NewServer

func NewServer(cfg ServerConfig) *Server

NewServer creates a Server with its configuration. Peers are configured separately via AddPeer, and listeners are handed to Run.

func (*Server) AddPeer

func (s *Server) AddPeer(addr netip.Addr, cfg PeerConfig) (*Peer, error)

AddPeer configures a peering with the remote speaker at addr. The configuration is validated by NewPeer, and the peering's key (if any) is installed on every matching-family listener. When the Server is running, the peer starts immediately; peers added before Run are started by Run. The returned Peer is valid for SendUpdate and SendRouteRefresh; its Run and DeliverConn belong to the Server and return errors if called.

addr is required: the Server demultiplexes inbound connections by it, so its peers cannot use NewPeer's unaddressed allowances. At most one peer may exist per remote address, and AddPeer returns an error for a duplicate. Configuration changes are RemovePeer then AddPeer: a Peer is immutable.

func (*Server) Peers

func (s *Server) Peers() iter.Seq2[netip.Addr, *Peer]

Peers iterates over the configured peerings in ascending remote address order. The sequence is a snapshot taken when iteration begins: it is safe to call AddPeer or RemovePeer during iteration, and concurrent changes are not reflected.

func (*Server) RemovePeer

func (s *Server) RemovePeer(addr netip.Addr, cause *MessageError) error

RemovePeer removes the peering with the given remote address: an established session ends with a NOTIFICATION, the peer's Run is waited for, and the peering's keys are removed from the Server's listeners. When RemovePeer returns, the peering is fully gone.

A nil cause ends the session with Cease / Peer De-configured (RFC 4486). A non-nil cause is sent verbatim in its place, like any cancellation cause (see PeerConfig.ShutdownCommunication): removal with Cease / Administrative Reset (see NewShutdownError) followed by AddPeer is the bounce, the "clear" of router CLIs, and per-peer farewells on a mass shutdown are removals before canceling Run's ctx. Each call blocks for its own drain, so a large fleet issues them concurrently.

RemovePeer must not be called from the removed peer's own handlers or OnClose: it waits for the peer's run to return, and the peer's run is the goroutine invoking them: the classic self-join deadlock. A handler which notices its own peering died removes it from a new goroutine (go s.RemovePeer(addr, nil)).

After removal, the remote speaker keeps retrying its open, and those connections arrive as unconfigured peers; see ServerConfig.OnUnconfiguredPeer.

func (*Server) Run

func (s *Server) Run(ctx context.Context, ls ...*Listener) error

Run accepts connections on the given listeners and runs every configured peer until ctx is canceled: delivering each connection to the Peer for its remote address, and rejecting unconfigured peers. The Server owns each Listener for the duration of the call and closes every one before returning, on every path; no listeners at all is valid, for a Server whose peers only dial. Session failures are retried by each Peer forever. An infrastructure failure, such as a key which cannot be installed or a listener whose Accept fails, takes Run down with an error. Run always returns a non-nil error: ctx's error, an infrastructure error, or an error if the Server is already running.

Each peering's TCP-MD5 key is installed on the listeners of its address family as Run starts, before any connection is accepted. The listen backlog predates Run, though: a handshake the kernel completed between the caller's Listen and Run met no key, the same window a live AddPeer has on any bound socket. Keep Listen and Run adjacent.

Like Peer.Run, cancellation sends every established session Cease / Administrative Shutdown, or the ctx's *MessageError cancellation cause; see PeerConfig.ShutdownCommunication.

Run returns once every peer it manages has stopped, with one exception: a peer being removed concurrently is joined by its RemovePeer call rather than by Run, so that removal's drain may still be in flight when Run returns.

type ServerConfig

type ServerConfig struct {
	// OnUnconfiguredPeer, if set, observes each connection from an address
	// with no configured Peer. o is the OPEN the remote sent, or nil when
	// none arrived in time; like handler values, it references the
	// connection's read buffer and is only valid for the duration of the
	// call. The hook observes only: the Server owns the connection, always
	// answers Cease / Connection Rejected, and closes it. It may be
	// invoked concurrently.
	//
	// ctx is run-scoped: canceled when Run shuts down. Like the Peer
	// handlers, the hook must watch it and return promptly. A hook which
	// ignores the cancellation is abandoned on its goroutine after a
	// bounded wait so Run can still return.
	//
	// Combined with AddPeer, the hook is the building block for dynamic
	// neighbors: observe the unconfigured peer, add a Peer for its
	// address, and the remote's retry connects normally moments later. A
	// removed peering's remote keeps retrying and arrives here too, so
	// consult intent (configuration, a deny list) before re-adding.
	//
	// When the hook is nil, connections from unconfigured peers are
	// rejected the same way, without waiting for an OPEN.
	OnUnconfiguredPeer func(ctx context.Context, raddr netip.AddrPort, o *Open)

	// Logger configures a logger for server events. The default is to
	// discard all logs. Each Peer carries its own PeerConfig.Logger.
	Logger *slog.Logger
}

A ServerConfig configures a Server. The listeners it accepts on are Run's parameters.

type Session

type Session struct {
	// Peer is the remote speaker's OPEN message, so uninterpreted
	// capabilities stay reachable. Unlike a Message from ParseMessage, it is
	// fully owned and remains valid for the life of the session and beyond.
	Peer *Open

	// Local is the OPEN this speaker sent on the session's connection, so
	// both-sides questions, such as RFC 8538's N bit conjunction or this
	// attempt's graceful restart Restart State bit, can be answered
	// without duplicating configuration. It is shared across sessions and
	// must not be modified.
	Local *Open

	// Families is the negotiated multiprotocol intersection, in the local
	// configuration's order. A peer which advertises no multiprotocol
	// capability at all is the implicit IPv4 unicast speaker of RFC 4760,
	// so a classic session reports IPv4 unicast here even though neither
	// speaker advertised it explicitly.
	Families []Family

	// RouteRefresh reports whether the peer advertised the route refresh
	// capability (RFC 2918).
	RouteRefresh bool

	// ExtendedNextHop lists the families the peer accepts IPv6 next hops
	// for (RFC 8950). Nothing in this package reads it: it is the caller's
	// input for deciding whether to advertise a family's routes with an
	// IPv6 next hop, an MPReachNLRI.NextHop the caller builds.
	ExtendedNextHop []Family

	// GracefulRestart is the peer's decoded graceful restart capability
	// (RFC 4724), or nil when the peer advertised none, or only a
	// malformed one. Like Peer, it is fully owned and remains valid after
	// the session ends: a helper decides retention after the close. All
	// graceful restart behavior is the caller's.
	GracefulRestart *GracefulRestart

	// HoldTime is the negotiated hold time: the minimum of the two
	// speakers' proposals, and the budget for a single handler invocation.
	HoldTime time.Duration

	// LocalAddr is the session connection's local address, carried
	// verbatim from its Conn: a *net.TCPAddr for a TCP transport, and
	// whatever the connection reports for a custom one.
	LocalAddr net.Addr

	// RemoteAddr is the peer's address on the session connection, in
	// LocalAddr's form. The pair serves logging, metrics, and liveness
	// bootstrap: a BFD session (RFC 5880) protecting the peering runs
	// between these endpoints.
	RemoteAddr net.Addr
}

A Session reports the negotiated parameters of an established session, passed to OnEstablished at both layers.

A Session is fully owned: every field, the Families and ExtendedNextHop slices included, remains valid after the handler returns and after the session ends, unlike the borrowed values an FSM's other handlers receive. Anything this package does not model stays raw and reachable through Peer.

type State

type State int

A State is an RFC 4271, section 8 state of the finite state machine. The FSM reports its transitions through OnStateChange. There is deliberately no accessor to poll, because polled state is stale by the time it is acted on; ErrNotEstablished remains the only queryable session fact.

During a connection collision (RFC 4271, section 6.8, which models the second connection as a second FSM) the reported state is the attempt's aggregate: the furthest-progressed of everything live. It may therefore regress, from OpenConfirm back to OpenSent, when the further connection loses the collision or dies while the other survives.

const (
	StateIdle State = iota + 1
	StateConnect
	StateActive
	StateOpenSent
	StateOpenConfirm
	StateEstablished
)

The RFC 4271, section 8 session states, numbered as in section 8.2.2 and as MRT (RFC 6396) and BMP (RFC 7854) carry them: Idle is 1 and Established is 6. The zero value is not a state.

func (State) String

func (s State) String() string

String returns the RFC 4271 name of the State.

type TCPOptions

type TCPOptions struct {
	// GTSM optionally enables the Generalized TTL Security Mechanism (RFC
	// 5082): outgoing packets are sent with a TTL of 255, and incoming
	// packets with a lower TTL are dropped by the kernel.
	//
	// GTSM only makes sense for a directly connected peer. Multihop peering
	// is its explicit opposite and needs no option at all, since the kernel
	// default TTL already crosses any reasonable multihop distance.
	GTSM bool

	// DSCP optionally marks outgoing packets with a Differentiated Services
	// Code Point (RFC 2474), a value from 0 to 63, so that routers along the
	// path can prioritize the session's traffic. The conventional marking
	// for BGP is DSCPCS6. The zero value leaves packets unmarked, which is
	// the kernel default.
	DSCP uint8

	// UserTimeout optionally bounds how long transmitted data may remain
	// unacknowledged before the kernel closes the connection
	// (TCP_USER_TIMEOUT). The failure surfaces on the next read or write.
	// The zero value leaves the kernel default, which gives up only after
	// tcp_retries2 exhausts: roughly fifteen minutes.
	//
	// UserTimeout is not a liveness mechanism; the BGP hold timer is, and
	// the FSM closes a session whose hold timer expires regardless of this
	// option. What UserTimeout buys is agreement between the kernel and
	// the session. Without it, a peer which stops acknowledging mid-write
	// keeps the kernel retransmitting long after BGP has declared the
	// session dead. Conventionally it is set to the hold time. The same
	// bound also caps how long TCP keepalive probes may go unanswered on
	// an idle connection, overriding the probe count.
	//
	// A positive value is rounded up to a whole millisecond. A negative
	// value is an error.
	UserTimeout time.Duration

	// SendBuffer optionally sets the size in bytes of the kernel's send
	// buffer (SO_SNDBUF). The zero value leaves the kernel to size the
	// buffer itself, adaptively. A nonzero value turns that off, and the
	// kernel may still round or clamp it. A negative value is an error.
	//
	// Operators tune the send buffer for the burst of a full-table push.
	// A larger buffer trades later detection of a stalled peer for
	// throughput. A write completes as soon as the kernel has buffered
	// it, so a larger buffer lets more of a push complete before a peer
	// which stopped reading becomes visible.
	SendBuffer int

	// RecvBuffer optionally sets the size in bytes of the kernel's
	// receive buffer (SO_RCVBUF), with SendBuffer's zero, rounding, and
	// negative-value semantics. It is set before the socket connects or
	// listens: the only point at which the buffer can influence the
	// window scale TCP negotiates.
	RecvBuffer int

	// KeepAlive optionally configures TCP keepalive probes. The nil value
	// leaves the net package's default, which enables probes at its own
	// idle time, interval, and count. A non-nil value with Enable set
	// replaces that configuration. A non-nil value with Enable clear
	// disables probes entirely.
	//
	// TCP keepalive is not BGP's liveness mechanism; the hold timer is,
	// and it detects a dead peer on its own. Probes are a backstop for
	// the one case the hold timer cannot see from inside the kernel: a
	// peer which vanished without a reset. The hold timer reports that
	// silence only when it expires, while probes can fail the connection
	// sooner. Callers who align the probes with the hold time should note
	// that UserTimeout, when set, caps unanswered probes as well.
	//
	// Unlike every other option, KeepAlive is portable and never yields
	// [errors.ErrUnsupported]: the net package applies it to dialed and
	// accepted connections alike.
	KeepAlive *net.KeepAliveConfig
}

TCPOptions carries the socket options a BGP speaker sets on both the active and the passive open. Embedded in Dialer, the options apply to each dialed connection. Embedded in ListenConfig, they apply to the listening socket, and every accepted connection inherits them. Each option is a whole-socket property: peers with different needs must be split across listeners. The zero value sets nothing.

With the exception of KeepAlive, these options are only supported on Linux. Elsewhere, setting any of them makes Dialer.Dial and ListenConfig.Listen return an error which wraps errors.ErrUnsupported.

type Update

type Update struct {
	// Withdrawn lists IPv4 unicast prefixes to be removed from service.
	Withdrawn []netip.Prefix

	// Attributes lists the path attributes for NLRI, in raw binary form.
	// Lookup fetches one attribute in typed form, RawAttributes.Parse decodes
	// them all, and MarshalAttributes converts typed attributes back for
	// sending. RFC 4271, section 5 recommends ascending type order; this
	// package marshals them in the order provided.
	Attributes RawAttributes

	// NLRI lists IPv4 unicast prefixes to be advertised: RFC 4271's
	// Network Layer Reachability Information field.
	NLRI []netip.Prefix
}

An Update is a BGP UPDATE message, used to advertise and withdraw routes, as described in RFC 4271, section 4.3.

Withdrawn and NLRI are the original RFC 4271 fields, limited by wire format to IPv4 unicast prefixes. The MPReachNLRI and MPUnreachNLRI attributes (RFC 4760) carry routes for any address family, including IPv4; multiprotocol sessions typically leave Withdrawn and NLRI empty.

func NewEndOfRIB

func NewEndOfRIB(f Family) *Update

NewEndOfRIB produces the End-of-RIB marker for a family, as described in RFC 4724, section 2. A speaker sends the marker after it has advertised its complete table for the family. It is Update.EndOfRIB's encoding counterpart.

The marker is meaningful without graceful restart: a speaker may send it after any initial table transfer as a convergence signal.

func (*Update) AppendBinary

func (u *Update) AppendBinary(b []byte) ([]byte, error)

AppendBinary implements encoding.BinaryAppender.

func (*Update) Clone

func (src *Update) Clone() *Update

Clone makes a deep copy of Update. The result aliases no memory with the original.

func (*Update) EndOfRIB

func (u *Update) EndOfRIB() (Family, bool)

EndOfRIB reports whether the Update is an End-of-RIB marker (RFC 4724, section 2) and, if so, for which address family. The marker signals that a speaker has sent its complete initial routing table for a family, and is meaningful without graceful restart: callers may use it to detect convergence.

An empty UPDATE marks the end of the IPv4 unicast table. For any other family, the marker is an UPDATE whose only content is an MP_UNREACH_NLRI attribute which withdraws nothing.

type ValidationState

type ValidationState uint8

A ValidationState is the result of route origin validation (RFC 6811) for a route, as carried between speakers in an extended community (RFC 8097). Validation itself — the prefix-to-origin database and the lookup — is the caller's, exactly as route policy is; this package only carries the result.

const (
	ValidationStateValid    ValidationState = 0
	ValidationStateNotFound ValidationState = 1
	ValidationStateInvalid  ValidationState = 2
)

The origin validation states of RFC 8097, section 2.

func (ValidationState) String

func (s ValidationState) String() string

String returns the name of the ValidationState.

Directories

Path Synopsis
internal
bgprib
Package bgprib is the simplest possible RIB: maps guarded by one mutex.
Package bgprib is the simplest possible RIB: maps guarded by one mutex.
mrt
Package mrt reads the BGP messages embedded in Multi-Threaded Routing Toolkit (MRT) files, as described in RFC 6396.
Package mrt reads the BGP messages embedded in Multi-Threaded Routing Toolkit (MRT) files, as described in RFC 6396.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL