Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib/geoip, cmd/relaypoolsrv, cmd/ursrv: Automatically manage GeoIP updates #9342

Merged
merged 17 commits into from May 18, 2024
10 changes: 1 addition & 9 deletions Dockerfile.strelaypoolsrv
Expand Up @@ -11,14 +11,6 @@ LABEL org.opencontainers.image.authors="The Syncthing Project" \

EXPOSE 8080

RUN apk add --no-cache ca-certificates su-exec curl
ENV PUID=1000 PGID=1000 MAXMIND_KEY=

RUN mkdir /var/strelaypoolsrv && chown 1000 /var/strelaypoolsrv
USER 1000

COPY strelaypoolsrv-linux-${TARGETARCH} /bin/strelaypoolsrv
COPY script/strelaypoolsrv-entrypoint.sh /bin/entrypoint.sh

WORKDIR /var/strelaypoolsrv
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/strelaypoolsrv", "-listen", ":8080"]
ENTRYPOINT ["/bin/strelaypoolsrv", "-listen", ":8080"]
34 changes: 15 additions & 19 deletions cmd/strelaypoolsrv/main.go
Expand Up @@ -21,10 +21,10 @@ import (
"time"

lru "github.com/hashicorp/golang-lru/v2"
"github.com/syncthing/syncthing/lib/geoip"
"github.com/syncthing/syncthing/lib/httpcache"
"github.com/syncthing/syncthing/lib/protocol"

"github.com/oschwald/geoip2-golang"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/syncthing/syncthing/cmd/strelaypoolsrv/auto"
Expand Down Expand Up @@ -100,11 +100,11 @@ var (
debug bool
permRelaysFile string
ipHeader string
geoipPath string
proto string
statsRefresh = time.Minute
requestQueueLen = 64
requestProcessors = 8
geoipLicenseKey = os.Getenv("GEOIP_LICENSE_KEY")

requests chan request

Expand All @@ -130,34 +130,35 @@ func main() {
flag.StringVar(&permRelaysFile, "perm-relays", "", "Path to list of permanent relays")
flag.StringVar(&knownRelaysFile, "known-relays", knownRelaysFile, "Path to list of current relays")
flag.StringVar(&ipHeader, "ip-header", "", "Name of header which holds clients ip:port. Only meaningful when running behind a reverse proxy.")
flag.StringVar(&geoipPath, "geoip", "GeoLite2-City.mmdb", "Path to GeoLite2-City database")
flag.StringVar(&proto, "protocol", "tcp", "Protocol used for listening. 'tcp' for IPv4 and IPv6, 'tcp4' for IPv4, 'tcp6' for IPv6")
flag.DurationVar(&statsRefresh, "stats-refresh", statsRefresh, "Interval at which to refresh relay stats")
flag.IntVar(&requestQueueLen, "request-queue", requestQueueLen, "Queue length for incoming test requests")
flag.IntVar(&requestProcessors, "request-processors", requestProcessors, "Number of request processor routines")
flag.StringVar(&geoipLicenseKey, "geoip-license-key", geoipLicenseKey, "License key for GeoIP database")

flag.Parse()

requests = make(chan request, requestQueueLen)
geoip := geoip.NewGeoLite2CityProvider(geoipLicenseKey, os.TempDir())

var listener net.Listener
var err error

if permRelaysFile != "" {
permanentRelays = loadRelays(permRelaysFile)
permanentRelays = loadRelays(permRelaysFile, geoip)
}

testCert = createTestCertificate()

for i := 0; i < requestProcessors; i++ {
go requestProcessor()
go requestProcessor(geoip)
}

// Load relays from cache in the background.
// Load them in a serial fashion to make sure any genuine requests
// are not dropped.
go func() {
for _, relay := range loadRelays(knownRelaysFile) {
for _, relay := range loadRelays(knownRelaysFile, geoip) {
resultChan := make(chan result)
requests <- request{relay, resultChan, nil}
result := <-resultChan
Expand Down Expand Up @@ -425,19 +426,19 @@ func handlePostRequest(w http.ResponseWriter, r *http.Request) {
}
}

func requestProcessor() {
func requestProcessor(geoip *geoip.Provider) {
for request := range requests {
if request.queueTimer != nil {
request.queueTimer.ObserveDuration()
}

timer := prometheus.NewTimer(relayTestActionsSeconds.WithLabelValues("test"))
handleRelayTest(request)
handleRelayTest(request, geoip)
timer.ObserveDuration()
}
}

func handleRelayTest(request request) {
func handleRelayTest(request request, geoip *geoip.Provider) {
if debug {
log.Println("Request for", request.relay)
}
Expand All @@ -450,7 +451,7 @@ func handleRelayTest(request request) {
}

stats := fetchStats(request.relay)
location := getLocation(request.relay.uri.Host)
location := getLocation(request.relay.uri.Host, geoip)

mut.Lock()
if stats != nil {
Expand Down Expand Up @@ -523,7 +524,7 @@ func evict(relay *relay) func() {
}
}

func loadRelays(file string) []*relay {
func loadRelays(file string, geoip *geoip.Provider) []*relay {
content, err := os.ReadFile(file)
if err != nil {
log.Println("Failed to load relays: " + err.Error())
Expand All @@ -547,7 +548,7 @@ func loadRelays(file string) []*relay {

relays = append(relays, &relay{
URL: line,
Location: getLocation(uri.Host),
Location: getLocation(uri.Host, geoip),
uri: uri,
})
if debug {
Expand Down Expand Up @@ -580,21 +581,16 @@ func createTestCertificate() tls.Certificate {
return cert
}

func getLocation(host string) location {
func getLocation(host string, geoip *geoip.Provider) location {
timer := prometheus.NewTimer(locationLookupSeconds)
defer timer.ObserveDuration()
db, err := geoip2.Open(geoipPath)
if err != nil {
return location{}
}
defer db.Close()

addr, err := net.ResolveTCPAddr("tcp", host)
if err != nil {
return location{}
}

city, err := db.City(addr.IP)
city, err := geoip.City(addr.IP)
if err != nil {
return location{}
}
Expand Down
35 changes: 14 additions & 21 deletions cmd/ursrv/serve/serve.go
Expand Up @@ -17,6 +17,7 @@ import (
"log"
"net"
"net/http"
"os"
"regexp"
"sort"
"strconv"
Expand All @@ -26,20 +27,20 @@ import (
"unicode"

_ "github.com/lib/pq" // PostgreSQL driver
"github.com/oschwald/geoip2-golang"
"github.com/prometheus/client_golang/prometheus/promhttp"
"golang.org/x/text/cases"
"golang.org/x/text/language"

"github.com/syncthing/syncthing/lib/geoip"
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/syncthing/syncthing/lib/ur/contract"
)

type CLI struct {
Debug bool `env:"UR_DEBUG"`
DBConn string `env:"UR_DB_URL" default:"postgres://user:password@localhost/ur?sslmode=disable"`
Listen string `env:"UR_LISTEN" default:"0.0.0.0:8080"`
GeoIPPath string `env:"UR_GEOIP" default:"GeoLite2-City.mmdb"`
Debug bool `env:"UR_DEBUG"`
DBConn string `env:"UR_DB_URL" default:"postgres://user:password@localhost/ur?sslmode=disable"`
Listen string `env:"UR_LISTEN" default:"0.0.0.0:8080"`
GeoIPLicenseKey string `env:"UR_GEOIP_LICENSE"`
}

//go:embed static
Expand Down Expand Up @@ -190,9 +191,9 @@ func (cli *CLI) Run() error {
}

srv := &server{
db: db,
debug: cli.Debug,
geoIPPath: cli.GeoIPPath,
db: db,
debug: cli.Debug,
geoip: geoip.NewGeoLite2CityProvider(cli.GeoIPLicenseKey, os.TempDir()),
}
http.HandleFunc("/", srv.rootHandler)
http.HandleFunc("/newdata", srv.newDataHandler)
Expand All @@ -213,9 +214,9 @@ func (cli *CLI) Run() error {
}

type server struct {
debug bool
db *sql.DB
geoIPPath string
debug bool
db *sql.DB
geoip *geoip.Provider

cacheMut sync.Mutex
cachedIndex []byte
Expand All @@ -238,7 +239,7 @@ func (s *server) cacheRefresher() {
}

func (s *server) refreshCacheLocked() error {
rep := getReport(s.db, s.geoIPPath)
rep := getReport(s.db, s.geoip)
buf := new(bytes.Buffer)
err := tpl.Execute(buf, rep)
if err != nil {
Expand Down Expand Up @@ -492,15 +493,7 @@ type weightedLocation struct {
Weight int `json:"weight"`
}

func getReport(db *sql.DB, geoIPPath string) map[string]interface{} {
geoip, err := geoip2.Open(geoIPPath)
if err != nil {
log.Println("opening geoip db", err)
geoip = nil
} else {
defer geoip.Close()
}

func getReport(db *sql.DB, geoip *geoip.Provider) map[string]interface{} {
nodes := 0
countriesTotal := 0
var versions []string
Expand Down