Skip to content

vitalvas/gonetconf

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Go NETCONF SDK

A comprehensive Go library for implementing NETCONF client connections supporting both NETCONF 1.0 and 1.1 protocols with automatic version detection and flexible transport layers.

Features

  • Protocol Support: NETCONF 1.0 and 1.1 with automatic version detection
  • Transport Interface: Extensible transport interface with built-in SSH and TLS implementations
  • Standard Operations: Get, GetConfig, EditConfig, CopyConfig, DeleteConfig, Lock, Unlock, Commit, DiscardChanges, Validate, KillSession
  • Partial Lock: RFC 5717 compliant partial locking of configuration subtrees
  • With-defaults: RFC 6243 compliant control of default values in responses
  • Notifications: Subscribe to and handle NETCONF notifications and events
  • Connection Pooling: Built-in connection pool for managing multiple concurrent connections
  • Concurrency Safe: Thread-safe operations with proper mutex handling

Supported RFCs

  • RFC 6241: Network Configuration Protocol (NETCONF) - Core protocol implementation
  • RFC 6242: Using the NETCONF Protocol over Secure Shell (SSH) - SSH transport layer
  • RFC 7589: Using the NETCONF Protocol over Transport Layer Security (TLS) - TLS transport layer
  • RFC 5717: Partial Lock Remote Procedure Call (RPC) for NETCONF - Partial locking capabilities
  • RFC 6243: With-defaults Capability for NETCONF - Default value handling in responses

Installation

go get github.com/vitalvas/gonetconf

Quick Start

Basic SSH Client Usage

package main

import (
    "context"
    "log"
    "time"

    "github.com/sirupsen/logrus"
    "github.com/vitalvas/gonetconf"
    "golang.org/x/crypto/ssh"
)

func main() {
    // Configure SSH authentication
    sshConfig := &ssh.ClientConfig{
        User: "admin",
        Auth: []ssh.AuthMethod{
            ssh.Password("password"),
        },
        HostKeyCallback: ssh.InsecureIgnoreHostKey(), // Use proper verification in production
        Timeout:         30 * time.Second,
    }

    // Create logger for transport
    logger := logrus.New()
    logger.SetLevel(logrus.InfoLevel) // Set appropriate log level
    
    // Create SSH transport
    transport, err := gonetconf.NewSSHTransport("192.168.1.1:830", sshConfig, 30*time.Second, logger)
    if err != nil {
        log.Fatal(err)
    }

    config := &gonetconf.Config{
        Address:   "192.168.1.1:830",  // host:port format
        Transport: transport,
        Timeout:   30 * time.Second,
    }

    client := gonetconf.NewClient(config)
    ctx := context.Background()

    if err := client.Connect(ctx); err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    // NETCONF version is automatically detected during handshake
    log.Printf("Connected with NETCONF version: %s", client.Session().Version)

    // Get running configuration
    reply, err := client.GetConfig(ctx, gonetconf.DatastoreRunning, nil)
    if err != nil {
        log.Fatal(err)
    }

    log.Printf("Operation successful: %v", reply.OK != nil)
}

TLS Transport Usage

package main

import (
    "context"
    "crypto/tls"
    "log"
    "time"

    "github.com/sirupsen/logrus"
    "github.com/vitalvas/gonetconf"
)

func main() {
    // Configure TLS settings
    tlsConfig := &tls.Config{
        ServerName: "netconf-server.example.com",
        MinVersion: tls.VersionTLS12,
        // Add certificates, CA settings, etc. as needed
    }

    // Create logger for transport
    logger := logrus.New()
    logger.SetLevel(logrus.InfoLevel) // Set appropriate log level
    
    // Create TLS transport
    transport, err := gonetconf.NewTLSTransport("192.168.1.1:6513", tlsConfig, 30*time.Second, logger)
    if err != nil {
        log.Fatal(err)
    }

    config := &gonetconf.Config{
        Address:   "192.168.1.1:6513",  // Standard NETCONF over TLS port
        Transport: transport,
        Timeout:   30 * time.Second,
    }

    client := gonetconf.NewClient(config)
    ctx := context.Background()

    if err := client.Connect(ctx); err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    // Use client for operations...
}

SSH Key Authentication

// Load SSH private key
keyData, err := os.ReadFile("/path/to/private/key")
if err != nil {
    log.Fatal(err)
}

key, err := ssh.ParsePrivateKey(keyData)
if err != nil {
    log.Fatal(err)
}

