Browse Source

feat(net): handle iptables upon association/dissociation

Mustafa Arici 8 years ago
parent
commit
3f128fa377
10 changed files with 530 additions and 105 deletions
  1. 47 18
      api/rpc.go
  2. 2 0
      cmd/ovpm/main.go
  3. 106 10
      cmd/ovpm/net.go
  4. 12 9
      cmd/ovpmd/main.go
  5. 1 1
      db.go
  6. 106 35
      net.go
  7. 179 31
      pb/network.pb.go
  8. 13 0
      pb/network.proto
  9. 4 0
      pb/user.pb.go
  10. 60 1
      vpn.go

+ 47 - 18
api/rpc.go

@@ -184,18 +184,14 @@ func (s *NetworkService) List(ctx context.Context, req *pb.NetworkListRequest) (
 	logrus.Debug("rpc call: network list")
 	var nt []*pb.Network
 
-	networks, err := ovpm.GetAllNetworks()
-	if err != nil {
-		logrus.Errorf("networks can not be fetched: %v", err)
-		os.Exit(1)
-		return nil, err
-	}
+	networks := ovpm.GetAllNetworks()
 	for _, network := range networks {
 		nt = append(nt, &pb.Network{
-			Name:      network.GetName(),
-			CIDR:      network.GetCIDR(),
-			Type:      network.GetType().String(),
-			CreatedAt: network.GetCreatedAt(),
+			Name:                network.GetName(),
+			CIDR:                network.GetCIDR(),
+			Type:                network.GetType().String(),
+			CreatedAt:           network.GetCreatedAt(),
+			AssociatedUsernames: network.GetAssociatedUsernames(),
 		})
 	}
 
@@ -210,10 +206,11 @@ func (s *NetworkService) Create(ctx context.Context, req *pb.NetworkCreateReques
 	}
 
 	n := pb.Network{
-		Name:      network.GetName(),
-		CIDR:      network.GetCIDR(),
-		Type:      network.GetType().String(),
-		CreatedAt: network.GetCreatedAt(),
+		Name:                network.GetName(),
+		CIDR:                network.GetCIDR(),
+		Type:                network.GetType().String(),
+		CreatedAt:           network.GetCreatedAt(),
+		AssociatedUsernames: network.GetAssociatedUsernames(),
 	}
 
 	return &pb.NetworkCreateResponse{Network: &n}, nil
@@ -232,10 +229,11 @@ func (s *NetworkService) Delete(ctx context.Context, req *pb.NetworkDeleteReques
 	}
 
 	n := pb.Network{
-		Name:      network.GetName(),
-		CIDR:      network.GetCIDR(),
-		Type:      network.GetType().String(),
-		CreatedAt: network.GetCreatedAt(),
+		Name:                network.GetName(),
+		CIDR:                network.GetCIDR(),
+		Type:                network.GetType().String(),
+		CreatedAt:           network.GetCreatedAt(),
+		AssociatedUsernames: network.GetAssociatedUsernames(),
 	}
 
 	return &pb.NetworkDeleteResponse{Network: &n}, nil
@@ -250,3 +248,34 @@ func (s *NetworkService) GetAllTypes(ctx context.Context, req *pb.NetworkGetAllT
 
 	return &pb.NetworkGetAllTypesResponse{Types: networkTypes}, nil
 }
+
+func (s *NetworkService) Associate(ctx context.Context, req *pb.NetworkAssociateRequest) (*pb.NetworkAssociateResponse, error) {
+	logrus.Debugf("rpc call: network associate")
+
+	network, err := ovpm.GetNetwork(req.Name)
+	if err != nil {
+		return nil, err
+	}
+	err = network.Associate(req.Username)
+	if err != nil {
+		return nil, err
+	}
+
+	return &pb.NetworkAssociateResponse{}, nil
+}
+
+func (s *NetworkService) Dissociate(ctx context.Context, req *pb.NetworkDissociateRequest) (*pb.NetworkDissociateResponse, error) {
+	logrus.Debugf("rpc call: network dissociate")
+
+	network, err := ovpm.GetNetwork(req.Name)
+	if err != nil {
+		return nil, err
+	}
+
+	err = network.Dissociate(req.Username)
+	if err != nil {
+		return nil, err
+	}
+
+	return &pb.NetworkDissociateResponse{}, nil
+}

+ 2 - 0
cmd/ovpm/main.go

@@ -61,6 +61,8 @@ func main() {
 				netListCommand,
 				netDefineCommand,
 				netUndefineCommand,
+				netAssociateCommand,
+				netDissociateCommand,
 			},
 		},
 	}

+ 106 - 10
cmd/ovpm/net.go

