ipam

package module
v1.14.7 Latest Latest
Warning

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

Go to latest
Published: Aug 28, 2024 License: MIT Imports: 27 Imported by: 13

README

go-ipam

Actions GoDoc Go Report Card codecov License

go-ipam is a module to handle IP address management. It can operate on networks, prefixes and IPs.

It also comes as a ready to go microservice which offers a grpc api.

IP

Most obvious this library is all about IP management. The main purpose is to acquire and release an IP, or a bunch of IP's from prefixes.

Prefix

A prefix is a network with IP and mask, typically in the form of 192.168.0.0/24. To be able to manage IPs you have to create a prefix first.

Library Example usage:


package main

import (
    "context"
    "fmt"
    "time"

    goipam "github.com/metal-stack/go-ipam"
)

func main() {
    // The background context
    bgCtx := context.Background()

    // Create a ipamer with in memory storage
    ipam := goipam.New(bgCtx)

    // Optionally, we can pass around a context for a given namespace
    namespace := "tenant-a"
    err := ipam.CreateNamespace(bgCtx, namespace)
    if err != nil {
        panic(err)
    }
    ctx := goipam.NewContextWithNamespace(bgCtx, namespace)
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    // Create a prefix to manage some IPs
    prefix, err := ipam.NewPrefix(ctx, "192.168.0.0/24")
    if err != nil {
        panic(err)
    }

    // Acquire and release an IP with this prefix
    ip, err := ipam.AcquireIP(ctx, prefix.Cidr)
    if err != nil {
        panic(err)
    }
    fmt.Printf("got IP: %s\n", ip.IP)

    prefix, err = ipam.ReleaseIP(ctx, ip)
    if err != nil {
        panic(err)
    }
    fmt.Printf("IP: %s released.\n", ip.IP)

    // Now a IPv6 Super Prefix with Child Prefixes
    prefix, err = ipam.NewPrefix(ctx, "2001:aabb::/48")
    if err != nil {
        panic(err)
    }

    cp1, err := ipam.AcquireChildPrefix(ctx, prefix.Cidr, 64)
    if err != nil {
        panic(err)
    }
    fmt.Printf("got Prefix: %s\n", cp1)

    cp2, err := ipam.AcquireChildPrefix(ctx, prefix.Cidr, 72)
    if err != nil {
        panic(err)
    }
    fmt.Printf("got Prefix: %s\n", cp2)
    ip21, err := ipam.AcquireIP(ctx, cp2.Cidr)
    if err != nil {
        panic(err)
    }
    fmt.Printf("got IP: %s\n", ip21.IP)
}

GRPC Service

First start the go-ipam container with the database backend of your choice already up and running. For example if you have a postgres database for storing the ipam data, you could run the grpc service like so:

docker run -it --rm ghcr.io/metal-stack/go-ipam postgres

From a client perspective you can now talk to this service via grpc.

GRPC Example usage:

package main

import (
    "http"

    "github.com/bufbuild/connect-go"
    v1 "github.com/metal-stack/go-ipam/api/v1"
    "github.com/metal-stack/go-ipam/api/v1/apiv1connect"
)
func main() {

    c := apiv1connect.NewIpamServiceClient(
            http.DefaultClient,
            "http://localhost:9090",
            connect.WithGRPC(),
    )

    bgCtx := context.Background()

    // Optional with Namespace
    ctx := goipam.NewContextWithNamespace(bgCtx, "tenant-a")

    result, err := c.CreatePrefix(ctx, connect.NewRequest(&v1.CreatePrefixRequest{Cidr: "192.168.0.0/16",}))
    if err != nil {
        panic(err)
    }
    fmt.Println("Prefix:%q created", result.Msg.GetPrefix().GetCidr())
}

GRPC client

There is also a cli provided in the container which can be used to make calls to the grpc endpoint manually:

docker run -it --rm --entrypoint /cli ghcr.io/metal-stack/go-ipam

Metrics

http://localhost:2112/metrics

pprof

go tool pprof -http :8080 localhost:2113/debug/pprof/heap
go tool pprof -http :8080 localhost:2113/debug/pprof/goroutine

Docker Compose example

Ensure you have docker with compose support installed. Then execute the following command:

docker compose up -d

# check if up and running
docker compose ps

NAME                 IMAGE             COMMAND                  SERVICE    CREATED          STATUS                    PORTS
go-ipam-ipam-1       go-ipam           "/server postgres"       ipam       14 seconds ago   Up 13 seconds (healthy)   0.0.0.0:9090->9090/tcp, :::9090->9090/tcp
go-ipam-postgres-1   postgres:alpine   "docker-entrypoint.s…"   postgres   8 minutes ago    Up 13 seconds             5432/tcp


