Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
39a56d1dd2 | |||
69e2890ea0 | |||
a3905135ff | |||
132d26305e | |||
ce32c58ed5 | |||
6836779a56 | |||
20a248af16 | |||
cbf92bec2e | |||
5d3ff44341 | |||
2769e419f6 |
|
@ -1,90 +0,0 @@
|
||||||
package capture
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"git.freifunk-franken.de/jkimmel/abbel/packet"
|
|
||||||
"github.com/google/gopacket"
|
|
||||||
"github.com/google/gopacket/afpacket"
|
|
||||||
"github.com/google/gopacket/layers"
|
|
||||||
"golang.org/x/net/bpf"
|
|
||||||
"inet.af/netaddr"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// tcpdump -y RAW -dd ip6 and udp dst port 6696
|
|
||||||
babelBPFRAW = []bpf.RawInstruction{
|
|
||||||
{0x30, 0, 0, 0x00000000},
|
|
||||||
{0x54, 0, 0, 0x000000f0},
|
|
||||||
{0x15, 0, 5, 0x00000060},
|
|
||||||
{0x30, 0, 0, 0x00000006},
|
|
||||||
{0x15, 0, 3, 0x00000011},
|
|
||||||
{0x28, 0, 0, 0x0000002a},
|
|
||||||
{0x15, 0, 1, 0x00001a28},
|
|
||||||
{0x6, 0, 0, 0x00040000},
|
|
||||||
{0x6, 0, 0, 0x00000000},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type Handle struct {
|
|
||||||
tp *afpacket.TPacket
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromInterface(iface string) (Handle, error) {
|
|
||||||
var h Handle
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if iface != "any" {
|
|
||||||
h.tp, err = afpacket.NewTPacket(
|
|
||||||
afpacket.SocketDgram,
|
|
||||||
afpacket.OptInterface(iface),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
h.tp, err = afpacket.NewTPacket(
|
|
||||||
afpacket.SocketDgram,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("here")
|
|
||||||
return Handle{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = h.tp.SetBPF(babelBPFRAW)
|
|
||||||
if err != nil {
|
|
||||||
return Handle{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Handle) Close() error {
|
|
||||||
h.tp.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Handle) ReadFrom(b []byte) (body []byte, src netaddr.IP, ifindex int, err error) {
|
|
||||||
data, ci, err := h.tp.ZeroCopyReadPacketData()
|
|
||||||
if err != nil {
|
|
||||||
return nil, netaddr.IP{}, 0, err
|
|
||||||
}
|
|
||||||
pckt := gopacket.NewPacket(data, layers.LayerTypeIPv6, gopacket.NoCopy)
|
|
||||||
ip6, ok := pckt.NetworkLayer().(*layers.IPv6)
|
|
||||||
if !ok {
|
|
||||||
return nil, netaddr.IP{}, 0, fmt.Errorf("Expected IPv6 layer, got %v", pckt.NetworkLayer())
|
|
||||||
}
|
|
||||||
|
|
||||||
src, ok = netaddr.FromStdIP(ip6.SrcIP)
|
|
||||||
if !ok {
|
|
||||||
return nil, netaddr.IP{}, 0, fmt.Errorf("Error parsing packet src address %q", ip6.SrcIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := pckt.ApplicationLayer().Payload()
|
|
||||||
copy(b, payload)
|
|
||||||
b, err = packet.Validate(b[:len(payload)])
|
|
||||||
if err != nil {
|
|
||||||
return nil, netaddr.IP{}, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return b, src, ci.InterfaceIndex, nil
|
|
||||||
}
|
|
10
go.mod
10
go.mod
|
@ -1,9 +1,7 @@
|
||||||
module git.freifunk-franken.de/jkimmel/abbel
|
module git.freifunk-franken.de/jkimmel/abbel
|
||||||
|
|
||||||
go 1.0
|
go 1.20
|
||||||
|
|
||||||
require (
|
require golang.org/x/net v0.8.0
|
||||||
github.com/google/gopacket v1.1.19
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
|
require golang.org/x/sys v0.6.0 // indirect
|
||||||
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
|
|
||||||
)
|
|
||||||
|
|
43
go.sum
43
go.sum
|
@ -1,39 +1,4 @@
|
||||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE=
|
|
||||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
|
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 h1:Tx9kY6yUkLge/pFG7IEMwDZy6CS2ajFc9TvQdPCW0uA=
|
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
|
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 h1:acCzuUSQ79tGsM/O50VRFySfMm19IoMKL+sZztZkCxw=
|
|
||||||
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6/go.mod h1:y3MGhcFMlh0KZPMuXXow8mpjxxAk3yoDNsp4cQz54i8=
|
|
||||||
|
|
50
main.go
50
main.go
|
@ -1,40 +1,50 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.freifunk-franken.de/jkimmel/abbel/capture"
|
|
||||||
"git.freifunk-franken.de/jkimmel/abbel/packet"
|
"git.freifunk-franken.de/jkimmel/abbel/packet"
|
||||||
"git.freifunk-franken.de/jkimmel/abbel/tlv"
|
"git.freifunk-franken.de/jkimmel/abbel/tlv"
|
||||||
"inet.af/netaddr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type options struct {
|
type options struct {
|
||||||
Group string
|
Group string
|
||||||
Port uint16
|
Port uint16
|
||||||
Ifs []string
|
Ifs []string
|
||||||
UseCapture bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOpts() (options, error) {
|
func parseOpts() (options, error) {
|
||||||
var opt options
|
var opt options
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
opt = options{
|
var unusedMulticast bool
|
||||||
Group: "ff02:0:0:0:0:0:1:6",
|
|
||||||
Port: 6696,
|
flag.StringVar(&opt.Group, "group", "ff02:0:0:0:0:0:1:6", "Multicast group to join")
|
||||||
Ifs: []string{"babel"},
|
port := flag.Uint("port", 6696, "Port to listen on")
|
||||||
UseCapture: false,
|
flag.BoolVar(&unusedMulticast, "m", false, "Use multicast mode (default, unused)")
|
||||||
|
ifs := flag.String("i", "any", "Comma-seperated list of interfaces to listen on or \"any\" for all interfaces in capture mode")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *port == 0 || *port >= 0xffff {
|
||||||
|
return options{}, fmt.Errorf("Invalid port %q", *port)
|
||||||
|
}
|
||||||
|
opt.Port = uint16(*port)
|
||||||
|
|
||||||
|
for _, iface := range strings.Split(*ifs, ",") {
|
||||||
|
opt.Ifs = append(opt.Ifs, iface)
|
||||||
}
|
}
|
||||||
|
|
||||||
return opt, err
|
return opt, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type updatesKey struct {
|
type updatesKey struct {
|
||||||
prefix netaddr.IPPrefix
|
prefix netip.Prefix
|
||||||
routerID tlv.RouterID
|
routerID tlv.RouterID
|
||||||
nexthop netaddr.IP
|
nexthop netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -42,7 +52,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type BabelPacketConn interface {
|
type BabelPacketConn interface {
|
||||||
ReadFrom(b []byte) (body []byte, src netaddr.IP, ifindex int, err error)
|
ReadFrom(b []byte) (body []byte, src netip.Addr, ifindex int, err error)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,11 +60,7 @@ func run(opt options) error {
|
||||||
var err error
|
var err error
|
||||||
var conn BabelPacketConn
|
var conn BabelPacketConn
|
||||||
|
|
||||||
if opt.UseCapture {
|
conn, err = packet.Listen(opt.Group, opt.Port, opt.Ifs...)
|
||||||
conn, err = capture.FromInterface(opt.Ifs[0])
|
|
||||||
} else {
|
|
||||||
conn, err = packet.Listen(opt.Group, opt.Port, opt.Ifs...)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -93,7 +99,7 @@ func run(opt options) error {
|
||||||
} else {
|
} else {
|
||||||
fmt.Print("\x1B[36m")
|
fmt.Print("\x1B[36m")
|
||||||
}
|
}
|
||||||
fmt.Printf("% 12s %-43s%+d %s\x1B[0m\n", t.T(), t.Prefix, diff, t.FormatHeader())
|
fmt.Printf("% 12s %-43s%+6d %s\x1B[0m\n", t.T(), t.Prefix, diff, t.FormatHeader())
|
||||||
case tlv.RouterID:
|
case tlv.RouterID:
|
||||||
//fmt.Printf("%12s %s\n", t.T(), t)
|
//fmt.Printf("%12s %s\n", t.T(), t)
|
||||||
case tlv.RouteRequest:
|
case tlv.RouteRequest:
|
||||||
|
@ -101,7 +107,9 @@ func run(opt options) error {
|
||||||
case tlv.Raw:
|
case tlv.Raw:
|
||||||
fmt.Printf(" T: %12s (%2d), L: %3d\n", t.T(), t.T(), len(t.V()))
|
fmt.Printf(" T: %12s (%2d), L: %3d\n", t.T(), t.T(), len(t.V()))
|
||||||
case tlv.IHU:
|
case tlv.IHU:
|
||||||
fmt.Printf("%12s %-43sRxcost %5d Interval %4d \n", t.T(), t.Address, t.Rxcost, t.Interval)
|
fmt.Printf("%12s %-43sRxcost %5d Interval %4d\n", t.T(), t.Address, t.Rxcost, t.Interval)
|
||||||
|
case tlv.SeqnoRequest:
|
||||||
|
fmt.Printf("%12s %-43sSeqno %5d HopCount %3d RouterID %s\n", t.T(), t.Prefix, t.Seqno, t.HopCount, t.RouterID)
|
||||||
default:
|
default:
|
||||||
if t != nil {
|
if t != nil {
|
||||||
fmt.Println("Unknown TLV", t.T(), t)
|
fmt.Println("Unknown TLV", t.T(), t)
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package packet
|
package packet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/net/ipv6"
|
"golang.org/x/net/ipv6"
|
||||||
"inet.af/netaddr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -23,6 +25,16 @@ func Listen(group string, port uint16, ifaces ...string) (Conn, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Conn{}, err
|
return Conn{}, err
|
||||||
}
|
}
|
||||||
|
if len(ifaces) == 1 && ifaces[0] == "any" {
|
||||||
|
netifs, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return Conn{}, err
|
||||||
|
}
|
||||||
|
ifaces = make([]string, 0, len(netifs))
|
||||||
|
for _, netif := range netifs {
|
||||||
|
ifaces = append(ifaces, netif.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, iface := range ifaces {
|
for _, iface := range ifaces {
|
||||||
if err = conn.JoinGroup(iface, group); err != nil {
|
if err = conn.JoinGroup(iface, group); err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
@ -34,10 +46,21 @@ func Listen(group string, port uint16, ifaces ...string) (Conn, error) {
|
||||||
func ListenPort(port uint16) (Conn, error) {
|
func ListenPort(port uint16) (Conn, error) {
|
||||||
var c Conn
|
var c Conn
|
||||||
|
|
||||||
uc, err := net.ListenUDP("udp6", &net.UDPAddr{Port: int(port)})
|
lc := net.ListenConfig{
|
||||||
|
Control: func(network, address string, c syscall.RawConn) error {
|
||||||
|
var err error
|
||||||
|
c.Control(func(fd uintptr) {
|
||||||
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
uc, err := lc.ListenPacket(context.Background(), "udp6", fmt.Sprintf(":%d", port))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.v6pc = ipv6.NewPacketConn(uc)
|
c.v6pc = ipv6.NewPacketConn(uc)
|
||||||
|
|
||||||
return c, c.v6pc.SetControlMessage(
|
return c, c.v6pc.SetControlMessage(
|
||||||
|
@ -52,15 +75,15 @@ func (c Conn) Close() error {
|
||||||
func (c Conn) JoinGroup(ifname string, addr string) error {
|
func (c Conn) JoinGroup(ifname string, addr string) error {
|
||||||
ifi, err := net.InterfaceByName(ifname)
|
ifi, err := net.InterfaceByName(ifname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("InterfaceByName(%s): %w", ifname, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := netaddr.ParseIP(addr)
|
ip, err := netip.ParseAddr(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.v6pc.JoinGroup(ifi, &net.UDPAddr{IP: ip.IPAddr().IP})
|
return c.v6pc.JoinGroup(ifi, &net.UDPAddr{IP: ip.AsSlice()})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Validate(b []byte) ([]byte, error) {
|
func Validate(b []byte) ([]byte, error) {
|
||||||
|
@ -88,22 +111,22 @@ func Validate(b []byte) ([]byte, error) {
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Conn) ReadFrom(b []byte) (body []byte, src netaddr.IP, ifindex int, err error) {
|
func (c Conn) ReadFrom(b []byte) (body []byte, src netip.Addr, ifindex int, err error) {
|
||||||
n, rcm, _, err := c.v6pc.ReadFrom(b)
|
n, rcm, _, err := c.v6pc.ReadFrom(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, netaddr.IP{}, 0, err
|
return nil, netip.Addr{}, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err = Validate(b[:n])
|
b, err = Validate(b[:n])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, netaddr.IP{}, 0, err
|
return nil, netip.Addr{}, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ok bool
|
var ok bool
|
||||||
src, ok = netaddr.FromStdIPRaw(rcm.Src)
|
src, ok = netip.AddrFromSlice(rcm.Src)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, netaddr.IP{}, 0, fmt.Errorf("Invalid src address %q", rcm.Src)
|
return nil, netip.Addr{}, 0, fmt.Errorf("Invalid src address %q", rcm.Src)
|
||||||
}
|
}
|
||||||
|
|
||||||
return b, src, rcm.IfIndex, err
|
return b, src, rcm.IfIndex, err
|
||||||
|
|
135
tlv/tlv.go
135
tlv/tlv.go
|
@ -12,8 +12,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
"inet.af/netaddr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Type encodes the type of TLV
|
// Type encodes the type of TLV
|
||||||
|
@ -78,14 +77,14 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// AEFromPrefix returns the address encoding of a prefix:
|
// AEFromPrefix returns the address encoding of a prefix:
|
||||||
func AEFromPrefix(p netaddr.IPPrefix) AEType {
|
func AEFromPrefix(p netip.Prefix) AEType {
|
||||||
return AEFromIP(p.IP())
|
return AEFromIP(p.Addr())
|
||||||
}
|
}
|
||||||
|
|
||||||
// AEFromIP returns the address encoding of an address.
|
// AEFromIP returns the address encoding of an address.
|
||||||
func AEFromIP(p netaddr.IP) AEType {
|
func AEFromIP(p netip.Addr) AEType {
|
||||||
switch {
|
switch {
|
||||||
case p.IsZero():
|
case p.IsUnspecified(), p == netip.Addr{}:
|
||||||
return AEWildcard
|
return AEWildcard
|
||||||
case p.Is4():
|
case p.Is4():
|
||||||
return AEIPv4
|
return AEIPv4
|
||||||
|
@ -172,8 +171,8 @@ type PacketDecoder struct {
|
||||||
|
|
||||||
// parser state
|
// parser state
|
||||||
routerID RouterID
|
routerID RouterID
|
||||||
nexthopv6 netaddr.IP
|
nexthopv6 netip.Addr
|
||||||
nexthopv4 netaddr.IP
|
nexthopv4 netip.Addr
|
||||||
v6 [16]byte
|
v6 [16]byte
|
||||||
v4 [4]byte
|
v4 [4]byte
|
||||||
|
|
||||||
|
@ -183,7 +182,7 @@ type PacketDecoder struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset clears the parser state and sets a new byte slice to work on.
|
// Reset clears the parser state and sets a new byte slice to work on.
|
||||||
func (s *PacketDecoder) Reset(b []byte, nexthop netaddr.IP, ifindex int) {
|
func (s *PacketDecoder) Reset(b []byte, nexthop netip.Addr, ifindex int) {
|
||||||
*s = PacketDecoder{}
|
*s = PacketDecoder{}
|
||||||
s.tlvscanner.Reset(b)
|
s.tlvscanner.Reset(b)
|
||||||
switch {
|
switch {
|
||||||
|
@ -482,7 +481,7 @@ type IHU struct {
|
||||||
// reserved uint8
|
// reserved uint8
|
||||||
Rxcost uint16
|
Rxcost uint16
|
||||||
Interval uint16
|
Interval uint16
|
||||||
Address netaddr.IP
|
Address netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// T returns TypeIHU.
|
// T returns TypeIHU.
|
||||||
|
@ -542,14 +541,12 @@ func routerIDFromBytes(b []byte) (RouterID, []byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip 2 reserved bytes
|
// skip 2 reserved bytes
|
||||||
var rid RouterID
|
rid, b := RouterID(b[2:10]), b[10:]
|
||||||
copy(rid[:], b[2:])
|
|
||||||
b = b[10:]
|
|
||||||
|
|
||||||
if rid == (RouterID{}) {
|
switch rid {
|
||||||
|
case RouterID{}:
|
||||||
return RouterID{}, b, ErrRouterIDZeros
|
return RouterID{}, b, ErrRouterIDZeros
|
||||||
}
|
case RouterID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}:
|
||||||
if rid == (RouterID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}) {
|
|
||||||
return RouterID{}, b, ErrRouterIDOnes
|
return RouterID{}, b, ErrRouterIDOnes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -585,7 +582,7 @@ func (r RouterID) String() string {
|
||||||
type NextHop struct {
|
type NextHop struct {
|
||||||
// AE uint8
|
// AE uint8
|
||||||
// reserved uint8
|
// reserved uint8
|
||||||
Address netaddr.IP
|
Address netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// T returns TypeNextHop.
|
// T returns TypeNextHop.
|
||||||
|
@ -629,8 +626,8 @@ type Update struct {
|
||||||
Seqno uint16
|
Seqno uint16
|
||||||
Metric uint16
|
Metric uint16
|
||||||
RouterID RouterID
|
RouterID RouterID
|
||||||
Prefix netaddr.IPPrefix
|
Prefix netip.Prefix
|
||||||
NextHop netaddr.IP
|
NextHop netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// T returns TypeUpdate.
|
// T returns TypeUpdate.
|
||||||
|
@ -640,7 +637,7 @@ func (Update) T() Type {
|
||||||
|
|
||||||
// L depends on the anounced prefix, at least 10.
|
// L depends on the anounced prefix, at least 10.
|
||||||
func (u Update) L() uint8 {
|
func (u Update) L() uint8 {
|
||||||
return 10 + psizeFromPlen(u.Prefix.Bits())
|
return 10 + psizeFromPlen(uint8(u.Prefix.Bits()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatHeader returns a string of the most important fields except the prefix
|
// FormatHeader returns a string of the most important fields except the prefix
|
||||||
|
@ -656,7 +653,7 @@ func (s *PacketDecoder) updateFromBytes(b []byte) (Update, []byte, error) {
|
||||||
var u Update
|
var u Update
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
ae := b[0]
|
ae := AEType(b[0])
|
||||||
u.Flags = b[1]
|
u.Flags = b[1]
|
||||||
plen := b[2]
|
plen := b[2]
|
||||||
u.Omitted = b[3]
|
u.Omitted = b[3]
|
||||||
|
@ -666,30 +663,34 @@ func (s *PacketDecoder) updateFromBytes(b []byte) (Update, []byte, error) {
|
||||||
|
|
||||||
b = b[10:]
|
b = b[10:]
|
||||||
switch ae {
|
switch ae {
|
||||||
case 0:
|
case AEWildcard:
|
||||||
case 1:
|
case AEIPv4:
|
||||||
u.Prefix, b, err = prefixV4Default(s.v4, plen, u.Omitted, b)
|
u.Prefix, b, err = prefixV4Default(s.v4, plen, u.Omitted, b)
|
||||||
if u.Flags&0x80 > 0 {
|
if u.Flags&0x80 > 0 {
|
||||||
s.v4 = u.Prefix.IP().As4()
|
s.v4 = u.Prefix.Addr().As4()
|
||||||
}
|
}
|
||||||
u.NextHop = s.nexthopv4
|
u.NextHop = s.nexthopv4
|
||||||
case 2:
|
case AEIPv6:
|
||||||
u.Prefix, b, err = prefixV6Default(s.v6, plen, u.Omitted, b)
|
u.Prefix, b, err = prefixV6Default(s.v6, plen, u.Omitted, b)
|
||||||
if u.Flags&0x80 > 0 {
|
if u.Flags&0x80 > 0 {
|
||||||
s.v6 = u.Prefix.IP().As16()
|
s.v6 = u.Prefix.Addr().As16()
|
||||||
}
|
}
|
||||||
u.NextHop = s.nexthopv6
|
u.NextHop = s.nexthopv6
|
||||||
case 3:
|
case AEIPv6LL:
|
||||||
u.Prefix, b, err = prefixV6LL(b)
|
u.Prefix, b, err = prefixV6LL(b)
|
||||||
u.NextHop = s.nexthopv6
|
u.NextHop = s.nexthopv6
|
||||||
|
case AEIPv4oIPv6:
|
||||||
|
return Update{}, b, fmt.Errorf("Not implemented AE Type %s", ae)
|
||||||
|
default:
|
||||||
|
return Update{}, b, fmt.Errorf("Unknown AE Type %d", ae)
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.Flags&0x40 > 0 {
|
if u.Flags&0x40 > 0 {
|
||||||
addr := u.Prefix.IP()
|
addr := u.Prefix.Addr()
|
||||||
switch {
|
switch {
|
||||||
case addr.Is6():
|
case addr.Is6():
|
||||||
v6 := addr.As16()
|
v6 := addr.As16()
|
||||||
copy(s.routerID[:], v6[8:])
|
s.routerID = RouterID(v6[8:])
|
||||||
case addr.Is4():
|
case addr.Is4():
|
||||||
s.routerID = RouterID{}
|
s.routerID = RouterID{}
|
||||||
v4 := addr.As4()
|
v4 := addr.As4()
|
||||||
|
@ -708,7 +709,7 @@ func (s *PacketDecoder) updateFromBytes(b []byte) (Update, []byte, error) {
|
||||||
type RouteRequest struct {
|
type RouteRequest struct {
|
||||||
// AE uint8
|
// AE uint8
|
||||||
// plen uint8
|
// plen uint8
|
||||||
Prefix netaddr.IPPrefix
|
Prefix netip.Prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
// AE returns the address encoding of the requested prefix
|
// AE returns the address encoding of the requested prefix
|
||||||
|
@ -723,7 +724,7 @@ func (RouteRequest) T() Type {
|
||||||
|
|
||||||
// L depends on the requested prefix, at least 4.
|
// L depends on the requested prefix, at least 4.
|
||||||
func (r RouteRequest) L() uint8 {
|
func (r RouteRequest) L() uint8 {
|
||||||
return 4 + psizeFromPlen(r.Prefix.Bits())
|
return 4 + psizeFromPlen(uint8(r.Prefix.Bits()))
|
||||||
}
|
}
|
||||||
func routeRequestFromBytes(b []byte) (RouteRequest, []byte, error) {
|
func routeRequestFromBytes(b []byte) (RouteRequest, []byte, error) {
|
||||||
if err := assertLengthGreater(b, TypeRouteRequest, 2); err != nil {
|
if err := assertLengthGreater(b, TypeRouteRequest, 2); err != nil {
|
||||||
|
@ -746,7 +747,7 @@ type SeqnoRequest struct {
|
||||||
Seqno uint16
|
Seqno uint16
|
||||||
HopCount uint8
|
HopCount uint8
|
||||||
RouterID RouterID
|
RouterID RouterID
|
||||||
Prefix netaddr.IPPrefix
|
Prefix netip.Prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
// T returns TypeSeqnoRequest.
|
// T returns TypeSeqnoRequest.
|
||||||
|
@ -756,7 +757,7 @@ func (SeqnoRequest) T() Type {
|
||||||
|
|
||||||
// L depends on the requested prefix, at least 14.
|
// L depends on the requested prefix, at least 14.
|
||||||
func (r SeqnoRequest) L() uint8 {
|
func (r SeqnoRequest) L() uint8 {
|
||||||
return 14 + psizeFromPlen(r.Prefix.Bits())
|
return 14 + psizeFromPlen(uint8(r.Prefix.Bits()))
|
||||||
}
|
}
|
||||||
func seqnoRequestFromBytes(b []byte) (SeqnoRequest, []byte, error) {
|
func seqnoRequestFromBytes(b []byte) (SeqnoRequest, []byte, error) {
|
||||||
if err := assertLengthGreater(b, TypeSeqnoRequest, 14); err != nil {
|
if err := assertLengthGreater(b, TypeSeqnoRequest, 14); err != nil {
|
||||||
|
@ -770,7 +771,7 @@ func seqnoRequestFromBytes(b []byte) (SeqnoRequest, []byte, error) {
|
||||||
plen := b[1]
|
plen := b[1]
|
||||||
sr.Seqno = uint16(b[2])<<8 | uint16(b[3])
|
sr.Seqno = uint16(b[2])<<8 | uint16(b[3])
|
||||||
sr.HopCount = b[4]
|
sr.HopCount = b[4]
|
||||||
copy(sr.RouterID[:], b[6:])
|
sr.RouterID = RouterID(b[6:])
|
||||||
sr.Prefix, b, err = prefixUncompressed(ae, plen, b[14:])
|
sr.Prefix, b, err = prefixUncompressed(ae, plen, b[14:])
|
||||||
|
|
||||||
return sr, b, err
|
return sr, b, err
|
||||||
|
@ -780,7 +781,7 @@ func seqnoRequestFromBytes(b []byte) (SeqnoRequest, []byte, error) {
|
||||||
//
|
//
|
||||||
// https://datatracker.ietf.org/doc/html/draft-ietf-babel-source-specific-07#section-7.1
|
// https://datatracker.ietf.org/doc/html/draft-ietf-babel-source-specific-07#section-7.1
|
||||||
type SourcePrefix struct {
|
type SourcePrefix struct {
|
||||||
netaddr.IPPrefix
|
netip.Prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
// T returns SubTypeSourcePrefix.
|
// T returns SubTypeSourcePrefix.
|
||||||
|
@ -790,7 +791,7 @@ func (SourcePrefix) T() SubType {
|
||||||
|
|
||||||
// L depends on the encoded prefix
|
// L depends on the encoded prefix
|
||||||
func (s SourcePrefix) L() uint8 {
|
func (s SourcePrefix) L() uint8 {
|
||||||
return psizeFromPlen(s.Bits())
|
return psizeFromPlen(uint8(s.Bits()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SourcePrefixFromBytes parses a SourcePrefix SubTLV
|
// SourcePrefixFromBytes parses a SourcePrefix SubTLV
|
||||||
|
@ -807,11 +808,11 @@ func SourcePrefixFromBytes(b []byte) (SourcePrefix, []byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func psizeFromPlen(plen uint8) uint8 {
|
func psizeFromPlen(plen uint8) uint8 {
|
||||||
return uint8((uint16(plen) + 7) / 8)
|
return (plen + 7) / 8
|
||||||
}
|
}
|
||||||
func prefixUncompressed(ae uint8, plen uint8, b []byte) (netaddr.IPPrefix, []byte, error) {
|
func prefixUncompressed(ae uint8, plen uint8, b []byte) (netip.Prefix, []byte, error) {
|
||||||
if len(b) < int(psizeFromPlen(plen)) {
|
if len(b) < int(psizeFromPlen(plen)) {
|
||||||
return netaddr.IPPrefix{}, b, fmt.Errorf("plen too large")
|
return netip.Prefix{}, b, fmt.Errorf("plen too large")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ae {
|
switch ae {
|
||||||
|
@ -824,71 +825,77 @@ func prefixUncompressed(ae uint8, plen uint8, b []byte) (netaddr.IPPrefix, []byt
|
||||||
case 3:
|
case 3:
|
||||||
return prefixV6LL(b)
|
return prefixV6LL(b)
|
||||||
default:
|
default:
|
||||||
return netaddr.IPPrefix{}, b, fmt.Errorf("Invalid AE %d", ae)
|
return netip.Prefix{}, b, fmt.Errorf("Invalid AE %d", ae)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func prefixWildcard() (netaddr.IPPrefix, []byte, error) {
|
func prefixWildcard() (netip.Prefix, []byte, error) {
|
||||||
return netaddr.IPPrefix{}, nil, nil
|
return netip.Prefix{}, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func prefixV4(plen uint8, b []byte) (netaddr.IPPrefix, []byte, error) {
|
func prefixV4(plen uint8, b []byte) (netip.Prefix, []byte, error) {
|
||||||
return prefixV4Default([4]byte{}, plen, 0, b)
|
return prefixV4Default([4]byte{}, plen, 0, b)
|
||||||
}
|
}
|
||||||
func prefixV4Default(ip4default [4]byte, plen uint8, omit uint8, b []byte) (netaddr.IPPrefix, []byte, error) {
|
func prefixV4Default(ip4default [4]byte, plen uint8, omit uint8, b []byte) (netip.Prefix, []byte, error) {
|
||||||
var ip4 [4]byte
|
var ip4 [4]byte
|
||||||
psize := psizeFromPlen(plen) - omit
|
psize := psizeFromPlen(plen) - omit
|
||||||
copy(ip4[:], ip4default[:omit])
|
copy(ip4[:], ip4default[:omit])
|
||||||
copy(ip4[omit:], b[:psize])
|
copy(ip4[omit:], b[:psize])
|
||||||
return netaddr.IPPrefixFrom(netaddr.IPFrom4(ip4), plen), b[psize:], nil
|
return netip.PrefixFrom(netip.AddrFrom4(ip4), int(plen)), b[psize:], nil
|
||||||
}
|
}
|
||||||
func prefixV6(plen uint8, b []byte) (netaddr.IPPrefix, []byte, error) {
|
func prefixV6(plen uint8, b []byte) (netip.Prefix, []byte, error) {
|
||||||
return prefixV6Default([16]byte{}, plen, 0, b)
|
return prefixV6Default([16]byte{}, plen, 0, b)
|
||||||
}
|
}
|
||||||
func prefixV6Default(ip6default [16]byte, plen uint8, omit uint8, b []byte) (netaddr.IPPrefix, []byte, error) {
|
func prefixV6Default(ip6default [16]byte, plen uint8, omit uint8, b []byte) (netip.Prefix, []byte, error) {
|
||||||
var ip6 [16]byte
|
var ip6 [16]byte
|
||||||
psize := psizeFromPlen(plen) - omit
|
psize := psizeFromPlen(plen) - omit
|
||||||
copy(ip6[:], ip6default[:omit])
|
copy(ip6[:], ip6default[:omit])
|
||||||
copy(ip6[omit:], b[:psize])
|
copy(ip6[omit:], b[:psize])
|
||||||
return netaddr.IPPrefixFrom(netaddr.IPv6Raw(ip6), plen), b[psize:], nil
|
return netip.PrefixFrom(netip.AddrFrom16(ip6), int(plen)), b[psize:], nil
|
||||||
}
|
}
|
||||||
func prefixV6LL(b []byte) (netaddr.IPPrefix, []byte, error) {
|
func prefixV6LL(b []byte) (netip.Prefix, []byte, error) {
|
||||||
var ip6ll [16]byte
|
var ip6ll [16]byte
|
||||||
ip6ll[0] = 0xfe
|
ip6ll[0] = 0xfe
|
||||||
ip6ll[1] = 0x80
|
ip6ll[1] = 0x80
|
||||||
copy(ip6ll[8:], b[:8])
|
copy(ip6ll[8:], b[:8])
|
||||||
return netaddr.IPPrefixFrom(netaddr.IPv6Raw(ip6ll), 8), b[8:], nil
|
return netip.PrefixFrom(netip.AddrFrom16(ip6ll), 8), b[8:], nil
|
||||||
}
|
}
|
||||||
func ipFromBytes(ae AEType, b []byte) (netaddr.IP, []byte, error) {
|
|
||||||
|
type ErrorIpFromBytesLength struct {
|
||||||
|
ae AEType
|
||||||
|
length int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrorIpFromBytesLength) Error() string {
|
||||||
|
return fmt.Sprintf("Not enough bytes for address encoding %s: %d", e.ae, e.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ipFromBytes(ae AEType, b []byte) (netip.Addr, []byte, error) {
|
||||||
switch ae {
|
switch ae {
|
||||||
case AEWildcard:
|
case AEWildcard:
|
||||||
return netaddr.IP{}, b, nil
|
return netip.Addr{}, b, nil
|
||||||
case AEIPv4:
|
case AEIPv4:
|
||||||
if len(b) < 4 {
|
if len(b) < 4 {
|
||||||
return netaddr.IP{}, b, fmt.Errorf("Not enough bytes for v4 address: %d", len(b))
|
return netip.Addr{}, b, ErrorIpFromBytesLength{ae: AEIPv4, length: len(b)}
|
||||||
}
|
}
|
||||||
var ip4 [4]byte
|
return netip.AddrFrom4([4]byte(b)), b[4:], nil
|
||||||
copy(ip4[:], b[:4])
|
|
||||||
return netaddr.IPFrom4(ip4), b[4:], nil
|
|
||||||
case AEIPv6:
|
case AEIPv6:
|
||||||
if len(b) < 16 {
|
if len(b) < 16 {
|
||||||
return netaddr.IP{}, b, fmt.Errorf("Not enough bytes for v6 address: %d", len(b))
|
return netip.Addr{}, b, ErrorIpFromBytesLength{ae: AEIPv6, length: len(b)}
|
||||||
}
|
}
|
||||||
var ip6 [16]byte
|
return netip.AddrFrom16([16]byte(b)), b[16:], nil
|
||||||
copy(ip6[:], b[:16])
|
|
||||||
return netaddr.IPv6Raw(ip6), b[16:], nil
|
|
||||||
case AEIPv6LL:
|
case AEIPv6LL:
|
||||||
if len(b) < 8 {
|
if len(b) < 8 {
|
||||||
return netaddr.IP{}, b, fmt.Errorf("Not enough bytes for v6ll address: %d", len(b))
|
return netip.Addr{}, b, ErrorIpFromBytesLength{ae: AEIPv6LL, length: len(b)}
|
||||||
}
|
}
|
||||||
var ip6ll [16]byte
|
var ip6ll [16]byte
|
||||||
ip6ll[0] = 0xfe
|
ip6ll[0] = 0xfe
|
||||||
ip6ll[1] = 0x80
|
ip6ll[1] = 0x80
|
||||||
copy(ip6ll[8:], b[:8])
|
copy(ip6ll[8:], b[:8])
|
||||||
return netaddr.IPv6Raw(ip6ll), b[8:], nil
|
return netip.AddrFrom16(ip6ll), b[8:], nil
|
||||||
case AEIPv4oIPv6:
|
case AEIPv4oIPv6:
|
||||||
return netaddr.IP{}, b, fmt.Errorf("Not implemented AE Type %s", ae)
|
return netip.Addr{}, b, fmt.Errorf("Not implemented AE Type %s", ae)
|
||||||
default:
|
default:
|
||||||
return netaddr.IP{}, b, fmt.Errorf("Invalid AE %d", ae)
|
return netip.Addr{}, b, fmt.Errorf("Invalid AE %d", ae)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user