Add cloudflare-ddns command

This commit is contained in:
世界 2022-04-20 16:06:39 +08:00
parent 12e645e9d2
commit 14da4a9d2e
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
10 changed files with 234 additions and 12 deletions

View file

@ -26,4 +26,24 @@ wget 'https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Countr
```shell
go install -v -trimpath -ldflags "-s -w -buildid=" ./cli/ss-local
```
### ddns
```shell
GOBIN=/usr/local/bin/ go install -v -trimpath -ldflags "-s -w -buildid=" ./cli/cloudflare-ddns
cat > /usr/local/etc/ddns.json <<EOF
{
"cloudflare_api_key": "",
"cloudflare_api_email": "",
"domain": "example.com",
"over_proxy": false
}
EOF
sudo cp ./cli/cloudflare-ddns/ddns.service /etc/systemd/system
sudo systemctl daemon-reload
sudo systemctl enable ddns
sudo systemctl start ddns
```

1
cli/cloudflare-ddns/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/config.json

View file

@ -0,0 +1,6 @@
{
"cloudflare_api_key": "",
"cloudflare_api_email": "",
"domain": "example.com",
"over_proxy": false
}

View file

@ -0,0 +1,11 @@
[Unit]
Description=DDNS Service
After=network.target
[Service]
ExecStart=/usr/local/bin/cloudflare-ddns -c /usr/local/etc/ddns.json
Restart=on-failure
RestartPreventExitStatus=23
[Install]
WantedBy=multi-user.target

156
cli/cloudflare-ddns/main.go Normal file
View file

@ -0,0 +1,156 @@
package main
import (
"context"
"encoding/json"
"io/ioutil"
"net/netip"
"strings"
"github.com/cloudflare/cloudflare-go"
"github.com/sagernet/sing"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/vishvananda/netlink"
)
var configPath string
func main() {
command := &cobra.Command{
Use: "cloudflare-ddns [-c config.json]",
Run: run,
Version: sing.VersionStr,
}
command.Flags().StringVarP(&configPath, "config", "c", "config.json", "set config path")
if err := command.Execute(); err != nil {
logrus.Fatal(err)
}
}
type Config struct {
APIKey string `json:"api_key"`
APIEmail string `json:"api_email"`
Domain string `json:"domain"`
OverProxy bool `json:"over_proxy"`
}
var (
domain string
overProxy bool
)
var (
flare *cloudflare.API
zone *cloudflare.Zone
)
func run(cmd *cobra.Command, args []string) {
c := new(Config)
cc, err := ioutil.ReadFile(configPath)
if err != nil {
logrus.Fatal(err)
}
err = json.Unmarshal(cc, c)
if err != nil {
logrus.Fatal(err)
}
domain = c.Domain
overProxy = c.OverProxy
flare, err = cloudflare.New(c.APIKey, c.APIEmail)
if err != nil {
logrus.Fatal(err)
}
zone, err = findZoneForDomain(domain)
if err != nil {
logrus.Fatal(err)
}
checkUpdate()
events := make(chan netlink.AddrUpdate, 1)
err = netlink.AddrSubscribe(events, nil)
if err != nil {
logrus.Fatal(err)
}
for event := range events {
addr, _ := netip.AddrFromSlice(event.LinkAddress.IP)
if !N.IsPublicAddr(addr) {
continue
}
checkUpdate()
}
}
func findZoneForDomain(domain string) (*cloudflare.Zone, error) {
zones, err := flare.ListZones(context.Background())
if err != nil {
return nil, err
}
for _, z := range zones {
if strings.HasSuffix(domain, z.Name) {
return &z, nil
}
}
return nil, E.New("unable to find zone for domain ", domain)
}
func checkUpdate() {
addrs, err := N.LocalPublicAddrs()
if err != nil {
logrus.Fatal(err)
}
addrMap := make(map[string]netip.Addr)
for _, addr := range addrs {
addrMap[addr.String()] = addr
}
if common.IsEmpty(addrs) {
logrus.Warn("this device has no public addresses!")
}
records, err := flare.DNSRecords(context.Background(), zone.ID, cloudflare.DNSRecord{
Name: domain,
})
if err != nil {
logrus.Fatal(err)
}
for _, record := range records {
if !(record.Type == "A" || record.Type == "AAAA") {
continue
}
if _, exists := addrMap[record.Content]; !exists || record.Proxied == nil && overProxy || record.Proxied != nil && *record.Proxied != overProxy {
logrus.Info("Deleting ", record.Type, " ", record.Content)
err = flare.DeleteDNSRecord(context.Background(), zone.ID, record.ID)
if err != nil {
logrus.Fatal(err)
}
} else {
delete(addrMap, record.Content)
}
}
for content, addr := range addrMap {
record := cloudflare.DNSRecord{
Name: domain,
Content: content,
Proxied: &overProxy,
}
if addr.Is4() {
record.Type = "A"
} else {
record.Type = "AAAA"
}
logrus.Info("Adding ", record)
_, err = flare.CreateDNSRecord(context.Background(), zone.ID, record)
if err != nil {
logrus.Fatal(err)
}
}
}