# Then execute the cli to create prefixes and acquire ips

docker compose exec ipam /cli prefix create --cidr 192.168.0.0/16
prefix:"192.168.0.0/16" created

docker compose exec ipam /cli ip acquire --prefix  192.168.0.0/16
ip:"192.168.0.1" acquired

# Queries can also made against the Rest api like so:

curl -v -X POST -d '{}' -H 'Content-Type: application/json' localhost:9090/api.v1.IpamService/ListPrefixes

Supported Databases & Performance

Database Acquire Child Prefix Acquire IP New Prefix Prefix Overlap Production-Ready Geo-Redundant
In-Memory 106,861/sec 196,687/sec 330,578/sec 248/sec N N
File N N
KeyDB 777/sec 975/sec 2,271/sec Y Y
Redis 773/sec 958/sec 2,349/sec Y N
MongoDB 415/sec 682/sec 772/sec Y Y
Etcd 258/sec 368/sec 533/sec Y N
Postgres 203/sec 331/sec 472/sec Y N
CockroachDB 170/sec 300/sec 470/sec Y Y

The benchmarks above were performed using:

  • cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
  • postgres:16-alpine
  • cockroach:v24.1.0
  • redis:7.4-alpine
  • keydb:alpine_x86_64_v6.3.1
  • etcd:v3.5.15
  • mongodb:7
Database Version Compatibility
Database Details
KeyDB
Redis
MongoDB mongodb-go compatibility
Etcd
Postgres
CockroachDB

Testing individual Backends

It is possible to test a individual backend only to speed up development roundtrip.

backend can be one of Memory, Postgres, Cockroach, Etcd, Redis, and MongoDB.

BACKEND=backend make test

Documentation

Overview

Package ipam is a ip address management library for ip's and prefixes (networks).

It uses either memory or postgresql database to store the ip's and prefixes. You can also bring you own Storage implementation as you need.

Example usage:

import (
	"fmt"
	goipam "github.com/metal-stack/go-ipam"
)

func main() {
	// create a ipamer with in memory storage
	ipam := goipam.New()

	prefix, err := ipam.NewPrefix("192.168.0.0/24")
	if err != nil {
		panic(err)
	}

	ip, err := ipam.AcquireIP(prefix)
	if err != nil {
		panic(err)
	}
	fmt.Printf("got IP: %s", ip.IP)

	err = ipam.ReleaseIP(ip)
	if err != nil {
		panic(err)
	}
	fmt.Printf("IP: %s released.", ip.IP)
}

Index

Constants

View Source
const (
	// SSLModeAllow I don't care about security
	// but I will pay the overhead of encryption if the server insists on it
	SSLModeAllow = SSLMode("allow")
	// SSLModeDisable I don't care about security
	// and I don't want to pay the overhead of encryption.
	SSLModeDisable = SSLMode("disable")
	// SSLModePrefer I don't care about encryption
	// but I wish to pay the overhead of encryption if the server supports it.
	SSLModePrefer = SSLMode("prefer")
	// SSLModeRequire I want my data to be encrypted and I accept the overhead.
	// I trust that the network will make sure I always connect to the server I want.
	SSLModeRequire = SSLMode("require")
	// SSLModeVerifyCA I want my data encrypted and I accept the overhead.
	// I want to be sure that I connect to a server that I trust.
	SSLModeVerifyCA = SSLMode("verify-ca")
	// SSLModeVerifyFull I want my data encrypted and I accept the overhead.
	// I want to be sure that I connect to a server I trust, and that it's the one I specify.
	SSLModeVerifyFull = SSLMode("verify-full")
)

Variables

View Source
var (
	// ErrNotFound is returned if prefix or cidr was not found
	ErrNotFound = errors.New("NotFound")
	// ErrNoIPAvailable is returned if no IP is available anymore
	ErrNoIPAvailable = errors.New("NoIPAvailableError")
	// ErrAlreadyAllocated is returned if the requested address is not available
	ErrAlreadyAllocated = errors.New("AlreadyAllocatedError")
	// ErrOptimisticLockError is returned if insert or update conflicts with the existing data
	ErrOptimisticLockError = errors.New("OptimisticLockError")
	// ErrNamespaceDoesNotExist is returned when an operation is performed in a namespace that does not exist.
	ErrNamespaceDoesNotExist = errors.New("NamespaceDoesNotExist")
	// ErrNameTooLong is returned when a name exceeds the databases max identifier length
	ErrNameTooLong = errors.New("NameTooLong")
)
View Source
var (
	DefaultLocalFilePath string
)

