File: main.go

package info (click to toggle)
golang-github-mdlayher-ndp 1.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental, forky, sid, trixie
  • size: 244 kB
  • sloc: makefile: 3
file content (163 lines) | stat: -rw-r--r-- 3,889 bytes parent folder | download
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...))
}