Browse Source

auto update script executed

Toby Chui 1 năm trước cách đây
mục cha
commit
cc5b303cef

+ 4 - 0
api.go

@@ -79,9 +79,13 @@ func initAPIs() {
 	authRouter.HandleFunc("/api/utm/list", HandleUptimeMonitorListing)
 
 	//Global Area Network APIs
+	authRouter.HandleFunc("/api/gan/network/info", ganManager.HandleGetNodeID)
 	authRouter.HandleFunc("/api/gan/network/add", ganManager.HandleAddNetwork)
 	authRouter.HandleFunc("/api/gan/network/remove", ganManager.HandleRemoveNetwork)
 	authRouter.HandleFunc("/api/gan/network/list", ganManager.HandleListNetwork)
+	authRouter.HandleFunc("/api/gan/network/name", ganManager.HandleNetworkNaming)
+	authRouter.HandleFunc("/api/gan/network/test", ganManager.HandleTest)
+	authRouter.HandleFunc("/api/gan/members/list", ganManager.HandleMemberList)
 
 	//mDNS APIs
 	authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing)

+ 9 - 0
deprecated/ganserv/dhcp.go

@@ -0,0 +1,9 @@
+package ganserv
+
+/*
+	DHCP.go
+
+	This script is use to emulate the use of DHCP of a
+	GAN network that assign local IP address to the connected
+	nodes via GET request
+*/

+ 118 - 0
deprecated/ganserv/ganserv.go

@@ -0,0 +1,118 @@
+package ganserv
+
+import (
+	"errors"
+	"fmt"
+	"net"
+
+	"imuslab.com/zoraxy/mod/database"
+)
+
+/*
+	Global Area Network
+	Server side implementation
+
+	This module do a few things to help manage
+	the system GANs
+
+	- Provide DHCP assign to client
+	- Provide a list of connected nodes in the same VLAN
+	- Provide proxy of packet if the target VLAN is online but not reachable
+
+	Also provide HTTP Handler functions for management
+	- Create Network
+	- Update Network Properties (Name / Desc)
+	- Delete Network
+
+	- Authorize Node
+	- Deauthorize Node
+	- Set / Get Network Prefered Subnet Mask
+	- Handle Node ping
+*/
+
+type Node struct {
+	Auth          bool   //If the node is authorized in this network
+	ClientID      string //The client ID
+	MAC           string //The tap MAC this client is using
+	Name          string //Name of the client in this network
+	Description   string //Description text
+	ManagedIP     net.IP //The IP address assigned by this network
+	LastSeen      int64  //Last time it is seen from this host
+	ClientVersion string //Client application version
+	PublicIP      net.IP //Public IP address as seen from this host
+}
+
+type Network struct {
+	UID         string  //UUID of the network, must be a 16 char random ASCII string
+	Name        string  //Name of the network, ASCII only
+	Description string  //Description of the network
+	CIDR        string  //The subnet masked use by this network
+	Nodes       []*Node //The nodes currently attached in this network
+}
+
+type NetworkManagerOptions struct {
+	Database *database.Database
+}
+
+type NetworkManager struct {
+	option   *NetworkManagerOptions
+	networks []*Network
+}
+
+//Create a new GAN manager
+func NewNetworkManager(option *NetworkManagerOptions) *NetworkManager {
+	option.Database.NewTable("ganserv")
+
+	existingNetworks := []*Network{}
+	if option.Database.KeyExists("ganserv", "networks") {
+		option.Database.Read("ganserv", "networks", &existingNetworks)
+	}
+	return &NetworkManager{
+		option:   option,
+		networks: existingNetworks,
+	}
+}
+
+//List all the networks within this network manager
+func (m *NetworkManager) ListNetworks() []*Network {
+	return m.networks
+}
+
+func (m *NetworkManager) AddNetwork(n *Network) error {
+	if m.NetworkExists(n.UID) {
+		return errors.New("network with given id already exists")
+	}
+	m.networks = append(m.networks, n)
+
+	//Write the latest network list into database
+	m.option.Database.Write("ganserv", "networks", m.networks)
+	return nil
+}
+
+func (m *NetworkManager) RemoveNetwork(id string) error {
+	for i, n := range m.networks {
+		if n.UID == id {
+			m.networks = append(m.networks[:i], m.networks[i+1:]...)
+
+			//Write the latest network list into database
+			m.option.Database.Write("ganserv", "networks", m.networks)
+			return nil
+		}
+	}
+
+	return fmt.Errorf("network not found: %s", id)
+}
+
+func (m *NetworkManager) GetNetworkByID(id string) (*Network, error) {
+	for _, n := range m.networks {
+		if n.UID == id {
+			return n, nil
+		}
+	}
+	return nil, fmt.Errorf("network not found: %s", id)
+}
+
+func (m *NetworkManager) NetworkExists(id string) bool {
+	_, err := m.GetNetworkByID(id)
+	return err == nil
+}

+ 79 - 0
deprecated/ganserv/handlers.go

