1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
|
// Command ndp is a utility for working with the Neighbor Discovery Protocol.
package main
import (
"context"
"errors"
"flag"
"fmt"
"log"
"net"
"net/netip"
"os"
"os/signal"
"github.com/mdlayher/ndp"
"github.com/mdlayher/ndp/internal/ndpcmd"
)
func main() {
var (
ifiFlag = flag.String("i", "", "network interface to use for NDP communication (default: automatic)")
addrFlag = flag.String("a", string(ndp.LinkLocal), "address to use for NDP communication (unspecified, linklocal, uniquelocal, global, or a literal IPv6 address)")
targetFlag = flag.String("t", "", "IPv6 target address for neighbor solicitation NDP messages")
)
flag.Usage = func() {
fmt.Println(usage)
fmt.Println("Flags:")
flag.PrintDefaults()
}
flag.Parse()
ll := log.New(os.Stderr, "ndp> ", 0)
if flag.NArg() > 1 {
ll.Fatalf("too many args on command line: %v", flag.Args()[1:])
}
ifi, err := findInterface(*ifiFlag)
if err != nil {
ll.Fatalf("failed to get interface: %v", err)
}
c, ip, err := ndp.Listen(ifi, ndp.Addr(*addrFlag))
if err != nil {
ll.Fatalf("failed to open NDP connection: %v", err)
}
defer c.Close()
var target netip.Addr
if t := *targetFlag; t != "" {
target, err = netip.ParseAddr(t)
if err != nil {
ll.Fatalf("failed to parse IPv6 target address: %v", err)
}
}
sigC := make(chan os.Signal, 1)
signal.Notify(sigC, os.Interrupt)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
<-sigC
cancel()
}()
// Non-Ethernet interfaces (such as PPPoE) may not have a MAC address.
var mac string
if ifi.HardwareAddr != nil {
mac = ifi.HardwareAddr.String()
} else {
mac = "none"
}
ll.Printf("interface: %s, link-layer address: %s, IPv6 address: %s",
ifi.Name, mac, ip)
if err := ndpcmd.Run(ctx, c, ifi, flag.Arg(0), target); err != nil {
// Context cancel means a signal was sent, so no need to log an error.
if err == context.Canceled {
os.Exit(1)
}
ll.Fatal(err)
}
}
// findInterface attempts to find the specified interface. If name is empty,
// it attempts to find a usable, up and ready, network interface.
func findInterface(name string) (*net.Interface, error) {
if name != "" {
ifi, err := net.InterfaceByName(name)
if err != nil {
return nil, fmt.Errorf("could not find interface %q: %v", name, err)
}
return ifi, nil
}
ifis, err := net.Interfaces()
if err != nil {
return nil, err
}
for _, ifi := range ifis {
// Is the interface up, multicast, and not a loopback?
if ifi.Flags&net.FlagUp == 0 ||
ifi.Flags&net.FlagMulticast == 0 ||
ifi.Flags&net.FlagLoopback != 0 {
continue
}
// Does the interface have an IPv6 address assigned?
addrs, err := ifi.Addrs()
if err != nil {
return nil, err
}
for _, a := range addrs {
ipNet, ok := a.(*net.IPNet)
if !ok {
continue
}
ip, ok := netip.AddrFromSlice(ipNet.IP)
if !ok {
panicf("failed to parse IPv6 address: %q", ipNet.IP)
}
// Is this address an IPv6 address?
if ip.Is6() && !ip.Is4In6() {
return &ifi, nil
}
}
}
return nil, errors.New("could not find a usable IPv6-enabled interface")
}
const usage = `ndp: utility for working with the Neighbor Discovery Protocol.
By default, this tool will automatically bind to IPv6 link-local address of the first interface which is capable of using NDP.
To enable more convenient use without sudo on Linux, apply the CAP_NET_RAW capability:
$ sudo setcap cap_net_raw+ep ./ndp
Examples:
Listen for incoming NDP messages on the default interface.
$ ndp
Send router solicitations on the default interface until a router advertisement is received.
$ ndp rs
Send neighbor solicitations on the default interface until a neighbor advertisement is received.
$ ndp -t fe80::1 ns`
func panicf(format string, a ...any) {
panic(fmt.Sprintf(format, a...))
}
|