From 82a02287acc1fc5750d5d6ff12c3aa5cea2984ac Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Sun, 27 Dec 2020 21:06:00 +0000 Subject: [PATCH] Public IP endpoint with GET /ip fixing #319 --- cmd/gluetun/main.go | 2 +- internal/publicip/loop.go | 5 ++++ internal/publicip/state.go | 17 +++++++++++ internal/server/handler.go | 5 +++- internal/server/handlerv1.go | 6 +++- internal/server/publicip.go | 55 ++++++++++++++++++++++++++++++++++++ internal/server/server.go | 7 +++-- 7 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 internal/publicip/state.go create mode 100644 internal/server/publicip.go diff --git a/cmd/gluetun/main.go b/cmd/gluetun/main.go index 2f3677c3..86187838 100644 --- a/cmd/gluetun/main.go +++ b/cmd/gluetun/main.go @@ -267,7 +267,7 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go controlServerAddress := fmt.Sprintf("0.0.0.0:%d", allSettings.ControlServer.Port) controlServerLogging := allSettings.ControlServer.Log httpServer := server.New(controlServerAddress, controlServerLogging, - logger, buildInfo, openvpnLooper, unboundLooper, updaterLooper) + logger, buildInfo, openvpnLooper, unboundLooper, updaterLooper, publicIPLooper) wg.Add(1) go httpServer.Run(ctx, wg) diff --git a/internal/publicip/loop.go b/internal/publicip/loop.go index 76adaf13..f4ef4bb6 100644 --- a/internal/publicip/loop.go +++ b/internal/publicip/loop.go @@ -2,6 +2,7 @@ package publicip import ( "context" + "net" "sync" "time" @@ -18,6 +19,7 @@ type Looper interface { Stop() GetPeriod() (period time.Duration) SetPeriod(period time.Duration) + GetPublicIP() (publicIP net.IP) } type looper struct { @@ -26,6 +28,8 @@ type looper struct { getter IPGetter logger logging.Logger fileManager files.FileManager + ipMutex sync.RWMutex + ip net.IP ipStatusFilepath models.Filepath uid int gid int @@ -115,6 +119,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) { l.logAndWait(ctx, err) continue } + l.setPublicIP(ip) l.logger.Info("Public IP address is %s", ip) const userReadWritePermissions = 0600 err = l.fileManager.WriteLinesToFile( diff --git a/internal/publicip/state.go b/internal/publicip/state.go new file mode 100644 index 00000000..032e571a --- /dev/null +++ b/internal/publicip/state.go @@ -0,0 +1,17 @@ +package publicip + +import "net" + +func (l *looper) GetPublicIP() (publicIP net.IP) { + l.ipMutex.RLock() + defer l.ipMutex.RUnlock() + publicIP = make(net.IP, len(l.ip)) + copy(publicIP, l.ip) + return publicIP +} + +func (l *looper) setPublicIP(publicIP net.IP) { + l.ipMutex.Lock() + defer l.ipMutex.Unlock() + l.ip = publicIP +} diff --git a/internal/server/handler.go b/internal/server/handler.go index 3f77ee5e..9be8cb79 100644 --- a/internal/server/handler.go +++ b/internal/server/handler.go @@ -7,6 +7,7 @@ import ( "github.com/qdm12/gluetun/internal/dns" "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/openvpn" + "github.com/qdm12/gluetun/internal/publicip" "github.com/qdm12/gluetun/internal/updater" "github.com/qdm12/golibs/logging" ) @@ -16,15 +17,17 @@ func newHandler(logger logging.Logger, logging bool, openvpnLooper openvpn.Looper, unboundLooper dns.Looper, updaterLooper updater.Looper, + publicIPLooper publicip.Looper, ) http.Handler { handler := &handler{} openvpn := newOpenvpnHandler(openvpnLooper, logger) dns := newDNSHandler(unboundLooper, logger) updater := newUpdaterHandler(updaterLooper, logger) + publicip := newPublicIPHandler(publicIPLooper, logger) handler.v0 = newHandlerV0(logger, openvpnLooper, unboundLooper, updaterLooper) - handler.v1 = newHandlerV1(logger, buildInfo, openvpn, dns, updater) + handler.v1 = newHandlerV1(logger, buildInfo, openvpn, dns, updater, publicip) handlerWithLog := withLogMiddleware(handler, logger, logging) handler.setLogEnabled = handlerWithLog.setEnabled diff --git a/internal/server/handlerv1.go b/internal/server/handlerv1.go index 948677dd..8ec53c2a 100644 --- a/internal/server/handlerv1.go +++ b/internal/server/handlerv1.go @@ -11,13 +11,14 @@ import ( ) func newHandlerV1(logger logging.Logger, buildInfo models.BuildInformation, - openvpn, dns, updater http.Handler) http.Handler { + openvpn, dns, updater, publicip http.Handler) http.Handler { return &handlerV1{ logger: logger, buildInfo: buildInfo, openvpn: openvpn, dns: dns, updater: updater, + publicip: publicip, } } @@ -27,6 +28,7 @@ type handlerV1 struct { openvpn http.Handler dns http.Handler updater http.Handler + publicip http.Handler } func (h *handlerV1) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -39,6 +41,8 @@ func (h *handlerV1) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.dns.ServeHTTP(w, r) case strings.HasPrefix(r.RequestURI, "/updater"): h.updater.ServeHTTP(w, r) + case strings.HasPrefix(r.RequestURI, "/publicip"): + h.publicip.ServeHTTP(w, r) default: errString := fmt.Sprintf("%s %s not found", r.Method, r.RequestURI) http.Error(w, errString, http.StatusNotFound) diff --git a/internal/server/publicip.go b/internal/server/publicip.go new file mode 100644 index 00000000..08671f5f --- /dev/null +++ b/internal/server/publicip.go @@ -0,0 +1,55 @@ +//nolint:dupl +package server + +import ( + "encoding/json" + "net/http" + "strings" + + "github.com/qdm12/gluetun/internal/publicip" + "github.com/qdm12/golibs/logging" +) + +func newPublicIPHandler( + looper publicip.Looper, + logger logging.Logger) http.Handler { + return &publicIPHandler{ + looper: looper, + logger: logger, + } +} + +type publicIPHandler struct { + looper publicip.Looper + logger logging.Logger +} + +func (h *publicIPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + r.RequestURI = strings.TrimPrefix(r.RequestURI, "/publicip") + switch r.RequestURI { + case "/ip": + switch r.Method { + case http.MethodGet: + h.getPublicIP(w) + default: + http.Error(w, "", http.StatusNotFound) + } + default: + http.Error(w, "", http.StatusNotFound) + } +} + +type publicIPWrapper struct { + PublicIP string `json:"public_ip"` +} + +func (h *publicIPHandler) getPublicIP(w http.ResponseWriter) { + publicIP := h.looper.GetPublicIP() + encoder := json.NewEncoder(w) + data := publicIPWrapper{PublicIP: publicIP.String()} + if err := encoder.Encode(data); err != nil { + h.logger.Warn(err) + w.WriteHeader(http.StatusInternalServerError) + return + } +} diff --git a/internal/server/server.go b/internal/server/server.go index f339d91d..3eef45fd 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -9,6 +9,7 @@ import ( "github.com/qdm12/gluetun/internal/dns" "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/openvpn" + "github.com/qdm12/gluetun/internal/publicip" "github.com/qdm12/gluetun/internal/updater" "github.com/qdm12/golibs/logging" ) @@ -25,9 +26,11 @@ type server struct { func New(address string, logging bool, logger logging.Logger, buildInfo models.BuildInformation, - openvpnLooper openvpn.Looper, unboundLooper dns.Looper, updaterLooper updater.Looper) Server { + openvpnLooper openvpn.Looper, unboundLooper dns.Looper, + updaterLooper updater.Looper, publicIPLooper publicip.Looper) Server { serverLogger := logger.WithPrefix("http server: ") - handler := newHandler(serverLogger, logging, buildInfo, openvpnLooper, unboundLooper, updaterLooper) + handler := newHandler(serverLogger, logging, buildInfo, + openvpnLooper, unboundLooper, updaterLooper, publicIPLooper) return &server{ address: address, logger: serverLogger,