@@ -0,0 +1,79 @@
+package ganserv
+
+import (
+	"encoding/json"
+	"net/http"
+
+	"imuslab.com/zoraxy/mod/utils"
+)
+
+func (m *NetworkManager) HandleAddNetwork(w http.ResponseWriter, r *http.Request) {
+	networkName, err := utils.PostPara(r, "name")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid or empty name given")
+		return
+	}
+
+	subnetMask, err := utils.PostPara(r, "subnetMask")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid or empty subnetMask given")
+		return
+	}
+
+	// Generate a new UUID for this network
+	networkUID := newNetworkID()
+
+	// Create a new network object and add it to the list of networks
+	newNetwork := &Network{
+		UID:         networkUID,
+		Name:        networkName,
+		CIDR:        subnetMask,
+		Description: "",
+		Nodes:       []*Node{},
+	}
+
+	err = m.AddNetwork(newNetwork)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	// Return the new network ID
+	js, _ := json.Marshal(networkUID)
+	utils.SendJSONResponse(w, string(js))
+}
+
+func (m *NetworkManager) HandleRemoveNetwork(w http.ResponseWriter, r *http.Request) {
+	networkID, err := utils.PostPara(r, "id")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid or empty network id given")
+		return
+	}
+
+	err = m.RemoveNetwork(networkID)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+	utils.SendOK(w)
+}
+
+func (m *NetworkManager) HandleListNetwork(w http.ResponseWriter, r *http.Request) {
+	netid, _ := utils.GetPara(r, "netid")
+	if netid != "" {
+		targetNet, err := m.GetNetworkByID(netid)
+		if err != nil {
+			utils.SendErrorResponse(w, err.Error())
+			return
+		}
+
+		js, _ := json.Marshal(targetNet)
+		utils.SendJSONResponse(w, string(js))
+
+	} else {
+		// Return the list of networks as JSON
+		js, _ := json.Marshal(m.networks)
+		utils.SendJSONResponse(w, string(js))
+	}
+
+}

+ 39 - 0
deprecated/ganserv/network.go

@@ -0,0 +1,39 @@
+package ganserv
+
+import (
+	"fmt"
+	"math/rand"
+	"net"
+	"time"
+)
+
+//Get a random free IP from the pool
+func (n *Network) GetRandomFreeIP() (net.IP, error) {
+	// Get all IP addresses in the subnet
+	ips, err := GetAllAddressFromCIDR(n.CIDR)
+	if err != nil {
+		return nil, err
+	}
+
+	// Filter out used IPs
+	usedIPs := make(map[string]bool)
+	for _, node := range n.Nodes {
+		usedIPs[node.ManagedIP.String()] = true
+	}
+	availableIPs := []string{}
+	for _, ip := range ips {
+		if !usedIPs[ip] {
+			availableIPs = append(availableIPs, ip)
+		}
+	}
+
+	// Randomly choose an available IP
+	if len(availableIPs) == 0 {
+		return nil, fmt.Errorf("no available IP")
+	}
+	rand.Seed(time.Now().UnixNano())
+	randIndex := rand.Intn(len(availableIPs))
+	pickedFreeIP := availableIPs[randIndex]
+
+	return net.ParseIP(pickedFreeIP), nil
+}

+ 55 - 0
deprecated/ganserv/network_test.go

@@ -0,0 +1,55 @@
+package ganserv_test
+
+import (
+	"fmt"
+	"net"
+	"strconv"
+	"testing"
+
+	"imuslab.com/zoraxy/mod/ganserv"
+)
+
+func TestGetRandomFreeIP(t *testing.T) {
+	n := ganserv.Network{
+		CIDR: "172.16.0.0/12",
+		Nodes: []*ganserv.Node{
+			{
+				Name:      "nodeC1",
+				ManagedIP: net.ParseIP("172.16.1.142"),
+			},
+			{
+				Name:      "nodeC2",
+				ManagedIP: net.ParseIP("172.16.5.174"),
+			},
+		},
+	}
+
+	// Call the function for 10 times
+	for i := 0; i < 10; i++ {
+		freeIP, err := n.GetRandomFreeIP()
+		fmt.Println("["+strconv.Itoa(i)+"] Free IP address assigned: ", freeIP)
+
+		// Assert that no error occurred
+		if err != nil {
+			t.Errorf("Unexpected error: %s", err.Error())
+		}
+
+		// Assert that the returned IP is a valid IPv4 address
+		if freeIP.To4() == nil {
+			t.Errorf("Invalid IP address format: %s", freeIP.String())
+		}
+
+		// Assert that the returned IP is not already used by a node
+		for _, node := range n.Nodes {
+			if freeIP.Equal(node.ManagedIP) {
+				t.Errorf("Returned IP is already in use: %s", freeIP.String())
+			}
+		}
+
+		n.Nodes = append(n.Nodes, &ganserv.Node{
+			Name:      "NodeT" + strconv.Itoa(i),
+			ManagedIP: freeIP,
+		})
+	}
+
+}

+ 87 - 0
deprecated/ganserv/utils.go

