Skip to content

Commit 66f58cb

Browse files
committed
feat: fs watcher && retry
1 parent 68de336 commit 66f58cb

File tree

4 files changed

+219
-88
lines changed

4 files changed

+219
-88
lines changed

conf/config.go

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package conf
22

33
import (
44
_ "embed"
5+
"github.com/fsnotify/fsnotify"
56
"github.com/sirupsen/logrus"
67
"github.com/spf13/viper"
78
"os"
@@ -56,6 +57,11 @@ func Init(file string) {
5657
if err != nil {
5758
logrus.WithError(err).Fatalf("read config from %s failed", file)
5859
}
60+
61+
viper.OnConfigChange(func(e fsnotify.Event) {
62+
update()
63+
})
64+
viper.WatchConfig()
5965
}
6066

6167
func update() {

daemon/daemon.go

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package daemon
2+
3+
import (
4+
"github.com/hdu-dn11/wg-quick-op/conf"
5+
"github.com/hdu-dn11/wg-quick-op/lib/dns"
6+
"github.com/hdu-dn11/wg-quick-op/quick"
7+
"github.com/hdu-dn11/wg-quick-op/utils"
8+
"github.com/sirupsen/logrus"
9+
"github.com/vishvananda/netlink"
10+
"slices"
11+
"sync"
12+
"time"
13+
)
14+
15+
type daemon struct {
16+
runIfaces map[string]*ddns
17+
pendingIfaces []string
18+
lock sync.Mutex
19+
}
20+
21+
func newDaemon() *daemon {
22+
d := &daemon{}
23+
d.runIfaces = make(map[string]*ddns)
24+
return d
25+
}
26+
27+
func (d *daemon) Run() {
28+
// prepare config
29+
for _, iface := range utils.FindIface(conf.DDNS.IfaceOnly, conf.DDNS.IfaceSkip) {
30+
ddns, err := newDDNS(iface)
31+
if err != nil {
32+
logrus.WithField("iface", iface).WithError(err).Error("failed to init ddns config")
33+
d.pendingIfaces = append(d.pendingIfaces, iface)
34+
continue
35+
}
36+
d.runIfaces[iface] = ddns
37+
}
38+
39+
d.registerWatch()
40+
d.goUpdate()
41+
42+
for {
43+
time.Sleep(conf.DDNS.Interval)
44+
d.lock.Lock()
45+
for _, iface := range d.runIfaces {
46+
peers, err := quick.PeerStatus(iface.name)
47+
if err != nil {
48+
logrus.WithError(err).WithField("iface", iface.name).Error("failed to get device")
49+
continue
50+
}
51+
52+
wgSync := false
53+
54+
for _, peer := range peers {
55+
if peer.Endpoint == nil || peer.Endpoint.IP == nil {
56+
logrus.WithField("iface", iface.name).WithField("peer", peer.PublicKey).Debugln("peer endpoint is nil, skip it")
57+
continue
58+
}
59+
if time.Since(peer.LastHandshakeTime) < conf.DDNS.HandleShakeMax {
60+
logrus.WithField("iface", iface.name).WithField("peer", peer.PublicKey).Debugln("peer ok")
61+
continue
62+
}
63+
logrus.WithField("iface", iface.name).WithField("peer", peer.PublicKey).Debugln("peer handshake timeout, re-resolve endpoint")
64+
endpoint, ok := iface.unresolvedEndpoints[peer.PublicKey]
65+
if !ok {
66+
continue
67+
}
68+
addr, err := dns.ResolveUDPAddr("", endpoint)
69+
if err != nil {
70+
logrus.WithField("iface", iface).WithField("peer", peer.PublicKey).WithError(err).Error("failed to resolve endpoint")
71+
continue
72+
}
73+
74+
for i, v := range iface.cfg.Peers {
75+
if v.PublicKey == peer.PublicKey && !peer.Endpoint.IP.Equal(addr.IP) {
76+
iface.cfg.Peers[i].Endpoint = addr
77+
wgSync = true
78+
break
79+
}
80+
}
81+
}
82+
83+
if !wgSync {
84+
logrus.WithField("iface", iface.name).Debugln("same addr, skip")
85+
continue
86+
}
87+
88+
link, err := netlink.LinkByName(iface.name)
89+
if err != nil {
90+
logrus.WithField("iface", iface.name).WithError(err).Error("get link failed")
91+
continue
92+
}
93+
94+
if err := quick.SyncWireguardDevice(iface.cfg, link, logrus.WithField("iface", iface.name)); err != nil {
95+
logrus.WithField("iface", iface.name).WithError(err).Error("sync device failed")
96+
continue
97+
}
98+
99+
logrus.WithField("iface", iface.name).Infoln("re-resolve done")
100+
}
101+
d.lock.Unlock()
102+
logrus.Infoln("endpoint re-resolve done")
103+
}
104+
}
105+
106+
func (d *daemon) registerWatch() {
107+
go (&WireguardWatcher{
108+
UpdateCallback: func(name string) {
109+
if conf.DDNS.IfaceOnly != nil && slices.Index(conf.DDNS.IfaceOnly, name) == -1 {
110+
return
111+
}
112+
if conf.DDNS.IfaceSkip != nil && slices.Index(conf.DDNS.IfaceSkip, name) != -1 {
113+
return
114+
}
115+
d.lock.Lock()
116+
defer d.lock.Unlock()
117+
if slices.Index(d.pendingIfaces, name) == -1 {
118+
d.pendingIfaces = append(d.pendingIfaces, name)
119+
}
120+
},
121+
RemoveCallback: func(name string) {
122+
if conf.DDNS.IfaceOnly != nil && slices.Index(conf.DDNS.IfaceOnly, name) == -1 {
123+
return
124+
}
125+
if conf.DDNS.IfaceSkip != nil && slices.Index(conf.DDNS.IfaceSkip, name) != -1 {
126+
return
127+
}
128+
d.lock.Lock()
129+
defer d.lock.Unlock()
130+
delete(d.runIfaces, name)
131+
slices.DeleteFunc(d.pendingIfaces, func(i string) bool {
132+
return i == name
133+
})
134+
},
135+
}).Watch()
136+
}
137+
138+
func (d *daemon) goUpdate() {
139+
for {
140+
d.lock.Lock()
141+
var deleteList []string
142+
for _, iface := range d.pendingIfaces {
143+
ddns, err := newDDNS(iface)
144+
if err != nil {
145+
logrus.WithField("iface", iface).WithError(err).Error("failed to init ddns config")
146+
continue
147+
}
148+
d.runIfaces[iface] = ddns
149+
deleteList = append(deleteList, iface)
150+
}
151+
for _, iface := range deleteList {
152+
slices.DeleteFunc(d.pendingIfaces, func(i string) bool {
153+
return i == iface
154+
})
155+
}
156+
d.lock.Unlock()
157+
time.Sleep(conf.DDNS.Interval * 2)
158+
}
159+
}

daemon/service.go

+5-88
Original file line numberDiff line numberDiff line change
@@ -3,109 +3,26 @@ package daemon
33
import (
44
_ "embed"
55
"errors"
6-
"github.com/hdu-dn11/wg-quick-op/lib/dns"
7-
"os"
8-
"os/exec"
9-
"sync"
10-
"time"
11-
126
"github.com/hdu-dn11/wg-quick-op/conf"
137
"github.com/hdu-dn11/wg-quick-op/quick"
148
"github.com/hdu-dn11/wg-quick-op/utils"
159
"github.com/sirupsen/logrus"
16-
"github.com/vishvananda/netlink"
10+
"os"
11+
"os/exec"
1712
)
1813

1914
const ServicePath = "/etc/init.d/wg-quick-op"
2015

2116
//go:embed wg-quick-op
2217
var ServiceFile []byte
23-
var (
24-
cfgs map[string]*ddns
25-
lock sync.Mutex
26-
)
27-
28-
func init() {
29-
cfgs = make(map[string]*ddns)
30-
}
3118

3219
func Serve() {
3320
if conf.StartOnBoot.Enabled {
3421
startOnBoot()
3522
}
3623

37-
// prepare config
38-
for _, iface := range utils.FindIface(conf.DDNS.IfaceOnly, conf.DDNS.IfaceSkip) {
39-
d, err := newDDNS(iface)
40-
if err != nil {
41-
logrus.WithField("iface", iface).WithError(err).Error("failed to init ddns config")
42-
continue
43-
}
44-
cfgs[iface] = d
45-
}
46-
47-
for {
48-
time.Sleep(conf.DDNS.Interval)
49-
lock.Lock()
50-
for _, iface := range cfgs {
51-
peers, err := quick.PeerStatus(iface.name)
52-
if err != nil {
53-
logrus.WithError(err).WithField("iface", iface.name).Error("failed to get device")
54-
continue
55-
}
56-
57-
sync := false
58-
59-
for _, peer := range peers {
60-
if peer.Endpoint == nil || peer.Endpoint.IP == nil {
61-
logrus.WithField("iface", iface.name).WithField("peer", peer.PublicKey).Debugln("peer endpoint is nil, skip it")
62-
continue
63-
}
64-
if time.Since(peer.LastHandshakeTime) < conf.DDNS.HandleShakeMax {
65-
logrus.WithField("iface", iface.name).WithField("peer", peer.PublicKey).Debugln("peer ok")
66-
continue
67-
}
68-
logrus.WithField("iface", iface.name).WithField("peer", peer.PublicKey).Debugln("peer handshake timeout, re-resolve endpoint")
69-
endpoint, ok := iface.unresolvedEndpoints[peer.PublicKey]
70-
if !ok {
71-
continue
72-
}
73-
addr, err := dns.ResolveUDPAddr("", endpoint)
74-
if err != nil {
75-
logrus.WithField("iface", iface).WithField("peer", peer.PublicKey).WithError(err).Error("failed to resolve endpoint")
76-
continue
77-
}
78-
79-
for i, v := range iface.cfg.Peers {
80-
if v.PublicKey == peer.PublicKey && !peer.Endpoint.IP.Equal(addr.IP) {
81-
iface.cfg.Peers[i].Endpoint = addr
82-
sync = true
83-
break
84-
}
85-
}
86-
}
87-
88-
if !sync {
89-
logrus.WithField("iface", iface.name).Debugln("same addr, skip")
90-
continue
91-
}
92-
93-
link, err := netlink.LinkByName(iface.name)
94-
if err != nil {
95-
logrus.WithField("iface", iface.name).WithError(err).Error("get link failed")
96-
continue
97-
}
98-
99-
if err := quick.SyncWireguardDevice(iface.cfg, link, logrus.WithField("iface", iface.name)); err != nil {
100-
logrus.WithField("iface", iface.name).WithError(err).Error("sync device failed")
101-
continue
102-
}
103-
104-
logrus.WithField("iface", iface.name).Infoln("re-resolve done")
105-
}
106-
lock.Unlock()
107-
logrus.Infoln("endpoint re-resolve done")
108-
}
24+
d := newDaemon()
25+
d.Run()
10926
}
11027

11128
func startOnBoot() {
@@ -135,7 +52,7 @@ func startOnBoot() {
13552
}()
13653
}
13754

138-
logrus.Infoln("all interface up")
55+
logrus.Infoln("all interface parsed")
13956
}
14057

14158
func AddService() {

daemon/watcher.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package daemon
2+
3+
import (
4+
"github.com/fsnotify/fsnotify"
5+
"github.com/sirupsen/logrus"
6+
"strings"
7+
)
8+
9+
type WireguardWatcher struct {
10+
UpdateCallback func(name string)
11+
RemoveCallback func(name string)
12+
}
13+
14+
func (w *WireguardWatcher) Watch() {
15+
watcher, err := fsnotify.NewWatcher()
16+
if err != nil {
17+
logrus.Errorf("failed to create watcher: %v", err)
18+
}
19+
watcher.Add("/etc/wireguard")
20+
for {
21+
select {
22+
case event, ok := <-watcher.Events:
23+
if !ok {
24+
return
25+
}
26+
name := strings.TrimSuffix(event.Name, ".conf")
27+
if len(name) == len(event.Name) {
28+
continue
29+
}
30+
if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create {
31+
logrus.Info("update file:", event.Name)
32+
if w.UpdateCallback != nil {
33+
w.UpdateCallback(name)
34+
}
35+
}
36+
if event.Op&fsnotify.Remove == fsnotify.Remove || event.Op&fsnotify.Rename == fsnotify.Rename {
37+
logrus.Info("remove file:", event.Name)
38+
if w.RemoveCallback != nil {
39+
w.RemoveCallback(name)
40+
}
41+
}
42+
case err, ok := <-watcher.Errors:
43+
if !ok {
44+
return
45+
}
46+
logrus.Error("error:", err)
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)