isis

package module
v0.0.0-...-3fa291c Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2026 License: MIT Imports: 23 Imported by: 0

README

isis Test Status Go Reference

Package isis implements the Intermediate System to Intermediate System (IS-IS) routing protocol, as described in ISO/IEC 10589, RFC 1195, and related RFCs: a link-state interior gateway protocol running directly over the data link, independent of IP. MIT Licensed.

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

  • The codec: the common Header, the PointToPointHello, the TLVs walk, and the typed TLV constructors and parsers, such as AreaAddressesTLV and TLV.AreaAddresses, with their binary encoding.
  • Transport carries PDUs between one Circuit and the link it attaches to, with ErrDropped for a frame an implementation discards or a read its link going down interrupts. ListenEthernet opens the Ethernet implementation.
  • Circuit runs one system's attachment to a link over its Transport, sending hellos and reporting every PDU in both directions to CircuitConfig.OnPDU. It forms one adjacency with its neighbor through the RFC 5303 three way handshake and reports its state changes to CircuitConfig.OnAdjacency. With CircuitConfig.Auth set, it signs its hellos and verifies every PDU it receives with the RFC 5304 HMAC-MD5 authentication TLV. A caller's liveness check reports through Circuit.SetUsable, and an unusable neighbor takes the adjacency Down.

Scope

There is no daemon, and the package computes no routes on its own. A caller owns the forwarding table, liveness, and policy, and gates them on the adjacency events a Circuit reports.

Platform support

The codec, Transport, and Circuit layers are portable Go. ListenEthernet, the Ethernet packet socket, is only supported on Linux; elsewhere, it returns an error which wraps errors.ErrUnsupported.

Testing

The package is tested against the standards, against itself, and against another implementation:

  • Codec tests encode and parse a hello assembled octet by octet from ISO 10589 and the RFCs defining its TLVs, requiring a byte-for-byte round trip.
  • Fuzz targets cover hello parsing, the TLV walk, the typed TLV parsers, and the HMAC-MD5 digest.
  • Transport conformance tests run against an in-memory link, and the Ethernet packet filter runs in a software BPF machine.
  • An interoperability suite behind the interop build tag runs FRRouting's isisd as an oracle in unprivileged network namespaces through github.com/mdlayher/frrtest, and the nix dev shell supplies FRR.

Documentation

Overview

Package isis implements the Intermediate System to Intermediate System (IS-IS) routing protocol, as described in ISO/IEC 10589, RFC 1195, and related RFCs: a link-state interior gateway protocol which runs directly over the data link and is independent of IP.

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

This package has no daemon and computes no routes on its own. A caller owns the forwarding table, liveness, and policy, and gates them on the adjacency events a Circuit reports.

Glossary

The acronyms this package uses, spelled out once each:

  • DIS: designated intermediate system, the instance elected on a broadcast link to speak for the link as a whole.
  • IGP: interior gateway protocol, a routing protocol which runs within one routing domain. IS-IS is one.
  • NET: network entity title, the NSAP which names an instance itself rather than a service on it. ParseNET parses one.
  • NSAP: network service access point, the ISO 8348 address of up to 20 octets from which an AreaAddress and a SystemID are drawn.
  • NSEL: NSAP selector, the final octet of an NSAP, which is zero in a NET.
  • PDU: protocol data unit, one IS-IS packet. Its type is a PDUType.

The terms which are types, such as SNPA, SystemID, AreaAddress, LSPID, Level, Topology, NLPID, and TLV, are defined in their own documentation.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrAuthentication = errors.New("isis: authentication failed")

ErrAuthentication reports a received PDU which failed authentication: it carries no authentication TLV, one this package does not verify, or a digest which does not match the key. A Circuit reports such a PDU to CircuitConfig.OnPDU with an error wrapping ErrAuthentication, whose text is the reason the PDU failed.

View Source
var ErrDropped = errors.New("isis: frame dropped")

ErrDropped reports a frame a Transport received and discarded, such as one which failed its filtering, or a read the link interrupted without ending the Transport, such as its interface going down. A ReadPDU error wrapping ErrDropped is not terminal: the caller reads again. ReadPDU still blocks for a frame or such an event before reporting a drop, never returning ErrDropped when nothing happened, and the count returned beside it is ignored. Wrap ErrDropped with the reason, as in fmt.Errorf("%w: LLC header 0xaaaa03", ErrDropped).

Functions

func ParseNET

func ParseNET(s string) (AreaAddress, SystemID, error)

ParseNET parses a network entity title in its conventional encoding: hexadecimal octets separated by periods, as in "49.0001.0000.0000.0001.00". The trailing NSEL octet must be zero, the six octets before it are the SystemID, and everything before those is the AreaAddress.

func TLVs

func TLVs(b []byte) iter.Seq2[TLV, error]

TLVs returns an iterator over the TLVs packed into b, which is a PDU's variable length part. Every octet of b must belong to a TLV: a b which ends part way through one yields a zero TLV with a non-nil error, once, and the iteration stops. Values alias b, with their capacity capped at their length, so an append to one copies it rather than overwriting the TLV which follows.

The iterator is the whole of the walk. A TLV whose code point this package does not name is yielded like any other, since RFC 8918 section 3.1 requires it be ignored rather than reject the PDU.

Types

type AdjacencyEvent