// Configure SSH with key authentication
sshConfig := &ssh.ClientConfig{
    User: "admin",
    Auth: []ssh.AuthMethod{
        ssh.PublicKeys(key),
        ssh.Password("fallback_password"), // Optional fallback
    },
    HostKeyCallback: ssh.InsecureIgnoreHostKey(), // Use proper verification in production
    Timeout:         30 * time.Second,
}

// Create logger
logger := logrus.New()
logger.SetLevel(logrus.InfoLevel)

// Create SSH transport
transport, err := gonetconf.NewSSHTransport("192.168.1.1:830", sshConfig, 30*time.Second, logger)
if err != nil {
    log.Fatal(err)
}

Configuration

Client Configuration

The Config struct uses the Transport interface directly:

config := &gonetconf.Config{
    Address:   "hostname:port",      // Required: host:port format (e.g., "192.168.1.1:830")
    Transport: transport,            // Required: Transport implementation (SSH or TLS)
    Timeout:   30 * time.Second,     // Optional: Connection timeout (default: 30s)
}

Creating Transports

SSH Transport

// SSH transport with password authentication
sshConfig := &ssh.ClientConfig{
    User: "admin",
    Auth: []ssh.AuthMethod{
        ssh.Password("password"),
    },
    HostKeyCallback: ssh.InsecureIgnoreHostKey(), // Use proper verification in production
    Timeout: 30 * time.Second,
}

logger := logrus.New()
logger.SetLevel(logrus.InfoLevel)

transport, err := gonetconf.NewSSHTransport("192.168.1.1:830", sshConfig, 30*time.Second, logger)

TLS Transport

// TLS transport
tlsConfig := &tls.Config{
    ServerName: "netconf-server.example.com",
    MinVersion: tls.VersionTLS12,
    // Add certificates, CA settings, etc. as needed
}

logger := logrus.New()
logger.SetLevel(logrus.InfoLevel)

transport, err := gonetconf.NewTLSTransport("192.168.1.1:6513", tlsConfig, 30*time.Second, logger)

Connection Pool Usage

poolConfig := &gonetconf.PoolConfig{
    MinSize:     2,              // Minimum pool size (default: 1)
    MaxSize:     10,             // Maximum pool size (default: 10)
    IdleTimeout: 5 * time.Minute, // Idle connection timeout (default: 5m)
}

pool, err := gonetconf.NewPool(config, poolConfig)
if err != nil {
    log.Fatal(err)
}
defer pool.Close()

ctx := context.Background()
if err := pool.Initialize(ctx); err != nil {
    log.Fatal(err)
}

// Get client from pool
client, err := pool.Get(ctx)
if err != nil {
    log.Fatal(err)
}
defer pool.Put(client)

// Use client for operations
reply, err := client.GetConfig(ctx, gonetconf.DatastoreRunning, nil)

Operations

Standard NETCONF Operations

// Get operational data
reply, err := client.Get(ctx, filter)

// Get configuration data from datastores
reply, err := client.GetConfig(ctx, gonetconf.DatastoreRunning, filter)
reply, err := client.GetConfig(ctx, gonetconf.DatastoreCandidate, filter)
reply, err := client.GetConfig(ctx, gonetconf.DatastoreStartup, filter)

// Edit configuration with options
reply, err := client.EditConfig(ctx, gonetconf.DatastoreCandidate, configData,
    gonetconf.WithDefaultOperation(gonetconf.DefaultOpMerge),
    gonetconf.WithTestOption(gonetconf.TestOpTestThenSet),
    gonetconf.WithErrorOption(gonetconf.ErrorOpRollbackOnError),
)

// Copy configuration between datastores
reply, err := client.CopyConfig(ctx, gonetconf.DatastoreRunning, gonetconf.DatastoreCandidate)

// Delete configuration
reply, err := client.DeleteConfig(ctx, gonetconf.DatastoreStartup)

// Lock and unlock datastores
reply, err := client.Lock(ctx, gonetconf.DatastoreCandidate)
reply, err := client.Unlock(ctx, gonetconf.DatastoreCandidate)

// Partial Lock operations (RFC 5717)
// Lock specific subtrees instead of entire datastores
reply, err := client.PartialLock(ctx, []string{
    "/interfaces/interface[name='eth0']",
    "/vlans/vlan[vlan-id='100']",
})

// Helper methods for common partial lock scenarios
reply, err := client.PartialLockInterface(ctx, "eth0")
reply, err := client.PartialLockVLAN(ctx, "100")
reply, err := client.PartialLockRouting(ctx)

// Unlock using lock ID from partial lock response
reply, err := client.PartialUnlock(ctx, "lock-id-12345")

// Commit changes
reply, err := client.Commit(ctx)
reply, err := client.Commit(ctx, gonetconf.WithConfirmed(300)) // Confirmed commit

// Discard changes
reply, err := client.DiscardChanges(ctx)

// Validate configuration
reply, err := client.Validate(ctx, gonetconf.DatastoreCandidate)

// Kill session
reply, err := client.KillSession(ctx, "session-id")

Filters

// Subtree filter
filter := gonetconf.NewSubtreeFilter(`
    <interfaces xmlns="urn:example:interfaces">
        <interface>
            <name/>
            <oper-status/>
        </interface>
    </interfaces>
`)

// XPath filter
filter := gonetconf.NewXPathFilter("//interface[name='eth0']")

Partial Lock Operations (RFC 5717)

Partial lock allows you to lock specific subtrees of configuration data instead of entire datastores:

// Lock specific configuration subtrees
xpaths := []string{
    "/interfaces/interface[name='eth0']",
    "/vlans/vlan[vlan-id='100']",
    "/routing/static-routes",
}

reply, err := client.PartialLock(ctx, xpaths)
if err != nil {
    log.Fatal(err)
}

// Extract lock ID from response for unlock operation
lockID := extractLockID(reply) // You need to parse this from the XML response

// Perform configuration changes on locked subtrees
configData := `
    <config>
        <interfaces xmlns="urn:example:interfaces">
            <interface>
                <name>eth0</name>
                <description>Updated via partial lock</description>
            </interface>
        </interfaces>
    </config>
`

editReply, err := client.EditConfig(ctx, gonetconf.DatastoreCandidate, configData)
if err != nil {
    log.Fatal(err)
}

// Commit changes
commitReply, err := client.Commit(ctx)
if err != nil {
    log.Fatal(err)
}

// Release the partial lock
unlockReply, err := client.PartialUnlock(ctx, lockID)
if err != nil {
    log.Fatal(err)
}

// Helper methods for common scenarios
reply, err = client.PartialLockInterface(ctx, "eth0")  // Locks specific interface
reply, err = client.PartialLockVLAN(ctx, "100")       // Locks specific VLAN
reply, err = client.PartialLockRouting(ctx)           // Locks routing configuration

With-defaults Operations (RFC 6243)

The with-defaults capability controls how default values are handled in NETCONF Get and GetConfig operations:

// Using option functions with Get operations
reply, err := client.Get(ctx, filter, gonetconf.WithDefaults(gonetconf.WithDefaultsReport))
reply, err := client.Get(ctx, filter, gonetconf.WithDefaults(gonetconf.WithDefaultsReportAll))
reply, err := client.Get(ctx, filter, gonetconf.WithDefaults(gonetconf.WithDefaultsTrim))
reply, err := client.Get(ctx, filter, gonetconf.WithDefaults(gonetconf.WithDefaultsExplicit))

// Using option functions with GetConfig operations
reply, err := client.GetConfig(ctx, gonetconf.DatastoreRunning, filter, 
    gonetconf.WithDefaultsConfig(gonetconf.WithDefaultsReport))

// Convenience functions for Get operations
reply, err := client.Get(ctx, filter, gonetconf.WithDefaultsGetReportAll())         // Include all defaults
reply, err := client.Get(ctx, filter, gonetconf.WithDefaultsGetReportAllTagged())   // Include defaults with tags
reply, err := client.Get(ctx, filter, gonetconf.WithDefaultsTrimmed())              // Exclude defaults
reply, err := client.Get(ctx, filter, gonetconf.WithDefaultsExplicitOnly())         // Only explicit values

// Convenience functions for GetConfig operations
reply, err := client.GetConfig(ctx, gonetconf.DatastoreRunning, filter, 
    gonetconf.WithDefaultsGetConfigReportAll())
reply, err := client.GetConfig(ctx, gonetconf.DatastoreRunning, filter, 
    gonetconf.WithDefaultsGetConfigReportAllTagged())
reply, err := client.GetConfig(ctx, gonetconf.DatastoreRunning, filter, 
    gonetconf.WithDefaultsGetConfigTrimmed())
reply, err := client.GetConfig(ctx, gonetconf.DatastoreRunning, filter, 
    gonetconf.WithDefaultsGetConfigExplicitOnly())

