Forráskód Böngészése

feat(network): implement network service

Mustafa Arici 8 éve
szülő
commit
c253193ef2
8 módosított fájl, 692 hozzáadás és 12 törlés
  1. 60 0
      api/rpc.go
  2. 1 1
      bindata/bindata.go
  3. 108 0
      net.go
  4. 163 0
      net_test.go
  5. 318 0
      pb/network.pb.go
  6. 33 0
      pb/network.proto
  7. 8 0
      pb/user.pb.go
  8. 1 11
      vpn.go

+ 60 - 0
api/rpc.go

@@ -177,3 +177,63 @@ func (s *VPNService) Init(ctx context.Context, req *pb.VPNInitRequest) (*pb.VPNI
 	}
 	return &pb.VPNInitResponse{}, nil
 }
+
+type NetworkService struct{}
+
+func (s *NetworkService) List(ctx context.Context, req *pb.NetworkListRequest) (*pb.NetworkListResponse, error) {
+	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
+	}
+	for _, network := range networks {
+		nt = append(nt, &pb.Network{
+			Name:      network.GetName(),
+			CIDR:      network.GetCIDR(),
+			CreatedAt: network.GetCreatedAt(),
+		})
+	}
+
+	return &pb.NetworkListResponse{Networks: nt}, nil
+}
+
+func (s *NetworkService) Create(ctx context.Context, req *pb.NetworkCreateRequest) (*pb.NetworkCreateResponse, error) {
+	logrus.Debugf("rpc call: network create: %s", req.Name)
+	network, err := ovpm.CreateNewNetwork(req.Name, req.CIDR)
+	if err != nil {
+		return nil, err
+	}
+
+	n := pb.Network{
+		Name:      network.GetName(),
+		CIDR:      network.GetCIDR(),
+		CreatedAt: network.GetCreatedAt(),
+	}
+
+	return &pb.NetworkCreateResponse{Network: &n}, nil
+}
+
+func (s *NetworkService) Delete(ctx context.Context, req *pb.NetworkDeleteRequest) (*pb.NetworkDeleteResponse, error) {
+	logrus.Debugf("rpc call: network delete: %s", req.Name)
+	network, err := ovpm.GetNetwork(req.Name)
+	if err != nil {
+		return nil, err
+	}
+
+	err = network.Delete()
+	if err != nil {
+		return nil, err
+	}
+
+	n := pb.Network{
+		Name:      network.GetName(),
+		CIDR:      network.GetCIDR(),
+		CreatedAt: network.GetCreatedAt(),
+	}
+
+	return &pb.NetworkDeleteResponse{Network: &n}, nil
+}

+ 1 - 1
bindata/bindata.go

@@ -107,7 +107,7 @@ func templateClientOvpnTmpl() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindataFileInfo{name: "template/client.ovpn.tmpl", size: 365, mode: os.FileMode(420), modTime: time.Unix(1503002198, 0)}
+	info := bindataFileInfo{name: "template/client.ovpn.tmpl", size: 365, mode: os.FileMode(420), modTime: time.Unix(1503411871, 0)}
 	a := &asset{bytes: bytes, info: info}
 	return a, nil
 }

+ 108 - 0
net.go

@@ -8,9 +8,115 @@ import (
 	"time"
 
 	"github.com/Sirupsen/logrus"
+	"github.com/asaskevich/govalidator"
 	"github.com/coreos/go-iptables/iptables"
+	"github.com/jinzhu/gorm"
 )
 
