mirror of
https://github.com/jeessy2/ddns-go.git
synced 2025-12-20 01:00:12 +08:00
feat: Add basic IBM NS1 Connect support (#1588)
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
## 特性
|
||||
|
||||
- 支持Mac、Windows、Linux系统,支持ARM、x86、RISC-V架构
|
||||
- 支持的域名服务商 `阿里云` `腾讯云` `Dnspod` `Cloudflare` `华为云` `Callback` `百度云` `Porkbun` `GoDaddy` `Namecheap` `NameSilo` `Dynadot` `DNSLA` `时代互联` `Eranet` `Gcore`
|
||||
- 支持的域名服务商 `阿里云` `腾讯云` `Dnspod` `Cloudflare` `华为云` `Callback` `百度云` `Porkbun` `GoDaddy` `Namecheap` `NameSilo` `Dynadot` `DNSLA` `时代互联` `Eranet` `Gcore` `IBM NS1 Connect`
|
||||
- 支持接口/网卡/[命令](https://github.com/jeessy2/ddns-go/wiki/通过命令获取IP参考)获取IP
|
||||
- 支持以服务的方式运行
|
||||
- 默认间隔5分钟同步一次
|
||||
|
||||
@@ -16,7 +16,7 @@ Automatically obtain your public IPv4 or IPv6 address and resolve it to the corr
|
||||
## Features
|
||||
|
||||
- Support Mac, Windows, Linux system, support ARM, x86, RISC-V architecture
|
||||
- Support domain service providers `Aliyun` `Tencent` `Dnspod` `Cloudflare` `Huawei` `Callback` `Baidu` `Porkbun` `GoDaddy` `Namecheap` `NameSilo` `Dynadot` `DNSLA` `Nowcn` `Eranet` `Gcore`
|
||||
- Support domain service providers `Aliyun` `Tencent` `Dnspod` `Cloudflare` `Huawei` `Callback` `Baidu` `Porkbun` `GoDaddy` `Namecheap` `NameSilo` `Dynadot` `DNSLA` `Nowcn` `Eranet` `Gcore` `IBM NS1 Connect`
|
||||
- Support interface / netcard / command to get IP
|
||||
- Support running as a service
|
||||
- Default interval is 5 minutes
|
||||
|
||||
@@ -100,6 +100,8 @@ func RunOnce() {
|
||||
dnsSelected = &Gcore{}
|
||||
case "edgeone":
|
||||
dnsSelected = &EdgeOne{}
|
||||
case "nsone":
|
||||
dnsSelected = &NSOne{}
|
||||
default:
|
||||
dnsSelected = &Alidns{}
|
||||
}
|
||||
|
||||
372
dns/nsone.go
Normal file
372
dns/nsone.go
Normal file
@@ -0,0 +1,372 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/jeessy2/ddns-go/v6/config"
|
||||
"github.com/jeessy2/ddns-go/v6/util"
|
||||
)
|
||||
|
||||
const nsoneAPIEndpoint = "https://api.nsone.net/v1/zones"
|
||||
|
||||
type NSOne struct {
|
||||
DNS config.DNS
|
||||
Domains config.Domains
|
||||
TTL int
|
||||
}
|
||||
|
||||
type NSOneZone struct {
|
||||
AssignedNameservers []string `json:"assigned_nameservers"`
|
||||
DNSServers []string `json:"dns_servers"`
|
||||
Expiry int `json:"expiry"`
|
||||
Name string `json:"name"`
|
||||
Link string `json:"link"`
|
||||
PrimaryMaster string `json:"primary_master"`
|
||||
Hostmaster string `json:"hostmaster"`
|
||||
ID string `json:"id"`
|
||||
Meta struct {
|
||||
Asn []string `json:"asn"`
|
||||
CaProvince []string `json:"ca_province"`
|
||||
Connections int `json:"connections"`
|
||||
Country []string `json:"country"`
|
||||
Georegion []string `json:"georegion"`
|
||||
HighWatermark float64 `json:"high_watermark"`
|
||||
IPPrefixes []string `json:"ip_prefixes"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
LoadAvg float64 `json:"loadAvg"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
LowWatermark float64 `json:"low_watermark"`
|
||||
Note string `json:"note"`
|
||||
Priority int `json:"priority"`
|
||||
Pulsar string `json:"pulsar"`
|
||||
Requests int `json:"requests"`
|
||||
Up bool `json:"up"`
|
||||
UsState []string `json:"us_state"`
|
||||
Weight float64 `json:"weight"`
|
||||
} `json:"meta"`
|
||||
NetworkPools []string `json:"network_pools"`
|
||||
Networks []int `json:"networks"`
|
||||
NxTTL int `json:"nx_ttl"`
|
||||
Serial int `json:"serial"`
|
||||
Primary struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Secondaries []struct {
|
||||
IP string `json:"ip"`
|
||||
Network int `json:"network"`
|
||||
Notify bool `json:"notify"`
|
||||
Port int `json:"port"`
|
||||
Tsig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Hash string `json:"hash"`
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
} `json:"tsig"`
|
||||
} `json:"secondaries"`
|
||||
} `json:"primary"`
|
||||
Refresh int `json:"refresh"`
|
||||
Retry int `json:"retry"`
|
||||
Secondary struct {
|
||||
Status string `json:"status"`
|
||||
Error string `json:"error"`
|
||||
LastXfr int `json:"last_xfr"`
|
||||
LastTry int `json:"last_try"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Expired bool `json:"expired"`
|
||||
PrimaryIP string `json:"primary_ip"`
|
||||
PrimaryPort int `json:"primary_port"`
|
||||
PrimaryNetwork int `json:"primary_network"`
|
||||
Tsig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Hash string `json:"hash"`
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
SignedNotifies bool `json:"signed_notifies"`
|
||||
} `json:"tsig"`
|
||||
OtherPorts []int `json:"other_ports"`
|
||||
OtherIps []string `json:"other_ips"`
|
||||
OtherNetworks []int `json:"other_networks"`
|
||||
OtherNotifyOnly []bool `json:"other_notify_only"`
|
||||
} `json:"secondary"`
|
||||
TTL int `json:"ttl"`
|
||||
Zone string `json:"zone"`
|
||||
Views []string `json:"views"`
|
||||
LocalTags []string `json:"local_tags"`
|
||||
Tags struct {
|
||||
ID int64 `json:"id"`
|
||||
} `json:"tags"`
|
||||
CreatedAt int `json:"created_at"`
|
||||
UpdatedAt int `json:"updated_at"`
|
||||
Dnssec bool `json:"dnssec"`
|
||||
Signatures []struct {
|
||||
Answer []string `json:"answer"`
|
||||
} `json:"signatures"`
|
||||
Presigned bool `json:"presigned"`
|
||||
IDVersion int `json:"id_version"`
|
||||
ActiveVersion bool `json:"active_version"`
|
||||
}
|
||||
|
||||
type NSOneRecordAnswer struct {
|
||||
Answer []string `json:"answer"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Meta struct {
|
||||
ID int64 `json:"id,omitempty"`
|
||||
} `json:"meta,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
Feeds []struct {
|
||||
Source string `json:"source,omitempty"`
|
||||
Feed string `json:"feed,omitempty"`
|
||||
} `json:"feeds,omitempty"`
|
||||
}
|
||||
|
||||
type NSOneRecordResponse struct {
|
||||
Answers []NSOneRecordAnswer `json:"answers"`
|
||||
Domain string `json:"domain"`
|
||||
Filters []struct {
|
||||
Config struct {
|
||||
Eliminate bool `json:"eliminate"`
|
||||
} `json:"config"`
|
||||
} `json:"filters"`
|
||||
Link string `json:"link"`
|
||||
Meta struct {
|
||||
ID int64 `json:"id"`
|
||||
} `json:"meta"`
|
||||
Networks []int `json:"networks"`
|
||||
Regions struct {
|
||||
ID int64 `json:"id"`
|
||||
} `json:"regions"`
|
||||
Tier int `json:"tier"`
|
||||
TTL int `json:"ttl"`
|
||||
OverrideTTL bool `json:"override_ttl"`
|
||||
Type string `json:"type"`
|
||||
UseClientSubnet bool `json:"use_client_subnet"`
|
||||
Zone string `json:"zone"`
|
||||
ZoneName string `json:"zone_name"`
|
||||
BlockedTags []string `json:"blocked_tags"`
|
||||
LocalTags []string `json:"local_tags"`
|
||||
Tags struct {
|
||||
ID int64 `json:"id"`
|
||||
} `json:"tags"`
|
||||
OverrideAddressRecords bool `json:"override_address_records"`
|
||||
Signatures []struct {
|
||||
Answer []string `json:"answer"`
|
||||
} `json:"signatures"`
|
||||
CreatedAt int `json:"created_at"`
|
||||
UpdatedAt int `json:"updated_at"`
|
||||
ID string `json:"id"`
|
||||
Customer int `json:"customer"`
|
||||
Feeds []struct {
|
||||
Source string `json:"source"`
|
||||
Feed string `json:"feed"`
|
||||
} `json:"feeds"`
|
||||
}
|
||||
|
||||
type NSOneRecordRequest struct {
|
||||
Answers []NSOneRecordAnswer `json:"answers"`
|
||||
Domain string `json:"domain"`
|
||||
TTL int `json:"ttl"`
|
||||
Type string `json:"type"`
|
||||
Zone string `json:"zone"`
|
||||
}
|
||||
|
||||
func (nsone *NSOne) Init(dnsConf *config.DnsConfig, ipv4cache *util.IpCache, ipv6cache *util.IpCache) {
|
||||
nsone.Domains.Ipv4Cache = ipv4cache
|
||||
nsone.Domains.Ipv6Cache = ipv6cache
|
||||
nsone.DNS = dnsConf.DNS
|
||||
nsone.Domains.GetNewIp(dnsConf)
|
||||
if dnsConf.TTL == "" {
|
||||
nsone.TTL = 60
|
||||
} else {
|
||||
ttl, err := strconv.Atoi(dnsConf.TTL)
|
||||
if err != nil {
|
||||
// Default TTL in documentation is 1 hour
|
||||
nsone.TTL = 3600
|
||||
} else {
|
||||
nsone.TTL = ttl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (nsone *NSOne) AddUpdateDomainRecords() config.Domains {
|
||||
nsone.addUpdateDomainRecords("A")
|
||||
nsone.addUpdateDomainRecords("AAAA")
|
||||
return nsone.Domains
|
||||
}
|
||||
|
||||
func (nsone *NSOne) addUpdateDomainRecords(recordType string) {
|
||||
ipAddr, domains := nsone.Domains.GetNewIpResult(recordType)
|
||||
|
||||
if ipAddr == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, domain := range domains {
|
||||
zoneInfo, err := nsone.getZone(domain)
|
||||
if err != nil {
|
||||
util.Log("查询域名信息发生异常! %s", err)
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
continue
|
||||
}
|
||||
|
||||
if zoneInfo == nil {
|
||||
util.Log("在DNS服务商中未找到根域名: %s", domain.DomainName)
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
continue
|
||||
}
|
||||
|
||||
existingRecord, err := nsone.getRecord(domain, recordType)
|
||||
if err != nil {
|
||||
util.Log("查询域名信息发生异常! %s", err)
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
continue
|
||||
}
|
||||
|
||||
if existingRecord != nil {
|
||||
nsone.updateRecord(domain, recordType, ipAddr, existingRecord)
|
||||
} else {
|
||||
nsone.createRecord(domain, recordType, ipAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (nsone *NSOne) getZone(domain *config.Domain) (*NSOneZone, error) {
|
||||
var result NSOneZone
|
||||
params := url.Values{}
|
||||
params.Set("records", "false")
|
||||
|
||||
err := nsone.request(
|
||||
"GET",
|
||||
fmt.Sprintf("%s/%s?%s", nsoneAPIEndpoint, domain.DomainName, params.Encode()),
|
||||
nil,
|
||||
&result,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (nsone *NSOne) getRecord(domain *config.Domain, recordType string) (*NSOneRecordResponse, error) {
|
||||
var result NSOneRecordResponse
|
||||
params := url.Values{}
|
||||
params.Set("records", "false")
|
||||
|
||||
err := nsone.request(
|
||||
"GET",
|
||||
fmt.Sprintf("%s/%s/%s/%s?%s", nsoneAPIEndpoint, domain.DomainName, domain.GetFullDomain(), recordType, params.Encode()),
|
||||
nil,
|
||||
&result,
|
||||
)
|
||||
|
||||
if err == nil && len(result.Answers) > 0 {
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (nsone *NSOne) createRecord(domain *config.Domain, recordType string, ipAddr string) {
|
||||
recordName := domain.GetFullDomain()
|
||||
request := NSOneRecordRequest{
|
||||
Answers: []NSOneRecordAnswer{
|
||||
{
|
||||
Answer: []string{
|
||||
ipAddr,
|
||||
},
|
||||
},
|
||||
},
|
||||
Domain: recordName,
|
||||
TTL: nsone.TTL,
|
||||
Type: recordType,
|
||||
Zone: domain.DomainName,
|
||||
}
|
||||
|
||||
var response NSOneRecordResponse
|
||||
err := nsone.request(
|
||||
"PUT",
|
||||
fmt.Sprintf("%s/%s/%s/%s", nsoneAPIEndpoint, domain.DomainName, recordName, recordType),
|
||||
request,
|
||||
&response,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, err)
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
return
|
||||
}
|
||||
|
||||
util.Log("新增域名解析 %s 成功! IP: %s", domain, ipAddr)
|
||||
domain.UpdateStatus = config.UpdatedSuccess
|
||||
}
|
||||
|
||||
func (nsone *NSOne) updateRecord(domain *config.Domain, recordType string, ipAddr string, existingRecord *NSOneRecordResponse) {
|
||||
if len(existingRecord.Answers) > 0 && len(existingRecord.Answers[0].Answer) > 0 {
|
||||
if existingRecord.Answers[0].Answer[0] == ipAddr {
|
||||
util.Log("你的IP %s 没有变化, 域名 %s", ipAddr, domain)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
recordName := domain.GetFullDomain()
|
||||
request := NSOneRecordRequest{
|
||||
Answers: []NSOneRecordAnswer{
|
||||
{
|
||||
Answer: []string{
|
||||
ipAddr,
|
||||
},
|
||||
},
|
||||
},
|
||||
Domain: recordName,
|
||||
TTL: nsone.TTL,
|
||||
Type: recordType,
|
||||
Zone: domain.DomainName,
|
||||
}
|
||||
|
||||
var response NSOneRecordResponse
|
||||
err := nsone.request(
|
||||
"POST",
|
||||
fmt.Sprintf("%s/%s/%s/%s", nsoneAPIEndpoint, domain.DomainName, recordName, recordType),
|
||||
request,
|
||||
&response,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, err)
|
||||
domain.UpdateStatus = config.UpdatedFailed
|
||||
return
|
||||
}
|
||||
|
||||
util.Log("更新域名解析 %s 成功! IP: %s", domain, ipAddr)
|
||||
domain.UpdateStatus = config.UpdatedSuccess
|
||||
}
|
||||
|
||||
func (nsone *NSOne) request(method string, url string, data interface{}, result interface{}) (err error) {
|
||||
jsonStr := make([]byte, 0)
|
||||
if data != nil {
|
||||
jsonStr, _ = json.Marshal(data)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(
|
||||
method,
|
||||
url,
|
||||
bytes.NewBuffer(jsonStr),
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("X-NSONE-Key", nsone.DNS.Secret)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := util.CreateHTTPClient()
|
||||
resp, err := client.Do(req)
|
||||
err = util.GetHTTPResponse(resp, err, result)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -244,6 +244,18 @@ const DNS_PROVIDERS = {
|
||||
"zh-cn": "<a target='_blank' href='https://console.cloud.tencent.com/cam/capi'>创建腾讯云 API 密钥</a>",
|
||||
}
|
||||
},
|
||||
nsone: {
|
||||
name: {
|
||||
"en": "IBM NS1 Connect",
|
||||
"zh-cn": "IBM NS1 Connect",
|
||||
},
|
||||
idLabel: "",
|
||||
secretLabel: "API Key",
|
||||
helpHtml: {
|
||||
"en": "<a target='_blank' href='https://my.nsone.net/#/account/settings/keys'>Create API Key</a>",
|
||||
"zh-cn": "<a target='_blank' href='https://my.nsone.net/#/account/settings/keys'>创建 API 密钥</a>",
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const SVG_CODE = {
|
||||
|
||||
Reference in New Issue
Block a user