// Direct helper methods
reply, err := client.GetWithDefaults(ctx, filter, gonetconf.WithDefaultsReport)
reply, err := client.GetConfigWithDefaults(ctx, gonetconf.DatastoreRunning, filter, gonetconf.WithDefaultsTrim)

// Specific mode helper methods
reply, err := client.GetReportAll(ctx, filter)                                     // Get with all defaults
reply, err := client.GetConfigReportAll(ctx, gonetconf.DatastoreRunning, filter)  // GetConfig with all defaults
reply, err := client.GetTrimmed(ctx, filter)                                       // Get without defaults
reply, err := client.GetConfigTrimmed(ctx, gonetconf.DatastoreRunning, filter)    // GetConfig without defaults
reply, err := client.GetExplicit(ctx, filter)                                      // Get explicit values only
reply, err := client.GetConfigExplicit(ctx, gonetconf.DatastoreRunning, filter)   // GetConfig explicit values only

With-defaults Modes

  • report-all: Include all default values in the response
  • report-all-tagged: Include default values with special tagging to identify them
  • trim: Exclude default values from the response
  • explicit: Include only explicitly set values (no defaults)

Notifications

notificationClient := gonetconf.NewNotificationClient(client)
defer notificationClient.Close()

// Register event handlers
notificationClient.RegisterHandler("*", func(notification *gonetconf.Notification) error {
    log.Printf("Received notification: %+v", notification)
    return nil
})

notificationClient.RegisterHandler("interface-change", func(notification *gonetconf.Notification) error {
    log.Printf("Interface change: %+v", notification.Event)
    return nil
})

// Create filter for specific events
filter := gonetconf.NewSubtreeFilter(`
    <interfaces xmlns="urn:example:interfaces">
        <interface>
            <name/>
            <oper-status/>
        </interface>
    </interfaces>
`)

// Subscribe to notifications
subscription, err := notificationClient.Subscribe(ctx, "NETCONF",
    gonetconf.WithFilter(filter),
    gonetconf.WithStartTime(time.Now()),
)
if err != nil {
    log.Fatal(err)
}

// Unsubscribe when done
notificationClient.Unsubscribe(subscription.ID)

Error Handling

The library provides specific error types for different failure scenarios:

if err != nil {
    switch e := err.(type) {
    case *gonetconf.NetconfError:
        log.Printf("NETCONF error: %s (type: %s, severity: %s)", e.Error(), e.Type(), e.Severity())
        if e.IsFatal() {
            log.Printf("Fatal error encountered")
        }
    case *gonetconf.TransportError:
        log.Printf("Transport error: %s", e.Error())
    case *gonetconf.AuthenticationError:
        log.Printf("Authentication error: %s", e.Error())
    case *gonetconf.SessionError:
        log.Printf("Session error: %s", e.Error())
    default:
        log.Printf("Unknown error: %s", e.Error())
    }
}

Constants and Enums

The library provides comprehensive constants for NETCONF operations:

Datastores

gonetconf.DatastoreRunning   // "running"
gonetconf.DatastoreCandidate // "candidate"
gonetconf.DatastoreStartup   // "startup"

Default Operations

gonetconf.DefaultOpMerge   // "merge"
gonetconf.DefaultOpReplace // "replace"
gonetconf.DefaultOpCreate  // "create"
gonetconf.DefaultOpDelete  // "delete"
gonetconf.DefaultOpRemove  // "remove"
gonetconf.DefaultOpNone    // "none"

Test Options

gonetconf.TestOpTestThenSet // "test-then-set"
gonetconf.TestOpSet         // "set"
gonetconf.TestOpTestOnly    // "test-only"

Error Options

gonetconf.ErrorOpStopOnError     // "stop-on-error"
gonetconf.ErrorOpContinueOnError // "continue-on-error"
gonetconf.ErrorOpRollbackOnError // "rollback-on-error"

With-defaults Modes

gonetconf.WithDefaultsReport     // "report-all"
gonetconf.WithDefaultsReportAll  // "report-all-tagged"
gonetconf.WithDefaultsTrim       // "trim"
gonetconf.WithDefaultsExplicit   // "explicit"

Contributing

Contributions are welcome! Please ensure that:

  1. All tests pass with go test -race ./...
  2. Code follows Go formatting standards with go fmt
  3. Code passes linting with golangci-lint run
  4. New features include appropriate tests

About

NetConf Client

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Languages