源码:
package main import ( "context" "errors" "flag" "fmt" "github.com/mdlayher/ndp" "io" "log" "net" "os" "strings" "time" ) 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)") ) 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, _, err := ndp.Listen(ifi, ndp.Addr(*addrFlag)) if err != nil { ll.Fatalf("failed to open NDP connection: %v", err) } defer c.Close() for { if err := doRA(c, ifi.HardwareAddr); 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) } time.Sleep(1*time.Second) } } // 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 and not a loopback? if ifi.Flags&net.FlagUp != 1 || 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 } // Is this address an IPv6 address? if ipNet.IP.To16() != nil && ipNet.IP.To4() == nil { return &ifi, nil } } } return nil, errors.New("could not find a usable IPv6-enabled interface") } func doRA(c *ndp.Conn, addr net.HardwareAddr) error { ll := log.New(os.Stderr, "ndp ra> ", 0) prefix:= net.ParseIP("fe80::100:") // This tool is mostly meant for testing so hardcode a bunch of values. m := &ndp.RouterAdvertisement{ CurrentHopLimit: 64, RouterSelectionPreference: ndp.Medium, RouterLifetime: 30 * time.Second, Options: []ndp.Option{ &ndp.PrefixInformation{ PrefixLength: 64, AutonomousAddressConfiguration: true, ValidLifetime: 60 * time.Second, PreferredLifetime: 30 * time.Second, Prefix: prefix, }, &ndp.LinkLayerAddress{ Direction: ndp.Source, Addr: addr, }, }, } printMessage(ll, m, net.ParseIP("::")) if err := c.WriteTo(m, nil, net.IPv6linklocalallnodes); err != nil { return fmt.Errorf("failed to send router advertisement: %v", err) } return nil } func printMessage(ll *log.Logger, m ndp.Message, from net.IP) { switch m := m.(type) { case *ndp.RouterAdvertisement: printRA(ll, m, from) default: ll.Printf("%s %#v", from, m) } } func printRA(ll *log.Logger, ra *ndp.RouterAdvertisement, from net.IP) { var flags []string if ra.ManagedConfiguration { flags = append(flags, "managed") } if ra.OtherConfiguration { flags = append(flags, "other") } if ra.MobileIPv6HomeAgent { flags = append(flags, "mobile") } if ra.NeighborDiscoveryProxy { flags = append(flags, "proxy") } var s strings.Builder writef(&s, "router advertisement from: %s:\n", from) if ra.CurrentHopLimit > 0 { writef(&s, " - hop limit: %d\n", ra.CurrentHopLimit) } if len(flags) > 0 { writef(&s, " - flags: [%s]\n", strings.Join(flags, ", ")) } writef(&s, " - preference: %s\n", ra.RouterSelectionPreference) if ra.RouterLifetime > 0 { writef(&s, " - router lifetime: %s\n", ra.RouterLifetime) } if ra.ReachableTime != 0 { writef(&s, " - reachable time: %s\n", ra.ReachableTime) } if ra.RetransmitTimer != 0 { writef(&s, " - retransmit timer: %s\n", ra.RetransmitTimer) } _, _ = s.WriteString(optionsString(ra.Options)) ll.Print(s.String()) } func writef(sw io.StringWriter, format string, a ...interface{}) { _, _ = sw.WriteString(fmt.Sprintf(format, a...)) } func optStr(o ndp.Option) string { switch o := o.(type) { case *ndp.LinkLayerAddress: dir := "source" if o.Direction == ndp.Target { dir = "target" } return fmt.Sprintf("%s link-layer address: %s", dir, o.Addr.String()) case *ndp.MTU: return fmt.Sprintf("MTU: %d", *o) case *ndp.PrefixInformation: var flags []string if o.OnLink { flags = append(flags, "on-link") } if o.AutonomousAddressConfiguration { flags = append(flags, "autonomous") } return fmt.Sprintf("prefix information: %s/%d, flags: [%s], valid: %s, preferred: %s", o.Prefix.String(), o.PrefixLength, strings.Join(flags, ", "), o.ValidLifetime, o.PreferredLifetime, ) case *ndp.RawOption: return fmt.Sprintf("type: %03d, value: %v", o.Type, o.Value) case *ndp.RouteInformation: return fmt.Sprintf("route information: %s/%d, preference: %s, lifetime: %s", o.Prefix.String(), o.PrefixLength, o.Preference.String(), o.RouteLifetime, ) case *ndp.RecursiveDNSServer: var ss []string for _, s := range o.Servers { ss = append(ss, s.String()) } servers := strings.Join(ss, ", ") return fmt.Sprintf("recursive DNS servers: lifetime: %s, servers: %s", o.Lifetime, servers) case *ndp.DNSSearchList: return fmt.Sprintf("DNS search list: lifetime: %s, domain names: %s", o.Lifetime, strings.Join(o.DomainNames, ", ")) case *ndp.CaptivePortal: return fmt.Sprintf("captive portal: %s", *o) default: panic(fmt.Sprintf("unrecognized option: %v", o)) } } func optionsString(options []ndp.Option) string { if len(options) == 0 { return "" } var s strings.Builder s.WriteString(" - options:\n") for _, o := range options { writef(&s, " - %s\n", optStr(o)) } return s.String() }
测试结果:
[root@junqiang ndp]# ./ra -i eth0 ndp ra> router advertisement from: ::: - hop limit: 64 - preference: Medium - router lifetime: 30s - options: - prefix information: <nil>/64, flags: [autonomous], valid: 1m0s, preferred: 30s - source link-layer address: 02:42:ac:11:00:02 ndp ra> router advertisement from: ::: - hop limit: 64 - preference: Medium - router lifetime: 30s - options: - prefix information: <nil>/64, flags: [autonomous], valid: 1m0s, preferred: 30s - source link-layer address: 02:42:ac:11:00:02
抓包:
[root@localhost ~]# tcpdump -nn -i eth0 icmp6 -e tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 14:22:43.860802 02:42:ac:11:00:02 > 33:33:00:00:00:01, ethertype IPv6 (0x86dd), length 110: fe80::42:acff:fe11:2 > ff02::1: ICMP6, router advertisement, length 56 14:22:44.865244 02:42:ac:11:00:02 > 33:33:00:00:00:01, ethertype IPv6 (0x86dd), length 110: fe80::42:acff:fe11:2 > ff02::1: ICMP6, router advertisement, length 56 14:22:45.868317 02:42:ac:11:00:02 > 33:33:00:00:00:01, ethertype IPv6 (0x86dd), length 110: fe80::42:acff:fe11:2 > ff02::1: ICMP6, router advertisement, length 56 14:22:46.874265 02:42:ac:11:00:02 > 33:33:00:00:00:01, ethertype IPv6 (0x86dd), length 110: fe80::42:acff:fe11:2 > ff02::1: ICMP6, router advertisement, length 56