Functions

func NewContextWithNamespace added in v1.12.0

func NewContextWithNamespace(ctx context.Context, namespace string) context.Context

func PrefixesOverlapping added in v1.11.1

func PrefixesOverlapping(existingPrefixes []string, newPrefixes []string) error

PrefixesOverlapping will check if one ore more prefix of newPrefixes is overlapping with one of existingPrefixes

Types

type IP

type IP struct {
	IP           netip.Addr
	ParentPrefix string
}

IP is a single ipaddress.

type Ipamer

type Ipamer interface {
	// NewPrefix creates a new Prefix from a string notation.
	// This operation is scoped to the root namespace unless a different namespace is provided in the context.
	NewPrefix(ctx context.Context, cidr string) (*Prefix, error)
	// DeletePrefix delete a Prefix from a string notation.
	// If the Prefix is not found an NotFoundError is returned.
	// This operation is scoped to the root namespace unless a different namespace is provided in the context.
	DeletePrefix(ctx context.Context, cidr string) (*Prefix, error)
	// AcquireChildPrefix will return a Prefix with a smaller length from the given Prefix.
	// This operation is scoped to the root namespace unless a different namespace is provided in the context.
	AcquireChildPrefix(ctx context.Context, parentCidr string, length uint8) (*Prefix, error)
	// AcquireSpecificChildPrefix will return a Prefix with a smaller length from the given Prefix.
	// This operation is scoped to the root namespace unless a different namespace is provided in the context.
	AcquireSpecificChildPrefix(ctx context.Context, parentCidr, childCidr string) (*Prefix, error)
	// ReleaseChildPrefix will mark this child Prefix as available again.
	// This operation is scoped to the root namespace unless a different namespace is provided in the context.
	ReleaseChildPrefix(ctx context.Context, child *Prefix) error
	// PrefixFrom will return a known Prefix.
	// This operation is scoped to the root namespace unless a different namespace is provided in the context.
	// If the IP is not found an NotFoundError is returned, otherwise the underlying error
	PrefixFrom(ctx context.Context, cidr string) (*Prefix, error)
	// AcquireSpecificIP will acquire given IP and mark this IP as used, if already in use, return nil.
	// If specificIP is empty, the next free IP is returned.
	// If there is no free IP an NoIPAvailableError is returned.
	// This operation is scoped to the root namespace unless a different namespace is provided in the context.
	AcquireSpecificIP(ctx context.Context, prefixCidr, specificIP string) (*IP, error)
	// AcquireIP will return the next unused IP from this Prefix.
	// This operation is scoped to the root namespace unless a different namespace is provided in the context.
	AcquireIP(ctx context.Context, prefixCidr string) (*IP, error)
	// ReleaseIP will release the given IP for later usage and returns the updated Prefix.
	// If the IP is not found an NotFoundError is returned.
	// This operation is scoped to the root namespace unless a different namespace is provided in the context.
	ReleaseIP(ctx context.Context, ip *IP) (*Prefix, error)
	// ReleaseIPFromPrefix will release the given IP for later usage.
	// If the Prefix or the IP is not found an NotFoundError is returned.
	// This operation is scoped to the root namespace unless a different namespace is provided in the context.
	ReleaseIPFromPrefix(ctx context.Context, prefixCidr, ip string) error
	// Dump all stored prefixes as json formatted string
	// This operation is scoped to the root namespace unless a different namespace is provided in the context.
	Dump(ctx context.Context) (string, error)
	// Load a previously created json formatted dump, deletes all prefixes before loading.
	// This operation is scoped to the root namespace unless a different namespace is provided in the context.
	Load(ctx context.Context, dump string) error
	// ReadAllPrefixCidrs retrieves all existing Prefix CIDRs from the underlying storage.
	// This operation is scoped to the root namespace unless a different namespace is provided in the context.
	ReadAllPrefixCidrs(ctx context.Context) ([]string, error)
	// CreateNamespace creates a namespace with the given name.
	// Any namespace provided in the context is ignored for this operation.
	// It is idempotent, so attempts to create a namespace which already exists will not return an error.
	CreateNamespace(ctx context.Context, namespace string) error
	// ListNamespaces returns a list of all namespaces.
	// Any namespace provided in the context is ignored for this operation.
	ListNamespaces(ctx context.Context) ([]string, error)
	// DeleteNamespaces deletes the namespace with the given name.
	// Any namespace provided in the context is ignored for this operation.
	// It not idempotent, so attempts to delete a namespace which does not exist will return an error.
	DeleteNamespace(ctx context.Context, namespace string) error
}

