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 }