| // 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 |
| 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 w.Stop() |
| |
| // 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.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 |
| } |