-
Notifications
You must be signed in to change notification settings - Fork 2
go plugins
Pre-Alpha. This page describes behavior that may change.
This page is the five-minute version of writing a Go plugin against the Ze SDK. It covers the skeleton, the lifecycle, registration, the callback model, and how to make engine calls. See YANG schema, Handlers, Commands, and Testing for the deeper material, or the in-tree plugin development guide for the authoritative reference.
Read the plugin development index first if you want the trade-off between the Go path and the external path. This page assumes you have already picked Go.
package main
import (
"context"
"fmt"
"os"
"github.com/ze-software/ze/pkg/plugin/sdk"
)
func main() {
p, err := sdk.NewFromEnv("my-plugin")
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
defer p.Close()
p.OnEvent(func(event string) error {
fmt.Println("event:", event)
return nil
})
p.SetStartupSubscriptions([]string{"update"}, nil, "")
err = p.Run(context.Background(), sdk.Registration{
Families: []sdk.FamilyDecl{
{Name: "ipv4/unicast", Mode: "both"},
},
})
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}That is a complete plugin. It connects to the engine over TLS, subscribes to UPDATE events at startup, prints each event to stdout, and runs until the engine sends bye.
go build -o my-plugin
ze plugin validate binary ./my-pluginsdk.NewFromEnv(name) reads the environment variables Ze sets when it launches a plugin.
| Variable | Purpose |
|---|---|
ZE_PLUGIN_HUB_HOST |
TLS host. Default 127.0.0.1. |
ZE_PLUGIN_HUB_PORT |
TLS port. Default 12700. |
ZE_PLUGIN_HUB_TOKEN |
Per-plugin auth token. Cleared from the environment after the SDK reads it. |
ZE_PLUGIN_CERT_FP |
SHA-256 fingerprint of the engine's TLS certificate, for cert pinning. |
ZE_PLUGIN_NAME |
Plugin name as configured in Ze. |
The token is bound to the plugin name. A plugin cannot impersonate a different plugin with its own token. The certificate fingerprint is verified during the TLS handshake to prevent man-in-the-middle attacks. The token is cleared from /proc/<pid>/environ after the SDK has read it.
For testing or for in-process plugins, use sdk.NewWithConn(name, conn) to pass an existing net.Conn.
The SDK runs the five-stage startup protocol for you. You register callbacks before Run. The SDK invokes them at the right stage.
| Stage | Method | Purpose |
|---|---|---|
| 1 | argument to Run
|
Plugin declares its families, commands, and schema. |
| 2 | OnConfigure |
Engine delivers the plugin's config sections. |
| 3 | argument to Run
|
Plugin declares BGP capabilities to inject. |
| 4 | OnShareRegistry |
Engine delivers the full command registry. |
| 5 | (implicit) | Plugin enters the runtime event loop. |
After ready, the runtime callbacks are the ones that matter.
| Callback | Signature | When called |
|---|---|---|
OnEvent |
func(string) error |
A BGP event is delivered to the plugin. |
OnExecuteCommand |
func(serial, command string, args []string, peer string) (status, data string, err error) |
A command from another surface targets this plugin. |
OnEncodeNLRI |
func(family string, args []string) (string, error) |
NLRI encode request for a registered family. |
OnDecodeNLRI |
func(family, hex string) (string, error) |
NLRI decode request for a registered family. |
OnConfigVerify |
func([]ConfigSection) error |
Engine asks the plugin to validate a candidate config (reload time). |
OnConfigApply |
func([]ConfigDiffSection) error |
Engine asks the plugin to apply a config diff. |
OnValidateOpen |
func(*ValidateOpenInput) *ValidateOpenOutput |
Plugin can accept or reject a peer's OPEN. |
OnBye |
func(string) |
Shutdown reason. |
OnStarted |
func(ctx) error |
Post-startup hook. Safe place to make engine calls. |
After startup, the plugin can call back into the engine through methods on the *sdk.Plugin.
| Method | Purpose |
|---|---|
UpdateRoute(ctx, peerSelector, command) |
Inject route updates to matching peers. |
DispatchCommand(ctx, command) |
Route a command through the engine's dispatcher. |
EmitEvent(ctx, namespace, eventType, direction, peer, payload) |
Push an event onto the bus for other plugins to consume. |
SubscribeEvents(ctx, events, peers, format) |
Subscribe to events at runtime (SetStartupSubscriptions is the race-free version for stage 5). |
DecodeNLRI / EncodeNLRI
|
Use the engine's family registry. |
DecodeMPReach, DecodeMPUnreach, DecodeUpdate
|
UPDATE message codec helpers. |
Plugins can register their own ze doctor checks via the DoctorChecks field on the Registration struct. Each check is a function that returns a diagnostic code, severity, and message. Plugin-registered checks run alongside the built-in doctor checks.
reg := registry.Registration{
Name: "my-plugin",
DoctorChecks: []registry.DoctorCheck{
{
Code: "doctor-myplugin-reachable",
Severity: "error",
Check: func() (bool, string) { /* ... */ },
},
},
}Plugins can contribute data to show commands through the show enricher registry. In-process plugins register via show.MustRegister() in init(). When a show command calls show.Enrich(), registered enrichers are invoked to add plugin-contributed fields to the output. External plugins use the ze-plugin-callback:enrich-show callback protocol instead. See External Plugins for the wire-level details.
A Go plugin that wants to expose Prometheus metrics declares a ConfigureMetrics callback in its Registration. The engine calls it with a metrics.Registry before RunEngine. The plugin uses the registry to register counters and gauges, then holds them in package-level state:
import "github.com/ze-software/ze/internal/core/metrics"
var metricsPtr atomic.Pointer[pluginMetrics]
type pluginMetrics struct {
peersUp metrics.Gauge
routesSent metrics.Counter
}
func SetMetricsRegistry(reg metrics.Registry) {
metricsPtr.Store(&pluginMetrics{
peersUp: reg.Gauge("ze_myplugin_peers_up",
"Peers currently up."),
routesSent: reg.Counter("ze_myplugin_routes_sent_total",
"Routes sent by this plugin."),
})
}Then in pluginSetup:
reg := registry.Registration{
Name: "my-plugin",
// ...
ConfigureMetrics: func(reg any) {
if r, ok := reg.(metrics.Registry); ok {
SetMetricsRegistry(r)
}
},
}Metrics land in Ze's Prometheus endpoint automatically, alongside the engine's own metrics. No second scrape target, no separate exporter.
Naming convention. ze_{scope}_{subject}_{detail}. The scope is the plugin area (watchdog, rpki, fib_kernel, sysrib, persist). The subject is what is being measured (peers, routes, cache). The detail is the specific metric (up, sent_total, items). Counters end in _total. Gauges do not.
Shipped examples. The in-tree plugins bgp-rib, bgp-watchdog, bgp-rpki, bgp-persist, fib-kernel, and sysrib each export their own metrics through ConfigureMetrics. Grep for SetMetricsRegistry in the source tree for concrete patterns.
Plugins are bound to peers under the process block, and each plugin has its own plugin { internal <name> { use <plugin-name> } } declaration. Built-in plugins use internal with the use keyword; external process plugins use external with run.
plugin {
hub {
server local {
ip 127.0.0.1;
port 12700;
secret "shared-token-min-32-characters!!!";
}
}
external my-plugin {
run "/usr/local/bin/my-plugin";
encoder json;
}
}
bgp {
peer upstream {
connection { remote { ip 10.0.0.1; } }
session { asn { remote 65001; } }
process my-plugin {
receive [ update state ]
}
}
}
The receive list is the set of base events the plugin wants. Plugins can also register custom event types they emit themselves.
- Plugin development index for the trade-off between Go and external plugins.
- Plugins for the catalogue of plugins already shipped.
- The Go example plugin for a complete worked example.
Adapted from main/docs/plugin-development/README.md.
Unreviewed draft. This wiki was authored in bulk and has not been reviewed. File corrections on the issue tracker.
- Overview
- YANG Model
- Editor Workflow
- Archive and Rollback
- System
- Interfaces
- VRRP
- BFD
- FIB
- OSPF
- IS-IS
- MPLS / LDP / RSVP-TE
- RSVP-TE
- SRv6
- Static Routes
- Policy Routing
- Firewall
- Traffic Control
- Class of Service
- L2TP/PPP
- PPPoE
- VPP Data Plane
- RPKI
- IPsec VPN
- TACACS+ AAA
- RADIUS AAA
- AS112 DNS
- Authorization
- Fleet
- BGP
- Starting and Stopping
- Show Commands
- Monitoring
- Flow Export
- DDoS Mitigation
- Anomaly Detection
- Health Checks
- Audit Trail
- Production Diagnostics
- Logging
- Operational Reports
- Healthcheck
- Self-Update
- Zero-Touch Provisioning
- MRT Analysis
- Upgrade and Restart
- Storage
- Policy
- Core
- Resilience
- Validation
- Capabilities
- Address Families
- Protocol
- Subsystems
- Infrastructure
- Route Server at an IXP
- Transit Edge with RPKI
- Public Looking Glass
- ExaBGP Migration Walkthrough
- FlowSpec Injection
- Chaos-Tested Peering
- AS Path Topology