Skip to content

Conversation

@juagargi
Copy link
Contributor

@juagargi juagargi commented Dec 1, 2025

This PR shows the current implementation of the Hummingbird border router for visibility.
This implementation is not intended to land in scionproto/scion as is.

Some explanation and questions are documented below:

Implementation

The implementation requires changes in the border router and slayers, namely:

  • The border router must support priority queues. Optionally, priority processing of packets.
  • There must be a new Hummingbird path type, allowing its (de)serialization.
  • There is a slightly different procedure to forward a Hummingbird packet compared to a SCION packet.

The requirement to support priorities by the border router can be relaxed to only support
priority egress queues, if we assume that forwarding packets at the border router is not bottlenecked
by the processing of the packets. If so, this can be done in a similar way to what we see in #4054.

Questions

I find two main questions to finish a design to the implementation:

  1. A border router with priorities is needed. Will it be enough to add priority queues like proposed on PR border router: traffic control (scheduling) #4054 ?
    I.e., is the existence of priority egress queues enough?
    If the reason to drop packets is the bandwidth of the egress queue, the answer is "yes, it's enough".
    If the reason to drop packets is CPU processing being a bottleneck, then the answer is "no, it's not enough".
  2. The processing of a Hummingbird packet and a SCION one is very similar, but the functions operate on different path types. A decision on how to avoid duplicating code must be made.

Avoiding code duplication

To answer the second question above, which regards the code duplication,
a decision has to be made having the Go programming language in mind.
The solution would, ideally, remove all code duplication, and not introduce overhead,
or very little overhead, to the implementation with duplicated code.

Generics to avoid code duplication

Generics in Go do not allow the definition of a generic function that uses a generic type as a receiver of
a function, unless the type is constrained to an interface. The following is NOT allowed:

func processPeer[T any](p *T) (bool, disposition) {
	peer, err := determinePeer(p.PathMeta, p.InfoFields[p.CurrInf])
	if err != nil {
		return peer, errorDiscard("error", err)
	}
	return peer, pForward
}

The following IS allowed:

type pathLike interface {
	PathMeta() scion.MetaHdr
	Infofield(int) path.InfoField
	CurrInf() int
}

func processPeer[T any](p pathLike) (bool, disposition) {
	peer, err := determinePeer(p.PathMeta(), p.Infofield(p.CurrInf()))
	if err != nil {
		return peer, errorDiscard("error", err)
	}
	return peer, pForward
}

This fact prevents us from simply writing a generic function with the correct text inside
(namely accessing the struct's fields and functions) because the compiler won't allow it:
an interface defining the common behavior of all paths must be defined.

Even more, Go does not support template specialization with its generics.
This means that we can't modify a default behavior (e.g. SCION packet processing) with the definition
of a function only for a specialized type (e.g. a Hummingbird path type).
Possibly, the only advantage of using generics here would be easing the task of the compiler to remove
type assertions when using the interface.
The Go compiler has proven to be very efficient in removing this superfluous type assertion code
in the presence of only one type in the call stack, although this would be true only when all functions are
inlined.

With the use of generics we would be forcing the compiler to effectively create distinct definitions
for the distinct path types, which is an assurance that the superfluous type assertion won't be there.

Interface at slayers

The need to create a common interface to express the processing of all path types is still present though.
This means that any existing path type in slayers which we want to process with a common processing step,
must follow a common slayers path type interface.

Processing a Packet

In the border router, the processing of a packet with certain (segment-based) path type
requires these functions:

// Pseudo-code:
func parsePath() disposition
func validate() disposition // expiry, ingress id, pkt length, transit underlay, src&dst&host.
func verifyMAC() disposition
func updateSegID() disposition
func determinePeer() disposition
func ingressRouterAlert() disposition
func goInbound() disposition // terminal state: local IA delivery.
func doXover() disposition // already present in the path like object?
func validateEgressID() disposition
func egressRouterAlert() disposition
func validateEgressRouterUp() disposition

These functions on the packet processor would be written once, dealing with a generic path type.
As an example, let's consider a ingressRouterAlert function:

type SlayersHopField interface{}
type SlayersPathType interface {
	CurrHfIdx() int
	GetIngressAlert() *bool // Internally uses ConsDir to determine in/eg.
	SetHopField(hf SlayersHopField, hfIdx int) error
}
type proc[T SlayersPathType] struct {
	ingressFromLink uint16
	path            SlayersPathType
	hopField        SlayersHopField

	pkt Packet
}

func (p *proc[T]) ingressRouterAlert() disposition {
	if p.ingressFromLink == 0 {
		return pForward
	}
	alert := p.path.GetIngressAlert()
	if !*alert {
		return pForward
	}
	*alert = false
	if err := p.path.SetHopField(p.hopField, int(p.path.CurrHfIdx())); err != nil {
		return errorDiscard("error", err)
	}
	p.pkt.slowPathRequest = slowPathRequest{
		spType: slowPathRouterAlertIngress,
	}
	return pSlowPath
}

Common parts of proc that don't depend on the path type (such as ingressFromLink)
could be extracted and promoted to a baseProc type.

Following this approach with interfaces,
the path types declared a the slayers level would either
have to support this SlayersPathType interface,
or we would create wrappers around them to support the interface.

Summary

Regarding the implementation, it can be designed as follows:

  • The border router will support priority queues.
    A new field trafficClass inside the Packet struct
    can be declared to support priorities.
  • The processing of a packet is written once:
    basically, the functions we already have in dataplane.go to process a SCION packet.
    These functions will operate on an interface (aka SlayersPathType or similar).
  • The path types from slayers will be each wrapped in a struct to support the SlayersPathType interface,
    or themselves support the SlayersPathType interface.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant