netconf

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2023 License: BSD-2-Clause-Views Imports: 12 Imported by: 0

README

Go netconf client library

WARNING: This is currently alpha quality. The API isn't solid yet and a lot of testing still needs to be done as well as additional feaures. Working for a solid beta API is incoming.

Go Reference Report Card stability-unstable Validate coverage

This library is used to create client applications for connecting to network devices via NETCONF.

Like Go itself, only the latest two Go versions are tested and supported (Go 1.19 or Go 1.20).

RFC Support

RFC Support
RFC6241 Network Configuration Protocol (NETCONF) 🚧 inprogress
RFC6242 Using the NETCONF Protocol over Secure Shell (SSH) ✅ supported
RFC7589 Using the NETCONF Protocol over Transport Layer Security (TLS) ✅ beta
RFC5277 NETCONF Event Notifications 💡 planned
RFC5717 Partial Lock Remote Procedure Call (RPC) for NETCONF 💡 planned
RFC8071 NETCONF Call Home and RESTCONF Call Home 💡 planned
RFC6243 With-defaults Capability for NETCONF 💡 planned
RFC4743 Using NETCONF over the Simple Object Access Protocol (SOAP) ❌ not planned
RFC4744 Using the NETCONF Protocol over the BEEP ❌ not planned

There are other RFC around YANG integration that will be looked at later.

See TODO.md for a list of what is left to implement these features.

Comparison

Differences from github.com/juniper/go-netconf/netconf
  • Much cleaner, idomatic API, less dumb I, @nemith, was the original creator of the netconf package and it was my very first Go project and it shows. There are number of questionable API design, code, and a lot of odd un tested bugs. Really this rewrite was created to fix this.
  • No impled vendor ownership Moving the project out of the Juniper organization allowes better control over the project, less implied support (or lack there of), and hopefully more contributions.
  • Transports are implemented in their own packages. This means if you are not using SSH or TLS you don't need to bring in the underlying depdendencies into your binary.
  • Stream based transports. Should mean less memory usage and much less allocation bringing overall performance higher.
Differences from github.com/scrapli/scrapligo/driver/netconf

Scrapligo driver is quite good and way better than the original juniper project. However this new package concentrates more on RFC correctness and implementing some of the more advanced RFC features like call-home and event notifications. If there is a desire there could be some callaboration with scrapligo in the future.

Documentation

Overview

Example (Ssh)
package main

import (
	"context"
	"log"
	"time"

	"github.com/nemith/netconf"
	ncssh "github.com/nemith/netconf/transport/ssh"
	"golang.org/x/crypto/ssh"
)

const sshAddr = "myrouter.example.com:830"

func main() {
	config := &ssh.ClientConfig{
		User: "admin",
		Auth: []ssh.AuthMethod{
			ssh.Password("secret"),
		},
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	transport, err := ncssh.Dial(ctx, "tcp", sshAddr, config)
	if err != nil {
		panic(err)
	}
	defer transport.Close()

	session, err := netconf.Open(transport)
	if err != nil {
		panic(err)
	}

	// timeout for the call itself.
	ctx, cancel = context.WithTimeout(ctx, 5*time.Second)
	defer cancel()
	deviceConfig, err := session.GetConfig(ctx, "running")
	if err != nil {
		log.Fatalf("failed to get config: %v", err)
	}

	log.Printf("Config:\n%s\n", deviceConfig)

	if err := session.Close(context.Background()); err != nil {
		log.Print(err)
	}
}
Output:

Example (Tls)
package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	_ "embed"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/nemith/netconf"
	nctls "github.com/nemith/netconf/transport/tls"
)

const tlsAddr = "myrouter.example.com:6513"

func main() {
	caCert, err := os.ReadFile("ca.crt")
	if err != nil {
		log.Fatalf("failed to load ca cert: %v", err)
	}

	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	clientCert, err := os.ReadFile("client.crt")
	if err != nil {
		log.Fatalf("failed to load client cert: %v", err)
	}

	clientKey, err := os.ReadFile("client.key")
	if err != nil {
		log.Fatalf("failed to load client key: %v", err)
	}

	cert, err := tls.X509KeyPair(clientCert, clientKey)
	if err != nil {
		panic(err)
	}

	// tls transport configuration
	config := tls.Config{
		InsecureSkipVerify: true,
		RootCAs:            caCertPool,
		Certificates:       []tls.Certificate{cert},
	}

	// Add a connection establish timeout of 5 seconds.  You can also accomplish
	// the same behavior with Timeout field of the ssh.ClientConfig.
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	transport, err := nctls.Dial(ctx, "tcp", tlsAddr, &config)
	if err != nil {
		panic(err)
	}
	defer transport.Close()

	session, err := netconf.Open(transport)
	if err != nil {
		panic(err)
	}
	defer session.Close(context.Background())

	// timeout for the call itself.
	ctx, cancel = context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	cfg, err := session.GetConfig(ctx, "running")
	if err != nil {
		panic(err)
	}

	fmt.Printf("Config: %s\n", cfg)

	if err := session.Close(context.Background()); err != nil {
		log.Print(err)
	}
}
Output:

Index

Examples

Constants

This section is empty.

Variables

View Source
var DefaultCapabilities = []string{
	"urn:ietf:params:netconf:base:1.0",
	"urn:ietf:params:netconf:base:1.1",
}

DefaultCapabilities are the capabilities sent by the client during the hello exchange by the server.

View Source
var ErrClosed = errors.New("closed connection")

Functions

func ExpandCapability

func ExpandCapability(s string) string

ExpandCapability will automatically add the standard capability prefix of `urn:ietf:params:netconf:capability` if not already present.

func WithPersistID

func WithPersistID(id string) persistID

WithPersistID is used to confirm a previous commit set with a given identifier. This allows you to confirm a commit from (potentially) another sesssion.

Types

type CancelCommitOption

type CancelCommitOption interface {
	// contains filtered or unexported methods
}

CancelCommitOption is a optional arguments to Session.CancelCommit method

type CommitOption

type CommitOption interface {
	// contains filtered or unexported methods
}

CommitOption is a optional arguments to Session.Commit method

func WithConfirmed

func WithConfirmed() CommitOption

WithConfirmed will mark the commits as requiring confirmation or will rollback after the default timeout on the device (default should be 600s). The commit can be confirmed with another `<commit>` call without the confirmed option, extended by calling with `Commit` With `WithConfirmed` or `WithConfirmedTimeout` or canceling the commit with a `CommitCancel` call. This requires the device to support the `:confirmed-commit:1.1` capability.

func WithConfirmedTimeout

func WithConfirmedTimeout(timeout time.Duration) CommitOption

WithConfirmedTimeout is like `WithConfirmed` but using the given timeout duration instead of the device's default.

func WithPersist

func WithPersist(id string) CommitOption

WithPersist allows you to set a identifier to confirm a commit in another sessions. Confirming the commit requires setting the `WithPersistID` in the following `Commit` call matching the id set on the confirmed commit. Will mark the commit as confirmed if not already set.

type CreateSubscriptionOption

type CreateSubscriptionOption interface {
	// contains filtered or unexported methods
}

CreateSubscriptionOption is a optional arguments to Session.CreateSubscription method

func WithEndTimeOption

func WithEndTimeOption(et time.Time) CreateSubscriptionOption

func WithStartTimeOption

func WithStartTimeOption(st time.Time) CreateSubscriptionOption

func WithStreamOption

func WithStreamOption(s string) CreateSubscriptionOption

type Datastore

type Datastore string
const (
	// Running configuration datastore. Required by RFC6241
	Running Datastore = "running"

	// Candidate configuration configuration datastore.  Supported with the
	// `:candidate` capability defined in RFC6241 section 8.3
	Candidate Datastore = "candidate"

	// Startup configuration configuration datastore.  Supported with the
	// `:startup` capability defined in RFC6241 section 8.7
	Startup Datastore = "startup" //
)

XXX: these should be typed?

func (Datastore) MarshalXML

func (s Datastore) MarshalXML(e *xml.Encoder, start xml.StartElement) error

type EditConfigOption

type EditConfigOption interface {
	// contains filtered or unexported methods
}

EditOption is a optional arguments to Session.EditConfig method

func WithDefaultMergeStrategy

func WithDefaultMergeStrategy(op MergeStrategy) EditConfigOption

WithDefaultMergeStrategy sets the default config merging strategy for the <edit-config> operation. Only [Merge], [Replace], and [None] are supported (the rest of the strategies are for defining as attributed in individual elements inside the `<config>` subtree).

func WithErrorStrategy

func WithErrorStrategy(opt ErrorStrategy) EditConfigOption

WithErrorStrategy sets the `error-option` in the `<edit-config>` operation. This defines the behavior when errors are encountered applying the supplied config. See ErrorStrategy for the available options.

func WithTestStrategy

func WithTestStrategy(op TestStrategy) EditConfigOption

WithTestStrategy sets the `test-option` in the `<edit-config>“ operation. This defines what testing should be done the supplied configuration. See the documentation on TestStrategy for details on each strategy.

type ErrSeverity

type ErrSeverity string
const (
	ErrSevError   ErrSeverity = "error"
	ErrSevWarning ErrSeverity = "warning"
)

type ErrTag

type ErrTag string
const (
	ErrInUse                 ErrTag = "in-use"
	ErrInvalidValue          ErrTag = "invalid-value"
	ErrTooBig                ErrTag = "too-big"
	ErrMissingAttribute      ErrTag = "missing-attribute"
	ErrBadAttribute          ErrTag = "bad-attribute"
	ErrUnknownAttribute      ErrTag = "unknown-attribute"
	ErrMissingElement        ErrTag = "missing-element"
	ErrBadElement            ErrTag = "bad-element"
	ErrUnknownElement        ErrTag = "unknown-element"
	ErrUnknownNamespace      ErrTag = "unknown-namespace"
	ErrAccesDenied           ErrTag = "access-denied"
	ErrLockDenied            ErrTag = "lock-denied"
	ErrResourceDenied        ErrTag = "resource-denied"
	ErrRollbackFailed        ErrTag = "rollback-failed"
	ErrDataExists            ErrTag = "data-exists"
	ErrDataMissing           ErrTag = "data-missing"
	ErrOperationNotSupported ErrTag = "operation-not-supported"
	ErrOperationFailed       ErrTag = "operation-failed"
	ErrPartialOperation      ErrTag = "partial-operation"
	ErrMalformedMessage      ErrTag = "malformed-message"
)

type ErrType

type ErrType string
const (
	ErrTypeTransport ErrType = "transport"
	ErrTypeRPC       ErrType = "rpc"
	ErrTypeProtocol  ErrType = "protocol"
	ErrTypeApp       ErrType = "app"
)

type ErrorStrategy

type ErrorStrategy string

ErrorStrategy defines the behavior when an error is encountered during a `<edit-config>` operation.

*Note*: in RFC6241 7.2 this is called the `error-option` parameter. Since the `option` term is already overloaded this was changed to `ErrorStrategy` for a cleaner API.

const (
	// StopOnError will about the `<edit-config>` operation on the first error.
	StopOnError ErrorStrategy = "stop-on-error"

	// ContinueOnError will continue to parse the configuration data even if an
	// error is encountered.  Errors are still recorded and reported in the
	// reply.
	ContinueOnError ErrorStrategy = "continue-on-error"

	// RollbackOnError will restore the configuration back to before the
	// `<edit-config>` operation took place.  This requires the device to
	// support the `:rollback-on-error` capabilitiy.
	RollbackOnError ErrorStrategy = "rollback-on-error"
)

type ExtantBool

type ExtantBool bool

func (ExtantBool) MarshalXML

func (b ExtantBool) MarshalXML(e *xml.Encoder, start xml.StartElement) error

func (*ExtantBool) UnmarshalXML

func (b *ExtantBool) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error

type HelloMsg

type HelloMsg struct {
	XMLName      xml.Name `xml:"urn:ietf:params:xml:ns:netconf:base:1.0 hello"`
	SessionID    uint64   `xml:"session-id,omitempty"`
	Capabilities []string `xml:"capabilities>capability"`
}

helloMsg maps the xml value of the <hello> message in RFC6241

type MergeStrategy

type MergeStrategy string

MergeStrategy defines the strategies for merging configuration in a `<edit-config> operation`.

*Note*: in RFC6241 7.2 this is called the `operation` attribute and `default-operation` parameter. Since the `operation` term is already overloaded this was changed to `MergeStrategy` for a cleaner API.

const (
	// MergeConfig configuration elements are merged together at the level at
	// which this specified.  Can be used for config elements as well as default
	// defined with [WithDefaultMergeStrategy] option.
	MergeConfig MergeStrategy = "merge"

	// ReplaceConfig defines that the incoming config change should replace the
	// existing config at the level which it is specified.  This can be
	// specified on individual config elements or set as the default strategy set
	// with [WithDefaultMergeStrategy] option.
	ReplaceConfig MergeStrategy = "replace"

	// NoMergeStrategy is only used as a default strategy defined in
	// [WithDefaultMergeStrategy].  Elements must specific one of the other
	// strategies with the `operation` Attribute on elements in the `<config>`
	// subtree.  Elements without the `operation` attribute are ignored.
	NoMergeStrategy MergeStrategy = "none"

	// CreateConfig allows a subtree element to be created only if it doesn't
	// already exist.
	// This strategy is only used as the `operation` attribute of
	// a `<config>` element and cannot be used as the default strategy.
	CreateConfig MergeStrategy = "create"

	// DeleteConfig will completely delete subtree from the config only if it
	// already exists.  This strategy is only used as the `operation` attribute
	// of a `<config>` element and cannot be used as the default strategy.
	DeleteConfig MergeStrategy = "delete"

	// RemoveConfig will remove subtree from the config.  If the subtree doesn't
	// exist in the datastore then it is silently skipped.  This strategy is
	// only used as the `operation` attribute of a `<config>` element and cannot
	// be used as the default strategy.
	RemoveConfig MergeStrategy = "remove"
)

type NotificationHandler

type NotificationHandler func(msg NotificationMsg)

NotificationHandler function allows to work with received notifications. A NotificationHandler function can be passed in as an option when calling Open method of Session object A typical use of the NofificationHandler function is to retrieve notifications once they are received so that they can be parsed and/or stored somewhere. Sample usage:

func GetNotificationsHandler(c chan string) netconf.NotificationsHandler {
	return func(nm NotificationMsg) {
		// just send the raw notification data to the channel
		c <- nm
	}
}

type NotificationMsg

type NotificationMsg struct {
	XMLName   xml.Name  `xml:"urn:ietf:params:xml:ns:netconf:notification:1.0 notification"`
	EventTime time.Time `xml:"eventTime"`
	Data      []byte    `xml:",innerxml"`
}

type OKResp

type OKResp struct {
	OK ExtantBool `xml:"ok"`
}

type RPCError

type RPCError struct {
	Type     ErrType     `xml:"error-type"`
	Tag      ErrTag      `xml:"error-tag"`
	Severity ErrSeverity `xml:"error-severity"`
	AppTag   string      `xml:"error-app-tag,omitempty"`
	Path     string      `xml:"error-path,omitempty"`
	Message  string      `xml:"error-message,omitempty"`
	Info     RawXML      `xml:"error-info,omitempty"`
}

func (RPCError) Error

func (e RPCError) Error() string

type RPCErrors

type RPCErrors []RPCError

func (RPCErrors) Error

func (errs RPCErrors) Error() string

func (RPCErrors) Unwrap

func (errs RPCErrors) Unwrap() []error

type RPCMsg

type RPCMsg struct {
	XMLName   xml.Name `xml:"urn:ietf:params:xml:ns:netconf:base:1.0 rpc"`
	MessageID uint64   `xml:"message-id,attr"`
	Operation any      `xml:",innerxml"`
}

RPCMsg maps the xml value of <rpc> in RFC6241

func (*RPCMsg) MarshalXML

func (msg *RPCMsg) MarshalXML(e *xml.Encoder, start xml.StartElement) error

type RPCReplyMsg

type RPCReplyMsg struct {
	XMLName   xml.Name  `xml:"urn:ietf:params:xml:ns:netconf:base:1.0 rpc-reply"`
	MessageID uint64    `xml:"message-id,attr"`
	Errors    RPCErrors `xml:"rpc-error,omitempty"`
	Body      []byte    `xml:",innerxml"`
}

RPCReplyMsg maps the xml value of <rpc-reply> in RFC6241

type RawXML

type RawXML []byte

RawXML captures the raw xml for the given element. Used to process certain elements later.

func (*RawXML) MarshalXML

func (x *RawXML) MarshalXML(e *xml.Encoder, start xml.StartElement) error

MarshalXML implements xml.Marshaller. Raw XML is passed verbatim, errors and all.

func (*RawXML) UnmarshalXML

func (x *RawXML) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session is represents a netconf session to a one given device.

func Open

func Open(transport transport.Transport, opts ...SessionOption) (*Session, error)

Open will create a new Session with th=e given transport and open it with the necessary hello messages.

func (*Session) Call

func (s *Session) Call(ctx context.Context, op any, resp any) error

Call issues a rpc call for the given NETCONF operation and unmarshaling the response into `resp`.

func (*Session) CancelCommit

func (s *Session) CancelCommit(ctx context.Context, opts ...CancelCommitOption) error

func (*Session) ClientCapabilities

func (s *Session) ClientCapabilities() []string

ClientCapabilities will return the capabilities initialized with the session.

func (*Session) Close

func (s *Session) Close(ctx context.Context) error

Close will gracefully close the sessions first by sending a `close-session` operation to the remote and then closing the underlying transport

func (*Session) Commit

func (s *Session) Commit(ctx context.Context, opts ...CommitOption) error

Commit will commit a canidate config to the running comming. This requires the device to support the `:canidate` capability.

func (*Session) CopyConfig

func (s *Session) CopyConfig(ctx context.Context, source, target any) error

CopyConfig issues the `<copy-config>` operation as defined in [RFC6241 7.3] for copying an entire config to/from a source and target datastore.

A `<config>` element defining a full config can be used as the source.

If a device supports the `:url` capability than a URL object can be used for the source or target datastore.

[RFC6241 7.3] https://www.rfc-editor.org/rfc/rfc6241.html#section-7.3

func (*Session) CreateSubscription

func (s *Session) CreateSubscription(ctx context.Context, opts ...CreateSubscriptionOption) error

func (*Session) DeleteConfig

func (s *Session) DeleteConfig(ctx context.Context, target Datastore) error

func (*Session) Do

func (s *Session) Do(ctx context.Context, msg *RPCMsg) (*RPCReplyMsg, error)

Do issues a low level RPC call taking in a full RPCMsg and returning the full RPCReplyMsg. In most cases `Session.Call` will do what you want handling errors and marshaling/unmarshaling your data.`