@@ -13,8 +13,9 @@ import (
 )
 
 var netDefineCommand = cli.Command{
-	Name:  "define",
-	Usage: "Define a network.",
+	Name:    "define",
+	Aliases: []string{"def", "d"},
+	Usage:   "Define a network.",
 	Flags: []cli.Flag{
 		cli.StringFlag{
 			Name:  "cidr, c",
@@ -66,8 +67,9 @@ var netDefineCommand = cli.Command{
 }
 
 var netListCommand = cli.Command{
-	Name:  "list",
-	Usage: "List network definitions.",
+	Name:    "list",
+	Aliases: []string{"lis", "l"},
+	Usage:   "List network definitions.",
 	Action: func(c *cli.Context) error {
 		action = "net:list"
 		conn := getConn(c.GlobalString("daemon-port"))
@@ -81,10 +83,21 @@ var netListCommand = cli.Command{
 			return err
 		}
 		table := tablewriter.NewWriter(os.Stdout)
-		table.SetHeader([]string{"#", "name", "cidr", "type", "users", "created at"})
+		table.SetHeader([]string{"#", "name", "cidr", "type", "assoc", "created at"})
 		//table.SetBorder(false)
 		for i, network := range resp.Networks {
-			data := []string{fmt.Sprintf("%v", i+1), network.Name, network.CIDR, network.Type, "-", network.CreatedAt}
+			var usernameList string
+			usernames := network.GetAssociatedUsernames()
+			count := len(usernames)
+			for i, uname := range usernames {
+				if i+1 == count {
+					usernameList = usernameList + fmt.Sprintf("%s", uname)
+				} else {
+					usernameList = usernameList + fmt.Sprintf("%s, ", uname)
+				}
+			}
+
+			data := []string{fmt.Sprintf("%v", i+1), network.Name, network.CIDR, network.Type, usernameList, network.CreatedAt}
 			table.Append(data)
 		}
 		table.Render()
@@ -94,17 +107,18 @@ var netListCommand = cli.Command{
 }
 
 var netUndefineCommand = cli.Command{
-	Name:  "undefine",
-	Usage: "Undefine an existing network.",
+	Name:    "undefine",
+	Aliases: []string{"undef", "u"},
+	Usage:   "Undefine an existing network.",
 	Flags: []cli.Flag{
 		cli.StringFlag{
-			Name:  "name, n",
+			Name:  "net, n",
 			Usage: "name of the network",
 		},
 	},
 	Action: func(c *cli.Context) error {
 		action = "net:delete"
-		name := c.String("name")
+		name := c.String("net")
 
 		if name == "" {
 			fmt.Println(cli.ShowSubcommandHelp(c))
@@ -126,3 +140,85 @@ var netUndefineCommand = cli.Command{
 		return nil
 	},
 }
+
+var netAssociateCommand = cli.Command{
+	Name:    "associate",
+	Aliases: []string{"assoc", "a"},
+	Usage:   "Associate a user with a network.",
+	Flags: []cli.Flag{
+		cli.StringFlag{
+			Name:  "net, n",
+			Usage: "name of the network",
+		},
+
+		cli.StringFlag{
+			Name:  "user, u",
+			Usage: "name of the user",
+		},
+	},
+	Action: func(c *cli.Context) error {
+		action = "net:associate"
+		netName := c.String("net")
+		userName := c.String("user")
+
+		if netName == "" || userName == "" {
+			fmt.Println(cli.ShowSubcommandHelp(c))
+			os.Exit(1)
+		}
+
+		conn := getConn(c.GlobalString("daemon-port"))
+		defer conn.Close()
+		netSvc := pb.NewNetworkServiceClient(conn)
+
+		_, err := netSvc.Associate(context.Background(), &pb.NetworkAssociateRequest{Name: netName, Username: userName})
+		if err != nil {
+			logrus.Errorf("networks can not be associated: %v", err)
+			os.Exit(1)
+			return err
+		}
+		logrus.Infof("network associated: user:%s <-> network:%s", userName, netName)
+
+		return nil
+	},
+}
+
+var netDissociateCommand = cli.Command{
+	Name:    "dissociate",
+	Aliases: []string{"dissoc", "d"},
+	Usage:   "Dissociate a user from a network.",
+	Flags: []cli.Flag{
+		cli.StringFlag{
+			Name:  "net, n",
+			Usage: "name of the network",
+		},
+
+		cli.StringFlag{
+			Name:  "user, u",
+			Usage: "name of the user",
+		},
+	},
+	Action: func(c *cli.Context) error {
+		action = "net:dissociate"
+		netName := c.String("net")
+		userName := c.String("user")
+
+		if netName == "" || userName == "" {
+			fmt.Println(cli.ShowSubcommandHelp(c))
+			os.Exit(1)
+		}
+
+		conn := getConn(c.GlobalString("daemon-port"))
+		defer conn.Close()
+		netSvc := pb.NewNetworkServiceClient(conn)
+
+		_, err := netSvc.Dissociate(context.Background(), &pb.NetworkDissociateRequest{Name: netName, Username: userName})
+		if err != nil {
+			logrus.Errorf("networks can not be dissociated: %v", err)
+			os.Exit(1)
+			return err
+		}
+		logrus.Infof("network dissociated: user:%s <-> network:%s", userName, netName)
+
+		return nil
+	},
+}

+ 12 - 9
cmd/ovpmd/main.go

@@ -83,16 +83,19 @@ func newServer(port string) *server {
 		fmt.Println(sig)
 		done <- true
 	}()
-
-	lis, err := net.Listen("tcp", fmt.Sprintf(":%s", port))
-	if err != nil {
-		logrus.Fatalf("could not listen to port %s: %v", port, err)
+	if !ovpm.Testing {
+		lis, err := net.Listen("tcp", fmt.Sprintf(":%s", port))
+		if err != nil {
+			logrus.Fatalf("could not listen to port %s: %v", port, err)
+		}
+		s := grpc.NewServer()
+		pb.RegisterUserServiceServer(s, &api.UserService{})
+		pb.RegisterVPNServiceServer(s, &api.VPNService{})
+		pb.RegisterNetworkServiceServer(s, &api.NetworkService{})
+		return &server{lis: lis, grpcServer: s, signal: sigs, done: done, port: port}
 	}
-	s := grpc.NewServer()
-	pb.RegisterUserServiceServer(s, &api.UserService{})
-	pb.RegisterVPNServiceServer(s, &api.VPNService{})
-	pb.RegisterNetworkServiceServer(s, &api.NetworkService{})
-	return &server{lis: lis, grpcServer: s, signal: sigs, done: done, port: port}
+	return &server{}
+
 }
 
 func (s *server) start() {

+ 1 - 1
db.go

@@ -24,9 +24,9 @@ func SetupDB(dialect string, args ...interface{}) {
 	}
 
 	db.AutoMigrate(&DBUser{})
-	db.AutoMigrate(&DBNetwork{})
 	db.AutoMigrate(&DBServer{})
 	db.AutoMigrate(&DBRevoked{})
+	db.AutoMigrate(&DBNetwork{})
 }
 
 // CeaseDB closes the database.

+ 106 - 35
net.go

@@ -3,6 +3,7 @@ package ovpm
 import (
 	"encoding/binary"
 	"fmt"
+	"log"
 	"net"
 
 	"time"
@@ -96,11 +97,11 @@ func GetNetwork(name string) (*DBNetwork, error) {
 }
 
 // GetAllNetworks returns all networks defined in the system.
-func GetAllNetworks() ([]*DBNetwork, error) {
+func GetAllNetworks() []*DBNetwork {
 	var networks []*DBNetwork
 	db.Preload("Users").Find(&networks)
 
-	return networks, nil
+	return networks
 }
 
 // CreateNewNetwork creates a new network definition in the system.
@@ -140,7 +141,8 @@ func CreateNewNetwork(name, cidr string, nettype NetworkType) (*DBNetwork, error
 	if db.NewRecord(&network) {
 		return nil, fmt.Errorf("can not create network in the db")
 	}
-
+	Emit()
+	logrus.Infof("network defined: %s (%s)", network.Name, network.CIDR)
 	return &network, nil
 
 }
@@ -152,8 +154,8 @@ func (n *DBNetwork) Delete() error {
 	}
 
 	db.Unscoped().Delete(n)
+	Emit()
 	logrus.Infof("network deleted: %s", n.Name)
-
 	return nil
 }
 
@@ -185,6 +187,7 @@ func (n *DBNetwork) Associate(username string) error {
 	if userAssoc.Error != nil {
 		return fmt.Errorf("association failed: %v", userAssoc.Error)
 	}
+	Emit()
 	logrus.Infof("user '%s' is associated with the network '%s'", user.GetUsername(), n.Name)
 	return nil
 }
@@ -218,6 +221,7 @@ func (n *DBNetwork) Dissociate(username string) error {
 	if userAssoc.Error != nil {
 		return fmt.Errorf("disassociation failed: %v", userAssoc.Error)
 	}
+	Emit()
 	logrus.Infof("user '%s' is dissociated with the network '%s'", user.GetUsername(), n.Name)
 	return nil
 }
@@ -247,10 +251,49 @@ func (n *DBNetwork) GetAssociatedUsers() []*DBUser {
 	return n.Users
 }
 
-// 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".
+// GetAssociatedUsernames returns network's associated user names.
+func (n *DBNetwork) GetAssociatedUsernames() []string {
+	var usernames []string
+
+	for _, user := range n.GetAssociatedUsers() {
+		usernames = append(usernames, user.Username)
+	}
+	return usernames
+}
+
+// interfaceOfIP returns a network interface that has the given IP.
+func interfaceOfIP(ipnet *net.IPNet) *net.Interface {
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return nil
+	}
+
+	for _, iface := range ifaces {
+		addrs, err := iface.Addrs()
+		if err != nil {
+			logrus.Error(err)
+			return nil
+		}
+		for _, addr := range addrs {
+			switch addr := addr.(type) {
+			case *net.IPAddr:
+				if ip := addr.IP; ip != nil {
+					if ipnet.Contains(ip) {
+						return &iface
+					}
+				}
+			case *net.IPNet:
+				if ip := addr.IP; ip != nil {
+					if ipnet.Contains(ip) {
+						return &iface
+					}
+				}
+			}
+		}
+	}
+	return nil
+}
+
 func routedInterface(network string, flags net.Flags) *net.Interface {
 	switch network {
 	case "ip", "ip4", "ip6":
@@ -273,6 +316,21 @@ func routedInterface(network string, flags net.Flags) *net.Interface {
 	return nil
 }
 
+func getOutboundInterface() *net.Interface {
+	conn, err := net.Dial("udp", "8.8.8.8:80")
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer conn.Close()
+
+	localAddr := conn.LocalAddr().(*net.UDPAddr)
+	ipnet := net.IPNet{
+		IP:   localAddr.IP.To4(),
+		Mask: localAddr.IP.To4().DefaultMask(),
+	}
+	return interfaceOfIP(&ipnet)
+}
+
 func hasRoutableIP(network string, ifi *net.Interface) (net.IP, bool) {
 	ifat, err := ifi.Addrs()
 	if err != nil {
@@ -294,32 +352,19 @@ func hasRoutableIP(network string, ifi *net.Interface) (net.IP, bool) {
 }
 
 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()
+	server, err := GetServerInstance()
 	if err != nil {
-		logrus.Errorf("can not get system network interfaces: %v", err)
+		logrus.Errorf("can't get server instance: %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
+	mask := net.IPMask(net.ParseIP(server.Mask))
+	prefix := net.ParseIP(server.Net)
+	netw := prefix.Mask(mask).To4()
+	netw[3] = byte(1) // Server is always gets xxx.xxx.xxx.1
+	ipnet := net.IPNet{IP: netw, Mask: mask}
+
+	return interfaceOfIP(&ipnet)
 }
 
 func routableIP(network string, ip net.IP) net.IP {
@@ -370,9 +415,16 @@ func ensureNatEnabled() {
 
 // enableNat is an idempotent command that ensures nat is enabled for the vpn server.
 func enableNat() error {
-	rif := routedInterface("ip", net.FlagUp|net.FlagBroadcast)
+	if Testing {
+		return nil
+	}
+	// rif := routedInterface("ip", net.FlagUp|net.FlagBroadcast)
+	// if rif == nil {
+	// 	return fmt.Errorf("can not get routable network interface")
+	// }
+	rif := getOutboundInterface()
 	if rif == nil {
-		return fmt.Errorf("can not get routable network interface")
+		return fmt.Errorf("can not get default gw interface")
 	}
 
 	vpnIfc := vpnInterface()
@@ -387,10 +439,29 @@ func enableNat() error {
 		return fmt.Errorf("can not create new iptables object: %v", err)
 	}
 
+	server, err := GetServerInstance()
+	if err != nil {
+		logrus.Errorf("can't get server instance: %v", err)
+		return nil
+	}
+
+	mask := net.IPMask(net.ParseIP(server.Mask))
+	prefix := net.ParseIP(server.Net)
+	netw := prefix.Mask(mask).To4()
+	netw[3] = byte(1) // Server is always gets xxx.xxx.xxx.1
+	ipnet := net.IPNet{IP: netw, Mask: mask}
+
 	// Append iptables nat rules.
-	ipt.AppendUnique("nat", "POSTROUTING", "-o", rif.Name, "-j", "MASQUERADE")
-	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")
+	if err := ipt.AppendUnique("nat", "POSTROUTING", "-s", ipnet.String(), "-o", rif.Name, "-j", "MASQUERADE"); err != nil {
+		return err
+	}
+
+	if err := ipt.AppendUnique("filter", "FORWARD", "-i", rif.Name, "-o", vpnIfc.Name, "-m", "state", "--state", "RELATED,ESTABLISHED", "-j", "ACCEPT"); err != nil {
+		return err
+	}
+	if err := ipt.AppendUnique("filter", "FORWARD", "-i", vpnIfc.Name, "-o", rif.Name, "-j", "ACCEPT"); err != nil {
+		return err
+	}
 	return nil
 
 }

+ 179 - 31
pb/network.pb.go

@@ -81,17 +81,66 @@ func (m *NetworkGetAllTypesRequest) String() string            { return proto.Co
 func (*NetworkGetAllTypesRequest) ProtoMessage()               {}
 func (*NetworkGetAllTypesRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{3} }
 
+type NetworkAssociateRequest struct {
+	Name     string `protobuf:"bytes,1,opt,name=Name" json:"Name,omitempty"`
+	Username string `protobuf:"bytes,2,opt,name=Username" json:"Username,omitempty"`
+}
+
+func (m *NetworkAssociateRequest) Reset()                    { *m = NetworkAssociateRequest{} }
+func (m *NetworkAssociateRequest) String() string            { return proto.CompactTextString(m) }
+func (*NetworkAssociateRequest) ProtoMessage()               {}
+func (*NetworkAssociateRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{4} }
+
+func (m *NetworkAssociateRequest) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *NetworkAssociateRequest) GetUsername() string {
+	if m != nil {
+		return m.Username
+	}
+	return ""
+}
+
+type NetworkDissociateRequest struct {
+	Name     string `protobuf:"bytes,1,opt,name=Name" json:"Name,omitempty"`
+	Username string `protobuf:"bytes,2,opt,name=Username" json:"Username,omitempty"`
+}
+
+func (m *NetworkDissociateRequest) Reset()                    { *m = NetworkDissociateRequest{} }
+func (m *NetworkDissociateRequest) String() string            { return proto.CompactTextString(m) }
+func (*NetworkDissociateRequest) ProtoMessage()               {}
+func (*NetworkDissociateRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{5} }
+
+func (m *NetworkDissociateRequest) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *NetworkDissociateRequest) GetUsername() string {
+	if m != nil {
+		return m.Username
+	}
+	return ""
+}
+
 type Network struct {
-	Name      string `protobuf:"bytes,1,opt,name=Name" json:"Name,omitempty"`
-	CIDR      string `protobuf:"bytes,2,opt,name=CIDR" json:"CIDR,omitempty"`
-	Type      string `protobuf:"bytes,3,opt,name=Type" json:"Type,omitempty"`
-	CreatedAt string `protobuf:"bytes,4,opt,name=CreatedAt" json:"CreatedAt,omitempty"`
+	Name                string   `protobuf:"bytes,1,opt,name=Name" json:"Name,omitempty"`
+	CIDR                string   `protobuf:"bytes,2,opt,name=CIDR" json:"CIDR,omitempty"`
+	Type                string   `protobuf:"bytes,3,opt,name=Type" json:"Type,omitempty"`
+	CreatedAt           string   `protobuf:"bytes,4,opt,name=CreatedAt" json:"CreatedAt,omitempty"`
+	AssociatedUsernames []string `protobuf:"bytes,5,rep,name=AssociatedUsernames" json:"AssociatedUsernames,omitempty"`
 }
 
 func (m *Network) Reset()                    { *m = Network{} }
 func (m *Network) String() string            { return proto.CompactTextString(m) }
 func (*Network) ProtoMessage()               {}
-func (*Network) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{4} }
+func (*Network) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{6} }
 
 func (m *Network) GetName() string {
 	if m != nil {
@@ -121,6 +170,13 @@ func (m *Network) GetCreatedAt() string {
 	return ""
 }
 
+func (m *Network) GetAssociatedUsernames() []string {
+	if m != nil {
+		return m.AssociatedUsernames
+	}
+	return nil
+}
+
 type NetworkCreateResponse struct {
 	Network *Network `protobuf:"bytes,1,opt,name=Network" json:"Network,omitempty"`
 }
@@ -128,7 +184,7 @@ type NetworkCreateResponse struct {
 func (m *NetworkCreateResponse) Reset()                    { *m = NetworkCreateResponse{} }
 func (m *NetworkCreateResponse) String() string            { return proto.CompactTextString(m) }
 func (*NetworkCreateResponse) ProtoMessage()               {}
-func (*NetworkCreateResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{5} }
+func (*NetworkCreateResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{7} }
 
 func (m *NetworkCreateResponse) GetNetwork() *Network {
 	if m != nil {
@@ -144,7 +200,7 @@ type NetworkListResponse struct {
 func (m *NetworkListResponse) Reset()                    { *m = NetworkListResponse{} }
 func (m *NetworkListResponse) String() string            { return proto.CompactTextString(m) }
 func (*NetworkListResponse) ProtoMessage()               {}
-func (*NetworkListResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{6} }
+func (*NetworkListResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{8} }
 
 func (m *NetworkListResponse) GetNetworks() []*Network {
 	if m != nil {
@@ -160,7 +216,7 @@ type NetworkDeleteResponse struct {
 func (m *NetworkDeleteResponse) Reset()                    { *m = NetworkDeleteResponse{} }
 func (m *NetworkDeleteResponse) String() string            { return proto.CompactTextString(m) }
 func (*NetworkDeleteResponse) ProtoMessage()               {}
-func (*NetworkDeleteResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{7} }
+func (*NetworkDeleteResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{9} }
 
 func (m *NetworkDeleteResponse) GetNetwork() *Network {
 	if m != nil {
@@ -176,7 +232,7 @@ type NetworkGetAllTypesResponse struct {
 func (m *NetworkGetAllTypesResponse) Reset()                    { *m = NetworkGetAllTypesResponse{} }
 func (m *NetworkGetAllTypesResponse) String() string            { return proto.CompactTextString(m) }
 func (*NetworkGetAllTypesResponse) ProtoMessage()               {}
-func (*NetworkGetAllTypesResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{8} }
+func (*NetworkGetAllTypesResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{10} }
 
 func (m *NetworkGetAllTypesResponse) GetTypes() []string {
 	if m != nil {
@@ -185,16 +241,36 @@ func (m *NetworkGetAllTypesResponse) GetTypes() []string {
 	return nil
 }
 
+type NetworkAssociateResponse struct {
+}
+
+func (m *NetworkAssociateResponse) Reset()                    { *m = NetworkAssociateResponse{} }
+func (m *NetworkAssociateResponse) String() string            { return proto.CompactTextString(m) }
+func (*NetworkAssociateResponse) ProtoMessage()               {}
+func (*NetworkAssociateResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{11} }
+
+type NetworkDissociateResponse struct {
+}
+
+func (m *NetworkDissociateResponse) Reset()                    { *m = NetworkDissociateResponse{} }
+func (m *NetworkDissociateResponse) String() string            { return proto.CompactTextString(m) }
+func (*NetworkDissociateResponse) ProtoMessage()               {}
+func (*NetworkDissociateResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{12} }
+
 func init() {
 	proto.RegisterType((*NetworkCreateRequest)(nil), "pb.NetworkCreateRequest")
 	proto.RegisterType((*NetworkListRequest)(nil), "pb.NetworkListRequest")
 	proto.RegisterType((*NetworkDeleteRequest)(nil), "pb.NetworkDeleteRequest")
 	proto.RegisterType((*NetworkGetAllTypesRequest)(nil), "pb.NetworkGetAllTypesRequest")
+	proto.RegisterType((*NetworkAssociateRequest)(nil), "pb.NetworkAssociateRequest")
+	proto.RegisterType((*NetworkDissociateRequest)(nil), "pb.NetworkDissociateRequest")
 	proto.RegisterType((*Network)(nil), "pb.Network")
 	proto.RegisterType((*NetworkCreateResponse)(nil), "pb.NetworkCreateResponse")
 	proto.RegisterType((*NetworkListResponse)(nil), "pb.NetworkListResponse")
 	proto.RegisterType((*NetworkDeleteResponse)(nil), "pb.NetworkDeleteResponse")
 	proto.RegisterType((*NetworkGetAllTypesResponse)(nil), "pb.NetworkGetAllTypesResponse")
+	proto.RegisterType((*NetworkAssociateResponse)(nil), "pb.NetworkAssociateResponse")
+	proto.RegisterType((*NetworkDissociateResponse)(nil), "pb.NetworkDissociateResponse")
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -212,6 +288,8 @@ type NetworkServiceClient interface {
 	List(ctx context.Context, in *NetworkListRequest, opts ...grpc.CallOption) (*NetworkListResponse, error)
 	Delete(ctx context.Context, in *NetworkDeleteRequest, opts ...grpc.CallOption) (*NetworkDeleteResponse, error)
 	GetAllTypes(ctx context.Context, in *NetworkGetAllTypesRequest, opts ...grpc.CallOption) (*NetworkGetAllTypesResponse, error)
+	Associate(ctx context.Context, in *NetworkAssociateRequest, opts ...grpc.CallOption) (*NetworkAssociateResponse, error)
+	Dissociate(ctx context.Context, in *NetworkDissociateRequest, opts ...grpc.CallOption) (*NetworkDissociateResponse, error)
 }
 
 type networkServiceClient struct {
@@ -258,6 +336,24 @@ func (c *networkServiceClient) GetAllTypes(ctx context.Context, in *NetworkGetAl
 	return out, nil
 }
 
+func (c *networkServiceClient) Associate(ctx context.Context, in *NetworkAssociateRequest, opts ...grpc.CallOption) (*NetworkAssociateResponse, error) {
+	out := new(NetworkAssociateResponse)
+	err := grpc.Invoke(ctx, "/pb.NetworkService/Associate", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *networkServiceClient) Dissociate(ctx context.Context, in *NetworkDissociateRequest, opts ...grpc.CallOption) (*NetworkDissociateResponse, error) {
+	out := new(NetworkDissociateResponse)
+	err := grpc.Invoke(ctx, "/pb.NetworkService/Dissociate", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 // Server API for NetworkService service
 
 type NetworkServiceServer interface {
@@ -265,6 +361,8 @@ type NetworkServiceServer interface {
 	List(context.Context, *NetworkListRequest) (*NetworkListResponse, error)
 	Delete(context.Context, *NetworkDeleteRequest) (*NetworkDeleteResponse, error)
 	GetAllTypes(context.Context, *NetworkGetAllTypesRequest) (*NetworkGetAllTypesResponse, error)
+	Associate(context.Context, *NetworkAssociateRequest) (*NetworkAssociateResponse, error)
+	Dissociate(context.Context, *NetworkDissociateRequest) (*NetworkDissociateResponse, error)
 }
 
 func RegisterNetworkServiceServer(s *grpc.Server, srv NetworkServiceServer) {
@@ -343,6 +441,42 @@ func _NetworkService_GetAllTypes_Handler(srv interface{}, ctx context.Context, d
 	return interceptor(ctx, in, info, handler)
 }
 
+func _NetworkService_Associate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(NetworkAssociateRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(NetworkServiceServer).Associate(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/pb.NetworkService/Associate",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(NetworkServiceServer).Associate(ctx, req.(*NetworkAssociateRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _NetworkService_Dissociate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(NetworkDissociateRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(NetworkServiceServer).Dissociate(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/pb.NetworkService/Dissociate",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(NetworkServiceServer).Dissociate(ctx, req.(*NetworkDissociateRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 var _NetworkService_serviceDesc = grpc.ServiceDesc{
 	ServiceName: "pb.NetworkService",
 	HandlerType: (*NetworkServiceServer)(nil),
@@ -363,6 +497,14 @@ var _NetworkService_serviceDesc = grpc.ServiceDesc{
 			MethodName: "GetAllTypes",
 			Handler:    _NetworkService_GetAllTypes_Handler,
 		},
+		{
+			MethodName: "Associate",
+			Handler:    _NetworkService_Associate_Handler,
+		},
+		{
+			MethodName: "Dissociate",
+			Handler:    _NetworkService_Dissociate_Handler,
+		},
 	},
 	Streams:  []grpc.StreamDesc{},
 	Metadata: "network.proto",
@@ -371,26 +513,32 @@ var _NetworkService_serviceDesc = grpc.ServiceDesc{
 func init() { proto.RegisterFile("network.proto", fileDescriptor2) }
 
 var fileDescriptor2 = []byte{
-	// 334 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x53, 0xcd, 0x4e, 0x32, 0x41,
-	0x10, 0xe4, 0xef, 0xe3, 0x93, 0x26, 0x7a, 0x68, 0x51, 0x87, 0xf5, 0x27, 0x64, 0x12, 0xa3, 0xf1,
-	0xc0, 0x01, 0x4f, 0x5e, 0x20, 0x04, 0x12, 0x63, 0x62, 0x38, 0x8c, 0xbe, 0x00, 0x60, 0x1f, 0x88,
-	0x2b, 0xbb, 0xee, 0x8c, 0x1a, 0x1f, 0xc6, 0x77, 0x35, 0xf3, 0xb3, 0xeb, 0x0c, 0xa0, 0x89, 0xf1,
-	0xd6, 0x5b, 0xd5, 0x5d, 0x35, 0x5d, 0xe9, 0x85, 0xed, 0x25, 0xa9, 0xb7, 0x24, 0x7b, 0xec, 0xa6,
-	0x59, 0xa2, 0x12, 0xac, 0xa4, 0x33, 0x2e, 0xa0, 0x35, 0xb1, 0xe0, 0x28, 0xa3, 0xa9, 0x22, 0x41,
-	0xcf, 0x2f, 0x24, 0x15, 0x22, 0xd4, 0x26, 0xd3, 0x27, 0x62, 0xe5, 0x4e, 0xf9, 0xbc, 0x21, 0x4c,
-	0xad, 0xb1, 0xd1, 0xcd, 0x58, 0xb0, 0x8a, 0xc5, 0x74, 0xad, 0xb1, 0xfb, 0xf7, 0x94, 0x58, 0xd5,
-	0x62, 0xba, 0xe6, 0x2d, 0x40, 0xa7, 0x79, 0xbb, 0x90, 0xca, 0x29, 0xf2, 0x8b, 0xc2, 0x69, 0x4c,
-	0x31, 0xfd, 0xe8, 0xc4, 0x0f, 0xa1, 0xed, 0x7a, 0xaf, 0x49, 0x0d, 0xe3, 0x58, 0xcb, 0xca, 0x5c,
-	0x68, 0x0e, 0xff, 0x1d, 0xf9, 0x97, 0x57, 0xe2, 0x11, 0x34, 0xec, 0xca, 0x0f, 0x43, 0xc5, 0x6a,
-	0x86, 0xf8, 0x02, 0x78, 0x1f, 0xf6, 0x56, 0x72, 0x91, 0x69, 0xb2, 0x94, 0x84, 0xa7, 0x85, 0xbb,
-	0x71, 0x6d, 0xf6, 0x9a, 0xdd, 0x74, 0xd6, 0x75, 0x90, 0xc8, 0x39, 0xde, 0x87, 0xdd, 0x20, 0x03,
-	0x37, 0x7d, 0x06, 0x5b, 0x0e, 0x96, 0xac, 0xdc, 0xa9, 0xae, 0x8e, 0x17, 0xa4, 0xe7, 0x9f, 0xa7,
-	0xf5, 0x3b, 0xff, 0x1e, 0x44, 0x9b, 0x12, 0x74, 0x22, 0x2d, 0xf8, 0x67, 0x00, 0xf3, 0x86, 0x86,
-	0xb0, 0x1f, 0xbd, 0x8f, 0x0a, 0xec, 0xb8, 0xa1, 0x3b, 0xca, 0x5e, 0x17, 0x73, 0xc2, 0x01, 0xd4,
-	0xed, 0xfe, 0xc8, 0x3c, 0x9b, 0xe0, 0x54, 0xa2, 0xf6, 0x06, 0xc6, 0xfa, 0xf0, 0x12, 0x5e, 0x41,
-	0x4d, 0x07, 0x80, 0xfb, 0x5e, 0x93, 0x77, 0x15, 0xd1, 0xc1, 0x1a, 0x5e, 0x8c, 0x0e, 0xa0, 0x6e,
-	0x77, 0x0f, 0xbc, 0x83, 0xe3, 0x09, 0xbc, 0xc3, 0xa0, 0x78, 0x09, 0x27, 0xd0, 0xf4, 0x96, 0xc7,
-	0x63, 0xaf, 0x77, 0xfd, 0xac, 0xa2, 0x93, 0xef, 0xe8, 0x5c, 0x6f, 0x56, 0x37, 0xbf, 0xcd, 0xe5,
-	0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0xeb, 0xb1, 0x53, 0x85, 0x47, 0x03, 0x00, 0x00,
+	// 429 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0xcd, 0x4e, 0xf2, 0x40,
+	0x14, 0xa5, 0x14, 0xf8, 0xe8, 0x25, 0x9f, 0x8b, 0x01, 0x65, 0x18, 0xc0, 0x90, 0x49, 0x8c, 0xc4,
+	0x05, 0x31, 0xb8, 0x72, 0x03, 0x21, 0x90, 0x28, 0x6a, 0x58, 0x54, 0x7d, 0x00, 0x7e, 0x66, 0xd1,
+	0x88, 0xb4, 0x76, 0xaa, 0xc6, 0x07, 0xf1, 0x4d, 0x7c, 0x40, 0xd3, 0xce, 0x74, 0x98, 0x42, 0xd1,
+	0x18, 0x76, 0xd3, 0x73, 0xee, 0x9c, 0x73, 0x39, 0x77, 0x2e, 0xf0, 0x7f, 0xc5, 0x82, 0x77, 0xd7,
+	0x7f, 0xea, 0x78, 0xbe, 0x1b, 0xb8, 0x28, 0xeb, 0xcd, 0xa8, 0x0d, 0x95, 0x89, 0x00, 0x87, 0x3e,
+	0x9b, 0x06, 0xcc, 0x66, 0x2f, 0xaf, 0x8c, 0x07, 0x08, 0x41, 0x6e, 0x32, 0x7d, 0x66, 0xd8, 0x68,
+	0x19, 0x6d, 0xcb, 0x8e, 0xce, 0x21, 0x36, 0x1c, 0x8f, 0x6c, 0x9c, 0x15, 0x58, 0x78, 0x0e, 0xb1,
+	0x87, 0x0f, 0x8f, 0x61, 0x53, 0x60, 0xe1, 0x99, 0x56, 0x00, 0x49, 0xcd, 0x3b, 0x87, 0x07, 0x52,
+	0x91, 0x9e, 0x29, 0xa7, 0x11, 0x5b, 0xb2, 0x1f, 0x9d, 0x68, 0x1d, 0x6a, 0xb2, 0xf6, 0x8a, 0x05,
+	0x83, 0xe5, 0x32, 0x94, 0xe5, 0xb1, 0xd0, 0x18, 0xaa, 0x92, 0x1c, 0x70, 0xee, 0xce, 0x9d, 0x5f,
+	0xba, 0x26, 0x50, 0x7c, 0xe4, 0xcc, 0x5f, 0x85, 0xb8, 0xe8, 0x5c, 0x7d, 0xd3, 0x1b, 0xc0, 0x71,
+	0x4f, 0xce, 0xbe, 0x5a, 0x9f, 0x06, 0xfc, 0x93, 0x62, 0xfb, 0xa4, 0x87, 0x1a, 0x60, 0x89, 0x51,
+	0x2c, 0x06, 0x01, 0xce, 0x45, 0xc4, 0x1a, 0x40, 0xe7, 0x50, 0x56, 0xbf, 0x7a, 0x11, 0x7b, 0x73,
+	0x9c, 0x6f, 0x99, 0x6d, 0xcb, 0x4e, 0xa3, 0x68, 0x0f, 0x0e, 0x37, 0x26, 0xcc, 0x3d, 0x77, 0xc5,
+	0x19, 0x3a, 0x51, 0xfd, 0x46, 0x7d, 0x96, 0xba, 0xa5, 0x8e, 0x37, 0xeb, 0x48, 0xc8, 0x8e, 0x39,
+	0xda, 0x83, 0x72, 0x62, 0x9a, 0xf2, 0xf6, 0x29, 0x14, 0x25, 0xcc, 0xb1, 0xd1, 0x32, 0x37, 0xaf,
+	0x2b, 0x52, 0xf3, 0x8f, 0xe7, 0xfe, 0x37, 0xff, 0x2e, 0x90, 0xb4, 0xb7, 0x20, 0x45, 0x2a, 0x90,
+	0x8f, 0x80, 0xa8, 0x07, 0xcb, 0x16, 0x1f, 0x94, 0xa8, 0xb9, 0x6a, 0x4f, 0x44, 0xdc, 0xd0, 0xde,
+	0x96, 0x3e, 0x73, 0x41, 0x76, 0xbf, 0x4c, 0x38, 0x90, 0xec, 0x3d, 0xf3, 0xdf, 0x9c, 0x39, 0x43,
+	0x7d, 0x28, 0x88, 0xe0, 0x10, 0xd6, 0xfa, 0x4b, 0x6c, 0x0b, 0xa9, 0xa5, 0x30, 0xd2, 0x2e, 0x83,
+	0x2e, 0x21, 0x17, 0x26, 0x87, 0x8e, 0xb4, 0x22, 0x6d, 0x31, 0x48, 0x75, 0x0b, 0x57, 0x57, 0xfb,
+	0x50, 0x10, 0xa1, 0x25, 0xbc, 0x13, 0xfb, 0x93, 0xf0, 0x4e, 0x26, 0x4c, 0x33, 0x68, 0x02, 0x25,
+	0x2d, 0x35, 0xd4, 0xd4, 0x6a, 0xb7, 0x37, 0x8b, 0x1c, 0xef, 0xa2, 0x95, 0xde, 0x35, 0x58, 0x2a,
+	0x51, 0x54, 0xd7, 0xca, 0x37, 0x57, 0x91, 0x34, 0xd2, 0x49, 0xa5, 0x74, 0x0b, 0xb0, 0xce, 0x1f,
+	0xe9, 0xd5, 0x5b, 0xab, 0x48, 0x9a, 0x3b, 0xd8, 0x58, 0x6c, 0x56, 0x88, 0xfe, 0xd0, 0x2e, 0xbe,
+	0x03, 0x00, 0x00, 0xff, 0xff, 0xd7, 0x4a, 0x6c, 0x70, 0xe1, 0x04, 0x00, 0x00,
 }

+ 13 - 0
pb/network.proto

@@ -12,17 +12,28 @@ message NetworkDeleteRequest {
   string Name = 1;
 }
 message NetworkGetAllTypesRequest {}
+message NetworkAssociateRequest {
+  string Name = 1;
+  string Username = 2;
+}
+message NetworkDissociateRequest {
+  string Name = 1;
+  string Username = 2;
+}
 service NetworkService {
   rpc Create (NetworkCreateRequest) returns (NetworkCreateResponse) {}
   rpc List (NetworkListRequest) returns (NetworkListResponse) {}
   rpc Delete (NetworkDeleteRequest) returns (NetworkDeleteResponse) {}
   rpc GetAllTypes(NetworkGetAllTypesRequest) returns (NetworkGetAllTypesResponse) {}
+  rpc Associate (NetworkAssociateRequest) returns (NetworkAssociateResponse) {}
+  rpc Dissociate (NetworkDissociateRequest) returns (NetworkDissociateResponse) {}
 }
 message Network {
   string Name = 1;
   string CIDR = 2;
   string Type = 3;
   string CreatedAt = 4;
+  repeated string AssociatedUsernames = 5;
 }
 message NetworkCreateResponse {
   Network Network = 1;
@@ -36,3 +47,5 @@ message NetworkDeleteResponse {
 message NetworkGetAllTypesResponse {
   repeated string Types = 1;
 }
+message NetworkAssociateResponse {}
+message NetworkDissociateResponse {}

+ 4 - 0
pb/user.pb.go

@@ -26,11 +26,15 @@ It has these top-level messages:
 	NetworkListRequest
 	NetworkDeleteRequest
 	NetworkGetAllTypesRequest
+	NetworkAssociateRequest
+	NetworkDissociateRequest
 	Network
 	NetworkCreateResponse
 	NetworkListResponse
 	NetworkDeleteResponse
 	NetworkGetAllTypesResponse
+	NetworkAssociateResponse
+	NetworkDissociateResponse
 */
 package pb
 

+ 60 - 1
vpn.go

@@ -7,6 +7,7 @@ import (
 	"bytes"
 	"fmt"
 	"math/big"
+	"net"
 	"os"
 	"os/exec"
 	"strings"
@@ -19,6 +20,7 @@ import (
 	"github.com/cad/ovpm/bindata"
 	"github.com/cad/ovpm/pki"
 	"github.com/cad/ovpm/supervisor"
+	"github.com/coreos/go-iptables/iptables"
 	"github.com/google/uuid"
 	"github.com/jinzhu/gorm"
 )
@@ -308,7 +310,7 @@ func Emit() error {
 	}
 
 	if err := emitIptables(); err != nil {
-		return fmt.Errorf("can not emit iptables conf: %s", err)
+		return fmt.Errorf("can not emit iptables: %s", err)
 	}
 
 	if err := emitCRL(); err != nil {
@@ -552,6 +554,63 @@ func emitDHParams() error {
 }
 
 func emitIptables() error {
+	if Testing {
+		return nil
+	}
+	ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
+	if err != nil {
+		return fmt.Errorf("can not create new iptables object: %v", err)
+	}
+
+	for _, network := range GetAllNetworks() {
+		associatedUsernames := network.GetAssociatedUsernames()
+		switch network.Type {
+		case SERVERNET:
+			users, err := GetAllUsers()
+			if err != nil {
+				return err
+			}
+			for _, user := range users {
+				var found bool
+				for _, auser := range associatedUsernames {
+					if user.Username == auser {
+						found = true
+						break
+					}
+				}
+
+				userIP, _, err := net.ParseCIDR(user.GetIPNet())
+				if err != nil {
+					return err
+				}
+				_, networkIPNet, err := net.ParseCIDR(network.CIDR)
+				if err != nil {
+					return err
+				}
+
+				// get destination network's iface
+				iface := interfaceOfIP(networkIPNet)
+				if iface == nil {
+					return fmt.Errorf("cant find interface for %s", networkIPNet.String())
+				}
+				logrus.Debugf("emitIptables: net(%s) iface name '%s'", network.Name, iface.Name)
+				logrus.Debugf("emitIptables: user '%s' ip addr '%s'", user.GetUsername(), userIP.String())
+				// enable nat for the user to the destination network n
+				if found {
+					err = ipt.AppendUnique("nat", "POSTROUTING", "-s", userIP.String(), "-o", iface.Name, "-j", "MASQUERADE")
+					if err != nil {
+						logrus.Error(err)
+						return err
+					}
+				} else {
+					err = ipt.Delete("nat", "POSTROUTING", "-s", userIP.String(), "-o", iface.Name, "-j", "MASQUERADE")
+					if err != nil {
+						logrus.Debug(err)
+					}
+				}
+			}
+		}
+	}
 	return nil
 }