Added embed server for plugin library

- Added embeded resources server for plugin library
- Added ztnc plugin for global area network
- Added wide mode for side wrapper
This commit is contained in:
Toby Chui
2025-02-28 15:46:57 +08:00
parent bddff0cf2f
commit 53657e8716
54 changed files with 4870 additions and 42 deletions

View File

@@ -0,0 +1,80 @@
package ganserv
import (
"errors"
"log"
"os"
"runtime"
"strings"
)
func TryLoadorAskUserForAuthkey() (string, error) {
//Check for zt auth token
value, exists := os.LookupEnv("ZT_AUTH")
if !exists {
log.Println("Environment variable ZT_AUTH not defined. Trying to load authtoken from file.")
} else {
return value, nil
}
authKey := ""
if runtime.GOOS == "windows" {
if isAdmin() {
//Read the secret file directly
b, err := os.ReadFile("C:\\ProgramData\\ZeroTier\\One\\authtoken.secret")
if err == nil {
log.Println("Zerotier authkey loaded")
authKey = string(b)
} else {
log.Println("Unable to read authkey at C:\\ProgramData\\ZeroTier\\One\\authtoken.secret: ", err.Error())
}
} else {
//Elavate the permission to admin
ak, err := readAuthTokenAsAdmin()
if err == nil {
log.Println("Zerotier authkey loaded")
authKey = ak
} else {
log.Println("Unable to read authkey at C:\\ProgramData\\ZeroTier\\One\\authtoken.secret: ", err.Error())
}
}
} else if runtime.GOOS == "linux" {
if isAdmin() {
//Try to read from source using sudo
ak, err := readAuthTokenAsAdmin()
if err == nil {
log.Println("Zerotier authkey loaded")
authKey = strings.TrimSpace(ak)
} else {
log.Println("Unable to read authkey at /var/lib/zerotier-one/authtoken.secret: ", err.Error())
}
} else {
//Try read from source
b, err := os.ReadFile("/var/lib/zerotier-one/authtoken.secret")
if err == nil {
log.Println("Zerotier authkey loaded")
authKey = string(b)
} else {
log.Println("Unable to read authkey at /var/lib/zerotier-one/authtoken.secret: ", err.Error())
}
}
} else if runtime.GOOS == "darwin" {
b, err := os.ReadFile("/Library/Application Support/ZeroTier/One/authtoken.secret")
if err == nil {
log.Println("Zerotier authkey loaded")
authKey = string(b)
} else {
log.Println("Unable to read authkey at /Library/Application Support/ZeroTier/One/authtoken.secret ", err.Error())
}
}
authKey = strings.TrimSpace(authKey)
if authKey == "" {
return "", errors.New("Unable to load authkey from file")
}
return authKey, nil
}

View File

@@ -0,0 +1,37 @@
//go:build linux
// +build linux
package ganserv
import (
"os"
"os/exec"
"os/user"
"strings"
"aroz.org/zoraxy/zerotiernc/mod/utils"
)
func readAuthTokenAsAdmin() (string, error) {
if utils.FileExists("./conf/authtoken.secret") {
authKey, err := os.ReadFile("./conf/authtoken.secret")
if err == nil {
return strings.TrimSpace(string(authKey)), nil
}
}
cmd := exec.Command("sudo", "cat", "/var/lib/zerotier-one/authtoken.secret")
output, err := cmd.Output()
if err != nil {
return "", err
}
return string(output), nil
}
func isAdmin() bool {
currentUser, err := user.Current()
if err != nil {
return false
}
return currentUser.Username == "root"
}

View File