@@ -0,0 +1,87 @@
+package ganserv
+
+import (
+	"fmt"
+	"math/rand"
+	"net"
+	"time"
+)
+
+//Generate all ip address from a CIDR
+func GetAllAddressFromCIDR(cidr string) ([]string, error) {
+	ip, ipnet, err := net.ParseCIDR(cidr)
+	if err != nil {
+		return nil, err
+	}
+
+	var ips []string
+	for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
+		ips = append(ips, ip.String())
+	}
+	// remove network address and broadcast address
+	return ips[1 : len(ips)-1], nil
+}
+
+func inc(ip net.IP) {
+	for j := len(ip) - 1; j >= 0; j-- {
+		ip[j]++
+		if ip[j] > 0 {
+			break
+		}
+	}
+}
+
+//Validate if a network struct is correct
+func validateNetwork(n *Network) error {
+	// Check if UID is a valid 16 char random ASCII string
+	if len(n.UID) != 16 {
+		return fmt.Errorf("UID must be 16 characters long")
+	}
+	for _, r := range n.UID {
+		if r < '0' || ('9' < r && r < 'A') || ('Z' < r && r < 'a') || r > 'z' {
+			return fmt.Errorf("UID must be a 16 char random ASCII string")
+		}
+	}
+
+	// Check if Name is ASCII only
+	if !isASCII(n.Name) {
+		return fmt.Errorf("Name must be ASCII only")
+	}
+
+	// Check if Subnet is valid
+	if n.CIDR == "" {
+		return fmt.Errorf("Subnet is not set")
+	}
+	_, _, err := net.ParseCIDR(n.CIDR)
+	if err != nil {
+		//This is not a valid CIDR
+		return fmt.Errorf("CIDR is not valid")
+	}
+
+	return nil
+}
+
+//Check if a string is ascii
+func isASCII(s string) bool {
+	for _, r := range s {
+		if r > 127 {
+			return false
+		}
+	}
+	return true
+}
+
+//Generate a new random network ID
+func newNetworkID() string {
+	rand.Seed(time.Now().UnixNano())
+
+	const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+	const length = 16
+
+	var result string
+	for i := 0; i < length; i++ {
+		result += string(chars[rand.Intn(len(chars))])
+	}
+
+	return result
+}

+ 37 - 47
mod/ganserv/ganserv.go

