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 }