Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

## 1.0.0 (2018-03-15)

Initial release tagging
Initial release tagging
12 changes: 12 additions & 0 deletions examples/nexthop-group/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module github.com/vishvananda/netlink/examples/nexthop-group

go 1.26.3

require github.com/vishvananda/netlink v1.3.1

require (
github.com/vishvananda/netns v0.0.5 // indirect
golang.org/x/sys v0.10.0 // indirect
)

replace github.com/vishvananda/netlink => ../..
12 changes: 12 additions & 0 deletions examples/nexthop-group/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
160 changes: 160 additions & 0 deletions examples/nexthop-group/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//go:build linux
// +build linux

// Multipath nexthop group creates a nexthop group without the resilient
// (NEXTHOP_GRP_TYPE_RES) support.
// The library provides the equivalent feature like commandline below:
//
// ip nexthop add id 1 via 10.0.0.1 dev eth0
// ip nexthop add id 2 via 10.0.0.2 dev eth0
// ip nexthop add id 100 group 1/2
//
// A group created without an explicit group type is a multi-path (mpath)
// nexthop group. Weight of each member can be specified with the ":" suffix,
// e.g. 10.0.0.1:2 for a weight of 2.

// Usage
// sudo nexthop-group -dev eth0 -gw 10.0.0.1,10.0.0.2
// sudo nexthop-group -dev eth0 -gw 10.0.0.1 -cleanup

package main

import (
"flag"
"fmt"
"log"
"net"
"strconv"
"strings"

"github.com/vishvananda/netlink"
)

var (
dev = flag.String("dev", "", "interface the members of nexthop group points to")
gateways = flag.String("gw", "", "comma separated list of gateways for the nexthop group")
groupID = flag.Uint("group-id", 100, "nexthop group ID")
memberBaseID = flag.Uint("members-base-id", 1, "base ID for the members of the nexthop group")
cleanup = flag.Bool("cleanup", false, "cleanup the nexthop group and its members")
)

func main() {
log.SetFlags(0)
flag.Parse()

if *dev == "" || *gateways == "" {
flag.Usage()
log.Fatal("device and gateways must be specified")
}

link, err := netlink.LinkByName(*dev)
if err != nil {
log.Fatalf("failed to get link by name %s: %v", *dev, err)
}

members, err := parseGateways(*gateways)
if err != nil {
log.Fatalf("failed to parse gateways: %v", err)
}

if *groupID >= *memberBaseID && *groupID < *memberBaseID+uint(len(members)) {
log.Fatalf("group ID %d overlaps with member nexthop IDs [%d, %d); pick a distinct ID",
*groupID, *memberBaseID, *memberBaseID+uint(len(members)))
}

if *cleanup {
cleanupNexthops(members)
return
}

group := make([]netlink.NexthopGroupMember, 0, len(members))

for i, m := range members {
id := uint32(*memberBaseID) + uint32(i)
nh := &netlink.Nexthop{
ID: id,
OIF: uint32(link.Attrs().Index),
Gateway: m.gateway,
}
if err := netlink.NexthopReplace(nh); err != nil {
log.Fatalf("failed to add nexthop %v: %v", nh, err)
}
log.Printf("created member nexthop %d via %s: %v", id, m.gateway, *dev)

group = append(group, netlink.NexthopGroupMember{
ID: id,
Weight: m.weight,
})
}

nhg := &netlink.Nexthop{
ID: uint32(*groupID),
Group: group,
}
if err := netlink.NexthopReplace(nhg); err != nil {
log.Fatalf("failed to add multipath nexthop group: %v", err)
}
log.Println(describeGroup(nhg))
}

func cleanupNexthops(members []member) {
group := &netlink.Nexthop{ID: uint32(*groupID)}
if err := netlink.NexthopDel(group); err != nil {
log.Fatalf("failed to delete multipath nexthop group %d: %v", *groupID, err)
}
log.Printf("deleted multipath nexthop group %d", *groupID)

for i := range members {
id := uint32(*memberBaseID) + uint32(i)
member := &netlink.Nexthop{ID: id}
if err := netlink.NexthopDel(member); err != nil {
log.Fatalf("failed to delete member nexthop %d: %v", id, err)
}
log.Printf("deleted member nexthop %d", id)
}
}

type member struct {
gateway net.IP
weight uint16
}

func parseGateways(gateways string) ([]member, error) {
var members []member
for _, field := range strings.Split(gateways, ",") {
field = strings.TrimSpace(field)
if field == "" {
continue
}

addr, weight := field, uint16(1)
if at := strings.Index(field, ":"); at != -1 {
addr = field[:at]
w, err := strconv.Atoi(field[at+1:])
if err != nil {
return nil, fmt.Errorf("invalid weight for gateway %s: %v", field, err)
}
if w < 1 || w > 256 {
return nil, fmt.Errorf("weight for gateway %s must be between 1 and 256", field)
}
weight = uint16(w)
}
ip := net.ParseIP(addr)
if ip == nil {
return nil, fmt.Errorf("invalid gateway IP: %s", addr)
}
members = append(members, member{
gateway: ip,
weight: weight,
})
}
return members, nil
}

