blob: d8bae900cb98fcd4e58119607a0fb5a38595e7e5 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin dragonfly freebsd netbsd openbsd
package netconfig
// We connect to the Route socket and parse messages to
// look for network configuration changes. This is generic
// to all BSD based systems (including MacOS). The net
// library already has code to parse the messages so all
// we need to do is look for message types.
import (
"errors"
"net"
"sync"
"syscall"
"time"
"v.io/x/lib/vlog"
)
/*
#include <sys/socket.h>
*/
import "C"
type bsdNetConfigWatcher struct {
sync.Mutex
t *time.Timer
c chan struct{}
s int
stopped bool
}
func (w *bsdNetConfigWatcher) Stop() {
w.Lock()
defer w.Unlock()
if w.stopped {
return
}
w.stopped = true
if w.t != nil {
w.t.Stop()
w.t = nil
}
syscall.Close(w.s)
}
func (w *bsdNetConfigWatcher) Channel() <-chan struct{} {
return w.c
}
// NewNetConfigWatcher returns a watcher that sends a message
// on the Channel() whenever the config changes.
func NewNetConfigWatcher() (NetConfigWatcher, error) {
s, err := syscall.Socket(C.PF_ROUTE, syscall.SOCK_RAW, syscall.AF_UNSPEC)
if err != nil {
vlog.Infof("socket failed: %s", err)
return nil, err
}
w := &bsdNetConfigWatcher{c: make(chan struct{}, 1), s: s}
go w.watcher()
return w, nil
}
func (w *bsdNetConfigWatcher) ding() {
w.Lock()
defer w.Unlock()
w.t = nil
if w.stopped {
return
}
// Don't let us hang in the lock. The default is safe because the requirement
// is that the client get a message after the last config change. Since this is
// a queued chan, we really don't have to stuff anything in it if there's already
// something there.
select {
case w.c <- struct{}{}:
default:
}
}
func (w *bsdNetConfigWatcher) watcher() {
defer func() {
w.Stop()
close(w.c)
}()
// Loop waiting for messages.
for {
b := make([]byte, 4096)
nr, err := syscall.Read(w.s, b)
if err != nil {
return
}
msgs, err := syscall.ParseRoutingMessage(b[:nr])
if err != nil {
vlog.Infof("Couldn't parse: %s", err)
continue
}
// Decode the addresses.
for _, m := range msgs {
switch m.(type) {
case *syscall.InterfaceMessage:
case *syscall.InterfaceAddrMessage:
case *syscall.RouteMessage:
default:
continue
}
// Changing networks usually spans many seconds and involves
// multiple network config changes. We add histeresis by
// setting an alarm when the first change is detected and
// not informing the client till the alarm goes off.
// NOTE(p): I chose 3 seconds because that covers all the
// events involved in moving from one wifi network to another.
w.Lock()
if w.t == nil && !w.stopped {
w.t = time.AfterFunc(3*time.Second, w.ding)
}
w.Unlock()
}
}
}
func toIP(sa syscall.Sockaddr) (net.IP, error) {
switch v := sa.(type) {
case *syscall.SockaddrInet4:
return net.IPv4(v.Addr[0], v.Addr[1], v.Addr[2], v.Addr[3]), nil
case *syscall.SockaddrInet6:
return net.IP(v.Addr[:]), nil
}
return net.IPv6zero, errors.New("unknown sockaddr ip")
}
func toIPNet(sa syscall.Sockaddr, msa syscall.Sockaddr) (net.IPNet, error) {
var x net.IPNet
var err error
x.IP, err = toIP(sa)
if err != nil {
return x, err
}
switch v := msa.(type) {
case *syscall.SockaddrInet4:
x.Mask = net.IPv4Mask(v.Addr[0], v.Addr[1], v.Addr[2], v.Addr[3])
return x, nil
case *syscall.SockaddrInet6:
x.Mask = net.IPMask(v.Addr[:])
return x, nil
}
return x, errors.New("unknown sockaddr ipnet")
}
// IPRoutes returns all kernel known routes. If defaultOnly is set, only default routes
// are returned.
func GetIPRoutes(defaultOnly bool) []*IPRoute {
var x []*IPRoute
rib, err := syscall.RouteRIB(syscall.NET_RT_DUMP, 0)
if err != nil {
vlog.Infof("Couldn't read: %s", err)
return x
}
msgs, err := syscall.ParseRoutingMessage(rib)
if err != nil {
vlog.Infof("Couldn't parse: %s", err)
return x
}
for _, m := range msgs {
switch v := m.(type) {
case *syscall.RouteMessage:
addrs, err := syscall.ParseRoutingSockaddr(m)
if err != nil {
return x
}
if addrs[0] == nil || addrs[1] == nil || addrs[2] == nil {
continue
}
r := new(IPRoute)
if r.Gateway, err = toIP(addrs[1]); err != nil {
continue
}
if r.Net, err = toIPNet(addrs[0], addrs[2]); err != nil {
continue
}
r.IfcIndex = int(v.Header.Index)
if !defaultOnly || IsDefaultIPRoute(r) {
x = append(x, r)
}
}
}
return x
}