Ipamer can be used to do IPAM stuff.

func New

func New(ctx context.Context) Ipamer

New returns a Ipamer with in memory storage for networks, prefixes and ips.

func NewWithStorage

func NewWithStorage(storage Storage) Ipamer

NewWithStorage allows you to create a Ipamer instance with your Storage implementation. The Storage interface must be implemented.

type MongoConfig added in v1.9.0

type MongoConfig struct {
	DatabaseName       string
	MongoClientOptions *options.ClientOptions
}

type Prefix

type Prefix struct {
	Cidr       string `json:"Cidr"`       // The Cidr of this prefix
	ParentCidr string `json:"ParentCidr"` // if this prefix is a child this is a pointer back
	// contains filtered or unexported fields
}

Prefix is a expression of a ip with length and forms a classless network.

func (*Prefix) GobDecode added in v1.8.0

func (p *Prefix) GobDecode(buf []byte) error

GobDecode implements GobDecode for Prefix

func (*Prefix) GobEncode added in v1.8.0

func (p *Prefix) GobEncode() ([]byte, error)

GobEncode implements GobEncode for Prefix

func (*Prefix) Network

func (p *Prefix) Network() (netip.Addr, error)

Network return the net.IP part of the Prefix

func (*Prefix) String

func (p *Prefix) String() string

func (*Prefix) Usage

func (p *Prefix) Usage() Usage

Usage report Prefix usage.

type Prefixes added in v1.9.0

type Prefixes []Prefix

type SSLMode added in v1.5.0

type SSLMode string

SSLMode specifies how to configure ssl encryption to the database

func (SSLMode) String added in v1.5.0

func (s SSLMode) String() string

type Storage

type Storage interface {
	Name() string
	CreatePrefix(ctx context.Context, prefix Prefix, namespace string) (Prefix, error)
	ReadPrefix(ctx context.Context, prefix string, namespace string) (Prefix, error)
	DeleteAllPrefixes(ctx context.Context, namespace string) error
	ReadAllPrefixes(ctx context.Context, namespace string) (Prefixes, error)
	ReadAllPrefixCidrs(ctx context.Context, namespace string) ([]string, error)
	UpdatePrefix(ctx context.Context, prefix Prefix, namespace string) (Prefix, error)
	DeletePrefix(ctx context.Context, prefix Prefix, namespace string) (Prefix, error)
	CreateNamespace(ctx context.Context, namespace string) error
	ListNamespaces(ctx context.Context) ([]string, error)
	DeleteNamespace(ctx context.Context, namespace string) error
}

Storage is a interface to store ipam objects.

func NewEtcd added in v1.9.0

func NewEtcd(ctx context.Context, ip, port string, cert, key []byte, insecureskip bool) (Storage, error)

NewEtcd create a etcd storage for ipam

func NewLocalFile added in v1.12.5

func NewLocalFile(ctx context.Context, path string) Storage

NewLocalFile creates a JSON file storage for ipam

func NewMemory

func NewMemory(ctx context.Context) Storage

NewMemory create a memory storage for ipam

func NewMongo added in v1.9.0

func NewMongo(ctx context.Context, config MongoConfig) (Storage, error)

func NewPostgresStorage

func NewPostgresStorage(host, port, user, password, dbname string, sslmode SSLMode) (Storage, error)

NewPostgresStorage creates a new Storage which uses postgres.

func NewRedis added in v1.8.6

func NewRedis(ctx context.Context, ip, port string) (Storage, error)

NewRedis create a redis storage for ipam

type Usage

type Usage struct {
	// AvailableIPs the number of available IPs if this is not a parent prefix
	// No more than 2^31 available IPs are reported
	AvailableIPs uint64
	// AcquiredIPs the number of acquired IPs if this is not a parent prefix
	AcquiredIPs uint64
	// AvailableSmallestPrefixes is the count of available Prefixes with 2 countable Bits
	// No more than 2^31 available Prefixes are reported
	AvailableSmallestPrefixes uint64
	// AvailablePrefixes is a list of prefixes which are available
	AvailablePrefixes []string
	// AcquiredPrefixes the number of acquired prefixes if this is a parent prefix
	AcquiredPrefixes uint64
}

Usage of ips and child Prefixes of a Prefix

func (*Usage) String

func (u *Usage) String() string

Directories

Path Synopsis
api
v1
cmd
pkg

Jump to

Keyboard shortcuts

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