func describeGroup(nh *netlink.Nexthop) string {
entries := make([]string, 0, len(nh.Group))
for _, entry := range nh.Group {
entries = append(entries, fmt.Sprintf("%d", entry.ID))
}
return fmt.Sprintf("Nexthop group ID: %d, Type: mpath, Members: [%s]", nh.ID, strings.Join(entries, ", "))
}
12 changes: 12 additions & 0 deletions examples/nexthop/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module github.com/vishvananda/netlink/examples/nexthop

go 1.26.3

require github.com/vishvananda/netlink v1.3.1

require (
github.com/vishvananda/netns v0.0.5 // indirect
golang.org/x/sys v0.10.0 // indirect
)

replace github.com/vishvananda/netlink => ../..
12 changes: 12 additions & 0 deletions examples/nexthop/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
128 changes: 128 additions & 0 deletions examples/nexthop/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//go:build linux
// +build linux

// Basic nexthop creates a nexthop object
// The library provides the equivalent feature like commandline below:
//
// ip nexthop add id 1 via 10.0.0.1 dev eth0
// ip nexthop del id 1
// ip nexthop show

// Usage
// sudo nexthop -dev eth0 -gw 10.0.0.1
// sudo nexthop -dev eth0 -gw 10.0.0.1 -id 2
// sudo nexthop -dev eth0 -gw 10.0.0.1 -cleanup

package main

import (
"flag"
"fmt"
"log"
"net"
"strconv"
"strings"

"github.com/vishvananda/netlink"
)

var (
dev = flag.String("dev", "", "interface the nexthop points to")
gateway = flag.String("gw", "", "gateway IP for the nexthop")
id = flag.Uint("id", 1, "nexthop ID")
blackhole = flag.Bool("blackhole", false, "create a blackhole nexthop")
cleanup = flag.Bool("cleanup", false, "cleanup the nexthop")
)

func main() {
log.SetFlags(0)
flag.Parse()

if *dev == "" {
flag.Usage()
log.Fatal("device must be specified")
}

link, err := netlink.LinkByName(*dev)
if err != nil {
log.Fatalf("failed to get link by name %s: %v", *dev, err)
}

nh, err := buildNexthop(link)
if err != nil {
log.Fatalf("failed to build nexthop: %v", err)
}

if *cleanup {
cleanupNexthop(nh)
return
}

if err := netlink.NexthopReplace(nh); err != nil {
log.Fatalf("failed to add nexthop %v: %v", nh, err)
}
log.Printf("created nexthop %d via %s dev %s", nh.ID, nh.Gateway, *dev)

listNexthops()
}

func buildNexthop(link netlink.Link) (*netlink.Nexthop, error) {
nh := &netlink.Nexthop{
ID: uint32(*id),
}

if *blackhole {
nh.Blackhole = true
return nh, nil
}

nh.OIF = uint32(link.Attrs().Index)
if *gateway == "" {
return nil, fmt.Errorf("gateway (-gw) is required unless -blackhole is set")
}
ip := net.ParseIP(*gateway)
if ip == nil {
return nil, fmt.Errorf("invalid gateway IP: %s", *gateway)
}
nh.Gateway = ip
return nh, nil
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

func cleanupNexthop(nh *netlink.Nexthop) {
del := &netlink.Nexthop{ID: nh.ID}
if err := netlink.NexthopDel(del); err != nil {
log.Fatalf("failed to delete nexthop %d: %v", nh.ID, err)
}
log.Printf("deleted nexthop %d", nh.ID)
}

func listNexthops() {
nhs, err := netlink.NexthopList()
if err != nil {
log.Fatalf("failed to list nexthops: %v", err)
}
if len(nhs) == 0 {
log.Println("no nexthops present")
return
}
rows := make([]string, 0, len(nhs))
for _, nh := range nhs {
rows = append(rows, describeNexthop(&nh))
}
log.Printf("nexthops:\n%s", strings.Join(rows, "\n"))
}

func describeNexthop(nh *netlink.Nexthop) string {
var parts []string
if nh.Blackhole {
parts = append(parts, "blackhole")
}
if nh.OIF > 0 {
parts = append(parts, "dev "+strconv.FormatUint(uint64(nh.OIF), 10))
}
if nh.Gateway != nil {
parts = append(parts, "via "+nh.Gateway.String())
}
parts = append(parts, "protocol "+nh.Protocol.String())
return fmt.Sprintf("id %d %s", nh.ID, strings.Join(parts, " "))
}
12 changes: 12 additions & 0 deletions examples/resilient-nexthop-group/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module github.com/vishvananda/netlink/examples/resilient-nexthop-group

go 1.26.3

require github.com/vishvananda/netlink v1.3.1

require (
github.com/vishvananda/netns v0.0.5 // indirect
golang.org/x/sys v0.10.0 // indirect
)

replace github.com/vishvananda/netlink => ../..
12 changes: 12 additions & 0 deletions examples/resilient-nexthop-group/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading
Loading