2020-04-12 08:55:13 -04:00
|
|
|
package routing
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"fmt"
|
2020-10-20 02:45:28 +00:00
|
|
|
"net"
|
2020-04-12 08:55:13 -04:00
|
|
|
|
2020-07-26 12:07:06 +00:00
|
|
|
"github.com/qdm12/gluetun/internal/constants"
|
2020-10-22 18:55:28 -04:00
|
|
|
"github.com/vishvananda/netlink"
|
2020-04-12 08:55:13 -04:00
|
|
|
)
|
|
|
|
|
|
2021-04-10 03:08:20 +10:00
|
|
|
type LocalNetwork struct {
|
|
|
|
|
Subnet net.IPNet
|
|
|
|
|
InterfaceName string
|
|
|
|
|
IP net.IP
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-22 18:55:28 -04:00
|
|
|
func (r *routing) DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error) {
|
|
|
|
|
routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", nil, fmt.Errorf("cannot list routes: %w", err)
|
|
|
|
|
}
|
|
|
|
|
for _, route := range routes {
|
|
|
|
|
if route.Dst == nil {
|
|
|
|
|
defaultGateway = route.Gw
|
|
|
|
|
linkIndex := route.LinkIndex
|
|
|
|
|
link, err := netlink.LinkByIndex(linkIndex)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", nil, fmt.Errorf("cannot obtain link with index %d for default route: %w", linkIndex, err)
|
|
|
|
|
}
|
|
|
|
|
attributes := link.Attrs()
|
|
|
|
|
defaultInterface = attributes.Name
|
2020-10-25 20:40:17 +00:00
|
|
|
if r.verbose {
|
|
|
|
|
r.logger.Info("default route found: interface %s, gateway %s", defaultInterface, defaultGateway.String())
|
|
|
|
|
}
|
2020-10-22 18:55:28 -04:00
|
|
|
return defaultInterface, defaultGateway, nil
|
2020-04-12 08:55:13 -04:00
|
|
|
}
|
|
|
|
|
}
|
2020-10-22 18:55:28 -04:00
|
|
|
return "", nil, fmt.Errorf("cannot find default route in %d routes", len(routes))
|
2020-04-12 08:55:13 -04:00
|
|
|
}
|
|
|
|
|
|
2020-10-29 19:23:44 -04:00
|
|
|
func (r *routing) DefaultIP() (ip net.IP, err error) {
|
2020-10-24 18:05:11 -04:00
|
|
|
routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("cannot get default IP address: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defaultLinkName := ""
|
|
|
|
|
for _, route := range routes {
|
|
|
|
|
if route.Dst == nil {
|
|
|
|
|
linkIndex := route.LinkIndex
|
|
|
|
|
link, err := netlink.LinkByIndex(linkIndex)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("cannot get default IP address: %w", err)
|
|
|
|
|
}
|
|
|
|
|
defaultLinkName = link.Attrs().Name
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if len(defaultLinkName) == 0 {
|
|
|
|
|
return nil, fmt.Errorf("cannot find default link name in %d routes", len(routes))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return r.assignedIP(defaultLinkName)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-22 18:55:28 -04:00
|
|
|
func (r *routing) LocalSubnet() (defaultSubnet net.IPNet, err error) {
|
|
|
|
|
routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
|
2020-04-12 08:55:13 -04:00
|
|
|
if err != nil {
|
2020-10-22 18:55:28 -04:00
|
|
|
return defaultSubnet, fmt.Errorf("cannot find local subnet: %w", err)
|
2020-04-12 08:55:13 -04:00
|
|
|
}
|
2020-10-12 10:55:08 -04:00
|
|
|
|
2020-10-22 18:55:28 -04:00
|
|
|
defaultLinkIndex := -1
|
|
|
|
|
for _, route := range routes {
|
|
|
|
|
if route.Dst == nil {
|
|
|
|
|
defaultLinkIndex = route.LinkIndex
|
2020-07-12 19:05:48 +00:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-22 18:55:28 -04:00
|
|
|
if defaultLinkIndex == -1 {
|
|
|
|
|
return defaultSubnet, fmt.Errorf("cannot find local subnet: cannot find default link")
|
2020-07-12 19:05:48 +00:00
|
|
|
}
|
|
|
|
|
|
2020-10-22 18:55:28 -04:00
|
|
|
for _, route := range routes {
|
|
|
|
|
if route.Gw != nil || route.LinkIndex != defaultLinkIndex {
|
|
|
|
|
continue
|
2020-07-12 19:05:48 +00:00
|
|
|
}
|
2020-10-22 18:55:28 -04:00
|
|
|
defaultSubnet = *route.Dst
|
2020-10-25 20:40:17 +00:00
|
|
|
if r.verbose {
|
|
|
|
|
r.logger.Info("local subnet found: %s", defaultSubnet.String())
|
|
|
|
|
}
|
2020-10-22 18:55:28 -04:00
|
|
|
return defaultSubnet, nil
|
2020-07-12 19:05:48 +00:00
|
|
|
}
|
2020-10-22 18:55:28 -04:00
|
|
|
|
|
|
|
|
return defaultSubnet, fmt.Errorf("cannot find default subnet in %d routes", len(routes))
|
2020-04-12 08:55:13 -04:00
|
|
|
}
|
|
|
|
|
|
2021-04-10 03:08:20 +10:00
|
|
|
func (r *routing) LocalNetworks() (localNetworks []LocalNetwork, err error) {
|
|
|
|
|
links, err := netlink.LinkList()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return localNetworks, fmt.Errorf("cannot find local subnet: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
localLinks := make(map[int]struct{})
|
|
|
|
|
|
|
|
|
|
for _, link := range links {
|
|
|
|
|
if link.Attrs().EncapType != "ether" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
localLinks[link.Attrs().Index] = struct{}{}
|
|
|
|
|
if r.verbose {
|
|
|
|
|
r.logger.Info("local ethernet link found: %s", link.Attrs().Name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(localLinks) == 0 {
|
|
|
|
|
return localNetworks, fmt.Errorf("cannot find any local interfaces")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return localNetworks, fmt.Errorf("cannot list local routes: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, route := range routes {
|
|
|
|
|
if route.Gw != nil || route.Dst == nil {
|
|
|
|
|
continue
|
|
|
|
|
} else if _, ok := localLinks[route.LinkIndex]; !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var localNet LocalNetwork
|
|
|
|
|
|
|
|
|
|
localNet.Subnet = *route.Dst
|
|
|
|
|
if r.verbose {
|
|
|
|
|
r.logger.Info("local subnet found: %s", localNet.Subnet.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
link, err := netlink.LinkByIndex(route.LinkIndex)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return localNetworks, fmt.Errorf("cannot get link by index: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
localNet.InterfaceName = link.Attrs().Name
|
|
|
|
|
|
|
|
|
|
ip, err := r.assignedIP(localNet.InterfaceName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return localNetworks, fmt.Errorf("cannot get IP assigned to link: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
localNet.IP = ip
|
|
|
|
|
|
|
|
|
|
localNetworks = append(localNetworks, localNet)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(localNetworks) == 0 {
|
|
|
|
|
return localNetworks, fmt.Errorf("cannot find any local networks across %d routes", len(routes))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return localNetworks, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-24 18:05:11 -04:00
|
|
|
func (r *routing) assignedIP(interfaceName string) (ip net.IP, err error) {
|
|
|
|
|
iface, err := net.InterfaceByName(interfaceName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
addresses, err := iface.Addrs()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
for _, address := range addresses {
|
|
|
|
|
switch value := address.(type) {
|
|
|
|
|
case *net.IPAddr:
|
|
|
|
|
return value.IP, nil
|
|
|
|
|
case *net.IPNet:
|
|
|
|
|
return value.IP, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil, fmt.Errorf("IP address not found in addresses of interface %s", interfaceName)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-22 18:55:28 -04:00
|
|
|
func (r *routing) VPNDestinationIP() (ip net.IP, err error) {
|
|
|
|
|
routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
|
2020-04-12 08:55:13 -04:00
|
|
|
if err != nil {
|
2020-10-22 18:55:28 -04:00
|
|
|
return nil, fmt.Errorf("cannot find VPN destination IP: %w", err)
|
2020-04-12 08:55:13 -04:00
|
|
|
}
|
2020-10-22 18:55:28 -04:00
|
|
|
|
|
|
|
|
defaultLinkIndex := -1
|
|
|
|
|
for _, route := range routes {
|
|
|
|
|
if route.Dst == nil {
|
|
|
|
|
defaultLinkIndex = route.LinkIndex
|
|
|
|
|
break
|
2020-04-12 08:55:13 -04:00
|
|
|
}
|
|
|
|
|
}
|
2020-10-22 18:55:28 -04:00
|
|
|
if defaultLinkIndex == -1 {
|
|
|
|
|
return nil, fmt.Errorf("cannot find VPN destination IP: cannot find default link")
|
2020-04-12 08:55:13 -04:00
|
|
|
}
|
2020-10-22 18:55:28 -04:00
|
|
|
|
|
|
|
|
for _, route := range routes {
|
|
|
|
|
if route.LinkIndex == defaultLinkIndex &&
|
|
|
|
|
route.Dst != nil &&
|
2020-11-04 03:14:27 +00:00
|
|
|
!IPIsPrivate(route.Dst.IP) &&
|
2020-10-22 18:55:28 -04:00
|
|
|
bytes.Equal(route.Dst.Mask, net.IPMask{255, 255, 255, 255}) {
|
|
|
|
|
return route.Dst.IP, nil
|
2020-04-12 08:55:13 -04:00
|
|
|
}
|
|
|
|
|
}
|
2020-10-22 18:55:28 -04:00
|
|
|
return nil, fmt.Errorf("cannot find VPN destination IP address from ip routes")
|
2020-04-12 08:55:13 -04:00
|
|
|
}
|
|
|
|
|
|
2020-10-12 10:55:08 -04:00
|
|
|
func (r *routing) VPNLocalGatewayIP() (ip net.IP, err error) {
|
2020-10-22 18:55:28 -04:00
|
|
|
routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
|
2020-10-12 10:55:08 -04:00
|
|
|
if err != nil {
|
2020-10-22 18:55:28 -04:00
|
|
|
return nil, fmt.Errorf("cannot find VPN local gateway IP: %w", err)
|
2020-10-12 10:55:08 -04:00
|
|
|
}
|
2020-10-22 18:55:28 -04:00
|
|
|
for _, route := range routes {
|
|
|
|
|
link, err := netlink.LinkByIndex(route.LinkIndex)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("cannot find VPN local gateway IP: %w", err)
|
|
|
|
|
}
|
|
|
|
|
interfaceName := link.Attrs().Name
|
|
|
|
|
if interfaceName == string(constants.TUN) &&
|
|
|
|
|
route.Dst != nil &&
|
|
|
|
|
route.Dst.IP.Equal(net.IP{0, 0, 0, 0}) {
|
|
|
|
|
return route.Gw, nil
|
2020-10-12 10:55:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil, fmt.Errorf("cannot find VPN local gateway IP address from ip routes")
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-04 03:14:27 +00:00
|
|
|
func IPIsPrivate(ip net.IP) bool {
|
2020-04-12 08:55:13 -04:00
|
|
|
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
privateCIDRBlocks := [8]string{
|
|
|
|
|
"127.0.0.0/8", // localhost
|
|
|
|
|
"10.0.0.0/8", // 24-bit block
|
|
|
|
|
"172.16.0.0/12", // 20-bit block
|
|
|
|
|
"192.168.0.0/16", // 16-bit block
|
|
|
|
|
"169.254.0.0/16", // link local address
|
|
|
|
|
"::1/128", // localhost IPv6
|
|
|
|
|
"fc00::/7", // unique local address IPv6
|
|
|
|
|
"fe80::/10", // link local address IPv6
|
|
|
|
|
}
|
|
|
|
|
for i := range privateCIDRBlocks {
|
|
|
|
|
_, CIDR, _ := net.ParseCIDR(privateCIDRBlocks[i])
|
|
|
|
|
if CIDR.Contains(ip) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|