Development workflow, Make targets, testing strategy, linting, protobuf generation, and contribution guidelines.
- Prerequisites
- Development Setup
- Make Targets
- Testing Strategy
- Linting
- Protobuf Workflow
- Code Conventions
- Contributing
- Podman + Podman Compose (all commands run inside containers)
- Git for version control
- Go 1.26 (only needed for local IDE support; builds run in containers)
Important: All testing, building, scanning, and linting runs inside Podman containers. No local Go toolchain is required for CI-equivalent builds.
# Clone the repository
git clone https://github.com/dantte-lp/gobfd.git
cd gobfd
# Start the development container
make up
# Build all binaries
make build
# Run all tests
make test
# Run linter
make lint
# All at once
make allAll Go commands run inside Podman containers via podman-compose exec.
| Target | Description |
|---|---|
make up |
Start development container |
make down |
Stop development container |
make restart |
Restart (down + up) |
make logs |
Follow development container logs |
make shell |
Open bash in development container |
| Target | Description |
|---|---|
make all |
Build + test + lint |
make build |
Compile all binaries with version info |
make test |
Run all tests with -race -count=1 |
make test-v |
Verbose test output |
make test-run RUN=TestFSM PKG=./internal/bfd |
Run specific test |
make fuzz FUNC=FuzzControlPacket PKG=./internal/bfd |
Fuzz test (60s) |
make test-integration |
Run integration tests |
| Target | Description |
|---|---|
make interop |
Full cycle: build + start + test + cleanup |
make interop-up |
Start 4-peer topology |
make interop-test |
Run interop Go tests |
make interop-down |
Stop and cleanup |
make interop-logs |
Follow interop container logs |
make interop-capture |
Live BFD packet capture |
make interop-pcap |
Decode captured packets |
make interop-pcap-summary |
CSV summary of captures |
make interop-bgp |
Full cycle BGP+BFD tests (FRR, BIRD3, ExaBGP) |
make interop-bgp-up |
Start BGP+BFD topology |
make interop-bgp-test |
Run BGP+BFD Go tests |
make interop-bgp-down |
Stop BGP+BFD topology |
make interop-clab |
Full cycle vendor NOS tests (Nokia, FRR, etc.) |
make interop-clab-up |
Deploy vendor NOS topology |
make interop-clab-test |
Run vendor interop Go tests |
make interop-clab-down |
Destroy vendor NOS containers |
| Target | Description |
|---|---|
make int-bgp-failover |
BGP fast failover demo (GoBFD + GoBGP + FRR) |
make int-haproxy |
HAProxy agent-check bridge demo |
make int-observability |
Prometheus + Grafana observability stack |
make int-exabgp-anycast |
ExaBGP anycast service announcement |
make int-k8s |
Kubernetes DaemonSet with GoBGP sidecar |
| Target | Description |
|---|---|
make lint |
Run golangci-lint v2 |
make lint-fix |
Auto-fix lint issues |
make vulncheck |
Run govulncheck |
make osv-scan |
Run OSV Scanner (osv-scanner scan -r .) |
| Target | Description |
|---|---|
make proto-gen |
Generate Go code from .proto files |
make proto-lint |
Lint protobuf definitions |
make proto-breaking |
Check for breaking changes |
make proto-update |
Update buf dependencies |
| Target | Description |
|---|---|
make tidy |
Run go mod tidy |
make download |
Download module dependencies |
make clean |
Remove binaries and caches |
make versions |
Show tool versions |
- Table-driven tests for all packages
t.Parallel()where safe (no shared mutable state)- Always run with
-race -count=1 goleak.VerifyTestMain(m)in every package for goroutine leak detection
Go 1.26 testing/synctest enables deterministic time-based testing:
func TestFSMDetectionTimeout(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
sess := newTestSession(t, SessionConfig{
DesiredMinTxInterval: 100 * time.Millisecond,
RequiredMinRxInterval: 100 * time.Millisecond,
DetectMultiplier: 3,
})
// Bring session to Up state
sess.injectPacket(controlPacket(StateInit, 0))
synctest.Wait()
require.Equal(t, StateUp, sess.State())
// Detection timeout = 3 x 100ms = 300ms
time.Sleep(350 * time.Millisecond) // virtual time
synctest.Wait()
require.Equal(t, StateDown, sess.State())
})
}Benefits:
- Tests run in virtual time (instant execution)
- Deterministic -- no flaky timer-dependent tests
- Perfect for BFD protocol timers and detection timeouts
Round-trip fuzz testing for the BFD packet parser:
make fuzz FUNC=FuzzControlPacket PKG=./internal/bfdThe fuzzer verifies: parse(marshal(packet)) == packet for arbitrary inputs.
make test-integrationUses testcontainers-go with Podman backend for testing the full daemon lifecycle.
See 05-interop.md for the 4-peer interop testing framework.
golangci-lint v2 with strict configuration (68 linters):
make lintConfiguration in .golangci.yml. Key linters enabled:
gosec(withaudit: true) -- security analysisgovet,staticcheck,errcheck-- standard Go checksgocognit,cyclop-- complexity limitsnoctx-- context propagation checksexhaustive-- exhaustive switch/map checks
Protobuf is managed by buf:
# After modifying api/bfd/v1/bfd.proto:
make proto-lint # Lint definitions
make proto-gen # Generate Go code to pkg/bfdpb/
make proto-breaking # Check for breaking changes vs masterNEVER edit files in
pkg/bfdpb/manually -- they are generated bybuf generate.
| Rule | Description |
|---|---|
| Errors | Always wrap with %w and context: fmt.Errorf("send control packet to %s: %w", peer, err) |
| Error matching | Use errors.Is/errors.As, never string matching |
| Context | First parameter, never stored in struct |
| Goroutines | Sender closes channels; tie lifetime to context.Context |
| Logging | ONLY log/slog with structured fields |
| Naming | Avoid stutter: package bfd; type Session not BFDSession |
| Imports | stdlib, blank line, external, blank line, internal |
| Interfaces | Small, near consumers |
| Tests | Table-driven, t.Parallel() where safe, always -race |
| FSM | All transitions MUST match RFC 5880 Section 6.8.6 exactly |
| Timers | BFD intervals in MICROSECONDS per RFC -- never confuse with ms |
- Open an issue to discuss the change before submitting a PR
- Follow the existing code style (see
CLAUDE.mdfor conventions) - Add tests for new functionality (
go test ./... -race -count=1) - Ensure
golangci-lint run ./...passes - Run
buf lintif proto files are modified - Keep commit messages descriptive and concise
# Development loop
make up # Start dev environment
# ... make changes ...
make all # Build + test + lint
# For protocol changes:
make interop # Verify interop with 4 peers
# For proto changes:
make proto-gen # Regenerate Go code
make proto-lint # Lint proto definitions- 01-architecture.md -- System architecture and package structure
- 05-interop.md -- Interoperability testing
- CLAUDE.md -- Full code conventions and commands
Last updated: 2026-02-24