@@ -0,0 +1,73 @@
//go:build windows
// +build windows
package ganserv
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"syscall"
"time"
"aroz.org/zoraxy/ztnc/mod/utils"
"golang.org/x/sys/windows"
)
// Use admin permission to read auth token on Windows
func readAuthTokenAsAdmin() (string, error) {
//Check if the previous startup already extracted the authkey
if utils.FileExists("./conf/authtoken.secret") {
authKey, err := os.ReadFile("./conf/authtoken.secret")
if err == nil {
return strings.TrimSpace(string(authKey)), nil
}
}
verb := "runas"
exe := "cmd.exe"
cwd, _ := os.Getwd()
output, _ := filepath.Abs(filepath.Join("./conf/", "authtoken.secret"))
os.WriteFile(output, []byte(""), 0775)
args := fmt.Sprintf("/C type \"C:\\ProgramData\\ZeroTier\\One\\authtoken.secret\" > \"" + output + "\"")
verbPtr, _ := syscall.UTF16PtrFromString(verb)
exePtr, _ := syscall.UTF16PtrFromString(exe)
cwdPtr, _ := syscall.UTF16PtrFromString(cwd)
argPtr, _ := syscall.UTF16PtrFromString(args)
var showCmd int32 = 1 //SW_NORMAL
err := windows.ShellExecute(0, verbPtr, exePtr, argPtr, cwdPtr, showCmd)
if err != nil {
return "", err
}
log.Println("Please click agree to allow access to ZeroTier authtoken from ProgramData")
retry := 0
time.Sleep(3 * time.Second)
for !utils.FileExists("./conf/authtoken.secret") && retry < 10 {
time.Sleep(3 * time.Second)
log.Println("Waiting for ZeroTier authtoken extraction...")
retry++
}
authKey, err := os.ReadFile("./conf/authtoken.secret")
if err != nil {
return "", err
}
return strings.TrimSpace(string(authKey)), nil
}
// Check if admin on Windows
func isAdmin() bool {
_, err := os.Open("\\\\.\\PHYSICALDRIVE0")
if err != nil {
return false
}
return true
}

View File

@@ -0,0 +1,130 @@
package ganserv
import (
"log"
"net"
"aroz.org/zoraxy/ztnc/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
AuthToken string
ApiPort int
}
type NetworkMetaData struct {
Desc string
}
type MemberMetaData struct {
Name string
}
type NetworkManager struct {
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")
//Load network metadata
networkMeta := map[string]NetworkMetaData{}
if option.Database.KeyExists("ganserv", "networkmeta") {
option.Database.Read("ganserv", "networkmeta", &networkMeta)
}
//Start the zerotier instance if not exists
//Get controller info
instanceInfo, err := getControllerInfo(option.AuthToken, option.ApiPort)
if err != nil {
log.Println("ZeroTier connection failed: ", err.Error())
return &NetworkManager{
authToken: option.AuthToken,
apiPort: option.ApiPort,
ControllerID: "",
option: option,
networksMetadata: networkMeta,
}
}
return &NetworkManager{
authToken: option.AuthToken,
apiPort: option.ApiPort,
ControllerID: instanceInfo.Address,
option: option,
networksMetadata: networkMeta,
}
}
func (m *NetworkManager) GetNetworkMetaData(netid string) *NetworkMetaData {
md, ok := m.networksMetadata[netid]
if !ok {
return &NetworkMetaData{}
}
return &md
}
func (m *NetworkManager) WriteNetworkMetaData(netid string, meta *NetworkMetaData) {
m.networksMetadata[netid] = *meta
m.option.Database.Write("ganserv", "networkmeta", m.networksMetadata)
}
func (m *NetworkManager) GetMemberMetaData(netid string, memid string) *MemberMetaData {
thisMemberData := MemberMetaData{}
m.option.Database.Read("ganserv", "memberdata_"+netid+"_"+memid, &thisMemberData)
return &thisMemberData
}
func (m *NetworkManager) WriteMemeberMetaData(netid string, memid string, meta *MemberMetaData) {
m.option.Database.Write("ganserv", "memberdata_"+netid+"_"+memid, meta)
}

View File

