Toby Chui 53657e8716 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
2025-02-28 15:46:57 +08:00

670 lines
17 KiB
Go

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
}