// Copyright 2012 Google, Inc. All rights reserved. // // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file in the root of the source // tree. // +build linux // Package routing provides a very basic but mostly functional implementation of // a routing table for IPv4/IPv6 addresses. It uses a routing table pulled from // the kernel via netlink to find the correct interface, gateway, and preferred // source IP address for packets destined to a particular location. // // The routing package is meant to be used with applications that are sending // raw packet data, which don't have the benefit of having the kernel route // packets for them. package routing import ( "bytes" "errors" "fmt" "net" "sort" "strings" "syscall" "unsafe" ) // Pulled from http://man7.org/linux/man-pages/man7/rtnetlink.7.html // See the section on RTM_NEWROUTE, specifically 'struct rtmsg'. type routeInfoInMemory struct { Family byte DstLen byte SrcLen byte TOS byte Table byte Protocol byte Scope byte Type byte Flags uint32 } // rtInfo contains information on a single route. type rtInfo struct { Src, Dst *net.IPNet Gateway, PrefSrc net.IP // We currently ignore the InputIface. InputIface, OutputIface uint32 Priority uint32 } // routeSlice implements sort.Interface to sort routes by Priority. type routeSlice []*rtInfo func (r routeSlice) Len() int { return len(r) } func (r routeSlice) Less(i, j int) bool { return r[i].Priority < r[j].Priority } func (r routeSlice) Swap(i, j int) { r[i], r[j] = r[j], r[i] } type router struct { ifaces []net.Interface addrs []ipAddrs v4, v6 routeSlice } func (r *router) String() string { strs := []string{"ROUTER", "--- V4 ---"} for _, route := range r.v4 { strs = append(strs, fmt.Sprintf("%+v", *route)) } strs = append(strs, "--- V6 ---") for _, route := range r.v6 { strs = append(strs, fmt.Sprintf("%+v", *route)) } return strings.Join(strs, "\n") } type ipAddrs struct { v4, v6 net.IP } func (r *router) Route(dst net.IP) (iface *net.Interface, gateway, preferredSrc net.IP, err error) { return r.RouteWithSrc(nil, nil, dst) } func (r *router) RouteWithSrc(input net.HardwareAddr, src, dst net.IP) (iface *net.Interface, gateway, preferredSrc net.IP, err error) { var ifaceIndex int switch { case dst.To4() != nil: ifaceIndex, gateway, preferredSrc, err = r.route(r.v4, input, src, dst) case dst.To16() != nil: ifaceIndex, gateway, preferredSrc, err = r.route(r.v6, input, src, dst) default: err = errors.New("IP is not valid as IPv4 or IPv6") } if err != nil { return } // Interfaces are 1-indexed, but we store them in a 0-indexed array. ifaceIndex-- iface = &r.ifaces[ifaceIndex] if preferredSrc == nil { switch { case dst.To4() != nil: preferredSrc = r.addrs[ifaceIndex].v4 case dst.To16() != nil: preferredSrc = r.addrs[ifaceIndex].v6 } } return } func (r *router) route(routes routeSlice, input net.HardwareAddr, src, dst net.IP) (iface int, gateway, preferredSrc net.IP, err error) { var inputIndex uint32 if input != nil { for i, iface := range r.ifaces { if bytes.Equal(input, iface.HardwareAddr) { // Convert from zero- to one-indexed. inputIndex = uint32(i + 1) break } } } for _, rt := range routes { if rt.InputIface != 0 && rt.InputIface != inputIndex { continue } if rt.Src != nil && !rt.Src.Contains(src) { continue } if rt.Dst != nil && !rt.Dst.Contains(dst) { continue } return int(rt.OutputIface), rt.Gateway, rt.PrefSrc, nil } err = fmt.Errorf("no route found for %v", dst) return } // New creates a new router object. The router returned by New currently does // not update its routes after construction... care should be taken for // long-running programs to call New() regularly to take into account any // changes to the routing table which have occurred since the last New() call. func New() (Router, error) { rtr := &router{} tab, err := syscall.NetlinkRIB(syscall.RTM_GETROUTE, syscall.AF_UNSPEC) if err != nil { return nil, err } msgs, err := syscall.ParseNetlinkMessage(tab) if err != nil { return nil, err } loop: for _, m := range msgs { switch m.Header.Type { case syscall.NLMSG_DONE: break loop case syscall.RTM_NEWROUTE: rt := (*routeInfoInMemory)(unsafe.Pointer(&m.Data[0])) routeInfo := rtInfo{} attrs, err := syscall.ParseNetlinkRouteAttr(&m) if err != nil { return nil, err } switch rt.Family { case syscall.AF_INET: rtr.v4 = append(rtr.v4, &routeInfo) case syscall.AF_INET6: rtr.v6 = append(rtr.v6, &routeInfo) default: continue loop } for _, attr := range attrs { switch attr.Attr.Type { case syscall.RTA_DST: routeInfo.Dst = &net.IPNet{ IP: net.IP(attr.Value), Mask: net.CIDRMask(int(rt.DstLen), len(attr.Value)*8), } case syscall.RTA_SRC: routeInfo.Src = &net.IPNet{ IP: net.IP(attr.Value), Mask: net.CIDRMask(int(rt.SrcLen), len(attr.Value)*8), } case syscall.RTA_GATEWAY: routeInfo.Gateway = net.IP(attr.Value) case syscall.RTA_PREFSRC: routeInfo.PrefSrc = net.IP(attr.Value) case syscall.RTA_IIF: routeInfo.InputIface = *(*uint32)(unsafe.Pointer(&attr.Value[0])) case syscall.RTA_OIF: routeInfo.OutputIface = *(*uint32)(unsafe.Pointer(&attr.Value[0])) case syscall.RTA_PRIORITY: routeInfo.Priority = *(*uint32)(unsafe.Pointer(&attr.Value[0])) } } } } sort.Sort(rtr.v4) sort.Sort(rtr.v6) ifaces, err := net.Interfaces() if err != nil { return nil, err } for i, iface := range ifaces { if i != iface.Index-1 { return nil, fmt.Errorf("out of order iface %d = %v", i, iface) } rtr.ifaces = append(rtr.ifaces, iface) var addrs ipAddrs ifaceAddrs, err := iface.Addrs() if err != nil { return nil, err } for _, addr := range ifaceAddrs { if inet, ok := addr.(*net.IPNet); ok { // Go has a nasty habit of giving you IPv4s as ::ffff:1.2.3.4 instead of 1.2.3.4. // We want to use mapped v4 addresses as v4 preferred addresses, never as v6 // preferred addresses. if v4 := inet.IP.To4(); v4 != nil { if addrs.v4 == nil { addrs.v4 = v4 } } else if addrs.v6 == nil { addrs.v6 = inet.IP } } } rtr.addrs = append(rtr.addrs, addrs) } return rtr, nil }