go-emlid/cmd/proxy/main.go

189 lines
3.9 KiB
Go

package main
import (
"context"
"flag"
"fmt"
"log/slog"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"os/signal"
"strconv"
"forge.cadoles.com/cadoles/go-emlid/reach/discovery"
"github.com/grandcat/zeroconf"
"github.com/pkg/errors"
sloghttp "github.com/samber/slog-http"
"github.com/wlynxg/anet"
)
var (
address = ":8080"
host = "192.168.42.1"
name = ""
)
func init() {
flag.StringVar(&address, "address", address, "proxy listening address")
flag.StringVar(&host, "host", host, "reachview host adress")
flag.StringVar(&name, "name", name, "reachview service name, default machine hostname")
}
func main() {
flag.Parse()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
server, err := startProxy(ctx, address, host)
if err != nil {
slog.Error("could not start proxy", slog.Any("error", errors.WithStack(err)))
os.Exit(1)
}
defer server.Close()
service, err := startMDNSService(ctx, address, name)
if err != nil {
slog.Error("could not start mdns service", slog.Any("error", errors.WithStack(err)))
os.Exit(1)
}
defer service.Shutdown()
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
slog.Info("Ctrl+C to interrupt")
<-interrupt
}
func startProxy(ctx context.Context, address string, host string) (*http.Server, error) {
backendURL, err := url.Parse(fmt.Sprintf("http://%s", host))
if err != nil {
return nil, errors.WithStack(err)
}
proxy := httputil.NewSingleHostReverseProxy(backendURL)
handler := sloghttp.Recovery(proxy)
handler = sloghttp.New(slog.Default())(handler)
server := &http.Server{
Addr: address,
Handler: handler,
}
go func() {
slog.Info("starting proxy server", slog.Any("address", server.Addr))
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
slog.Error("could not listen and proxy http requests", slog.Any("error", errors.WithStack(err)))
}
}()
go func() {
<-ctx.Done()
if err := server.Close(); err != nil {
slog.Error("could not close proxy server", slog.Any("error", errors.WithStack(err)))
}
}()
return server, nil
}
func startMDNSService(ctx context.Context, address string, name string) (*zeroconf.Server, error) {
if name == "" {
hostname, err := os.Hostname()
if err != nil {
return nil, errors.WithStack(err)
}
name = hostname
}
ifaces, err := anet.Interfaces()
if err != nil {
return nil, errors.WithStack(err)
}
ips, err := GetLANIPv4Addrs()
if err != nil {
return nil, errors.WithStack(err)
}
info := []string{
fmt.Sprintf("local_name=%s", name),
"device=ReachProxy",
"manufacturer_data=egoHIABggkMnZWSuoy0KAgAA",
}
host, rawPort, err := net.SplitHostPort(address)
if err != nil {
return nil, errors.WithStack(err)
}
var port int64
if rawPort != "" {
port, err = strconv.ParseInt(rawPort, 10, 32)
if err != nil {
return nil, errors.WithStack(err)
}
}
if host != "" {
ips = []string{host}
}
slog.Info("announcing mdns service", slog.Any("ips", ips), slog.Any("info", info))
server, err := zeroconf.RegisterProxy("Reach", discovery.ReachService, "local.", int(port), name, ips, info, ifaces)
if err != nil {
return nil, errors.WithStack(err)
}
go func() {
<-ctx.Done()
server.Shutdown()
}()
return server, nil
}
var (
_, lanA, _ = net.ParseCIDR("10.0.0.0/8")
_, lanB, _ = net.ParseCIDR("172.16.0.0/12")
_, lanC, _ = net.ParseCIDR("192.168.0.0/16")
)
func GetLANIPv4Addrs() ([]string, error) {
ips := make([]string, 0)
addrs, err := anet.InterfaceAddrs()
if err != nil {
return nil, errors.WithStack(err)
}
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
ipv4 := ipnet.IP.To4()
if ipv4 == nil {
continue
}
isLAN := lanA.Contains(ipv4) || lanB.Contains(ipv4) || lanC.Contains(ipv4)
if !isLAN {
continue
}
ips = append(ips, ipv4.String())
}
}
return ips, nil
}