golang 定期发送 RA 报文

源码:

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:
", from)

    if ra.CurrentHopLimit > 0 {
        writef(&s, "  - hop limit:        %d
", ra.CurrentHopLimit)
    }
    if len(flags) > 0 {
        writef(&s, "  - flags:            [%s]
", strings.Join(flags, ", "))
    }

    writef(&s, "  - preference:       %s
", ra.RouterSelectionPreference)

    if ra.RouterLifetime > 0 {
        writef(&s, "  - router lifetime:  %s
", ra.RouterLifetime)
    }
    if ra.ReachableTime != 0 {
        writef(&s, "  - reachable time:   %s
", ra.ReachableTime)
    }
    if ra.RetransmitTimer != 0 {
        writef(&s, "  - retransmit timer: %s
", 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:
")

    for _, o := range options {
        writef(&s, "    - %s
", 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
原文地址:https://www.cnblogs.com/wangjq19920210/p/15074903.html