View file

@ -22,12 +22,16 @@ func LocalAddrs() ([]netip.Addr, error) {
}), nil
}
func IsPublicAddr(addr netip.Addr) bool {
return !(addr.IsPrivate() || addr.IsLoopback() || addr.IsMulticast() || addr.IsGlobalUnicast() || addr.IsLinkLocalUnicast() || addr.IsInterfaceLocalMulticast())
}
func LocalPublicAddrs() ([]netip.Addr, error) {
publicAddrs, err := LocalAddrs()
if err != nil {
return nil, err
}
return common.Filter(publicAddrs, func(addr netip.Addr) bool {
return !(addr.IsPrivate() || addr.IsLoopback() || addr.IsMulticast() || addr.IsGlobalUnicast() || addr.IsLinkLocalUnicast() || addr.IsInterfaceLocalMulticast())
return IsPublicAddr(addr)
}), nil
}

View file

@ -3,12 +3,10 @@ package common
import (
"os"
"strings"
"github.com/v2fly/v2ray-core/v5/common"
)
func FileExists(path string) bool {
return common.Error2(os.Stat(path)) == nil
return Error(os.Stat(path)) == nil
}
func WriteFile(path string, content []byte) error {

13
go.mod
View file

@ -3,6 +3,7 @@ module github.com/sagernet/sing
go 1.18
require (
github.com/cloudflare/cloudflare-go v0.37.0
github.com/openacid/low v0.1.21
github.com/oschwald/geoip2-golang v1.7.0
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb
@ -12,8 +13,9 @@ require (
github.com/ulikunitz/xz v0.5.10
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
github.com/v2fly/v2ray-core/v5 v5.0.3
github.com/vishvananda/netlink v1.1.0
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
golang.org/x/net v0.0.0-20220412020605-290c469a71a5
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d
google.golang.org/protobuf v1.28.0
@ -22,16 +24,21 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/oschwald/maxminddb-golang v1.9.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

27
go.sum
View file

@ -1,21 +1,28 @@
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/cloudflare/cloudflare-go v0.37.0 h1:8LUyAtev5PZ92Wzxzsm9jOQj0ZTz3BBc5F1ISfwgxvk=
github.com/cloudflare/cloudflare-go v0.37.0/go.mod h1:EfqOgbgOTf9U+67Z5MR2t/MuVj5OjLlXs+dN6DBh6C8=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 h1:BS21ZUJ/B5X2UVUbczfmdWH7GapPWAhxcMsDnjJTU1E=
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@ -31,6 +38,7 @@ github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXs
github.com/oschwald/geoip2-golang v1.7.0/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ=
github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y=
github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm82Cp5HyvYbt8K3zLY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -58,13 +66,24 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/v2fly/v2ray-core/v5 v5.0.3 h1:2rnJ9vZbBQ7V4upWsoUVYGoqZl4grrx8SxOReKx+jjc=
github.com/v2fly/v2ray-core/v5 v5.0.3/go.mod h1:zhDdsUJcNE8LcLRA3l7fEQ6QLuveD4/OLbQM2CceSHM=
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2 h1:6mzvA99KwZxbOrxww4EvWVQUnN1+xEu9tafK5ZxkYeA=
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0=

View file

@ -9,7 +9,7 @@ import (
"github.com/sagernet/sing/common/buf"
)
//https://shadowsocks.org/en/wiki/AEAD-Ciphers.html
// https://shadowsocks.org/en/wiki/AEAD-Ciphers.html
const (
MaxPacketSize = 16*1024 - 1
PacketLengthBufferSize = 2