type AdjacencyEvent struct {
	// State is the state the adjacency has reached.
	State AdjacencyState

	// Levels is the set of levels the adjacency carries: the levels both
	// systems run, narrowed to Level 2 when they share no area address
	// (ISO 10589 clause 8.2.4.2).
	Levels LevelSet

	// Neighbor is the neighbor's system identifier.
	Neighbor SystemID

	// SNPA is the neighbor's link layer address on the Circuit.
	SNPA SNPA

	// AreaAddresses are the area addresses the neighbor claims.
	AreaAddresses []AreaAddress

	// Protocols are the network layer protocols the neighbor routes.
	Protocols []NLPID

	// IPv4Addresses and IPv6Addresses are the neighbor's interface
	// addresses on this link. The IPv6 addresses are link-local, which is
	// all RFC 5308 section 3 permits a hello to carry, and they are what
	// a caller needs to bring up a BFD session over the link.
	IPv4Addresses, IPv6Addresses []netip.Addr

	// Reason explains a fall to Down, and is zero otherwise.
	Reason DownReason
}

An AdjacencyEvent reports one adjacency reaching a new state. Its slices are parsed from the neighbor's hello. A later hello replaces them rather than editing them in place, so an event's slices never change after the hook returns and a caller may keep them.

type AdjacencyState

type AdjacencyState uint8

An AdjacencyState is the state of an adjacency, as described in ISO 10589 clause 8.2. An adjacency which does not exist is Down, so Down is the zero value.

const (
	AdjacencyDown AdjacencyState = iota
	AdjacencyInitializing
	AdjacencyUp
)

The adjacency states.

func (AdjacencyState) String

func (s AdjacencyState) String() string

String returns the name of an AdjacencyState.

type AreaAddress

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

An AreaAddress is the variable length prefix of an instance's NSAP which names its area. A Level 1 adjacency requires one in common and a Level 2 adjacency does not. It is comparable by value, so it serves as a map key.

func NewAreaAddress

func NewAreaAddress(b []byte) (AreaAddress, error)

NewAreaAddress produces an AreaAddress from b, which must be 1 to 20 octets. It copies b.

func (AreaAddress) String

func (a AreaAddress) String() string

String returns the conventional encoding of an AreaAddress: the first octet, then pairs of octets, separated by periods, as in "49.0001".

type AuthConfig

type AuthConfig struct {
	// Type is the authentication type. It must be AuthTypeHMACMD5, the
	// type this package implements.
	Type AuthType

	// Key is the shared secret. It must not be empty.
	Key []byte
}

An AuthConfig is the authentication type and shared key with which a Circuit signs the PDUs it sends and verifies those it receives.

HMAC-MD5 guards against misconfiguration and casual injection, not a determined attacker: RFC 5304 section 5 describes its limits.

type AuthType

type AuthType uint8

An AuthType is the type octet which opens an authentication TLV's value, from the IANA IS-IS Authentication Type Codes registry.

const (
	AuthTypeCleartext            AuthType = 1
	AuthTypeGenericCryptographic AuthType = 3
	AuthTypeHMACMD5              AuthType = 54
)

The authentication types. ISO 10589 clause 9.5 assigns the cleartext password, RFC 5304 HMAC-MD5, and RFC 5310 the generic cryptographic type which carries a key ID and an HMAC-SHA digest.

func (AuthType) String

func (t AuthType) String() string

String returns the name of an AuthType.

type Authentication

type Authentication struct {
	// Type is the authentication type.
	Type AuthType

	// Value follows the type octet. For AuthTypeCleartext it is the
	// password. For AuthTypeHMACMD5 it is the 16 octet digest. For
	// AuthTypeGenericCryptographic it is the two octet key ID and then
	// the digest. The Value which TLV.Authentication returns aliases the
	// TLV it was parsed from.
	Value []byte
}

An Authentication is the value of the authentication TLV, code 10: a type octet and the octets that type defines.

type Circuit

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

A Circuit is one system's attachment to a single link: it sends that link's hellos over its Transport, forms one adjacency with its neighbor and reports its state changes to CircuitConfig.OnAdjacency, and reports every PDU in both directions to CircuitConfig.OnPDU. NewCircuit builds one and Run runs it.

The fields SetUsable reaches are guarded for any goroutine, a hook included. Every other field is owned by the goroutine running Run, so a hook touches a Circuit only through SetUsable or by comparing its identity.

Example

A Circuit runs over a Transport on one interface. The caller gates its own liveness check on the adjacency events the Circuit reports, and reports back through SetUsable when that check changes state.

package main

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

	"github.com/mdlayher/isis"
	"golang.org/x/sync/errgroup"
)