+// DBNetwork is database model for external networks on the VPN server.
+type DBNetwork struct {
+	gorm.Model
+	ServerID uint
+	Server   DBServer
+
+	Name string
+	CIDR string
+}
+
+// GetNetwork returns a network specified by its name.
+func GetNetwork(name string) (*DBNetwork, error) {
+	if !IsInitialized() {
+		return nil, fmt.Errorf("you first need to create server")
+	}
+	// Validate user input.
+	if govalidator.IsNull(name) {
+		return nil, fmt.Errorf("validation error: %s can not be null", name)
+	}
+	if !govalidator.IsAlphanumeric(name) {
+		return nil, fmt.Errorf("validation error: `%s` can only contain letters and numbers", name)
+	}
+
+	var network DBNetwork
+	db.Where(&DBNetwork{Name: name}).First(&network)
+
+	if db.NewRecord(&network) {
+		return nil, fmt.Errorf("network not found %s", name)
+	}
+
+	return &network, nil
+}
+
+// GetAllNetworks returns all networks defined in the system.
+func GetAllNetworks() ([]*DBNetwork, error) {
+	var networks []*DBNetwork
+	db.Find(&networks)
+
+	return networks, nil
+}
+
+// CreateNewNetwork creates a new network definition in the system.
+func CreateNewNetwork(name, cidr string) (*DBNetwork, error) {
+	if !IsInitialized() {
+		return nil, fmt.Errorf("you first need to create server")
+	}
+	// Validate user input.
+	if govalidator.IsNull(name) {
+		return nil, fmt.Errorf("validation error: %s can not be null", name)
+	}
+	if !govalidator.IsAlphanumeric(name) {
+		return nil, fmt.Errorf("validation error: `%s` can only contain letters and numbers", name)
+	}
+
+	if !govalidator.IsCIDR(cidr) {
+		return nil, fmt.Errorf("validation error: `%s` must be a network in the CIDR form", name)
+	}
+
+	_, ipnet, err := net.ParseCIDR(cidr)
+	if err != nil {
+		return nil, fmt.Errorf("can not parse CIDR %s: %v", cidr, err)
+	}
+
+	network := DBNetwork{
+		Name: name,
+		CIDR: ipnet.String(),
+	}
+	db.Save(&network)
+
+	if db.NewRecord(&network) {
+		return nil, fmt.Errorf("can not create network in the db")
+	}
+
+	return &network, nil
+
+}
+
+// Delete deletes a network definition in the system.
+func (n *DBNetwork) Delete() error {
+	if !IsInitialized() {
+		return fmt.Errorf("you first need to create server")
+	}
+
+	db.Unscoped().Delete(n)
+	logrus.Infof("network deleted: %s", n.Name)
+
+	return nil
+}
+
+// GetName returns network's name.
+func (n *DBNetwork) GetName() string {
+	return n.Name
+}
+
+// GetCIDR returns network's CIDR.
+func (n *DBNetwork) GetCIDR() string {
+	return n.Name
+}
+
+// GetCreatedAt returns network's name.
+func (n *DBNetwork) GetCreatedAt() string {
+	return n.CreatedAt.Format(time.UnixDate)
+}
+
 // 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
@@ -161,12 +267,14 @@ func enableNat() error {
 
 }
 
+// HostID2IP converts a host id (32-bit unsigned integer) to an IP address.
 func HostID2IP(hostid uint32) net.IP {
 	ip := make([]byte, 4)
 	binary.BigEndian.PutUint32(ip, hostid)
 	return net.IP(ip)
 }
 