func (*Session) EditConfig

func (s *Session) EditConfig(ctx context.Context, target Datastore, config any, opts ...EditConfigOption) error

EditConfig issues the `<edit-config>` operation defined in RFC6241 7.2 for updating an existing target config datastore.

func (*Session) Get

func (s *Session) Get(ctx context.Context) error

func (*Session) GetConfig

func (s *Session) GetConfig(ctx context.Context, source Datastore) ([]byte, error)

GetConfig implements the <get-config> rpc operation defined in RFC6241 7.1. `source` is the datastore to query.

func (*Session) KillSession

func (s *Session) KillSession(ctx context.Context, sessionID uint32) error

func (*Session) Lock

func (s *Session) Lock(ctx context.Context, target Datastore) error

func (*Session) ServerCapabilities

func (s *Session) ServerCapabilities() []string

ServerCapabilities will return the capabilities returned by the server in it's hello message.

func (*Session) SessionID

func (s *Session) SessionID() uint64

SessionID returns the current session ID exchanged in the hello messages. Will return 0 if there is no session ID.

func (*Session) Unlock

func (s *Session) Unlock(ctx context.Context, target Datastore) error

func (*Session) Validate

func (s *Session) Validate(ctx context.Context, source any) error

type SessionOption

type SessionOption interface {
	// contains filtered or unexported methods
}

func WithCapability

func WithCapability(capabilities ...string) SessionOption

func WithNotificationHandler

func WithNotificationHandler(nh NotificationHandler) SessionOption

type TestStrategy

type TestStrategy string

TestStrategy defines the beahvior for testing configuration before applying it in a `<edit-config>` operation.

*Note*: in RFC6241 7.2 this is called the `test-option` parameter. Since the `option` term is already overloaded this was changed to `TestStrategy` for a cleaner API.

const (
	// TestThenSet will validate the configuration and only if is is valid then
	// apply the configuration to the datastore.
	TestThenSet TestStrategy = "test-then-set"

	// SetOnly will not do any testing before applying it.
	SetOnly TestStrategy = "set"

	// Test only will validation the incoming configuration and return the
	// results without modifying the underlying store.
	TestOnly TestStrategy = "test-only"
)

type URL

type URL string

func (URL) MarshalXML

func (u URL) MarshalXML(e *xml.Encoder, start xml.StartElement) error

Directories

Path Synopsis
ssh
tls

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL