|
|
@@ -0,0 +1,139 @@
|
|
|
+package ovpm
|
|
|
+
|
|
|
+import (
|
|
|
+ "fmt"
|
|
|
+ "net"
|
|
|
+
|
|
|
+ "github.com/Sirupsen/logrus"
|
|
|
+ "github.com/coreos/go-iptables/iptables"
|
|
|
+)
|
|
|
+
|
|
|
+// routedInterface returns a network interface that can route IP
|
|
|
+// traffic and satisfies flags. It returns nil when an appropriate
|
|
|
+// network interface is not found. Network must be "ip", "ip4" or
|
|
|
+// "ip6".
|
|
|
+func routedInterface(network string, flags net.Flags) *net.Interface {
|
|
|
+ switch network {
|
|
|
+ case "ip", "ip4", "ip6":
|
|
|
+ default:
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ ift, err := net.Interfaces()
|
|
|
+ if err != nil {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ for _, ifi := range ift {
|
|
|
+ if ifi.Flags&flags != flags {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if _, ok := hasRoutableIP(network, &ifi); !ok {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ return &ifi
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func hasRoutableIP(network string, ifi *net.Interface) (net.IP, bool) {
|
|
|
+ ifat, err := ifi.Addrs()
|
|
|
+ if err != nil {
|
|
|
+ return nil, false
|
|
|
+ }
|
|
|
+ for _, ifa := range ifat {
|
|
|
+ switch ifa := ifa.(type) {
|
|
|
+ case *net.IPAddr:
|
|
|
+ if ip := routableIP(network, ifa.IP); ip != nil {
|
|
|
+ return ip, true
|
|
|
+ }
|
|
|
+ case *net.IPNet:
|
|
|
+ if ip := routableIP(network, ifa.IP); ip != nil {
|
|
|
+ return ip, true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil, false
|
|
|
+}
|
|
|
+
|
|
|
+func vpnInterface() *net.Interface {
|
|
|
+ mask := net.IPMask(net.ParseIP(_DefaultServerNetMask))
|
|
|
+ prefix := net.ParseIP(_DefaultServerNetwork)
|
|
|
+ netw := prefix.Mask(mask).To4()
|
|
|
+ netw[3] = byte(1) // Server is always gets xxx.xxx.xxx.1
|
|
|
+ ipnet := net.IPNet{IP: netw, Mask: mask}
|
|
|
+
|
|
|
+ ifs, err := net.Interfaces()
|
|
|
+ if err != nil {
|
|
|
+ logrus.Errorf("can not get system network interfaces: %v", err)
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, ifc := range ifs {
|
|
|
+ addrs, err := ifc.Addrs()
|
|
|
+ if err != nil {
|
|
|
+ logrus.Errorf("can not get interface addresses: %v", err)
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ for _, addr := range addrs {
|
|
|
+ //logrus.Debugf("addr: %s == %s", addr.String(), ipnet.String())
|
|
|
+ if addr.String() == ipnet.String() {
|
|
|
+ return &ifc
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func routableIP(network string, ip net.IP) net.IP {
|
|
|
+ if !ip.IsLoopback() && !ip.IsLinkLocalUnicast() && !ip.IsGlobalUnicast() {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ switch network {
|
|
|
+ case "ip4":
|
|
|
+ if ip := ip.To4(); ip != nil {
|
|
|
+ return ip
|
|
|
+ }
|
|
|
+ case "ip6":
|
|
|
+ if ip.IsLoopback() { // addressing scope of the loopback address depends on each implementation
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if ip := ip.To16(); ip != nil && ip.To4() == nil {
|
|
|
+ return ip
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ if ip := ip.To4(); ip != nil {
|
|
|
+ return ip
|
|
|
+ }
|
|
|
+ if ip := ip.To16(); ip != nil {
|
|
|
+ return ip
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// EnsureNatEnabled is an idempotent command that ensures nat is enabled for the vpn server.
|
|
|
+func EnsureNatEnabled() error {
|
|
|
+ rif := routedInterface("ip", net.FlagUp|net.FlagBroadcast)
|
|
|
+ if rif == nil {
|
|
|
+ return fmt.Errorf("can not get routable network interface")
|
|
|
+ }
|
|
|
+
|
|
|
+ vpnIfc := vpnInterface()
|
|
|
+ if vpnIfc == nil {
|
|
|
+ return fmt.Errorf("can not get vpn network interface on the system")
|
|
|
+ }
|
|
|
+
|
|
|
+ // Enable ip forwarding.
|
|
|
+ emitToFile("/proc/sys/net/ipv4/ip_forward", "1", 0)
|
|
|
+ ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("can not create new iptables object: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Append iptables nat rules.
|
|
|
+ ipt.AppendUnique("nat", "POSTROUTING", "-o", rif.Name, "-j", "MASQUERADE")
|
|
|
+ // TODO(cad): we should use the interface name that we get when we query the system
|
|
|
+ // with the vpn server's internal ip address, instead of default "tun0".
|
|
|
+ ipt.AppendUnique("filter", "FORWARD", "-i", rif.Name, "-o", vpnIfc.Name, "-m", "state", "--state", "RELATED, ESTABLISHED", "-j", "ACCEPT")
|
|
|
+ ipt.AppendUnique("filter", "FORWARD", "-i", vpnIfc.Name, "-o", rif.Name, "-j", "ACCEPT")
|
|
|
+ return nil
|
|
|
+}
|