add the diagnostics daemon
This commit is contained in:
parent
7164b27041
commit
518b9f843c
63
cmd/diagd/diagd.go
Normal file
63
cmd/diagd/diagd.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Binary diagd provides automated network diagnostics.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"router7/internal/diag"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dump(w io.Writer, re *diag.EvalResult) {
|
||||||
|
symbol := "✔"
|
||||||
|
if re.Error {
|
||||||
|
symbol = "✘"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "<li>%s %s: %s<ul>", symbol, html.EscapeString(re.Name), html.EscapeString(re.Status))
|
||||||
|
for _, ch := range re.Children {
|
||||||
|
dump(w, ch)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "</ul></li>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func logic() error {
|
||||||
|
const (
|
||||||
|
uplink = "uplink0" /* enp0s31f6 */
|
||||||
|
ip6allrouters = "ff02::2" // no /etc/hosts on gokrazy
|
||||||
|
)
|
||||||
|
m := diag.NewMonitor(diag.Link(uplink).
|
||||||
|
Then(diag.DHCPv4().
|
||||||
|
Then(diag.Ping4Gateway().
|
||||||
|
Then(diag.Ping4("google.ch").
|
||||||
|
Then(diag.TCP4("www.google.ch:80"))))).
|
||||||
|
Then(diag.DHCPv6().
|
||||||
|
Then(diag.Ping6("lan0", "google.ch"))).
|
||||||
|
Then(diag.RouterAdvertisments(uplink).
|
||||||
|
Then(diag.Ping6Gateway().
|
||||||
|
Then(diag.Ping6(uplink, "google.ch").
|
||||||
|
Then(diag.TCP6("www.google.ch:80"))))).
|
||||||
|
Then(diag.Ping6("", ip6allrouters+"%"+uplink)))
|
||||||
|
var mu sync.Mutex
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mu.Lock()
|
||||||
|
re := m.Evaluate()
|
||||||
|
mu.Unlock()
|
||||||
|
fmt.Fprintf(w, `<!DOCTYPE html><style type="text/css">ul { list-style-type: none; }</style><ul>`)
|
||||||
|
dump(w, re)
|
||||||
|
})
|
||||||
|
// TODO: only listen on private IP addresses
|
||||||
|
return http.ListenAndServe(":7733", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if err := logic(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
79
internal/diag/dhcp.go
Normal file
79
internal/diag/dhcp.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package diag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func leaseValid(fn string) (status string, _ error) {
|
||||||
|
var lease struct {
|
||||||
|
ValidUntil time.Time `json:"valid_until"`
|
||||||
|
}
|
||||||
|
b, err := ioutil.ReadFile(fn)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(b, &lease); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if time.Now().After(lease.ValidUntil) {
|
||||||
|
return "", fmt.Errorf("lease expired at %v", lease.ValidUntil)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("lease valid until %v", lease.ValidUntil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dhcpv4 struct {
|
||||||
|
children []Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpv4) String() string {
|
||||||
|
return "dhcp4"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpv4) Then(t Node) Node {
|
||||||
|
d.children = append(d.children, t)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpv4) Children() []Node {
|
||||||
|
return d.children
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpv4) Evaluate() (string, error) {
|
||||||
|
return leaseValid("/perm/dhcp4/wire/lease.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DHCPv4 returns a Node which succeeds if /perm/dhcp4/wire/lease.json contains
|
||||||
|
// a non-expired DHCPv4 lease.
|
||||||
|
func DHCPv4() Node {
|
||||||
|
return &dhcpv4{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type dhcpv6 struct {
|
||||||
|
children []Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpv6) String() string {
|
||||||
|
return "dhcp6"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpv6) Then(t Node) Node {
|
||||||
|
d.children = append(d.children, t)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpv6) Children() []Node {
|
||||||
|
return d.children
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dhcpv6) Evaluate() (string, error) {
|
||||||
|
return leaseValid("/perm/dhcp6/wire/lease.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DHCPv6 returns a Node which succeeds if /perm/dhcp6/wire/lease.json contains
|
||||||
|
// a non-expired DHCPv6 lease.
|
||||||
|
func DHCPv6() Node {
|
||||||
|
return &dhcpv6{}
|
||||||
|
}
|
54
internal/diag/diag.go
Normal file
54
internal/diag/diag.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Package diag implements network diagnostics.
|
||||||
|
package diag
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Node interface {
|
||||||
|
Then(t Node) Node
|
||||||
|
Children() []Node
|
||||||
|
Evaluate() (status string, _ error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Monitor struct {
|
||||||
|
root Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMonitor(n Node) *Monitor {
|
||||||
|
return &Monitor{root: n}
|
||||||
|
}
|
||||||
|
|
||||||
|
type EvalResult struct {
|
||||||
|
Name string
|
||||||
|
Error bool
|
||||||
|
Status string
|
||||||
|
Children []*EvalResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluate(n Node, err string) *EvalResult {
|
||||||
|
r := EvalResult{
|
||||||
|
Name: fmt.Sprintf("%s", n),
|
||||||
|
Status: err,
|
||||||
|
Error: err != "",
|
||||||
|
}
|
||||||
|
if r.Status == "" {
|
||||||
|
status, err := n.Evaluate()
|
||||||
|
if err != nil {
|
||||||
|
r.Error = true
|
||||||
|
r.Status = err.Error()
|
||||||
|
} else {
|
||||||
|
r.Status = status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var childErr string
|
||||||
|
if r.Error {
|
||||||
|
childErr = fmt.Sprintf("dependency %s failed", r.Name)
|
||||||
|
}
|
||||||
|
for _, n := range n.Children() {
|
||||||
|
r.Children = append(r.Children, evaluate(n, childErr))
|
||||||
|
}
|
||||||
|
return &r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Monitor) Evaluate() *EvalResult {
|
||||||
|
return evaluate(m.root, "")
|
||||||
|
}
|
39
internal/diag/diag_test.go
Normal file
39
internal/diag/diag_test.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package diag_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"router7/internal/diag"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDiagLink(t *testing.T) {
|
||||||
|
if _, err := diag.Link("nonexistant").Evaluate(); err == nil {
|
||||||
|
t.Errorf("Link(nonexistant).Evaluate = nil, want non-nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := diag.Link("lo").Evaluate(); err != nil {
|
||||||
|
t.Errorf("Link(lo).Evaluate = %v, want nil", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiagMonitor(t *testing.T) {
|
||||||
|
m := diag.NewMonitor(diag.Link("nonexistant").
|
||||||
|
Then(diag.DHCPv4()))
|
||||||
|
got := m.Evaluate()
|
||||||
|
want := &diag.EvalResult{
|
||||||
|
Name: "link/nonexistant",
|
||||||
|
Error: true,
|
||||||
|
Status: "Link not found",
|
||||||
|
Children: []*diag.EvalResult{
|
||||||
|
{
|
||||||
|
Name: "dhcp4",
|
||||||
|
Error: true,
|
||||||
|
Status: "dependency link/nonexistant failed",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(got, want); diff != "" {
|
||||||
|
t.Fatalf("Evaluate(): unexpected result: diff (-got +want):\n%s", diff)
|
||||||
|
}
|
||||||
|
}
|
12
internal/diag/ipv6.go
Normal file
12
internal/diag/ipv6.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package diag
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
var (
|
||||||
|
global = mustParseCIDR("2000::/3") // RFC 4291
|
||||||
|
)
|
||||||
|
|
||||||
|
func mustParseCIDR(s string) *net.IPNet {
|
||||||
|
_, ipnet, _ := net.ParseCIDR(s)
|
||||||
|
return ipnet
|
||||||
|
}
|
47
internal/diag/link.go
Normal file
47
internal/diag/link.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package diag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
type link struct {
|
||||||
|
children []Node
|
||||||
|
ifname string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *link) String() string {
|
||||||
|
return "link/" + l.ifname
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *link) Then(t Node) Node {
|
||||||
|
l.children = append(l.children, t)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *link) Children() []Node {
|
||||||
|
return l.children
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *link) Evaluate() (string, error) {
|
||||||
|
link, err := netlink.LinkByName(l.ifname)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
attrs := link.Attrs()
|
||||||
|
|
||||||
|
// TODO: check RUNNING as well?
|
||||||
|
if attrs.Flags&net.FlagUp == 0 {
|
||||||
|
return "", fmt.Errorf("link %s not UP", l.ifname)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%d rx, %d tx", attrs.Statistics.RxPackets, attrs.Statistics.TxPackets), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link returns a Node which succeeds when the specified network interface is in
|
||||||
|
// state UP and RUNNING.
|
||||||
|
func Link(ifname string) Node {
|
||||||
|
return &link{ifname: ifname}
|
||||||
|
}
|
254
internal/diag/ping.go
Normal file
254
internal/diag/ping.go
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
package diag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/digineo/go-ping"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
func formatRTT(rtt time.Duration) string {
|
||||||
|
return fmt.Sprintf("%.2fms", float64(rtt)/float64(time.Millisecond))
|
||||||
|
}
|
||||||
|
|
||||||
|
type ping4gw struct {
|
||||||
|
children []Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping4gw) String() string {
|
||||||
|
return "ping4/<default-gateway>"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping4gw) Then(t Node) Node {
|
||||||
|
d.children = append(d.children, t)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping4gw) Children() []Node {
|
||||||
|
return d.children
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultIPv4Gateway() (string, error) {
|
||||||
|
rl, err := netlink.RouteGet(net.ParseIP("8.8.8.8"))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if got, want := len(rl), 1; got != want {
|
||||||
|
return "", fmt.Errorf("unexpected number of default routes: got %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
r := rl[0]
|
||||||
|
|
||||||
|
return r.Gw.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping4gw) Evaluate() (string, error) {
|
||||||
|
const timeout = 1 * time.Second
|
||||||
|
gw, err := defaultIPv4Gateway()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
addr, err := net.ResolveIPAddr("ip4", gw)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
p, err := ping.New("0.0.0.0", "")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
rtt, err := p.Ping(addr, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
//return fmt.Errorf("%s did not respond within %v", gw, timeout)
|
||||||
|
}
|
||||||
|
return formatRTT(rtt), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping4Gateway returns a Node which succeeds when the default gateway responds
|
||||||
|
// to an ICMPv4 ping.
|
||||||
|
func Ping4Gateway() Node {
|
||||||
|
return &ping4gw{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ping4 struct {
|
||||||
|
children []Node
|
||||||
|
addr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping4) String() string {
|
||||||
|
return "ping4/" + d.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping4) Then(t Node) Node {
|
||||||
|
d.children = append(d.children, t)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping4) Children() []Node {
|
||||||
|
return d.children
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping4) Evaluate() (string, error) {
|
||||||
|
const timeout = 1 * time.Second
|
||||||
|
addr, err := net.ResolveIPAddr("ip4", d.addr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
p, err := ping.New("0.0.0.0", "")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
rtt, err := p.Ping(addr, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
//return fmt.Errorf("%s did not respond within %v", gw, timeout)
|
||||||
|
}
|
||||||
|
return formatRTT(rtt), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping4 returns a Node which succeeds when the specified address responds to an
|
||||||
|
// ICMPv4 ping.
|
||||||
|
func Ping4(addr string) Node {
|
||||||
|
return &ping4{addr: addr}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ping6gw struct {
|
||||||
|
children []Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping6gw) String() string {
|
||||||
|
return "ping6gw/<default-gateway>"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping6gw) Then(t Node) Node {
|
||||||
|
d.children = append(d.children, t)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping6gw) Children() []Node {
|
||||||
|
return d.children
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultIPv6Gateway() (string, error) {
|
||||||
|
rl, err := netlink.RouteGet(net.IPv6zero)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if got, want := len(rl), 1; got != want {
|
||||||
|
return "", fmt.Errorf("unexpected number of default routes: got %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
r := rl[0]
|
||||||
|
|
||||||
|
iface, err := net.InterfaceByIndex(r.LinkIndex)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return r.Gw.String() + "%" + iface.Name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping6gw) Evaluate() (string, error) {
|
||||||
|
const timeout = 1 * time.Second
|
||||||
|
gw, err := defaultIPv6Gateway()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
addr, err := net.ResolveIPAddr("ip6", gw)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
p, err := ping.New("", "::")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
rtt, err := p.Ping(addr, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
//return fmt.Errorf("%s did not respond within %v", gw, timeout)
|
||||||
|
}
|
||||||
|
return formatRTT(rtt), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping6Gateway returns a Node which succeeds when the default gateway responds
|
||||||
|
// to an ICMPv6 ping.
|
||||||
|
func Ping6Gateway() Node {
|
||||||
|
return &ping6gw{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ping6 struct {
|
||||||
|
children []Node
|
||||||
|
addr string
|
||||||
|
ifname string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping6) String() string {
|
||||||
|
if d.ifname == "" {
|
||||||
|
return "ping6/" + d.addr
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("ping6/%s→%s", d.ifname, d.addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping6) Then(t Node) Node {
|
||||||
|
d.children = append(d.children, t)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping6) Children() []Node {
|
||||||
|
return d.children
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ping6) Evaluate() (string, error) {
|
||||||
|
const timeout = 1 * time.Second
|
||||||
|
addr, err := net.ResolveIPAddr("ip6", d.addr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
bind6 := "::"
|
||||||
|
if d.ifname != "" {
|
||||||
|
iface, err := net.InterfaceByName(d.ifname)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, addr := range addrs {
|
||||||
|
ipnet, ok := addr.(*net.IPNet)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipnet.IP.To4() != nil {
|
||||||
|
continue // skip IPv4 addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
if !global.Contains(ipnet.IP) {
|
||||||
|
continue // skip local IPv6 addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
bind6 = ipnet.IP.String()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := ping.New("", bind6)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
rtt, err := p.Ping(addr, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
//return fmt.Errorf("%s did not respond within %v", gw, timeout)
|
||||||
|
}
|
||||||
|
return formatRTT(rtt), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping6 returns a Node which succeeds when the specified address responds to an
|
||||||
|
// ICMPv6 ping.
|
||||||
|
func Ping6(ifname, addr string) Node {
|
||||||
|
return &ping6{
|
||||||
|
ifname: ifname,
|
||||||
|
addr: addr,
|
||||||
|
}
|
||||||
|
}
|
71
internal/diag/ra6.go
Normal file
71
internal/diag/ra6.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package diag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ra6 struct {
|
||||||
|
children []Node
|
||||||
|
ifname string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ra6) String() string {
|
||||||
|
return "ra6/" + d.ifname
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ra6) Then(t Node) Node {
|
||||||
|
d.children = append(d.children, t)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ra6) Children() []Node {
|
||||||
|
return d.children
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEUI64(ip net.IP) bool {
|
||||||
|
if ip.To16() == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ip = ip.To16()
|
||||||
|
return ip[11] == 0xff && ip[12] == 0xfe
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ra6) Evaluate() (string, error) {
|
||||||
|
iface, err := net.InterfaceByName(d.ifname)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var first string
|
||||||
|
for _, addr := range addrs {
|
||||||
|
ipnet, ok := addr.(*net.IPNet)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ones, _ := ipnet.Mask.Size(); ones != 64 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !global.Contains(ipnet.IP) {
|
||||||
|
continue // skip local IPv6 addresses
|
||||||
|
}
|
||||||
|
if !isEUI64(ipnet.IP) {
|
||||||
|
continue // skip non-autoconf addresses (e.g. DHCPv6 temporary IP)
|
||||||
|
}
|
||||||
|
|
||||||
|
first = ipnet.String()
|
||||||
|
}
|
||||||
|
if first == "" {
|
||||||
|
return "", fmt.Errorf("no SLAAC address found")
|
||||||
|
}
|
||||||
|
return first, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouterAdvertisments returns a Node which succeeds if the specified interface
|
||||||
|
// obtained at least one address from IPv6 router advertisments.
|
||||||
|
func RouterAdvertisments(ifname string) Node {
|
||||||
|
return &ra6{ifname: ifname}
|
||||||
|
}
|
71
internal/diag/tcp.go
Normal file
71
internal/diag/tcp.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package diag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tcp4 struct {
|
||||||
|
children []Node
|
||||||
|
addr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tcp4) String() string {
|
||||||
|
return "tcp4/" + d.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tcp4) Then(t Node) Node {
|
||||||
|
d.children = append(d.children, t)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tcp4) Children() []Node {
|
||||||
|
return d.children
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tcp4) Evaluate() (string, error) {
|
||||||
|
conn, err := net.Dial("tcp4", d.addr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
return "connection established", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCP4 returns a Node which succeeds when the specified address accepts a TCPv4
|
||||||
|
// connection.
|
||||||
|
func TCP4(addr string) Node {
|
||||||
|
return &tcp4{addr: addr}
|
||||||
|
}
|
||||||
|
|
||||||
|
type tcp6 struct {
|
||||||
|
children []Node
|
||||||
|
addr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tcp6) String() string {
|
||||||
|
return "tcp6/" + d.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tcp6) Then(t Node) Node {
|
||||||
|
d.children = append(d.children, t)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tcp6) Children() []Node {
|
||||||
|
return d.children
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tcp6) Evaluate() (string, error) {
|
||||||
|
conn, err := net.Dial("tcp6", d.addr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
return "connection established", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCP6 returns a Node which succeeds when the specified address accepts a TCPv6
|
||||||
|
// connection.
|
||||||
|
func TCP6(addr string) Node {
|
||||||
|
return &tcp6{addr: addr}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user