@@ -0,0 +1,504 @@
package ganserv
import (
"encoding/json"
"net"
"net/http"
"regexp"
"strings"
"aroz.org/zoraxy/ztnc/mod/utils"
)
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
}
m.ControllerID = instanceInfo.Address
}
js, _ := json.Marshal(m.ControllerID)
utils.SendJSONResponse(w, string(js))
}
func (m *NetworkManager) HandleAddNetwork(w http.ResponseWriter, r *http.Request) {
networkInfo, err := m.createNetwork()
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//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(networkInfo.Nwid)
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
}
if !m.networkExists(networkID) {
utils.SendErrorResponse(w, "network id not exists")
return
}
err = m.deleteNetwork(networkID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
}
utils.SendOK(w)
}
func (m *NetworkManager) HandleListNetwork(w http.ResponseWriter, r *http.Request) {
netid, _ := utils.GetPara(r, "netid")
if netid != "" {
targetNetInfo, err := m.getNetworkInfoById(netid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
js, _ := json.Marshal(targetNetInfo)
utils.SendJSONResponse(w, string(js))
} else {
// Return the list of networks as JSON
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) HandleNetworkDetails(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
utils.SendErrorResponse(w, "netid not given")
return
}
targetNetwork, err := m.getNetworkInfoById(netid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
js, _ := json.Marshal(targetNetwork)
utils.SendJSONResponse(w, string(js))
}
func (m *NetworkManager) HandleSetRanges(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
utils.SendErrorResponse(w, "netid not given")
return
}
cidr, err := utils.PostPara(r, "cidr")
if err != nil {
utils.SendErrorResponse(w, "cidr not given")
return
}
ipstart, err := utils.PostPara(r, "ipstart")
if err != nil {
utils.SendErrorResponse(w, "ipstart not given")
return
}
ipend, err := utils.PostPara(r, "ipend")
if err != nil {
utils.SendErrorResponse(w, "ipend not given")
return
}
//Validate the CIDR is real, the ip range is within the CIDR range
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
utils.SendErrorResponse(w, "invalid cidr string given")
return
}
startIP := net.ParseIP(ipstart)
endIP := net.ParseIP(ipend)
if startIP == nil || endIP == nil {
utils.SendErrorResponse(w, "invalid start or end ip given")
return
}
withinRange := ipnet.Contains(startIP) && ipnet.Contains(endIP)
if !withinRange {
utils.SendErrorResponse(w, "given CIDR did not cover all of the start to end ip range")
return
}
err = m.configureNetwork(netid, startIP.String(), endIP.String(), strings.TrimSpace(cidr))
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
// Handle listing of network members. Set details=true for listing all details
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")
memberIds, err := m.getNetworkMembers(netid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
if details == "" {
//Only show client ids
js, _ := json.Marshal(memberIds)
utils.SendJSONResponse(w, string(js))
} else {
//Show detail members info
detailMemberInfo := []*MemberInfo{}
for _, thisMemberId := range memberIds {
memInfo, err := m.getNetworkMemberInfo(netid, thisMemberId)
if err == nil {
detailMemberInfo = append(detailMemberInfo, memInfo)
}
}
js, _ := json.Marshal(detailMemberInfo)
utils.SendJSONResponse(w, string(js))
}
}
// Handle Authorization of members
func (m *NetworkManager) HandleMemberAuthorization(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
utils.SendErrorResponse(w, "net id not set")
return
}
memberid, err := utils.PostPara(r, "memid")
if err != nil {
utils.SendErrorResponse(w, "memid not set")
return
}
//Check if the target memeber exists
if !m.memberExistsInNetwork(netid, memberid) {
utils.SendErrorResponse(w, "member not exists in given network")
return
}
setAuthorized, err := utils.PostPara(r, "auth")
if err != nil || setAuthorized == "" {
//Get the member authorization state
memberInfo, err := m.getNetworkMemberInfo(netid, memberid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
js, _ := json.Marshal(memberInfo.Authorized)
utils.SendJSONResponse(w, string(js))
} else if setAuthorized == "true" {
m.AuthorizeMember(netid, memberid, true)
} else if setAuthorized == "false" {
m.AuthorizeMember(netid, memberid, false)
} else {
utils.SendErrorResponse(w, "unknown operation state: "+setAuthorized)
}
}
// Handle Delete or Add IP for a member in a network
func (m *NetworkManager) HandleMemberIP(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
utils.SendErrorResponse(w, "net id not set")
return
}
memberid, err := utils.PostPara(r, "memid")
if err != nil {
utils.SendErrorResponse(w, "memid not set")
return
}
opr, err := utils.PostPara(r, "opr")
if err != nil {
utils.SendErrorResponse(w, "opr not defined")
return
}
targetip, _ := utils.PostPara(r, "ip")
memberInfo, err := m.getNetworkMemberInfo(netid, memberid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
if opr == "add" {
if targetip == "" {
utils.SendErrorResponse(w, "ip not set")
return
}
if !isValidIPAddr(targetip) {
utils.SendErrorResponse(w, "ip address not valid")
return
}
newIpList := append(memberInfo.IPAssignments, targetip)
err = m.setAssignedIps(netid, memberid, newIpList)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
} else if opr == "del" {
if targetip == "" {
utils.SendErrorResponse(w, "ip not set")
return
}
//Delete user ip from the list
newIpList := []string{}
for _, thisIp := range memberInfo.IPAssignments {
if thisIp != targetip {
newIpList = append(newIpList, thisIp)
}
}
err = m.setAssignedIps(netid, memberid, newIpList)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
} else if opr == "get" {
js, _ := json.Marshal(memberInfo.IPAssignments)
utils.SendJSONResponse(w, string(js))
} else {
utils.SendErrorResponse(w, "unsupported opr type: "+opr)
}
}
// Handle naming for members
func (m *NetworkManager) HandleMemberNaming(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
utils.SendErrorResponse(w, "net id not set")
return
}
memberid, err := utils.PostPara(r, "memid")
if err != nil {
utils.SendErrorResponse(w, "memid not set")
return
}
if !m.memberExistsInNetwork(netid, memberid) {
utils.SendErrorResponse(w, "target member not exists in given network")
return
}
//Read memeber data
targetMemberData := m.GetMemberMetaData(netid, memberid)
newname, err := utils.PostPara(r, "name")
if err != nil {
//Send over the member data
js, _ := json.Marshal(targetMemberData)
utils.SendJSONResponse(w, string(js))
} else {
//Write member data
targetMemberData.Name = newname
m.WriteMemeberMetaData(netid, memberid, targetMemberData)
utils.SendOK(w)
}
}
// Handle delete of a given memver
func (m *NetworkManager) HandleMemberDelete(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
utils.SendErrorResponse(w, "net id not set")
return
}
memberid, err := utils.PostPara(r, "memid")
if err != nil {
utils.SendErrorResponse(w, "memid not set")
return
}
//Check if that member is authorized.
memberInfo, err := m.getNetworkMemberInfo(netid, memberid)
if err != nil {
utils.SendErrorResponse(w, "member not exists in given GANet")
return
}
if memberInfo.Authorized {
//Deauthorized this member before deleting
m.AuthorizeMember(netid, memberid, false)
}
//Remove the memeber
err = m.deleteMember(netid, memberid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
// Check if a given network id is a network hosted on this zoraxy node
func (m *NetworkManager) IsLocalGAN(networkId string) bool {
networks, err := m.listNetworkIds()
if err != nil {
return false
}
for _, network := range networks {
if network == networkId {
return true
}
}
return false
}
// Handle server instant joining a given network
func (m *NetworkManager) HandleServerJoinNetwork(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
utils.SendErrorResponse(w, "net id not set")
return
}
//Check if the target network is a network hosted on this server
if !m.IsLocalGAN(netid) {
utils.SendErrorResponse(w, "given network is not a GAN hosted on this node")
return
}
if m.memberExistsInNetwork(netid, m.ControllerID) {
utils.SendErrorResponse(w, "controller already inside network")
return
}
//Join the network
err = m.joinNetwork(netid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
// Handle server instant leaving a given network
func (m *NetworkManager) HandleServerLeaveNetwork(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
utils.SendErrorResponse(w, "net id not set")
return
}
//Check if the target network is a network hosted on this server
if !m.IsLocalGAN(netid) {
utils.SendErrorResponse(w, "given network is not a GAN hosted on this node")
return
}
//Leave the network
err = m.leaveNetwork(netid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Remove it from target network if it is authorized
err = m.deleteMember(netid, m.ControllerID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}

View File

@@ -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
}

View File

@@ -0,0 +1,55 @@
package ganserv_test
import (
"fmt"
"net"
"strconv"
"testing"
"aroz.org/zoraxy/ztnc/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,
})
}
}