+//IP2HostID converts an IP address to a host id (32-bit unsigned integer).
 func IP2HostID(ip net.IP) uint32 {
 	hostid := binary.BigEndian.Uint32(ip)
 	return hostid

+ 163 - 0
net_test.go

@@ -0,0 +1,163 @@
+package ovpm
+
+import "testing"
+
+func TestVPNCreateNewNetwork(t *testing.T) {
+	// Initialize:
+	setupTestCase()
+	SetupDB("sqlite3", ":memory:")
+	defer CeaseDB()
+	Init("localhost", "")
+
+	// Prepare:
+	// Test:
+	netName := "testnet"
+	cidrStr := "192.168.1.0/24"
+
+	n, err := CreateNewNetwork(netName, cidrStr)
+	if err != nil {
+		t.Fatalf("unexpected error when creating a new network: %v", err)
+	}
+
+	if n.Name != netName {
+		t.Fatalf("network Name is expected to be '%s' but it's '%s' instead", netName, n.Name)
+	}
+
+	if n.CIDR != cidrStr {
+		t.Fatalf("network CIDR is expected to be '%s' but it's '%s' instead", cidrStr, n.CIDR)
+	}
+
+	var network DBNetwork
+	db.First(&network)
+
+	if db.NewRecord(&network) {
+		t.Fatalf("network is not created in the database.")
+	}
+
+	if network.Name != netName {
+		t.Fatalf("network Name is expected to be '%s' but it's '%s' instead", netName, network.Name)
+	}
+
+	if network.CIDR != cidrStr {
+		t.Fatalf("network CIDR is expected to be '%s' but it's '%s' instead", cidrStr, network.CIDR)
+	}
+
+}
+
+func TestVPNDeleteNetwork(t *testing.T) {
+	// Initialize:
+	setupTestCase()
+	SetupDB("sqlite3", ":memory:")
+	defer CeaseDB()
+	Init("localhost", "")
+
+	// Prepare:
+	// Test:
+	netName := "testnet"
+	cidrStr := "192.168.1.0/24"
+
+	n, err := CreateNewNetwork(netName, cidrStr)
+	if err != nil {
+		t.Fatalf("unexpected error when creating a new network: %v", err)
+	}
+
+	var network DBNetwork
+	db.First(&network)
+
+	if db.NewRecord(&network) {
+		t.Fatalf("network is not created in the database.")
+	}
+
+	err = n.Delete()
+	if err != nil {
+		t.Fatalf("can't delete network: %v", err)
+	}
+
+	// Empty the existing network object.
+	network = DBNetwork{}
+	db.First(&network)
+	if !db.NewRecord(&network) {
+		t.Fatalf("network is not deleted from the database. %+v", network)
+	}
+}
+
+func TestVPNGetNetwork(t *testing.T) {
+	// Initialize:
+	setupTestCase()
+	SetupDB("sqlite3", ":memory:")
+	defer CeaseDB()
+	Init("localhost", "")
+
+	// Prepare:
+	// Test:
+	netName := "testnet"
+	cidrStr := "192.168.1.0/24"
+
+	_, err := CreateNewNetwork(netName, cidrStr)
+	if err != nil {
+		t.Fatalf("unexpected error when creating a new network: %v", err)
+	}
+
+	var network DBNetwork
+	db.First(&network)
+
+	if db.NewRecord(&network) {
+		t.Fatalf("network is not created in the database.")
+	}
+
+	n, err := GetNetwork(netName)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+
+	if db.NewRecord(&n) {
+		t.Fatalf("network is not correctly returned from db.")
+	}
+}
+
+func TestVPNGetAllNetworks(t *testing.T) {
+	// Initialize:
+	setupTestCase()
+	SetupDB("sqlite3", ":memory:")
+	defer CeaseDB()
+	Init("localhost", "")
+
+	// Prepare:
+	// Test:
+	var getallnettests = []struct {
+		name    string
+		cidr    string
+		passing bool
+	}{
+		{"testnet1", "192.168.1.0/24", true},
+		{"testnet2", "10.10.0.0/16", true},
+		{"testnet3", "asdkfjadflsa", false},
+	}
+	for _, tt := range getallnettests {
+		_, err := CreateNewNetwork(tt.name, tt.cidr)
+		if (err == nil) != tt.passing {
+			t.Fatalf("unexpected error when creating a new network: %v", err)
+		}
+	}
+
+	for _, tt := range getallnettests {
+		n, err := GetNetwork(tt.name)
+		if (err == nil) != tt.passing {
+			t.Fatalf("network's presence is expected to be '%t' but it's '%t' instead", tt.passing, !tt.passing)
+		}
+
+		if tt.passing {
+			if n.Name != tt.name {
+				t.Fatalf("network Name is expected to be '%s' but it's '%s'", tt.name, n.Name)
+			}
+			if n.CIDR != tt.cidr {
+				t.Fatalf("network CIDR is expected to be '%s' but it's '%s'", tt.cidr, n.CIDR)
+			}
+		}
+	}
+}
+
+func init() {
+	// Init
+	Testing = true
+}

+ 318 - 0
pb/network.pb.go

@@ -0,0 +1,318 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: network.proto
+
+package pb
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+import (
+	context "golang.org/x/net/context"
+	grpc "google.golang.org/grpc"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+type NetworkCreateRequest struct {
+	Name string `protobuf:"bytes,1,opt,name=Name" json:"Name,omitempty"`
+	CIDR string `protobuf:"bytes,2,opt,name=CIDR" json:"CIDR,omitempty"`
+}
+
+func (m *NetworkCreateRequest) Reset()                    { *m = NetworkCreateRequest{} }
+func (m *NetworkCreateRequest) String() string            { return proto.CompactTextString(m) }
+func (*NetworkCreateRequest) ProtoMessage()               {}
+func (*NetworkCreateRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} }
+
+func (m *NetworkCreateRequest) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *NetworkCreateRequest) GetCIDR() string {
+	if m != nil {
+		return m.CIDR
+	}
+	return ""
+}
+
+type NetworkListRequest struct {
+}
+
+func (m *NetworkListRequest) Reset()                    { *m = NetworkListRequest{} }
+func (m *NetworkListRequest) String() string            { return proto.CompactTextString(m) }
+func (*NetworkListRequest) ProtoMessage()               {}
+func (*NetworkListRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{1} }
+
+type NetworkDeleteRequest struct {
+	Name string `protobuf:"bytes,1,opt,name=Name" json:"Name,omitempty"`
+}
+
+func (m *NetworkDeleteRequest) Reset()                    { *m = NetworkDeleteRequest{} }
+func (m *NetworkDeleteRequest) String() string            { return proto.CompactTextString(m) }
+func (*NetworkDeleteRequest) ProtoMessage()               {}
+func (*NetworkDeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{2} }
+
+func (m *NetworkDeleteRequest) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	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"`
+	CreatedAt string `protobuf:"bytes,3,opt,name=CreatedAt" json:"CreatedAt,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{3} }
+
+func (m *Network) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *Network) GetCIDR() string {
+	if m != nil {
+		return m.CIDR
+	}
+	return ""
+}
+
+func (m *Network) GetCreatedAt() string {
+	if m != nil {
+		return m.CreatedAt
+	}
+	return ""
+}
+
+type NetworkCreateResponse struct {
+	Network *Network `protobuf:"bytes,1,opt,name=Network" json:"Network,omitempty"`
+}
+
+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{4} }
+
+func (m *NetworkCreateResponse) GetNetwork() *Network {
+	if m != nil {
+		return m.Network
+	}
+	return nil
+}
+
+type NetworkListResponse struct {
+	Networks []*Network `protobuf:"bytes,1,rep,name=Networks" json:"Networks,omitempty"`
+}
+
+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{5} }
+
+func (m *NetworkListResponse) GetNetworks() []*Network {
+	if m != nil {
+		return m.Networks
+	}
+	return nil
+}
+
+type NetworkDeleteResponse struct {
+	Network *Network `protobuf:"bytes,1,opt,name=Network" json:"Network,omitempty"`
+}
+
+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{6} }
+
+func (m *NetworkDeleteResponse) GetNetwork() *Network {
+	if m != nil {
+		return m.Network
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*NetworkCreateRequest)(nil), "pb.NetworkCreateRequest")
+	proto.RegisterType((*NetworkListRequest)(nil), "pb.NetworkListRequest")
+	proto.RegisterType((*NetworkDeleteRequest)(nil), "pb.NetworkDeleteRequest")
+	proto.RegisterType((*Network)(nil), "pb.Network")
+	proto.RegisterType((*NetworkCreateResponse)(nil), "pb.NetworkCreateResponse")
+	proto.RegisterType((*NetworkListResponse)(nil), "pb.NetworkListResponse")
+	proto.RegisterType((*NetworkDeleteResponse)(nil), "pb.NetworkDeleteResponse")
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// Client API for NetworkService service
+
+type NetworkServiceClient interface {
+	Create(ctx context.Context, in *NetworkCreateRequest, opts ...grpc.CallOption) (*NetworkCreateResponse, error)
+	List(ctx context.Context, in *NetworkListRequest, opts ...grpc.CallOption) (*NetworkListResponse, error)
+	Delete(ctx context.Context, in *NetworkDeleteRequest, opts ...grpc.CallOption) (*NetworkDeleteResponse, error)
+}
+
+type networkServiceClient struct {
+	cc *grpc.ClientConn
+}
+
+func NewNetworkServiceClient(cc *grpc.ClientConn) NetworkServiceClient {
+	return &networkServiceClient{cc}
+}
+
+func (c *networkServiceClient) Create(ctx context.Context, in *NetworkCreateRequest, opts ...grpc.CallOption) (*NetworkCreateResponse, error) {
+	out := new(NetworkCreateResponse)
+	err := grpc.Invoke(ctx, "/pb.NetworkService/Create", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *networkServiceClient) List(ctx context.Context, in *NetworkListRequest, opts ...grpc.CallOption) (*NetworkListResponse, error) {
+	out := new(NetworkListResponse)
+	err := grpc.Invoke(ctx, "/pb.NetworkService/List", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *networkServiceClient) Delete(ctx context.Context, in *NetworkDeleteRequest, opts ...grpc.CallOption) (*NetworkDeleteResponse, error) {
+	out := new(NetworkDeleteResponse)
+	err := grpc.Invoke(ctx, "/pb.NetworkService/Delete", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// Server API for NetworkService service
+
+type NetworkServiceServer interface {
+	Create(context.Context, *NetworkCreateRequest) (*NetworkCreateResponse, error)
+	List(context.Context, *NetworkListRequest) (*NetworkListResponse, error)
+	Delete(context.Context, *NetworkDeleteRequest) (*NetworkDeleteResponse, error)
+}
+
+func RegisterNetworkServiceServer(s *grpc.Server, srv NetworkServiceServer) {
+	s.RegisterService(&_NetworkService_serviceDesc, srv)
+}
+
+func _NetworkService_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(NetworkCreateRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(NetworkServiceServer).Create(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/pb.NetworkService/Create",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(NetworkServiceServer).Create(ctx, req.(*NetworkCreateRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _NetworkService_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(NetworkListRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(NetworkServiceServer).List(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/pb.NetworkService/List",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(NetworkServiceServer).List(ctx, req.(*NetworkListRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _NetworkService_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(NetworkDeleteRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(NetworkServiceServer).Delete(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/pb.NetworkService/Delete",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(NetworkServiceServer).Delete(ctx, req.(*NetworkDeleteRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+var _NetworkService_serviceDesc = grpc.ServiceDesc{
+	ServiceName: "pb.NetworkService",
+	HandlerType: (*NetworkServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "Create",
+			Handler:    _NetworkService_Create_Handler,
+		},
+		{
+			MethodName: "List",
+			Handler:    _NetworkService_List_Handler,
+		},
+		{
+			MethodName: "Delete",
+			Handler:    _NetworkService_Delete_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "network.proto",
+}
+
+func init() { proto.RegisterFile("network.proto", fileDescriptor2) }
+
+var fileDescriptor2 = []byte{
+	// 273 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcd, 0x4b, 0x2d, 0x29,
+	0xcf, 0x2f, 0xca, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2a, 0x48, 0x52, 0xb2, 0xe3,
+	0x12, 0xf1, 0x83, 0x08, 0x3a, 0x17, 0xa5, 0x26, 0x96, 0xa4, 0x06, 0xa5, 0x16, 0x96, 0xa6, 0x16,
+	0x97, 0x08, 0x09, 0x71, 0xb1, 0xf8, 0x25, 0xe6, 0xa6, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06,
+	0x81, 0xd9, 0x20, 0x31, 0x67, 0x4f, 0x97, 0x20, 0x09, 0x26, 0x88, 0x18, 0x88, 0xad, 0x24, 0xc2,
+	0x25, 0x04, 0xd5, 0xef, 0x93, 0x59, 0x5c, 0x02, 0xd5, 0xad, 0xa4, 0x05, 0x37, 0xd5, 0x25, 0x35,
+	0x27, 0x15, 0xaf, 0xa9, 0x4a, 0xfe, 0x5c, 0xec, 0x50, 0xb5, 0xc4, 0x5a, 0x2a, 0x24, 0xc3, 0xc5,
+	0x09, 0x71, 0x6d, 0x8a, 0x63, 0x89, 0x04, 0x33, 0x58, 0x02, 0x21, 0xa0, 0x64, 0xc7, 0x25, 0x8a,
+	0xe6, 0xa5, 0xe2, 0x82, 0xfc, 0xbc, 0xe2, 0x54, 0x21, 0x55, 0xb8, 0x4d, 0x60, 0x1b, 0xb8, 0x8d,
+	0xb8, 0xf5, 0x0a, 0x92, 0xf4, 0xa0, 0x42, 0x41, 0x30, 0x39, 0x25, 0x3b, 0x2e, 0x61, 0x14, 0x2f,
+	0x41, 0x75, 0xab, 0x73, 0x71, 0x40, 0x85, 0x8b, 0x25, 0x18, 0x15, 0x98, 0xd1, 0xb5, 0xc3, 0x25,
+	0x91, 0xec, 0x87, 0x79, 0x9e, 0x24, 0xfb, 0x8d, 0xce, 0x32, 0x72, 0xf1, 0x41, 0xd9, 0xc1, 0xa9,
+	0x45, 0x65, 0x99, 0xc9, 0xa9, 0x42, 0xf6, 0x5c, 0x6c, 0x10, 0xbf, 0x08, 0x49, 0x20, 0x69, 0x41,
+	0x89, 0x31, 0x29, 0x49, 0x2c, 0x32, 0x10, 0x8b, 0x95, 0x18, 0x84, 0x2c, 0xb9, 0x58, 0x40, 0x9e,
+	0x11, 0x12, 0x43, 0x52, 0x84, 0x14, 0x61, 0x52, 0xe2, 0x18, 0xe2, 0x70, 0xad, 0xf6, 0x5c, 0x6c,
+	0x10, 0x7f, 0xa0, 0xd8, 0x8d, 0x12, 0xaf, 0x28, 0x76, 0xa3, 0x7a, 0x5a, 0x89, 0x21, 0x89, 0x0d,
+	0x9c, 0xda, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x5e, 0xe8, 0x7e, 0x02, 0x00,
+	0x00,
+}

+ 33 - 0
pb/network.proto

@@ -0,0 +1,33 @@
+syntax = "proto3";
+
+package pb;
+
+message NetworkCreateRequest {
+  string Name = 1;
+  string CIDR = 2;
+}
+message NetworkListRequest {}
+message NetworkDeleteRequest {
+  string Name = 1;
+}
+
+service NetworkService {
+  rpc Create (NetworkCreateRequest) returns (NetworkCreateResponse) {}
+  rpc List (NetworkListRequest) returns (NetworkListResponse) {}
+  rpc Delete (NetworkDeleteRequest) returns (NetworkDeleteResponse) {}
+}
+message Network {
+  string Name = 1;
+  string CIDR = 2;
+  string CreatedAt = 3;
+}
+message NetworkCreateResponse {
+  Network Network = 1;
+}
+
+message NetworkListResponse {
+  repeated Network Networks = 1;
+}
+message NetworkDeleteResponse {
+  Network Network = 1;
+}

+ 8 - 0
pb/user.pb.go

@@ -7,6 +7,7 @@ Package pb is a generated protocol buffer package.
 It is generated from these files:
 	user.proto
 	vpn.proto
+	network.proto
 
 It has these top-level messages:
 	UserListRequest
@@ -21,6 +22,13 @@ It has these top-level messages:
 	VPNInitRequest
 	VPNStatusResponse
 	VPNInitResponse
+	NetworkCreateRequest
+	NetworkListRequest
+	NetworkDeleteRequest
+	Network
+	NetworkCreateResponse
+	NetworkListResponse
+	NetworkDeleteResponse
 */
 package pb
 

+ 1 - 11
vpn.go

@@ -1,5 +1,5 @@
 //go:generate go-bindata -pkg bindata -o bindata/bindata.go template/
-//go:generate protoc -I pb/ pb/user.proto pb/vpn.proto --go_out=plugins=grpc:pb
+//go:generate protoc -I pb/ pb/user.proto pb/vpn.proto pb/network.proto --go_out=plugins=grpc:pb
 
 package ovpm
 
@@ -23,16 +23,6 @@ import (
 	"github.com/jinzhu/gorm"
 )
 
-// DBNetwork is database model for external networks on the VPN server.
-type DBNetwork struct {
-	gorm.Model
-	ServerID uint
-	Server   DBServer
-
-	Name        string
-	NetworkCIDR string
-}
-
 // DBServer is database model for storing VPN server related stuff.
 type DBServer struct {
 	gorm.Model