diff --git a/.gitignore b/.gitignore index 0e5aed63f..0c9b6dc37 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ launch.json backup/* dist/* /.idea +/bin +/bin/* \ No newline at end of file diff --git a/cmd/daemon.go b/cmd/daemon.go index 30948ed0c..1c3d9f955 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -10,6 +10,10 @@ import ( "github.com/spf13/cobra" ) +var ( + onpremDaemon bool +) + // daemonCmd represents the daemon command var daemonCmd = &cobra.Command{ Use: "daemon", @@ -17,13 +21,13 @@ var daemonCmd = &cobra.Command{ Long: `nodeshift daemon gets and sends updates to netmaker server"`, Run: func(cmd *cobra.Command, args []string) { fmt.Println("daemon called") - functions.Daemon() + functions.Daemon(onpremDaemon) }, } func init() { rootCmd.AddCommand(daemonCmd) - + daemonCmd.Flags().BoolVarP(&onpremDaemon, "onprem", "", false, "set if using on-prem server") // Here you will define your flags and configuration settings. // Cobra supports Persistent Flags which will work for this command diff --git a/cmd/install.go b/cmd/install.go index 4c251a737..3ffb57709 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -8,6 +8,10 @@ import ( "github.com/spf13/cobra" ) +var ( + onpremInstall bool +) + // installCmd represents the install command var installCmd = &cobra.Command{ Use: "install", @@ -18,12 +22,13 @@ var installCmd = &cobra.Command{ ensure you specify the full path to then new binary to be installed`, Run: func(cmd *cobra.Command, args []string) { - functions.Install() + functions.Install(onpremInstall) }, } func init() { rootCmd.AddCommand(installCmd) + installCmd.Flags().BoolVarP(&onpremInstall, "onprem", "", false, "set if using on-prem server") // Here you will define your flags and configuration settings. diff --git a/cmd/root.go b/cmd/root.go index 1157c92c7..913120fc9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,11 +6,14 @@ package cmd import ( "crypto/rand" + "crypto/tls" "errors" + "net/http" "os" "path/filepath" "runtime" + "github.com/devilcove/httpclient" "github.com/google/uuid" "github.com/gravitl/netclient/config" "github.com/gravitl/netclient/daemon" @@ -55,6 +58,10 @@ func init() { rootCmd.PersistentFlags().IntP("verbosity", "v", 0, "set logging verbosity 0-4") viper.BindPFlags(rootCmd.Flags()) + // nodehisft on-prem + httpclient.Client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } // Cobra also supports local flags, which will only run // when this action is called directly. } @@ -96,7 +103,8 @@ func InitConfig(viper *viper.Viper) { config.ReadNodeConfig() config.ReadServerConf() config.SetServerCtx() - checkConfig() + + checkConfig(true) //check netclient dirs exist if _, err := os.Stat(config.GetNetclientPath()); err != nil { if os.IsNotExist(err) { @@ -147,7 +155,7 @@ func setupLogging(flags *viper.Viper) { } // checkConfig - verifies and updates configuration settings -func checkConfig() { +func checkConfig(onprem bool) { fail := false saveRequired := false netclient := config.Netclient() @@ -227,9 +235,16 @@ func checkConfig() { saveRequired = true } } + if onprem { + netclient.MTU = config.DefaultMTUOnPrem + } + if netclient.MTU == 0 { logger.Log(0, "setting MTU") netclient.MTU = config.DefaultMTU + if onprem { + netclient.MTU = config.DefaultMTUOnPrem + } } if len(netclient.TrafficKeyPrivate) == 0 { diff --git a/config/config.go b/config/config.go index 2451fffce..b15e3acb0 100644 --- a/config/config.go +++ b/config/config.go @@ -21,7 +21,7 @@ import ( "gopkg.in/yaml.v3" ) -const ( +var ( // LinuxAppDataPath - linux path LinuxAppDataPath = "/etc/netclient/" // MacAppDataPath - mac path @@ -37,7 +37,8 @@ const ( // DefaultListenPort default port for wireguard DefaultListenPort = 51821 // DefaultMTU default MTU for wireguard - DefaultMTU = 1420 + DefaultMTU = 1420 + DefaultMTUOnPrem = 1320 ) const ( diff --git a/daemon/common.go b/daemon/common.go index 17800aaf9..33aa62bc2 100644 --- a/daemon/common.go +++ b/daemon/common.go @@ -12,8 +12,8 @@ import ( ) // Install - Calls the correct function to install the netclient as a daemon service on the given operating system. -func Install() error { - return install() +func Install(onprem bool) error { + return install(onprem) } // Restart - restarts a system daemon diff --git a/daemon/common_darwin.go b/daemon/common_darwin.go index 41a0fd303..ec1508262 100644 --- a/daemon/common_darwin.go +++ b/daemon/common_darwin.go @@ -15,7 +15,7 @@ const MacServiceName = "com.gravitl.netclient" const MacExecDir = "/usr/local/bin/" // install- Creates a daemon service from the netclient under LaunchAgents for MacOS -func install() error { +func install(onprem bool) error { stop() binarypath, err := os.Executable() if err != nil { diff --git a/daemon/common_linux.go b/daemon/common_linux.go index 89a506892..9804635e3 100644 --- a/daemon/common_linux.go +++ b/daemon/common_linux.go @@ -15,7 +15,7 @@ import ( const ExecDir = "/sbin/" -func install() error { +func install(onprem bool) error { slog.Info("installing netclient binary") binarypath, err := os.Executable() if err != nil { @@ -33,7 +33,7 @@ func install() error { slog.Info("install netclient service file") switch config.Netclient().InitType { case config.Systemd: - return setupSystemDDaemon() + return setupSystemDDaemon(onprem) case config.SysVInit: return setupSysVint() case config.OpenRC: diff --git a/daemon/systemd_linux.go b/daemon/systemd_linux.go index 9d6c1ee8c..a4479d9d1 100644 --- a/daemon/systemd_linux.go +++ b/daemon/systemd_linux.go @@ -1,7 +1,10 @@ package daemon import ( + "bytes" "errors" + "fmt" + "html/template" "os" "github.com/gravitl/netclient/ncutils" @@ -9,10 +12,8 @@ import ( "golang.org/x/exp/slog" ) -// setupSystemDDaemon - sets system daemon for supported machines -func setupSystemDDaemon() error { - systemservice := `[Unit] -Description=Netclient Daemon +var systemdTemplate = `[Unit] +Description=Nodeshift Netclient Daemon Documentation=https://docs.netmaker.org https://k8s.netmaker.org After=network-online.target Wants=network-online.target @@ -21,7 +22,11 @@ Wants=network-online.target User=root Type=simple ExecStartPre=/bin/sleep 17 +{{- if .OnPrem }} +ExecStart=/sbin/netclient daemon --onprem +{{- else }} ExecStart=/sbin/netclient daemon +{{- end }} Restart=on-failure RestartSec=15s @@ -29,7 +34,23 @@ RestartSec=15s WantedBy=multi-user.target ` - servicebytes := []byte(systemservice) +type UnitData struct { + OnPrem bool +} + +// setupSystemDDaemon - sets system daemon for supported machines +func setupSystemDDaemon(onprem bool) error { + tmpl, err := template.New("unit").Parse(systemdTemplate) + if err != nil { + return fmt.Errorf("error parsing systemd template: %w", err) + } + var buf bytes.Buffer + err = tmpl.Execute(&buf, UnitData{OnPrem: onprem}) + if err != nil { + return fmt.Errorf("error executing systemd template: %w", err) + } + + servicebytes := []byte(buf.Bytes()) if !ncutils.FileExists("/etc/systemd/system/netclient.service") { err := os.WriteFile("/etc/systemd/system/netclient.service", servicebytes, 0644) diff --git a/functions/daemon.go b/functions/daemon.go index bc086a6af..c358f1840 100644 --- a/functions/daemon.go +++ b/functions/daemon.go @@ -25,6 +25,7 @@ import ( "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" + "github.com/vishvananda/netlink" "golang.org/x/exp/slog" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) @@ -48,7 +49,7 @@ type cachedMessage struct { } // Daemon runs netclient daemon -func Daemon() { +func Daemon(onprem bool) { slog.Info("starting netclient daemon", "version", config.Version) daemon.RemoveAllLockFiles() go deleteAllDNS() @@ -70,7 +71,7 @@ func Daemon() { if err != nil { logger.Log(0, "failed to intialize firewall: ", err.Error()) } - cancel := startGoRoutines(&wg) + cancel := startGoRoutines(&wg, onprem) for { select { @@ -92,7 +93,7 @@ func Daemon() { cancel, }, &wg) slog.Info("resetting daemon") - cancel = startGoRoutines(&wg) + cancel = startGoRoutines(&wg, onprem) } } } @@ -137,7 +138,7 @@ func closeRoutines(closers []context.CancelFunc, wg *sync.WaitGroup) { } // startGoRoutines starts the daemon goroutines -func startGoRoutines(wg *sync.WaitGroup) context.CancelFunc { +func startGoRoutines(wg *sync.WaitGroup, onprem bool) context.CancelFunc { ctx, cancel := context.WithCancel(context.Background()) if _, err := config.ReadNetclientConfig(); err != nil { slog.Warn("error reading netclient config file", "error", err) @@ -170,7 +171,7 @@ func startGoRoutines(wg *sync.WaitGroup) context.CancelFunc { if !config.Netclient().IsStatic { // IPV4 - config.HostPublicIP, config.WgPublicListenPort, config.HostNatType = holePunchWgPort(4, config.Netclient().ListenPort) + config.HostPublicIP, config.WgPublicListenPort, config.HostNatType = holePunchWgPort(4, config.Netclient().ListenPort, onprem) slog.Info("wireguard public listen port: ", "port", config.WgPublicListenPort) if config.HostPublicIP != nil && !config.HostPublicIP.IsUnspecified() { config.Netclient().EndpointIP = config.HostPublicIP @@ -185,7 +186,7 @@ func startGoRoutines(wg *sync.WaitGroup) context.CancelFunc { updateConfig = true } // IPV6 - publicIP6, wgport, natType := holePunchWgPort(6, config.Netclient().ListenPort) + publicIP6, wgport, natType := holePunchWgPort(6, config.Netclient().ListenPort, onprem) if publicIP6 != nil && !publicIP6.IsUnspecified() { config.Netclient().EndpointIPv6 = publicIP6 config.HostPublicIP6 = publicIP6 @@ -261,7 +262,7 @@ func startGoRoutines(wg *sync.WaitGroup) context.CancelFunc { wg.Add(1) go messageQueue(ctx, wg, server) wg.Add(1) - go Checkin(ctx, wg) + go Checkin(ctx, wg, onprem) wg.Add(1) go networking.StartIfaceDetection(ctx, wg, config.Netclient().ListenPort, 4) wg.Add(1) @@ -533,11 +534,23 @@ func UpdateKeys() error { return nil } -func holePunchWgPort(proto, portToStun int) (pubIP net.IP, pubPort int, natType string) { +func holePunchWgPort(proto, portToStun int, onprem bool) (pubIP net.IP, pubPort int, natType string) { + + if onprem { + publicIP, err := GetPublicIP(uint(proto), onprem) + if err != nil { + slog.Warn("failed to get publicIP onprem", "error", err) + return + } + pubIP = publicIP + pubPort = portToStun + + return + } pubIP, pubPort, natType = stun.HolePunch(portToStun, proto) if pubIP == nil || pubIP.IsUnspecified() { // if stun has failed fallback to ip service to get publicIP - publicIP, err := GetPublicIP(uint(proto)) + publicIP, err := GetPublicIP(uint(proto), onprem) if err != nil { slog.Warn("failed to get publicIP", "error", err) return @@ -548,19 +561,87 @@ func holePunchWgPort(proto, portToStun int) (pubIP net.IP, pubPort int, natType return } -func GetPublicIP(proto uint) (net.IP, error) { - // Create the default consensus, - // using the default configuration and no logger. - consensus := externalip.NewConsensus(&externalip.ConsensusConfig{ - Timeout: time.Second * 10, - }, nil) - consensus.AddVoter(externalip.NewHTTPSource("https://icanhazip.com/"), 3) - consensus.AddVoter(externalip.NewHTTPSource("https://ifconfig.me/ip"), 3) - consensus.AddVoter(externalip.NewHTTPSource("https://myexternalip.com/raw"), 3) - // By default Ipv4 or Ipv6 is returned, - // use the function below to limit yourself to IPv4, - // or pass in `6` instead to limit yourself to IPv6. - consensus.UseIPProtocol(proto) - // Get your IP, - return consensus.ExternalIP() +func GetPublicIP(proto uint, onprem bool) (net.IP, error) { + if !onprem { + // Create the default consensus, + // using the default configuration and no logger. + consensus := externalip.NewConsensus(&externalip.ConsensusConfig{ + Timeout: time.Second * 10, + }, nil) + consensus.AddVoter(externalip.NewHTTPSource("https://icanhazip.com/"), 3) + consensus.AddVoter(externalip.NewHTTPSource("https://ifconfig.me/ip"), 3) + consensus.AddVoter(externalip.NewHTTPSource("https://myexternalip.com/raw"), 3) + // By default Ipv4 or Ipv6 is returned, + // use the function below to limit yourself to IPv4, + // or pass in `6` instead to limit yourself to IPv6. + consensus.UseIPProtocol(proto) + // Get your IP, + return consensus.ExternalIP() + } + + dst := net.ParseIP("155.10.10.10") + + routes, err := netlink.RouteGet(dst) + if err != nil || len(routes) == 0 { + slog.Error("failed to get IP by using RouteGet, fallback to route list", "error", err) + return getPublicUsingRouteList(proto) + } + + return routes[0].Src, nil +} + +func getPublicUsingRouteList(proto uint) (net.IP, error) { + family := syscall.AF_INET + if proto == 6 { + family = syscall.AF_INET6 + } + + routes, err := netlink.RouteList(nil, family) + if err != nil { + return nil, err + } + + var best *netlink.Route + for i := range routes { + r := &routes[i] + if r.Dst == nil { + if best == nil || r.Priority < best.Priority { + best = r + } + } + } + if best == nil { + return nil, fmt.Errorf("no default route found") + } + + if best.Src != nil { + return best.Src, nil + } + + link, err := netlink.LinkByIndex(best.LinkIndex) + if err != nil { + return nil, err + } + + addrs, err := netlink.AddrList(link, family) + if err != nil { + return nil, err + } + + for _, a := range addrs { + if proto == 4 { + if ip4 := a.IP.To4(); ip4 != nil { + return ip4, nil + } + + continue + } + + if ip6 := a.IP.To16(); ip6 != nil && ip6.To4() == nil { + return ip6, nil + } + + } + + return nil, fmt.Errorf("no suitable address found") } diff --git a/functions/install.go b/functions/install.go index 59962f13b..1b740566a 100644 --- a/functions/install.go +++ b/functions/install.go @@ -12,7 +12,7 @@ import ( ) // Install - installs binary/daemon -func Install() error { +func Install(onprem bool) error { source, err := os.Executable() if err != nil { return err @@ -29,7 +29,7 @@ func Install() error { slog.Warn("stopping netclient daemon", "error", err) } time.Sleep(time.Second << 1) - if err := daemon.Install(); err != nil { + if err := daemon.Install(onprem); err != nil { slog.Error("daemon install error", "error", err) return err } diff --git a/functions/mqpublish.go b/functions/mqpublish.go index 4d3fb4dc0..7cddbbca4 100644 --- a/functions/mqpublish.go +++ b/functions/mqpublish.go @@ -34,7 +34,7 @@ const ( // Checkin -- go routine that checks for public or local ip changes, publishes changes // // if there are no updates, simply "pings" the server as a checkin -func Checkin(ctx context.Context, wg *sync.WaitGroup) { +func Checkin(ctx context.Context, wg *sync.WaitGroup, onprem bool) { logger.Log(2, "starting checkin goroutine") defer wg.Done() ticker := time.NewTicker(time.Minute * CheckInInterval) @@ -80,12 +80,12 @@ func Checkin(ctx context.Context, wg *sync.WaitGroup) { // if config.Netclient().CurrGwNmIP is not nil, it's an InetClient, then it skips the network change detection if !config.Netclient().IsStatic && config.Netclient().CurrGwNmIP == nil { restart := false - ip4, _, _ := holePunchWgPort(4, 0) + ip4, _, _ := holePunchWgPort(4, 0, onprem) if ip4 != nil && !ip4.IsUnspecified() && !config.HostPublicIP.Equal(ip4) { slog.Warn("IP CHECKIN", "ipv4", ip4, "HostPublicIP", config.HostPublicIP) restart = true } - ip6, _, _ := holePunchWgPort(6, 0) + ip6, _, _ := holePunchWgPort(6, 0, onprem) if ip6 != nil && !ip6.IsUnspecified() && !config.HostPublicIP6.Equal(ip6) { slog.Warn("IP CHECKIN", "ipv6", ip6, "HostPublicIP6", config.HostPublicIP6) restart = true @@ -102,7 +102,6 @@ func Checkin(ctx context.Context, wg *sync.WaitGroup) { // hostServerUpdate - used to send host updates to server via restful api func hostServerUpdate(hu models.HostUpdate) error { - server := config.GetServer(config.CurrServer) if server == nil { return errors.New("server config not found") diff --git a/ncutils/netclientutils.go b/ncutils/netclientutils.go index 767814849..cdbb50285 100644 --- a/ncutils/netclientutils.go +++ b/ncutils/netclientutils.go @@ -12,7 +12,6 @@ import ( "io" "log" "net" - "net/http" "os" "os/exec" "regexp" @@ -123,71 +122,71 @@ func IsEmptyRecord(err error) bool { } // GetPublicIP - gets public ip -func GetPublicIP(api string) (net.IP, error) { - - iplist := []string{"https://ifconfig.me", "https://api.ipify.org", "https://ipinfo.io/ip"} - - if api != "" { - api = "https://" + api + "/api/getip" - iplist = append([]string{api}, iplist...) - } - - endpoint := "" - var err error - for _, ipserver := range iplist { - client := &http.Client{ - Timeout: time.Second * 10, - } - resp, err := client.Get(ipserver) - if err != nil { - continue - } - defer resp.Body.Close() - if resp.StatusCode == http.StatusOK { - bodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - continue - } - endpoint = string(bodyBytes) - break - } - } - if err == nil && endpoint == "" { - err = errors.New("public address not found") - } - return net.ParseIP(endpoint), err -} +// func GetPublicIP(api string) (net.IP, error) { + +// iplist := []string{"https://ifconfig.me", "https://api.ipify.org", "https://ipinfo.io/ip"} + +// if api != "" { +// api = "https://" + api + "/api/getip" +// iplist = append([]string{api}, iplist...) +// } + +// endpoint := "" +// var err error +// for _, ipserver := range iplist { +// client := &http.Client{ +// Timeout: time.Second * 10, +// } +// resp, err := client.Get(ipserver) +// if err != nil { +// continue +// } +// defer resp.Body.Close() +// if resp.StatusCode == http.StatusOK { +// bodyBytes, err := io.ReadAll(resp.Body) +// if err != nil { +// continue +// } +// endpoint = string(bodyBytes) +// break +// } +// } +// if err == nil && endpoint == "" { +// err = errors.New("public address not found") +// } +// return net.ParseIP(endpoint), err +// } // GetPublicIPv6 - gets public ipv6 address -func GetPublicIPv6() (net.IP, error) { - - iplist := []string{"https://ifconfig.me"} - - endpoint := "" - var err error - for _, ipserver := range iplist { - client := &http.Client{ - Timeout: time.Second * 10, - } - resp, err := client.Get(ipserver) - if err != nil { - continue - } - defer resp.Body.Close() - if resp.StatusCode == http.StatusOK { - bodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - continue - } - endpoint = string(bodyBytes) - break - } - } - if err == nil && endpoint == "" { - err = errors.New("public ipv6 address not found") - } - return net.ParseIP(endpoint), err -} +// func GetPublicIPv6() (net.IP, error) { + +// iplist := []string{"https://ifconfig.me"} + +// endpoint := "" +// var err error +// for _, ipserver := range iplist { +// client := &http.Client{ +// Timeout: time.Second * 10, +// } +// resp, err := client.Get(ipserver) +// if err != nil { +// continue +// } +// defer resp.Body.Close() +// if resp.StatusCode == http.StatusOK { +// bodyBytes, err := io.ReadAll(resp.Body) +// if err != nil { +// continue +// } +// endpoint = string(bodyBytes) +// break +// } +// } +// if err == nil && endpoint == "" { +// err = errors.New("public ipv6 address not found") +// } +// return net.ParseIP(endpoint), err +// } // GetMacAddr - get's mac address func GetMacAddr() ([]net.HardwareAddr, error) {