View File

@@ -0,0 +1,55 @@
package ganserv
import (
"net"
)
//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
}
}
}
func isValidIPAddr(ipAddr string) bool {
ip := net.ParseIP(ipAddr)
if ip == nil {
return false
}
return true
}
func ipWithinCIDR(ipAddr string, cidr string) bool {
// Parse the CIDR string
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
return false
}
// Parse the IP address
ip := net.ParseIP(ipAddr)
if ip == nil {
return false
}
// Check if the IP address is in the CIDR range
return ipNet.Contains(ip)
}

View File

@@ -0,0 +1,669 @@
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,omitempty"`
ForceTCPRelay bool `json:"forceTcpRelay,omitempty"`
HomeDir string `json:"homeDir,omitempty"`
ListeningOn []string `json:"listeningOn,omitempty"`
PortMappingEnabled bool `json:"portMappingEnabled,omitempty"`
PrimaryPort int `json:"primaryPort,omitempty"`
SecondaryPort int `json:"secondaryPort,omitempty"`
SoftwareUpdate string `json:"softwareUpdate,omitempty"`
SoftwareUpdateChannel string `json:"softwareUpdateChannel,omitempty"`
SurfaceAddresses []string `json:"surfaceAddresses,omitempty"`
TertiaryPort int `json:"tertiaryPort,omitempty"`
} `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"`
}
type MemberInfo struct {
ActiveBridge bool `json:"activeBridge"`
Address string `json:"address"`
AuthenticationExpiryTime int `json:"authenticationExpiryTime"`
Authorized bool `json:"authorized"`
Capabilities []interface{} `json:"capabilities"`
CreationTime int64 `json:"creationTime"`
ID string `json:"id"`
Identity string `json:"identity"`
IPAssignments []string `json:"ipAssignments"`
LastAuthorizedCredential interface{} `json:"lastAuthorizedCredential"`
LastAuthorizedCredentialType string `json:"lastAuthorizedCredentialType"`
LastAuthorizedTime int `json:"lastAuthorizedTime"`
LastDeauthorizedTime int `json:"lastDeauthorizedTime"`
NoAutoAssignIps bool `json:"noAutoAssignIps"`
Nwid string `json:"nwid"`
Objtype string `json:"objtype"`
RemoteTraceLevel int `json:"remoteTraceLevel"`
RemoteTraceTarget interface{} `json:"remoteTraceTarget"`
Revision int `json:"revision"`
SsoExempt bool `json:"ssoExempt"`
Tags []interface{} `json:"tags"`
VMajor int `json:"vMajor"`
VMinor int `json:"vMinor"`
VProto int `json:"vProto"`
VRev int `json:"vRev"`
}
// 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) setAssignedIps(networkID string, memid string, newIps []string) error {
url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + networkID + "/member/" + memid
data := map[string]interface{}{
"ipAssignments": newIps,
}
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
}
func (m *NetworkManager) memberExistsInNetwork(netid string, memid string) bool {
//Get a list of member
memberids, err := m.getNetworkMembers(netid)
if err != nil {
return false
}
for _, thisMemberId := range memberids {
if thisMemberId == memid {
return true
}
}
return false
}
// Get a network memeber info by netid and memberid
func (m *NetworkManager) getNetworkMemberInfo(netid string, memberid string) (*MemberInfo, error) {
req, err := http.NewRequest("GET", "http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/"+netid+"/member/"+memberid, 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()
thisMemeberInfo := &MemberInfo{}
payload, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(payload, &thisMemeberInfo)
if err != nil {
return nil, err
}
return thisMemeberInfo, nil
}
// Set the authorization state of a member
func (m *NetworkManager) AuthorizeMember(netid string, memberid string, setAuthorized bool) error {
url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + netid + "/member/" + memberid
payload := []byte(`{"authorized": true}`)
if !setAuthorized {
payload = []byte(`{"authorized": false}`)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
if err != nil {
return err
}
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()
if resp.StatusCode != 200 {
return errors.New("network error. Status code: " + strconv.Itoa(resp.StatusCode))
}
return nil
}
// Delete a member from the network
func (m *NetworkManager) deleteMember(netid string, memid string) error {
req, err := http.NewRequest("DELETE", "http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/"+netid+"/member/"+memid, nil)
if err != nil {
return err
}
req.Header.Set("X-Zt1-Auth", os.ExpandEnv(m.authToken))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return errors.New("network error. Status code: " + strconv.Itoa(resp.StatusCode))
}
return nil
}
// Make the host to join a given network
func (m *NetworkManager) joinNetwork(netid string) error {
req, err := http.NewRequest("POST", "http://localhost:"+strconv.Itoa(m.apiPort)+"/network/"+netid, nil)
if err != nil {
return err
}
req.Header.Set("X-Zt1-Auth", os.ExpandEnv(m.authToken))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return errors.New("network error. Status code: " + strconv.Itoa(resp.StatusCode))
}
return nil
}
// Make the host to leave a given network
func (m *NetworkManager) leaveNetwork(netid string) error {
req, err := http.NewRequest("DELETE", "http://localhost:"+strconv.Itoa(m.apiPort)+"/network/"+netid, nil)
if err != nil {
return err
}
req.Header.Set("X-Zt1-Auth", os.ExpandEnv(m.authToken))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return errors.New("network error. Status code: " + strconv.Itoa(resp.StatusCode))
}
return nil
}