mirror of
https://github.com/praetorian-inc/nerva.git
synced 2026-06-20 09:27:27 +00:00
Page:
Plugin Development
No results
1
Plugin Development
nsportsman edited this page 2026-01-29 20:12:34 -06:00
Plugin Development
Guide to creating new protocol detection plugins for Nerva.
Plugin Architecture
Nerva uses a plugin architecture where each protocol has its own detection module. Plugins are registered at startup and executed against targets.
Plugin Interface
type Plugin interface {
// Run performs service detection on an open connection
Run(conn net.Conn, timeout time.Duration, target Target) (*Service, error)
// PortPriority returns true if this is the default port for this protocol
PortPriority(port uint16) bool
// Name returns the protocol identifier (e.g., "ssh", "http")
Name() string
// Type returns the transport protocol
Type() Protocol // TCP, UDP, or SCTP
// Priority returns execution priority (lower = run first)
Priority() int
}
Creating a New Plugin
1. Create Plugin Directory
pkg/plugins/services/myprotocol/
├── myprotocol.go
└── myprotocol_test.go
2. Implement the Plugin
package myprotocol
import (
"net"
"time"
"github.com/praetorian-inc/nerva/pkg/plugins"
)
type Plugin struct{}
func init() {
plugins.RegisterPlugin(&Plugin{})
}
func (p *Plugin) Run(conn net.Conn, timeout time.Duration, target plugins.Target) (*plugins.Service, error) {
// Set read deadline
conn.SetReadDeadline(time.Now().Add(timeout))
// Send probe (if needed)
probe := []byte("PROBE\r\n")
_, err := conn.Write(probe)
if err != nil {
return nil, err
}
// Read response
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
return nil, err
}
response := buf[:n]
// Validate protocol signature
if !isMyProtocol(response) {
return nil, nil // Not this protocol
}
// Extract metadata
metadata := ServiceMyProtocol{
Version: extractVersion(response),
Banner: string(response),
}
// Return service
return plugins.CreateServiceFrom(target, metadata, false, "", plugins.TCP), nil
}
func (p *Plugin) PortPriority(port uint16) bool {
return port == 12345 // Default port for this protocol
}
func (p *Plugin) Name() string {
return "myprotocol"
}
func (p *Plugin) Type() plugins.Protocol {
return plugins.TCP
}
func (p *Plugin) Priority() int {
return 100 // Medium priority
}
// Metadata structure
type ServiceMyProtocol struct {
Version string `json:"version"`
Banner string `json:"banner"`
}
func isMyProtocol(data []byte) bool {
// Check for protocol signature
return len(data) > 0 && data[0] == 'M'
}
func extractVersion(data []byte) string {
// Parse version from response
return "1.0"
}
3. Register the Plugin
Add import to pkg/scan/plugin_list.go:
import (
_ "github.com/praetorian-inc/nerva/pkg/plugins/services/myprotocol"
)
4. Write Tests
package myprotocol
import (
"testing"
"github.com/praetorian-inc/nerva/pkg/test"
)
func TestMyProtocol(t *testing.T) {
// Use dockertest for integration testing
// See existing plugins for examples
}
Best Practices
- Validate early — Return
nilquickly if response doesn't match protocol - Set timeouts — Always set read deadlines on connections
- Extract metadata — Capture as much useful information as possible
- Handle errors — Return appropriate errors for debugging
- Test thoroughly — Use dockertest for integration tests
Existing Plugins as Reference
- Simple:
pkg/plugins/services/echo/ - TCP with banner:
pkg/plugins/services/ftp/ - Binary protocol:
pkg/plugins/services/redis/ - TLS detection:
pkg/plugins/services/http/ - UDP:
pkg/plugins/services/dns/