// A Circuit runs over a Transport on one interface. The caller gates its
// own liveness check on the adjacency events the Circuit reports, and
// reports back through SetUsable when that check changes state.
func main() {
	ifi, err := net.InterfaceByName("eth0")
	if err != nil {
		log.Fatal(err)
	}

	t, err := isis.ListenEthernet(ifi, nil)
	if err != nil {
		log.Fatal(err)
	}

	area, id, err := isis.ParseNET("49.0001.0000.0000.0001.00")
	if err != nil {
		log.Fatal(err)
	}

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

	// Run and every liveness check share one group, so all of them end
	// together and Wait returns only once each has.
	eg, ctx := errgroup.WithContext(ctx)

	// The hook runs on the Circuit's own goroutine, one event at a time, so
	// stop needs no lock. It ends the running liveness check, if any.
	stop := func() {}
	c, err := isis.NewCircuit(t, isis.CircuitConfig{
		SystemID:      id,
		AreaAddresses: []isis.AreaAddress{area},
		Levels:        isis.Level2Only,
		Type:          isis.CircuitPointToPoint,
		IPv4Addresses: []netip.Addr{netip.MustParseAddr("192.0.2.1")},
		IPv6Addresses: []netip.Addr{netip.MustParseAddr("fe80::1")},

		OnAdjacency: func(c *isis.Circuit, e isis.AdjacencyEvent) {
			switch e.State {
			case isis.AdjacencyUp:
				// Liveness starts once the adjacency is Up. A hook must
				// return promptly, so the check runs on a goroutine of its
				// own.
				lctx, lcancel := context.WithCancel(ctx)
				stop = lcancel
				eg.Go(func() error {
					checkLiveness(lctx, e.IPv4Addresses, func(usable bool) {
						c.SetUsable(e.Neighbor, usable)
					})

					return nil
				})
			case isis.AdjacencyDown:
				stop()
			}
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	// Run joins the group, beside any other work the caller adds to it.
	eg.Go(func() error { return c.Run(ctx) })

	// Run returns the context's error once the caller is interrupted, which
	// is a clean shutdown.
	if err := eg.Wait(); err != nil && !errors.Is(err, context.Canceled) {
		log.Fatal(err)
	}
}

// checkLiveness stands in for the caller's own liveness check of a
// neighbor at addrs, for which a BFD session is the usual choice. It calls
// report each time the neighbor becomes usable or unusable, and returns
// once ctx ends.
func checkLiveness(ctx context.Context, addrs []netip.Addr, report func(usable bool)) {
	// A real check opens a session to addrs here, calls report(false) when
	// that session fails, and calls report(true) when it recovers. This one
	// only ever finds the neighbor usable.
	log.Printf("checking liveness of %v", addrs)
	report(true)
	<-ctx.Done()
}

func NewCircuit

func NewCircuit(t Transport, c CircuitConfig) (*Circuit, error)

NewCircuit validates the configuration and produces a Circuit over t. Nothing runs until Run, and ownership of t passes only when Run is called: a caller which never reaches Run closes t itself.

func (*Circuit) Run

func (c *Circuit) Run(ctx context.Context) error

Run runs the Circuit until ctx is canceled or its Transport fails: a hello at once and another each hello interval after, the RFC 5303 three way handshake and holding timer of the adjacency with its neighbor, and every PDU in both directions reported to OnPDU. Every exit takes the adjacency Down and reports it. Cancellation then sends a last hello advertising three way Down, so a neighbor leaves Up at once rather than after a holding time (RFC 5303 section 3.2). Every exit closes the Transport and waits for the reader goroutine. Nothing retries: a caller wanting the Circuit back builds a fresh one.

Run always returns a non-nil error:

  • ctx's error, once canceled. A non-nil ctx.Err() marks a clean shutdown even when a Transport failure raced it.
  • The Transport's read error, wrapped with the circuit ID.
  • An error when Run was already called, which touches nothing.

func (*Circuit) SetUsable

func (c *Circuit) SetUsable(neighbor SystemID, usable bool)

SetUsable reports whether the caller's liveness check, such as a BFD session over the link, finds neighbor usable on this Circuit.

Reporting a neighbor unusable takes the adjacency with it Down if that adjacency is Up: an adjacency forms without waiting on liveness, and only a later failure tears it down. The neighbor's next hellos may bring the adjacency back through the three way handshake, so a caller which starts its liveness check on Up sees a fresh Up to start on. Reporting a neighbor usable changes nothing, since usable is the steady state, and a report naming a neighbor the Circuit holds no Up adjacency with is ignored.

SetUsable never blocks and is safe from any goroutine, a hook included. The report applies on the goroutine running Run when it next wakes; a report made after Run has returned is never applied.

type CircuitConfig

type CircuitConfig struct {
	// SystemID is this system's six octet identifier, unique in the
	// routing domain. It must be nonzero.
	SystemID SystemID

	// AreaAddresses are the areas this system claims, advertised in every
	// hello. At least one is required, since ISO 10589 clause 8.2.4.2
	// compares them on every received hello even where Level 2 makes the
	// comparison moot.
	AreaAddresses []AreaAddress

	// Levels is the set of levels the Circuit runs. It is required.
	Levels LevelSet

	// Protocols are the network layer protocols this system routes,
	// advertised in every hello as RFC 1195 section 5.2 requires. Each
	// needs an interface address of its family. The zero value is IPv4
	// and IPv6.
	Protocols []NLPID

	// Type is the kind of link the Circuit attaches to. It must be
	// CircuitPointToPoint. CircuitBroadcast returns an error wrapping
	// errors.ErrUnsupported.
	Type CircuitType

	// HelloInterval is the pause between hellos, reduced by up to a
	// quarter on each transmission so Circuits do not synchronize (ISO
	// 10589 clause 10.1). The zero value is three seconds.
	HelloInterval time.Duration

	// HoldingMultiplier is how many hello intervals of silence a neighbor
	// waits before declaring this system down. The advertised holding
	// time is the product of the two, rounded up to whole seconds and
	// clamped to the wire's 16 bits. The zero value is ten.
	HoldingMultiplier uint8

	// LocalCircuitID is the one octet circuit identifier of ISO 10589
	// clause 9.7, which must be unique among this system's circuits.
	LocalCircuitID uint8

	// ExtendedLocalCircuitID is the four octet circuit identifier of RFC
	// 5303 section 3.1, which a point to point Circuit advertises in its
	// three way adjacency TLV. It is unique on this system and stable for
	// the life of the Circuit. A neighbor learns this system restarted by
	// seeing it change, so the zero value is a random value rather than a
	// fixed one.
	ExtendedLocalCircuitID uint32

	// IPv4Addresses and IPv6Addresses are this end's interface addresses,
	// advertised in every hello. A Circuit advertising IPv4 needs at least
	// one IPv4 address, since RFC 3787 section 10 requires the IPv4 TLV
	// for interoperation, and one advertising IPv6 needs at least one
	// IPv6 address, all link-local, which is what RFC 5308 section 3
	// permits in a hello. A family with no addresses sends no TLV.
	IPv4Addresses, IPv6Addresses []netip.Addr

	// Padding says when the Circuit pads its hellos. The zero value is
	// PadAlways.
	Padding PaddingMode

	// Auth, if set, authenticates the Circuit's hellos: every hello sent
	// carries the authentication TLV, and a received PDU which does not
	// verify is reported to OnPDU with an error wrapping
	// ErrAuthentication and dropped. A nil Auth sends no authentication
	// TLV and accepts hellos whether or not they carry one.
	Auth *AuthConfig

	// OnAdjacency, if set, is called on each adjacency state change:
	// Initializing, Up, and Down. It is the signal a caller gates
	// forwarding and liveness checks such as a BFD session on, and it
	// carries the neighbor's interface addresses so the caller has the
	// endpoints to dial.
	//
	// The hook runs on the goroutine running Run and must return
	// promptly: a stalled hook stalls the Circuit's hellos and its
	// adjacency's holding timer. A hook which must block does that work
	// on a new goroutine. c names the Circuit which fired, so one hook
	// function may serve many.
	OnAdjacency func(c *Circuit, e AdjacencyEvent)

	// OnPDU, if set, observes every PDU in both directions, for audit and
	// diagnostics. It steers nothing: adjacency logic belongs on
	// OnAdjacency.
	//
	// See OnAdjacency for the hook contract.
	OnPDU func(c *Circuit, e PDUEvent)

	// Logger, if set, records the Circuit starting, adjacency state
	// changes, and the end of a run of authentication failures at Info,
	// the start of such a run at Warn, a hello which cannot be built at
	// Error, and dropped PDUs, failed writes, and a Transport which fails
	// to close at Debug. nil discards everything.
	Logger *slog.Logger
}

A CircuitConfig configures a Circuit. SystemID, AreaAddresses, Levels, and Type are required and every other field has a default or is optional. Configuration is immutable once the Circuit is built.

type CircuitType

type CircuitType uint8

A CircuitType is the kind of link a Circuit attaches to. It selects the hello PDUs the Circuit exchanges and whether the link elects a DIS. The zero value is not valid: the layer which takes a CircuitType supplies its default.

const (

	// CircuitBroadcast is a multi-access link: Level 1 and Level 2 LAN
	// hellos, a DIS, and a Pseudonode.
	CircuitBroadcast CircuitType

	// CircuitPointToPoint is a link with exactly one neighbor: point to
	// point hellos and the RFC 5303 three way handshake, no DIS and no
	// Pseudonode. RFC 5309 allows an Ethernet link to be one.
	CircuitPointToPoint
)

The circuit types.

func (CircuitType) String

func (t CircuitType) String() string

String returns the name of a CircuitType.

type Direction

type Direction uint8

A Direction is which way a PDU crossed a Circuit. The zero value is not valid.

const (
	DirectionReceived Direction
	DirectionSent
)

The directions.

func (Direction) String

func (d Direction) String() string

String returns the name of a Direction.

type DownReason

type DownReason uint8

A DownReason is why an adjacency fell to Down. The zero value is the reason of an event which is not a fall to Down.

const (

	// DownHoldingTimeExpired reports that the neighbor's holding time ran
	// out with no further hello.
	DownHoldingTimeExpired DownReason

	// DownCircuitStopped reports that the Circuit stopped running.
	DownCircuitStopped

	// DownNeighborSystemIDChanged reports that a hello named a system other
	// than the neighbor's (RFC 3719 section 9).
	DownNeighborSystemIDChanged

	// DownNoLevelInCommon reports that a hello left the two systems no
	// level in common (ISO 10589 clause 8.2.4.2).
	DownNoLevelInCommon

	// DownNeighborUnusable reports that the caller's liveness check found
	// the neighbor unusable, through Circuit.SetUsable.
	DownNeighborUnusable
)

The reasons an adjacency falls to Down.

func (DownReason) String

func (r DownReason) String() string

String returns a description of a DownReason.

type EthernetConfig

type EthernetConfig struct {
	// Groups are the link layer multicast destinations to join on the
	// interface. A nil Groups joins AllISs, AllL1ISs, and AllL2ISs. A
	// non-nil empty Groups joins nothing.
	//
	// Whether an interface delivers IS-IS multicast without a membership
	// depends on its driver, not on IS-IS.
	Groups []SNPA
}

An EthernetConfig configures an Ethernet transport. The zero value is usable: every field has a default or is optional.

type EthernetTransport

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

An EthernetTransport is a Transport over one Ethernet interface, and the implementation for a real link. It is an AF_PACKET datagram socket bound to ETH_P_802_2, filtered to IS-IS PDUs, joined to the IS-IS link layer multicast groups, and framed with the 802.2 LLC header of ISO 10589 clause 8.4.8.

The socket needs CAP_NET_RAW. Nothing here touches netlink or knows what a route is: an EthernetTransport carries PDUs and nothing else.

func ListenEthernet

func ListenEthernet(ifi *net.Interface, cfg *EthernetConfig) (*EthernetTransport, error)

ListenEthernet opens an Ethernet transport on ifi. A nil cfg is the zero EthernetConfig.

The PDU size comes from the interface MTU, not from ISO 10589's ReceiveLSPBufferSize of 1492: a neighbor may pad its hellos to the MTU, and a smaller buffer would truncate them.

func (*EthernetTransport) Close

func (e *EthernetTransport) Close() error

Close implements Transport.

func (*EthernetTransport) Groups

func (e *EthernetTransport) Groups() []SNPA

Groups reports the link layer multicast destinations this EthernetTransport joined, in the order it joined them. A caller which asked for none sees none, which is what makes the membership observable.

func (*EthernetTransport) LocalSNPA

func (e *EthernetTransport) LocalSNPA() SNPA

LocalSNPA implements Transport: the interface's own link layer address.

func (*EthernetTransport) MaxPDULen

func (e *EthernetTransport) MaxPDULen() int

MaxPDULen implements Transport: the interface MTU less the LLC header.

func (*EthernetTransport) ReadPDU

func (e *EthernetTransport) ReadPDU(b []byte) (int, SNPA, error)

ReadPDU implements Transport.

func (*EthernetTransport) WritePDU

func (e *EthernetTransport) WritePDU(dst SNPA, b []byte) error

WritePDU implements Transport.

type Header struct {
	// Type is the PDU type.
	Type PDUType

	// LengthIndicator is the length in octets of the PDU's whole fixed
	// header. It is fixed by Type, so a Header which parses always
	// carries the length Type requires.
	LengthIndicator uint8
}

A Header is the common fixed header every IS-IS PDU begins with, as described in ISO 10589 clause 9.5. The type specific header follows it and LengthIndicator covers both.

Maximum area addresses is fixed at three, the ISO 10589 default.

func ParseHeader

func ParseHeader(b []byte) (Header, error)

ParseHeader parses the common fixed header at the start of b, which may hold the rest of the PDU after it.

The validation rules of ISO 10589, clause 9.5 are applied:

  • the discriminator must be 0x83
  • the version/protocol ID extension and the version must be 1
  • the ID length must be 0 or 6
  • the PDU type must exist, and the length indicator must match it
  • b must hold at least the whole fixed header the type requires
  • the maximum area addresses must be 0 or 3

Reserved bits are ignored.

func (Header) AppendBinary

func (h Header) AppendBinary(b []byte) ([]byte, error)

AppendBinary implements encoding.BinaryAppender. LengthIndicator is written from Type, so a zero value there is filled in rather than rejected. Call AppendBinary with a nil buffer for a standalone encoding.

type LSPID

type LSPID struct {
	// SystemID is the originating instance.
	SystemID SystemID

	// Pseudonode names one of the originator's broadcast circuits, or is
	// zero for the originator itself.
	Pseudonode uint8

	// Fragment numbers the fragments sharing one origin.
	Fragment uint8
}

An LSPID identifies one link state PDU: the SystemID of the instance which originated it, the pseudonode octet naming a broadcast circuit's Pseudonode or zero for the instance itself, and the fragment number.

func (LSPID) String

func (l LSPID) String() string

String returns the conventional encoding of an LSPID: the SystemID, the pseudonode octet, then the fragment number, as in "0000.0000.0001.00-00".

type Level

type Level uint8

A Level is one of the two IS-IS routing hierarchies. Level 1 routes within an area and Level 2 between areas.

const (
	Level1 Level = 1
	Level2 Level = 2
)

The levels, valued as the bit each occupies in a LevelSet.

func (Level) String

func (l Level) String() string

String returns the name of a Level.

type LevelSet

type LevelSet uint8

A LevelSet is a set of Levels: the wire encoding of a hello's circuit type field and of a link state PDU's IS type field. Level 1 is bit zero and Level 2 is bit one, so a Circuit running both encodes as 3. Zero means neither, which the wire reserves and no Circuit may use.

const (
	Level1Only LevelSet = 0b01
	Level2Only LevelSet = 0b10
	Level1And2 LevelSet = 0b11
)

The LevelSet values ISO 10589 clause 9.5 assigns to the circuit type field.

func (LevelSet) Has

func (s LevelSet) Has(l Level) bool

Has reports whether s contains l.

func (LevelSet) String

func (s LevelSet) String() string

String returns the name of a LevelSet.

type NLPID

type NLPID uint8

An NLPID is a network layer protocol identifier, from the IANA NLPID registry: what a protocols supported TLV lists. This package names only the NLPIDs it routes, but an NLPID it does not name is valid: it parses and re-encodes unchanged.

const (
	NLPIDIPv4 NLPID = 0xcc
	NLPIDIPv6 NLPID = 0x8e
)

The NLPIDs of the protocols this package routes.

func (NLPID) String

func (n NLPID) String() string

String returns the name of an NLPID.

type PDUEvent

type PDUEvent struct {
	// Direction is which way the PDU crossed.
	Direction Direction

	// SNPA is the link layer address a received PDU came from, or the
	// destination a sent PDU went to.
	SNPA SNPA

	// Header is the PDU's decoded common header. It is the zero value for
	// a received PDU whose common header failed validation.
	Header Header

	// Raw is the PDU as it crossed the wire. It aliases a buffer the
	// Circuit reuses, so copy it to retain it past the hook's return.
	Raw []byte

	// Err is nil for a PDU which was received or written cleanly. On a
	// received event it is otherwise why the Circuit drops the PDU: the
	// error its common header failed validation with, or an error
	// wrapping ErrAuthentication for a PDU which failed authentication.
	// On a sent event it is the Transport's write error, and the PDU may
	// not have reached the link.
	Err error
}

A PDUEvent reports one PDU crossing a Circuit, for audit and diagnostics. Every PDU a Circuit sends or receives is reported once, whatever its type, including a received PDU whose common header fails validation or which fails authentication, and a sent PDU whose write failed. A hello which could not be built never reaches the link and is not reported.

type PDUType

type PDUType uint8

A PDUType is the type of an IS-IS PDU, as assigned in ISO 10589 clause 9.4.

const (
	PDUL1LANHello         PDUType = 15
	PDUL2LANHello         PDUType = 16
	PDUPointToPointHello  PDUType = 17
	PDUL1LinkState        PDUType = 18
	PDUL2LinkState        PDUType = 20
	PDUL1CompleteSequence PDUType = 24
	PDUL2CompleteSequence PDUType = 25
	PDUL1PartialSequence  PDUType = 26
	PDUL2PartialSequence  PDUType = 27
)

The PDU types.

func (PDUType) String

func (t PDUType) String() string

String returns the name of a PDUType.

type PaddingMode

type PaddingMode uint8

A PaddingMode says when a Circuit pads its hellos to the link's full PDU size. Padding proves the link carries a full sized PDU before an adjacency depends on it (ISO 10589 clause 8.2.3). It is never an acceptance test on receipt: RFC 3719 section 6 forbids that, so a Circuit accepts an unpadded hello whatever its own mode.

const (
	// PadAlways pads every hello, which is ISO 10589's behavior.
	PadAlways PaddingMode = iota

	// PadDuringAdjacencyFormation pads until the Circuit holds an
	// adjacency in state Up, which RFC 3719 section 6 recommends.
	PadDuringAdjacencyFormation

	// PadNever sends unpadded hellos.
	PadNever
)

The padding modes.

func (PaddingMode) String

func (m PaddingMode) String() string

String returns the name of a PaddingMode.

type PointToPointHello

type PointToPointHello struct {
	// Levels is the set of levels the sending Circuit runs: the wire's
	// circuit type field. It must name at least one level.
	Levels LevelSet

	// SourceID is the sending instance's system identifier.
	SourceID SystemID

	// HoldingTime is how long a receiver keeps the adjacency alive
	// without a further hello. The wire carries whole seconds in 16 bits,
	// so it must be a whole number of seconds from 1 to 65535.
	HoldingTime time.Duration

	// LocalCircuitID is the sender's one octet identifier for the sending
	// Circuit. The extended local circuit ID of the three way adjacency
	// TLV supersedes it (RFC 5303 section 3.1).
	LocalCircuitID uint8

	// TLVs is the hello's variable length part, in wire order. Padding
	// TLVs a sender emitted appear here like any other; use PadTo to
	// generate them instead of building them by hand.
	TLVs []TLV

	// PadTo, when nonzero, pads the encoded hello to that many octets
	// with padding TLVs. ISO 10589 clause 8.2.3 pads so an adjacency
	// forms only between systems able to exchange PDUs of the link's full
	// size. A PadTo the two octet TLV header cannot reach exactly leaves
	// the hello one octet short of it.
	PadTo int
}

A PointToPointHello is the point to point hello PDU, type 17, described in ISO 10589 clause 9.7. A point to point Circuit sends one every hello interval and every adjacency on it lives or dies by their arrival. RFC 5309 allows an Ethernet link to carry them.

func ParsePointToPointHello

func ParsePointToPointHello(b []byte) (*PointToPointHello, error)

ParsePointToPointHello parses a point to point hello from b, which must begin with exactly one whole PDU, as received from the link. Octets past the PDU length field are ignored: an Ethernet frame below the medium's minimum size arrives padded, and the padding is not part of the PDU.

Every octet of the variable length part must belong to a TLV, and their values alias b. A TLV this package does not model is returned like any other, since RFC 8918 section 3.1 requires it be ignored rather than reject the hello.

func (*PointToPointHello) AppendBinary

func (h *PointToPointHello) AppendBinary(b []byte) ([]byte, error)

AppendBinary implements encoding.BinaryAppender. Call it with a nil buffer for a standalone encoding; with a longer buffer, the PDU length field counts only the octets AppendBinary added.

type SNPA

type SNPA [6]byte

An SNPA, a subnetwork point of attachment, is a neighbor's link layer address on a Circuit: the destination a Circuit puts on a frame. IS-IS over Ethernet, including the RFC 5309 point to point form, addresses frames by six octet link layer address, so an SNPA is six octets.

func AllISs

func AllISs() SNPA

AllISs returns the ISO 10589 clause 8.4.8 multicast destination 09:00:2b:00:00:05, which carries every PDU on a point to point Circuit over a LAN, as RFC 5309 section 4.1 recommends.

func AllL1ISs

func AllL1ISs() SNPA

AllL1ISs returns the ISO 10589 clause 8.4.8 multicast destination 01:80:c2:00:00:14, which carries Level 1 traffic on a broadcast Circuit.

func AllL2ISs

func AllL2ISs() SNPA

AllL2ISs returns the ISO 10589 clause 8.4.8 multicast destination 01:80:c2:00:00:15, which carries Level 2 traffic on a broadcast Circuit.

func (SNPA) String

func (s SNPA) String() string

String returns the SNPA as colon separated hexadecimal octets, as in "01:80:c2:00:00:14".

type SystemID

type SystemID [6]byte

A SystemID is the six octet identifier of one instance, unique within the routing domain. It is not an address and is not derived from one.

func (SystemID) String

func (s SystemID) String() string

String returns the conventional encoding of a SystemID: three groups of two octets in hexadecimal, separated by periods, as in "0000.0000.0001".

type TLV

type TLV struct {
	// Type is the TLV's code point.
	Type TLVType

	// Value is the TLV's value. A TLV from TLVs aliases the PDU it was
	// parsed from, so copy Value to retain it past that PDU's lifetime.
	Value []byte
}

A TLV is one element of a PDU's variable length part, as described in ISO 10589 clause 9.1: a one octet type, a one octet length, and up to 255 octets of value.

func AreaAddressesTLV

func AreaAddressesTLV(as []AreaAddress) (TLV, error)

AreaAddressesTLV builds the area addresses TLV, code 1, described in ISO 10589 clause 9.5. An instance sends it in every hello and in link state PDU fragment zero.

func AuthenticationTLV

func AuthenticationTLV(a Authentication) (TLV, error)

AuthenticationTLV builds the authentication TLV, code 10, described in ISO 10589 clause 9.5, RFC 5304, and RFC 5310. The value must be the length its type requires: at least one octet of cleartext password, exactly 16 octets of HMAC-MD5 digest, or a key ID and a digest.

func IPv4InterfaceAddressesTLV

func IPv4InterfaceAddressesTLV(addrs []netip.Addr) (TLV, error)

IPv4InterfaceAddressesTLV builds the IP interface address TLV, code 132, described in RFC 1195 section 4.2, which carries only IPv4 addresses. RFC 3787 section 10 requires it in every hello so an implementation which checks the neighbor's addressing still forms the adjacency.

func IPv6InterfaceAddressesTLV

func IPv6InterfaceAddressesTLV(addrs []netip.Addr) (TLV, error)

IPv6InterfaceAddressesTLV builds the IPv6 interface address TLV, code 232, described in RFC 5308 section 3. A hello carries only the link-local addresses of the sending Circuit. An IPv4 mapped address is an error: unmap it and carry it in the IP interface address TLV.

func ProtocolsSupportedTLV

func ProtocolsSupportedTLV(ps []NLPID) (TLV, error)

ProtocolsSupportedTLV builds the protocols supported TLV, code 129, described in RFC 1195 section 4.2. Section 5.2 requires it in every hello and in link state PDU fragment zero of an IP capable instance.

func ThreeWayAdjacencyTLV

func ThreeWayAdjacencyTLV(a ThreeWayAdjacency) (TLV, error)

ThreeWayAdjacencyTLV builds the three way adjacency TLV, code 240. The neighbor fields are emitted only when NeighborSystemID is set, giving the 15 octet form RFC 5303 defines for a sender which has heard its neighbor and the 5 octet form for one which has not. A NeighborExtendedLocalCircuitID without a NeighborSystemID has no form to carry it and is an error.

func (TLV) AppendBinary

func (t TLV) AppendBinary(b []byte) ([]byte, error)

AppendBinary implements encoding.BinaryAppender. Call it with a nil buffer for a standalone encoding.

func (TLV) AreaAddresses

func (t TLV) AreaAddresses() ([]AreaAddress, error)

AreaAddresses parses t, which must be the area addresses TLV, code 1.

func (TLV) Authentication

func (t TLV) Authentication() (Authentication, error)

Authentication parses t, which must be the authentication TLV, code 10. A type this package does not name parses with its value unchecked, since ISO 10589 clause 8.2.4.1 leaves accepting it to the receiver.

func (TLV) IPv4InterfaceAddresses

func (t TLV) IPv4InterfaceAddresses() ([]netip.Addr, error)

IPv4InterfaceAddresses parses t, which must be the IP interface address TLV, code 132.

func (TLV) IPv6InterfaceAddresses

func (t TLV) IPv6InterfaceAddresses() ([]netip.Addr, error)

IPv6InterfaceAddresses parses t, which must be the IPv6 interface address TLV, code 232. An IPv4 mapped address is an error, as it is to IPv6InterfaceAddressesTLV, since IPv4 belongs in the IP interface address TLV.

func (TLV) ProtocolsSupported

func (t TLV) ProtocolsSupported() ([]NLPID, error)

ProtocolsSupported parses t, which must be the protocols supported TLV, code 129.

func (TLV) ThreeWayAdjacency

func (t TLV) ThreeWayAdjacency() (ThreeWayAdjacency, error)

ThreeWayAdjacency parses t, which must be the three way adjacency TLV, code 240. RFC 5303 lets a sender omit the trailing fields it does not know, so the 1, 5, 11, and 15 octet forms all parse and an omitted field is zero. A neighbor system ID which is present but zero names no system and is an error.

type TLVType

type TLVType uint8

A TLVType is the code point of a TLV, from the IANA IS-IS TLV Codepoints registry.

const (
	TLVAreaAddresses                TLVType = 1
	TLVLANNeighbors                 TLVType = 6
	TLVPadding                      TLVType = 8
	TLVLSPEntries                   TLVType = 9
	TLVAuthentication               TLVType = 10
	TLVExtendedISReachability       TLVType = 22
	TLVProtocolsSupported           TLVType = 129
	TLVIPv4InterfaceAddresses       TLVType = 132
	TLVExtendedIPReachability       TLVType = 135
	TLVDynamicHostname              TLVType = 137
	TLVMTISReachability             TLVType = 222
	TLVMultiTopology                TLVType = 229
	TLVIPv6InterfaceAddresses       TLVType = 232
	TLVIPv6GlobalInterfaceAddresses TLVType = 233
	TLVMTIPv4Reachability           TLVType = 235
	TLVIPv6Reachability             TLVType = 236
	TLVMTIPv6Reachability           TLVType = 237
	TLVThreeWayAdjacency            TLVType = 240
)

The TLV code points this package names: those an IPv4 and IPv6 IGP with wide metrics and multi-topology carries in its hellos, link state PDUs, and sequence numbers PDUs. Naming a code point is not the same as modeling its value: every code point without a typed accessor is walked past, since RFC 8918 section 3.1 requires that a disallowed or unrecognised TLV be ignored rather than reject the PDU carrying it.

func (TLVType) String

func (t TLVType) String() string

String returns the registry name of a TLVType, or its code point when this package does not name it.

type ThreeWayAdjacency

type ThreeWayAdjacency struct {
	// State is the sender's view of the adjacency. The zero State is
	// ThreeWayUp.
	State ThreeWayState

	// ExtendedLocalCircuitID identifies the sending Circuit, uniquely on
	// the sending system and for the life of that Circuit. A change to
	// it is how the receiver learns the sender restarted.
	ExtendedLocalCircuitID uint32

	// NeighborSystemID is the system the sender last heard on this
	// Circuit, or zero if it has heard none. The receiver treats a hello
	// which names some other system as evidence the adjacency is not yet
	// two way.
	NeighborSystemID SystemID

	// NeighborExtendedLocalCircuitID is the ExtendedLocalCircuitID the
	// sender last heard from NeighborSystemID. It is zero when
	// NeighborSystemID is.
	NeighborExtendedLocalCircuitID uint32
}

A ThreeWayAdjacency is the point to point three way adjacency TLV, code 240, described in RFC 5303 section 3.1. RFC 5303 requires it in every point to point hello from a system which implements the handshake.

A sender omits the neighbor fields until it has heard the receiving system, so both are zero until then. A SystemID is never zero, so the zero NeighborSystemID is unambiguous: it means the neighbor fields are absent, never that they name a system.

type ThreeWayState

type ThreeWayState uint8

A ThreeWayState is one system's view of a point to point adjacency, as carried in the three way adjacency TLV of RFC 5303 section 3.1. It is the sender's claim about the adjacency, not the receiver's state. Its values are the wire encoding, so the zero ThreeWayState is ThreeWayUp.

const (
	ThreeWayUp           ThreeWayState = 0
	ThreeWayInitializing ThreeWayState = 1
	ThreeWayDown         ThreeWayState = 2
)

The three way states, in their wire encoding.

func (ThreeWayState) String

func (s ThreeWayState) String() string

String returns the name of a ThreeWayState.

type Topology

type Topology uint16

A Topology is one RFC 5120 multi-topology identifier, twelve bits on the wire. Each Topology has its own reachability TLVs and its own computed result.

const (
	TopologyStandard    Topology = 0
	TopologyIPv6Unicast Topology = 2
)

The reserved topology identifiers this package names, from RFC 5120 section 7.5. TopologyStandard carries IPv4 unicast and is the topology a neighbor which sends no multi-topology TLV participates in.

func (Topology) String

func (t Topology) String() string

String returns the name of a Topology.

type Transport

type Transport interface {
	// ReadPDU blocks until a PDU arrives, then fills b with exactly one
	// whole PDU and reports the neighbor's SNPA. A Circuit learns of
	// transport death only through ReadPDU: any error not wrapping
	// ErrDropped is terminal. ReadPDU must not retain b after returning.
	ReadPDU(b []byte) (n int, src SNPA, err error)

	// WritePDU sends one PDU to dst, best effort: an error is never
	// terminal, since IS-IS recovers through the next hello. WritePDU must
	// not retain b after returning.
	WritePDU(dst SNPA, b []byte) error

	// MaxPDULen reports how many PDU octets the link carries, which is
	// the interface MTU less any framing the Transport adds. A Circuit
	// sizes its receive buffer and pads its hellos from it, so it must
	// account for the largest PDU the link can deliver. A link which will
	// carry link state PDUs must report at least 1492, ISO 10589's
	// ReceiveLSPBufferSize.
	MaxPDULen() int

	// LocalSNPA reports this end's own link layer address, which a
	// Circuit uses to drop its own transmissions delivered back to it.
	LocalSNPA() SNPA

	// Close unblocks a pending ReadPDU, which then returns an error. It is
	// called concurrently with a pending ReadPDU so a Circuit can always
	// tear down.
	Close() error
}

A Transport carries IS-IS PDUs between one Circuit and the link it attaches to. ListenEthernet opens one over a Linux AF_PACKET socket; callers may supply their own, such as an in-memory link for tests.

Framing belongs to the implementation: the LLC header, the 802.3 length field, multicast memberships, and filtering a Circuit's own transmissions out of its reads.

A Transport must be safe for concurrent use by multiple goroutines.

Jump to

Keyboard shortcuts

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