Skip to content

handlers

Thomas Mangin edited this page Jul 25, 2026 · 2 revisions

Pre-Alpha. This page describes behavior that may change.

Handlers are the callbacks a plugin registers with the SDK to react to engine-initiated events. They cover the whole plugin lifecycle: the stage-2 config delivery, the stage-4 registry share, runtime BGP events, command dispatch, NLRI codec requests, config reload, OPEN validation, and shutdown. Every handler is registered with an On* method on *sdk.Plugin before you call Run().

The full list is in pkg/plugin/sdk/sdk_callbacks.go. This page covers the ones you will touch first.

Registration

Register every handler you need before calling Run(). The order of registration does not matter, but the fact that they are all registered before Run() does: the SDK wires them into the event loop at startup and handlers registered afterwards are ignored.

p := sdk.NewWithConn("my-plugin", conn)

// Startup
p.OnConfigure(func(sections []sdk.ConfigSection) error { ... })
p.OnShareRegistry(func(commands []sdk.RegistryCommand) { ... })

// Runtime
p.OnEvent(func(event string) error { ... })
p.OnExecuteCommand(func(serial, command string, args []string, peer string) (string, string, error) { ... })
p.OnConfigVerify(func(sections []sdk.ConfigSection) error { ... })
p.OnConfigApply(func(diffs []sdk.ConfigDiffSection) error { ... })
p.OnValidateOpen(func(input *sdk.ValidateOpenInput) *sdk.ValidateOpenOutput { ... })

// NLRI codecs
p.OnEncodeNLRI(func(family string, args []string) (string, error) { ... })
p.OnDecodeNLRI(func(family, hex string) (string, error) { ... })
p.OnDecodeCapability(func(code uint8, hex string) (string, error) { ... })

// Lifecycle
p.OnStarted(func(ctx context.Context) error { ... })
p.OnBye(func(reason string) { ... })

p.Run(ctx, sdk.Registration{ ... })

The startup handlers

OnConfigure

Stage 2. The engine delivers the plugin's config sections. Each section is a {Root, Data} pair where Data is the JSON encoding of the subtree under Root.

p.OnConfigure(func(sections []sdk.ConfigSection) error {
    for _, s := range sections {
        var cfg MyConfig
        if err := json.Unmarshal([]byte(s.Data), &cfg); err != nil {
            return fmt.Errorf("parse config: %w", err)
        }
        // Store cfg for later use.
    }
    return nil
})

Return nil to accept the config. Return an error to reject it: that aborts startup, and the engine logs the failure.

OnShareRegistry

Stage 4. The engine hands you the command registry from every other loaded plugin. The handler is informational: use the registry to route command dispatches or to know which commands are available to DispatchCommand.

p.OnShareRegistry(func(commands []sdk.RegistryCommand) {
    for _, cmd := range commands {
        // cmd.Name, cmd.Plugin, cmd.Encoding
    }
})

OnStarted

A post-stage-5 hook. Safe to call engine methods here (EmitEvent, DispatchCommand, SubscribeEvents, UpdateRoute). Runs on the plugin's goroutine, so do not block it forever.

p.OnStarted(func(ctx context.Context) error {
    return p.EmitEvent(ctx, "my-plugin", "ready", "", "", `{"ok":true}`)
})

The runtime handlers

OnEvent

The BGP event delivery path. The engine delivers each subscribed event as a JSON string, and you handle it.

p.OnEvent(func(event string) error {
    // event is the full JSON envelope:
    // {"type":"bgp","bgp":{"peer":{"address":"10.0.0.1"},"message":{"type":"update"},...}}
    return nil
})

Event delivery is synchronous. The handler must return quickly. If you need to do real work, push the event onto a queue and process it on a worker goroutine.

OnExecuteCommand

The CLI and API dispatch path. When a user runs a command that your plugin declared in its Registration, the engine delivers it through execute-command.

p.OnExecuteCommand(func(serial, command string, args []string, peer string) (string, string, error) {
    switch command {
    case "my-plugin status":
        return "done", `{"status":"running","uptime":3600}`, nil

    case "my-plugin check":
        if len(args) < 1 {
            return "error", "usage: my-plugin check <target>", nil
        }
        result := performCheck(args[0])
        return "done", result, nil

    default:
        return "error", "unknown command: " + command, nil
    }
})

The return tuple is (status, data, error). status is a short string (done, error, or a custom status). data is the structured result, typically JSON. If error is non-nil, the SDK sends an error response and the status and data values are ignored.

OnConfigVerify and OnConfigApply

The reload path. When the operator reloads the config, every plugin with a Verify handler gets the candidate config first, before anything is applied. If any plugin returns an error, the reload is rejected and nothing is applied.

p.OnConfigVerify(func(sections []sdk.ConfigSection) error {
    // Read-only check. Return nil to accept, error to reject.
    return nil
})

p.OnConfigApply(func(diffs []sdk.ConfigDiffSection) error {
    // Apply the diff. The plugin's current state must match the new config after this returns.
    return nil
})

ConfigDiffSection carries added, removed, and changed paths, so you can react minimally instead of re-parsing the full tree.

OnValidateOpen

Called when a peer sends an OPEN message. The plugin can accept, reject, or modify the session parameters before the FSM moves to OpenConfirm. The Role plugin uses this hook to enforce RFC 9234 role compatibility; the Hostname and Software Version plugins use it to surface the peer's identity to subscribers.

p.OnValidateOpen(func(in *sdk.ValidateOpenInput) *sdk.ValidateOpenOutput {
    // Inspect in.Capabilities, in.RouterID, in.ASN, in.Peer.
    return &sdk.ValidateOpenOutput{Accept: true}
})

The codec handlers

OnEncodeNLRI, OnDecodeNLRI, and OnDecodeCapability let a plugin provide encode and decode functions for a custom address family or capability. These are the hooks every bgp-nlri-* plugin uses.

p.OnEncodeNLRI(func(family string, args []string) (string, error) {
    // Return hex-encoded NLRI bytes.
})

p.OnDecodeNLRI(func(family, hex string) (string, error) {
    // Return JSON-encoded parsed NLRI.
})

The lifecycle handler

OnBye

The engine is shutting the plugin down. The reason string tells you why. Use this to flush state, close files, or release external resources.

p.OnBye(func(reason string) {
    log.Printf("plugin shutting down: %s", reason)
})

See also

Adapted from main/docs/plugin-development/handlers.md.

Home

About

First Steps

Configuration

Operation

Interfaces

Plugins

Plugin Development

Chaos Testing

Blueprints

Development

Reference

Clone this wiki locally