@@ -1,8 +1,6 @@
 package ganserv
 
 import (
-	"errors"
-	"fmt"
 	"net"
 
 	"imuslab.com/zoraxy/mod/database"
@@ -51,68 +49,60 @@ type Network struct {
 }
 
 type NetworkManagerOptions struct {
-	Database *database.Database
+	Database  *database.Database
+	AuthToken string
+	ApiPort   int
+}
+
+type NetworkMetaData struct {
+	Desc string
 }
 
 type NetworkManager struct {
-	option   *NetworkManagerOptions
-	networks []*Network
+	authToken        string
+	apiPort          int
+	ControllerID     string
+	option           *NetworkManagerOptions
+	networksMetadata map[string]NetworkMetaData
 }
 
 //Create a new GAN manager
 func NewNetworkManager(option *NetworkManagerOptions) *NetworkManager {
 	option.Database.NewTable("ganserv")
 
-	existingNetworks := []*Network{}
-	if option.Database.KeyExists("ganserv", "networks") {
-		option.Database.Read("ganserv", "networks", &existingNetworks)
-	}
-	return &NetworkManager{
-		option:   option,
-		networks: existingNetworks,
-	}
-}
-
-//List all the networks within this network manager
-func (m *NetworkManager) ListNetworks() []*Network {
-	return m.networks
-}
+	//Start the zerotier instance if not exists
 
-func (m *NetworkManager) AddNetwork(n *Network) error {
-	if m.NetworkExists(n.UID) {
-		return errors.New("network with given id already exists")
+	//Get controller info
+	instanceInfo, err := getControllerInfo(option.AuthToken, option.ApiPort)
+	if err != nil {
+		panic(err)
 	}
-	m.networks = append(m.networks, n)
-
-	//Write the latest network list into database
-	m.option.Database.Write("ganserv", "networks", m.networks)
-	return nil
-}
-
-func (m *NetworkManager) RemoveNetwork(id string) error {
-	for i, n := range m.networks {
-		if n.UID == id {
-			m.networks = append(m.networks[:i], m.networks[i+1:]...)
 
-			//Write the latest network list into database
-			m.option.Database.Write("ganserv", "networks", m.networks)
-			return nil
-		}
+	//Load network metadata
+	networkMeta := map[string]NetworkMetaData{}
+	if option.Database.KeyExists("ganserv", "networkmeta") {
+		option.Database.Read("ganserv", "networkmeta", &networkMeta)
 	}
 
-	return fmt.Errorf("network not found: %s", id)
+	return &NetworkManager{
+		authToken:        option.AuthToken,
+		apiPort:          option.ApiPort,
+		ControllerID:     instanceInfo.Address,
+		option:           option,
+		networksMetadata: networkMeta,
+	}
 }
 
-func (m *NetworkManager) GetNetworkByID(id string) (*Network, error) {
-	for _, n := range m.networks {
-		if n.UID == id {
-			return n, nil
-		}
+func (m *NetworkManager) GetNetworkMetaData(netid string) *NetworkMetaData {
+	md, ok := m.networksMetadata[netid]
+	if !ok {
+		return &NetworkMetaData{}
 	}
-	return nil, fmt.Errorf("network not found: %s", id)
+
+	return &md
 }
 
-func (m *NetworkManager) NetworkExists(id string) bool {
-	_, err := m.GetNetworkByID(id)
-	return err == nil
+func (m *NetworkManager) WriteNetworkMetaData(netid string, meta *NetworkMetaData) {
+	m.networksMetadata[netid] = *meta
+	m.option.Database.Write("ganserv", "networkmeta", m.networksMetadata)
 }

+ 116 - 26
mod/ganserv/handlers.go

@@ -3,43 +3,43 @@ package ganserv
 import (
 	"encoding/json"
 	"net/http"
+	"regexp"
 
 	"imuslab.com/zoraxy/mod/utils"
 )
 
-func (m *NetworkManager) HandleAddNetwork(w http.ResponseWriter, r *http.Request) {
-	networkName, err := utils.PostPara(r, "name")
-	if err != nil {
-		utils.SendErrorResponse(w, "invalid or empty name given")
-		return
-	}
+func (m *NetworkManager) HandleGetNodeID(w http.ResponseWriter, r *http.Request) {
+	if m.ControllerID == "" {
+		//Node id not exists. Check again
+		instanceInfo, err := getControllerInfo(m.option.AuthToken, m.option.ApiPort)
+		if err != nil {
+			utils.SendErrorResponse(w, "unable to access node id information")
+			return
+		}
 
-	subnetMask, err := utils.PostPara(r, "subnetMask")
-	if err != nil {
-		utils.SendErrorResponse(w, "invalid or empty subnetMask given")
-		return
+		m.ControllerID = instanceInfo.Address
 	}
 
-	// Generate a new UUID for this network
-	networkUID := newNetworkID()
+	js, _ := json.Marshal(m.ControllerID)
+	utils.SendJSONResponse(w, string(js))
+}
 
-	// Create a new network object and add it to the list of networks
-	newNetwork := &Network{
-		UID:         networkUID,
-		Name:        networkName,
-		CIDR:        subnetMask,
-		Description: "",
-		Nodes:       []*Node{},
+func (m *NetworkManager) HandleAddNetwork(w http.ResponseWriter, r *http.Request) {
+	networkInfo, err := m.createNetwork()
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
 	}
 
-	err = m.AddNetwork(newNetwork)
+	//Network created. Assign it the standard network settings
+	err = m.configureNetwork(networkInfo.Nwid, "192.168.192.1", "192.168.192.254", "192.168.192.0/24")
 	if err != nil {
 		utils.SendErrorResponse(w, err.Error())
 		return
 	}
 
 	// Return the new network ID
-	js, _ := json.Marshal(networkUID)
+	js, _ := json.Marshal(networkInfo.Nwid)
 	utils.SendJSONResponse(w, string(js))
 }
 
@@ -50,30 +50,120 @@ func (m *NetworkManager) HandleRemoveNetwork(w http.ResponseWriter, r *http.Requ
 		return
 	}
 
-	err = m.RemoveNetwork(networkID)
+	if !m.networkExists(networkID) {
+		utils.SendErrorResponse(w, "network id not exists")
+		return
+	}
+
+	err = m.deleteNetwork(networkID)
 	if err != nil {
 		utils.SendErrorResponse(w, err.Error())
-		return
 	}
+
 	utils.SendOK(w)
 }
 
 func (m *NetworkManager) HandleListNetwork(w http.ResponseWriter, r *http.Request) {
 	netid, _ := utils.GetPara(r, "netid")
 	if netid != "" {
-		targetNet, err := m.GetNetworkByID(netid)
+		targetNetInfo, err := m.getNetworkInfoById(netid)
 		if err != nil {
 			utils.SendErrorResponse(w, err.Error())
 			return
 		}
 
-		js, _ := json.Marshal(targetNet)
+		js, _ := json.Marshal(targetNetInfo)
 		utils.SendJSONResponse(w, string(js))
 
 	} else {
 		// Return the list of networks as JSON
-		js, _ := json.Marshal(m.networks)
+		networkIds, err := m.listNetworkIds()
+		if err != nil {
+			utils.SendErrorResponse(w, err.Error())
+			return
+		}
+
+		networkInfos := []*NetworkInfo{}
+		for _, id := range networkIds {
+			thisNetInfo, err := m.getNetworkInfoById(id)
+			if err == nil {
+				networkInfos = append(networkInfos, thisNetInfo)
+			}
+		}
+
+		js, _ := json.Marshal(networkInfos)
+		utils.SendJSONResponse(w, string(js))
+	}
+
+}
+
+func (m *NetworkManager) HandleNetworkNaming(w http.ResponseWriter, r *http.Request) {
+	netid, err := utils.PostPara(r, "netid")
+	if err != nil {
+		utils.SendErrorResponse(w, "network id not given")
+		return
+	}
+
+	if !m.networkExists(netid) {
+		utils.SendErrorResponse(w, "network not eixsts")
+	}
+
+	newName, _ := utils.PostPara(r, "name")
+	newDesc, _ := utils.PostPara(r, "desc")
+	if newName != "" && newDesc != "" {
+		//Strip away html from name and desc
+		re := regexp.MustCompile("<[^>]*>")
+		newName := re.ReplaceAllString(newName, "")
+		newDesc := re.ReplaceAllString(newDesc, "")
+
+		//Set the new network name and desc
+		err = m.setNetworkNameAndDescription(netid, newName, newDesc)
+		if err != nil {
+			utils.SendErrorResponse(w, err.Error())
+			return
+		}
+
+		utils.SendOK(w)
+	} else {
+		//Get current name and description
+		name, desc, err := m.getNetworkNameAndDescription(netid)
+		if err != nil {
+			utils.SendErrorResponse(w, err.Error())
+			return
+		}
+
+		js, _ := json.Marshal([]string{name, desc})
 		utils.SendJSONResponse(w, string(js))
 	}
+}
 
+func (m *NetworkManager) HandleTest(w http.ResponseWriter, r *http.Request) {
+	err := m.configureNetwork("e7dd1ce7bfd3b1f9", "192.168.192.1", "192.168.192.254", "192.168.192.0/24")
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	utils.SendOK(w)
+}
+
+func (m *NetworkManager) HandleMemberList(w http.ResponseWriter, r *http.Request) {
+	netid, err := utils.GetPara(r, "netid")
+	if err != nil {
+		utils.SendErrorResponse(w, "netid is empty")
+		return
+	}
+
+	details, _ := utils.GetPara(r, "detail")
+	if details == "" {
+		//Only show client ids
+		memberIds, err := m.getNetworkMembers(netid)
+		if err != nil {
+			utils.SendErrorResponse(w, err.Error())
+			return
+		}
+
+		js, _ := json.Marshal(memberIds)
+		utils.SendJSONResponse(w, string(js))
+	}
 }

+ 469 - 0
mod/ganserv/zerotier.go

@@ -0,0 +1,469 @@
+package ganserv
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"strconv"
+	"strings"
+)
+
+/*
+	zerotier.go
+
+	This hold the functions that required to communicate with
+	a zerotier instance
+
+	See more on
+	https://docs.zerotier.com/self-hosting/network-controllers/
+
+*/
+
+type NodeInfo struct {
+	Address string `json:"address"`
+	Clock   int64  `json:"clock"`
+	Config  struct {
+		Settings struct {
+			AllowTCPFallbackRelay bool   `json:"allowTcpFallbackRelay"`
+			PortMappingEnabled    bool   `json:"portMappingEnabled"`
+			PrimaryPort           int    `json:"primaryPort"`
+			SoftwareUpdate        string `json:"softwareUpdate"`
+			SoftwareUpdateChannel string `json:"softwareUpdateChannel"`
+		} `json:"settings"`
+	} `json:"config"`
+	Online               bool   `json:"online"`
+	PlanetWorldID        int    `json:"planetWorldId"`
+	PlanetWorldTimestamp int64  `json:"planetWorldTimestamp"`
+	PublicIdentity       string `json:"publicIdentity"`
+	TCPFallbackActive    bool   `json:"tcpFallbackActive"`
+	Version              string `json:"version"`
+	VersionBuild         int    `json:"versionBuild"`
+	VersionMajor         int    `json:"versionMajor"`
+	VersionMinor         int    `json:"versionMinor"`
+	VersionRev           int    `json:"versionRev"`
+}
+
+type ErrResp struct {
+	Message string `json:"message"`
+}
+
+type NetworkInfo struct {
+	AuthTokens            []interface{} `json:"authTokens"`
+	AuthorizationEndpoint string        `json:"authorizationEndpoint"`
+	Capabilities          []interface{} `json:"capabilities"`
+	ClientID              string        `json:"clientId"`
+	CreationTime          int64         `json:"creationTime"`
+	DNS                   []interface{} `json:"dns"`
+	EnableBroadcast       bool          `json:"enableBroadcast"`
+	ID                    string        `json:"id"`
+	IPAssignmentPools     []interface{} `json:"ipAssignmentPools"`
+	Mtu                   int           `json:"mtu"`
+	MulticastLimit        int           `json:"multicastLimit"`
+	Name                  string        `json:"name"`
+	Nwid                  string        `json:"nwid"`
+	Objtype               string        `json:"objtype"`
+	Private               bool          `json:"private"`
+	RemoteTraceLevel      int           `json:"remoteTraceLevel"`
+	RemoteTraceTarget     interface{}   `json:"remoteTraceTarget"`
+	Revision              int           `json:"revision"`
+	Routes                []interface{} `json:"routes"`
+	Rules                 []struct {
+		Not  bool   `json:"not"`
+		Or   bool   `json:"or"`
+		Type string `json:"type"`
+	} `json:"rules"`
+	RulesSource  string        `json:"rulesSource"`
+	SsoEnabled   bool          `json:"ssoEnabled"`
+	Tags         []interface{} `json:"tags"`
+	V4AssignMode struct {
+		Zt bool `json:"zt"`
+	} `json:"v4AssignMode"`
+	V6AssignMode struct {
+		SixPlane bool `json:"6plane"`
+		Rfc4193  bool `json:"rfc4193"`
+		Zt       bool `json:"zt"`
+	} `json:"v6AssignMode"`
+}
+
+//Get the zerotier node info from local service
+func getControllerInfo(token string, apiPort int) (*NodeInfo, error) {
+	url := "http://localhost:" + strconv.Itoa(apiPort) + "/status"
+
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	req.Header.Set("X-ZT1-AUTH", token)
+	client := &http.Client{}
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	//Read from zerotier service instance
+
+	defer resp.Body.Close()
+	payload, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+
+	//Parse the payload into struct
+	thisInstanceInfo := NodeInfo{}
+	err = json.Unmarshal(payload, &thisInstanceInfo)
+	if err != nil {
+		return nil, err
+	}
+
+	return &thisInstanceInfo, nil
+}
+
+/*
+	Network Functions
+*/
+//Create a zerotier network
+func (m *NetworkManager) createNetwork() (*NetworkInfo, error) {
+	url := fmt.Sprintf("http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/%s______", m.ControllerID)
+
+	data := []byte(`{}`)
+	req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
+	if err != nil {
+		return nil, err
+	}
+
+	req.Header.Set("X-ZT1-AUTH", m.authToken)
+	client := &http.Client{}
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	defer resp.Body.Close()
+
+	payload, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+
+	networkInfo := NetworkInfo{}
+	err = json.Unmarshal(payload, &networkInfo)
+	if err != nil {
+		return nil, err
+	}
+
+	return &networkInfo, nil
+}
+
+//List network details
+func (m *NetworkManager) getNetworkInfoById(networkId string) (*NetworkInfo, error) {
+	req, err := http.NewRequest("GET", os.ExpandEnv("http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/"+networkId+"/"), nil)
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Set("X-Zt1-Auth", m.authToken)
+
+	resp, err := http.DefaultClient.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != 200 {
+		return nil, errors.New("network error. Status code: " + strconv.Itoa(resp.StatusCode))
+	}
+
+	thisNetworkInfo := NetworkInfo{}
+	payload, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+
+	err = json.Unmarshal(payload, &thisNetworkInfo)
+	if err != nil {
+		return nil, err
+	}
+
+	return &thisNetworkInfo, nil
+}
+
+func (m *NetworkManager) setNetworkInfoByID(networkId string, newNetworkInfo *NetworkInfo) error {
+	payloadBytes, err := json.Marshal(newNetworkInfo)
+	if err != nil {
+		return err
+	}
+	payloadBuffer := bytes.NewBuffer(payloadBytes)
+
+	// Create the HTTP request
+	url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + networkId + "/"
+	req, err := http.NewRequest("POST", url, payloadBuffer)
+	if err != nil {
+		return err
+	}
+	req.Header.Set("X-Zt1-Auth", m.authToken)
+	req.Header.Set("Content-Type", "application/json")
+
+	// Send the HTTP request
+	resp, err := http.DefaultClient.Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+
+	// Print the response status code
+	if resp.StatusCode != 200 {
+		return errors.New("network error. status code: " + strconv.Itoa(resp.StatusCode))
+	}
+
+	return nil
+}
+
+//List network IDs
+func (m *NetworkManager) listNetworkIds() ([]string, error) {
+	req, err := http.NewRequest("GET", "http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/", nil)
+	if err != nil {
+		return []string{}, err
+	}
+	req.Header.Set("X-Zt1-Auth", m.authToken)
+
+	resp, err := http.DefaultClient.Do(req)
+	if err != nil {
+		return []string{}, err
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != 200 {
+		return []string{}, errors.New("network error")
+	}
+
+	networkIds := []string{}
+	payload, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return []string{}, err
+	}
+
+	err = json.Unmarshal(payload, &networkIds)
+	if err != nil {
+		return []string{}, err
+	}
+
+	return networkIds, nil
+}
+
+//wrapper for checking if a network id exists
+func (m *NetworkManager) networkExists(networkId string) bool {
+	networkIds, err := m.listNetworkIds()
+	if err != nil {
+		return false
+	}
+
+	for _, thisid := range networkIds {
+		if thisid == networkId {
+			return true
+		}
+	}
+
+	return false
+}
+
+//delete a network
+func (m *NetworkManager) deleteNetwork(networkID string) error {
+	url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + networkID + "/"
+	client := &http.Client{}
+
+	// Create a new DELETE request
+	req, err := http.NewRequest("DELETE", url, nil)
+	if err != nil {
+		return err
+	}
+
+	// Add the required authorization header
+	req.Header.Set("X-Zt1-Auth", m.authToken)
+
+	// Send the request and get the response
+	resp, err := client.Do(req)
+	if err != nil {
+		return err
+	}
+
+	// Close the response body when we're done
+	defer resp.Body.Close()
+	s, err := io.ReadAll(resp.Body)
+	fmt.Println(string(s), err, resp.StatusCode)
+
+	// Print the response status code
+	if resp.StatusCode != 200 {
+		return errors.New("network error. status code: " + strconv.Itoa(resp.StatusCode))
+	}
+
+	return nil
+}
+
+//Configure network
+//Example: configureNetwork(netid, "192.168.192.1", "192.168.192.254", "192.168.192.0/24")
+func (m *NetworkManager) configureNetwork(networkID string, ipRangeStart string, ipRangeEnd string, routeTarget string) error {
+	url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + networkID + "/"
+	data := map[string]interface{}{
+		"ipAssignmentPools": []map[string]string{
+			{
+				"ipRangeStart": ipRangeStart,
+				"ipRangeEnd":   ipRangeEnd,
+			},
+		},
+		"routes": []map[string]interface{}{
+			{
+				"target": routeTarget,
+				"via":    nil,
+			},
+		},
+		"v4AssignMode": "zt",
+		"private":      true,
+	}
+
+	payload, err := json.Marshal(data)
+	if err != nil {
+		return err
+	}
+
+	req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
+	if err != nil {
+		return err
+	}
+
+	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("X-ZT1-AUTH", m.authToken)
+
+	client := &http.Client{}
+	resp, err := client.Do(req)
+	if err != nil {
+		return err
+	}
+
+	defer resp.Body.Close()
+	// Print the response status code
+	if resp.StatusCode != 200 {
+		return errors.New("network error. status code: " + strconv.Itoa(resp.StatusCode))
+	}
+
+	return nil
+}
+
+func (m *NetworkManager) setNetworkNameAndDescription(netid string, name string, desc string) error {
+	// Convert string to rune slice
+	r := []rune(name)
+
+	// Loop over runes and remove non-ASCII characters
+	for i, v := range r {
+		if v > 127 {
+			r[i] = ' '
+		}
+	}
+
+	// Convert back to string and trim whitespace
+	name = strings.TrimSpace(string(r))
+
+	url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + netid + "/"
+	data := map[string]interface{}{
+		"name": name,
+	}
+
+	payload, err := json.Marshal(data)
+	if err != nil {
+		return err
+	}
+
+	req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
+	if err != nil {
+		return err
+	}
+
+	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("X-ZT1-AUTH", m.authToken)
+
+	client := &http.Client{}
+	resp, err := client.Do(req)
+	if err != nil {
+		return err
+	}
+
+	defer resp.Body.Close()
+	// Print the response status code
+	if resp.StatusCode != 200 {
+		return errors.New("network error. status code: " + strconv.Itoa(resp.StatusCode))
+	}
+
+	meta := m.GetNetworkMetaData(netid)
+	if meta != nil {
+		meta.Desc = desc
+		m.WriteNetworkMetaData(netid, meta)
+	}
+
+	return nil
+}
+
+func (m *NetworkManager) getNetworkNameAndDescription(netid string) (string, string, error) {
+	//Get name from network info
+	netinfo, err := m.getNetworkInfoById(netid)
+	if err != nil {
+		return "", "", err
+	}
+
+	name := netinfo.Name
+
+	//Get description from meta
+	desc := ""
+	networkMeta := m.GetNetworkMetaData(netid)
+	if networkMeta != nil {
+		desc = networkMeta.Desc
+	}
+
+	return name, desc, nil
+}
+
+/*
+	Member functions
+*/
+
+func (m *NetworkManager) getNetworkMembers(networkId string) ([]string, error) {
+	url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + networkId + "/member"
+	reqBody := bytes.NewBuffer([]byte{})
+	req, err := http.NewRequest("GET", url, reqBody)
+	if err != nil {
+		return nil, err
+	}
+
+	req.Header.Set("X-ZT1-AUTH", m.authToken)
+
+	client := &http.Client{}
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		return nil, errors.New("failed to get network members")
+	}
+
+	memberList := map[string]int{}
+	payload, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+
+	err = json.Unmarshal(payload, &memberList)
+	if err != nil {
+		return nil, err
+	}
+
+	members := make([]string, 0, len(memberList))
+	for k := range memberList {
+		members = append(members, k)
+	}
+
+	return members, nil
+}

+ 3 - 1
start.go

@@ -131,7 +131,9 @@ func startupSequence() {
 
 	//Create GAN Manager
 	ganManager = ganserv.NewNetworkManager(&ganserv.NetworkManagerOptions{
-		Database: sysdb,
+		AuthToken: "hgaode9ptnpuaoi1ilbdw9i4",
+		ApiPort:   9993,
+		Database:  sysdb,
 	})
 
 	//Create WebSSH Manager

+ 29 - 27
web/components/gan.html

@@ -4,7 +4,13 @@
         <p>Virtual Network Hub that allows all networked devices to communicate as if they all reside in the same physical data center or cloud region</p>
     </div>
     <div class="gansnetworks">
-        <div class="ganstats">
+        <div class="ganstats ui basic segment">
+            <div style="float: right; max-width: 300px; margin-top: 0.4em;">
+                <h1 class="ui header" style="text-align: right;">
+                    <span class="ganControllerID"></span>
+                    <div class="sub header">Network Controller ID</div>
+                </h1>
+            </div>
             <div class="ui list">
                 <div class="item">
                     <i class="exchange icon"></i>
@@ -21,21 +27,9 @@
                     </div>
                 </div>
             </div>
-            <br>
         </div>
         <div class="ganlist">
-            <button class="ui basic orange button" onclick="$('#newGanForm').fadeToggle('fast');">Create New Network</button>
-            <div class="" id="newGanForm" style="display:none; position: relative;">
-                <div class="ui divider"></div>
-                <p>Enter a new network name to create new network</p>
-                <div class="ui action fluid input">
-                    <input type="text" id="networkName" placeholder="Network Name">
-                    <button class="ui basic button" onclick="handleAddNetwork();">
-                        <i class="blue add icon"></i> Add Network
-                    </button>
-                </div>
-                <button onclick="$('#newGanForm').fadeOut('fast');" class="ui mini circular basic icon button" style="position: absolute; right: 0.4em; top: 1em;"><i class="remove icon"></i></button>
-            </div>
+            <button class="ui basic orange button" onclick="addGANet();">Create New Network</button>
             <div class="ui divider"></div>
             <div class="ui icon input" style="margin-bottom: 1em;">
                 <input type="text" placeholder="Search a Network">
@@ -79,15 +73,22 @@
         $("#networkName").val("");
     }
 
-    function addGANet(name, subnetMask) {
+    function initGANetID(){
+        $.get("/api/gan/network/info", function(data){
+            if (data.error !== undefined){
+                msgbox(data.error, false, 5000)
+            }else{
+                $(".ganControllerID").text(data);
+            }
+        })
+    }
+
+    function addGANet() {
         $.ajax({
             url: "/api/gan/network/add",
             type: "POST",
             dataType: "json",
-            data: {
-                name: name,
-                subnetMask: subnetMask
-            },
+            data: {},
             success: function(response) {
                 if (response.error != undefined){
                     msgbox(response.error, false, 5000);
@@ -112,18 +113,18 @@
                 var nodeCount = 0;
                 data.forEach(function(gan){
                     $("#GANetList").append(`<tr>
-                        <td>${gan.UID}</td>
-                        <td>${gan.Name}</td>
-                        <td>${gan.Description}</td>
-                        <td>${gan.CIDR}</td>
-                        <td>${gan.Nodes.length}</td>
+                        <td>${gan.nwid}</td>
+                        <td>${""}</td>
+                        <td>${""}</td>
+                        <td>${""}</td>
+                        <td>${""}</td>
                         <td>
-                            <button onclick="openGANetDetails('${gan.UID}');" class="ui tiny basic icon button" title="Edit Network"><i class="edit icon"></i></button>
-                            <button onclick="removeGANet('${gan.UID}');" class="ui tiny basic icon button" title="Remove Network"><i class="red remove icon"></i></button>
+                            <button onclick="openGANetDetails('${gan.nwid}');" class="ui tiny basic icon button" title="Edit Network"><i class="edit icon"></i></button>
+                            <button onclick="removeGANet('${gan.nwid}');" class="ui tiny basic icon button" title="Remove Network"><i class="red remove icon"></i></button>
                         </td>
                     </tr>`);
 
-                    nodeCount += gan.Nodes.length;
+                    nodeCount += 0;
                 });
 
                 if (data.length == 0){
@@ -172,5 +173,6 @@
      tabSwitchEventBind["gan"] = function(){
         //On switch over to this page, load info
         listGANet();
+        initGANetID();
     }
 </script>

+ 75 - 9
web/components/gandetails.html

@@ -1,28 +1,92 @@
 <div class="standardContainer">
     <button onclick="exitToGanList();" class="ui large circular black icon button"><i class="angle left icon"></i></button>
-    <div style="float: right; max-width: 300px;">
-        <h1 class="ui header" style="text-align: right;">
+    <div style="max-width: 300px; margin-top: 1em;">
+        <button onclick='$("#gannetDetailEdit").toggle();' class="ui mini basic right floated circular icon button" style="display: inline-block; margin-top: 2.5em;"><i class="ui edit icon"></i></button>
+        <h1 class="ui header">
             <span class="ganetID"></span>
             <div class="sub header ganetName"></div>
         </h1>
+        <div class="ui divider"></div>
+        <p><span class="ganetDesc"></span></p>
+        
     </div>
-    
+    <div id="gannetDetailEdit" class="ui form" style="margin-top: 1em; display:none;">
+        <div class="ui divider"></div>
+        <p>You can change the network name and description below. <br>The name and description is only for easy management purpose and will not effect the network operation.</p>
+        <div class="field">
+            <label>Network Name</label>
+            <input type="text" id="gaNetNameInput" placeholder="">
+          </div>
+        <div class="field">
+          <label>Network Description</label>
+          <textarea id="gaNetDescInput" style="resize: none;"></textarea>
+          <button onclick="saveNameAndDesc(this);" class="ui basic right floated button" style="margin-top: 0.6em;"><i class="ui save icon"></i> Save</button>
+        </div>
+        <div class="field">
+            <table class="ui very basic collapsing celled table">
+                <tbody>
+                  <tr>
+                    <td>
+                     
+                    </td>
+                    <td>
+                      
+                    </td>
+                  </tr>
+                </tbody>
+            </table>
+        </div>
+    </div>
+    <div class="ui divider"></div>
     <br><br>
 </div>
 <script>
+    var currentGANetID = "";
     var currentGaNetDetails = {};
-    //Entry points
-    function initGanetDetails(ganetId){
-        $(".ganetID").text(ganetId);
 
+    function saveNameAndDesc(object=undefined){
+        var name = $("#gaNetNameInput").val();
+        var desc = $("#gaNetDescInput").val();
+        if (object != undefined){
+            $(object).addClass("loading");
+        }
+        $.ajax({
+            url: "/api/gan/network/name",
+            method: "POST",
+            data: {
+                netid: currentGANetID,
+                name: name,
+                desc: desc,
+            },
+            success: function(data){
+                initNetNameAndDesc();
+                if (object != undefined){
+                    $(object).removeClass("loading");
+                    msgbox("Network Metadata Updated");
+                }
+            }
+        });
+    }
+
+    function initNetNameAndDesc(){
         //Get the details of the net
-        $.get("/api/gan/network/list?netid=" + ganetId, function(data){
+        $.get("/api/gan/network/name?netid=" + currentGANetID, function(data){
             if (data.error !== undefined){
                 msgbox(data.error, false, 6000);
             }else{
-                $(".ganetName").html(`${data.Name}<br>${data.Description}`);
+                $("#gaNetNameInput").val(data[0]);
+                $(".ganetName").html(data[0]);
+                $("#gaNetDescInput").val(data[1]);
+                $(".ganetDesc").text(data[1]);
             }
-        })
+        });
+    }
+
+    //Entry points
+    function initGanetDetails(ganetId){
+        currentGANetID = ganetId;
+        $(".ganetID").text(ganetId);
+        initNetNameAndDesc(ganetId);
     }
 
     //Exit point
@@ -33,4 +97,6 @@
             }
         });
     }
+
+    
 </script>

+ 1 - 1
web/index.html

@@ -106,7 +106,7 @@
                 <!-- Blacklist -->
                 <div id="blacklist" class="functiontab" target="blacklist.html"></div>
 
-                <!-- UPnP based port fowarding -->
+                <!-- Global Area Networking -->
                 <div id="gan" class="functiontab" target="gan.html"></div>
 
                 <!-- Up Time Monitor -->