commands.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. package nebula
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "flag"
  7. "fmt"
  8. "log/slog"
  9. "maps"
  10. "net/netip"
  11. "sort"
  12. "strings"
  13. "github.com/slackhq/nebula/commands"
  14. "github.com/slackhq/nebula/config"
  15. "github.com/slackhq/nebula/header"
  16. "github.com/slackhq/nebula/logging"
  17. )
  18. // The commands nebula serves. attachCommands builds the one registry every surface
  19. // reads. See the commands package for what a Command is.
  20. type sshListHostMapFlags struct {
  21. Json bool
  22. Pretty bool
  23. ByIndex bool
  24. }
  25. type sshPrintCertFlags struct {
  26. Json bool
  27. Pretty bool
  28. Raw bool
  29. }
  30. type sshPrintTunnelFlags struct {
  31. Pretty bool
  32. }
  33. type sshChangeRemoteFlags struct {
  34. Address string
  35. }
  36. type sshCloseTunnelFlags struct {
  37. LocalOnly bool
  38. }
  39. type sshCreateTunnelFlags struct {
  40. Address string
  41. }
  42. type sshDeviceInfoFlags struct {
  43. Json bool
  44. Pretty bool
  45. }
  46. func attachCommands(l *slog.Logger, c *config.C, reg *commands.Registry, f *Interface) {
  47. reg.Register(&commands.Command{
  48. Name: commands.ListHostmap,
  49. ShortDescription: "List all known previously connected hosts",
  50. Flags: func() (*flag.FlagSet, any) {
  51. fl := flag.NewFlagSet("", flag.ContinueOnError)
  52. s := sshListHostMapFlags{}
  53. fl.BoolVar(&s.Json, "json", false, "outputs as json with more information")
  54. fl.BoolVar(&s.Pretty, "pretty", false, "pretty prints json, assumes -json")
  55. fl.BoolVar(&s.ByIndex, "by-index", false, "gets all hosts in the hostmap from the index table")
  56. return fl, &s
  57. },
  58. Callback: func(fs any, a []string, w commands.StringWriter) error {
  59. return sshListHostMap(f.hostMap, fs, w)
  60. },
  61. })
  62. reg.Register(&commands.Command{
  63. Name: commands.ListPendingHostmap,
  64. ShortDescription: "List all handshaking hosts",
  65. Flags: func() (*flag.FlagSet, any) {
  66. fl := flag.NewFlagSet("", flag.ContinueOnError)
  67. s := sshListHostMapFlags{}
  68. fl.BoolVar(&s.Json, "json", false, "outputs as json with more information")
  69. fl.BoolVar(&s.Pretty, "pretty", false, "pretty prints json, assumes -json")
  70. fl.BoolVar(&s.ByIndex, "by-index", false, "gets all hosts in the hostmap from the index table")
  71. return fl, &s
  72. },
  73. Callback: func(fs any, a []string, w commands.StringWriter) error {
  74. return sshListHostMap(f.handshakeManager, fs, w)
  75. },
  76. })
  77. reg.Register(&commands.Command{
  78. Name: commands.ListLighthouseAddrmap,
  79. ShortDescription: "List all lighthouse map entries",
  80. Flags: func() (*flag.FlagSet, any) {
  81. fl := flag.NewFlagSet("", flag.ContinueOnError)
  82. s := sshListHostMapFlags{}
  83. fl.BoolVar(&s.Json, "json", false, "outputs as json with more information")
  84. fl.BoolVar(&s.Pretty, "pretty", false, "pretty prints json, assumes -json")
  85. return fl, &s
  86. },
  87. Callback: func(fs any, a []string, w commands.StringWriter) error {
  88. return sshListLighthouseMap(f.lightHouse, fs, w)
  89. },
  90. })
  91. reg.Register(&commands.Command{
  92. Name: commands.Reload,
  93. ShortDescription: "Reloads configuration from disk, same as sending HUP to the process",
  94. Callback: func(fs any, a []string, w commands.StringWriter) error {
  95. return sshReload(l, c, w)
  96. },
  97. })
  98. reg.Register(&commands.Command{
  99. Name: commands.LogLevel,
  100. ShortDescription: "Gets or sets the current log level",
  101. Callback: func(fs any, a []string, w commands.StringWriter) error {
  102. return sshLogLevel(l, fs, a, w)
  103. },
  104. })
  105. reg.Register(&commands.Command{
  106. Name: commands.LogFormat,
  107. ShortDescription: "Gets or sets the current log format",
  108. Callback: func(fs any, a []string, w commands.StringWriter) error {
  109. return sshLogFormat(l, fs, a, w)
  110. },
  111. })
  112. reg.Register(&commands.Command{
  113. Name: commands.Version,
  114. ShortDescription: "Prints the currently running version of nebula",
  115. Callback: func(fs any, a []string, w commands.StringWriter) error {
  116. return sshVersion(f, fs, a, w)
  117. },
  118. })
  119. reg.Register(&commands.Command{
  120. Name: commands.DeviceInfo,
  121. ShortDescription: "Prints information about the network device.",
  122. Flags: func() (*flag.FlagSet, any) {
  123. fl := flag.NewFlagSet("", flag.ContinueOnError)
  124. s := sshDeviceInfoFlags{}
  125. fl.BoolVar(&s.Json, "json", false, "outputs as json with more information")
  126. fl.BoolVar(&s.Pretty, "pretty", false, "pretty prints json, assumes -json")
  127. return fl, &s
  128. },
  129. Callback: func(fs any, a []string, w commands.StringWriter) error {
  130. return sshDeviceInfo(f, fs, w)
  131. },
  132. })
  133. reg.Register(&commands.Command{
  134. Name: commands.PrintCert,
  135. ShortDescription: "Prints the current certificate being used or the certificate for the provided vpn addr",
  136. Flags: func() (*flag.FlagSet, any) {
  137. fl := flag.NewFlagSet("", flag.ContinueOnError)
  138. s := sshPrintCertFlags{}
  139. fl.BoolVar(&s.Json, "json", false, "outputs as json")
  140. fl.BoolVar(&s.Pretty, "pretty", false, "pretty prints json, assumes -json")
  141. fl.BoolVar(&s.Raw, "raw", false, "raw prints the PEM encoded certificate, not compatible with -json or -pretty")
  142. return fl, &s
  143. },
  144. Callback: func(fs any, a []string, w commands.StringWriter) error {
  145. return sshPrintCert(f, fs, a, w)
  146. },
  147. })
  148. reg.Register(&commands.Command{
  149. Name: commands.PrintTunnel,
  150. ShortDescription: "Prints json details about a tunnel for the provided vpn addr",
  151. Flags: func() (*flag.FlagSet, any) {
  152. fl := flag.NewFlagSet("", flag.ContinueOnError)
  153. s := sshPrintTunnelFlags{}
  154. fl.BoolVar(&s.Pretty, "pretty", false, "pretty prints json")
  155. return fl, &s
  156. },
  157. Callback: func(fs any, a []string, w commands.StringWriter) error {
  158. return sshPrintTunnel(f, fs, a, w)
  159. },
  160. })
  161. reg.Register(&commands.Command{
  162. Name: commands.PrintRelays,
  163. ShortDescription: "Prints json details about all relay info",
  164. Flags: func() (*flag.FlagSet, any) {
  165. fl := flag.NewFlagSet("", flag.ContinueOnError)
  166. s := sshPrintTunnelFlags{}
  167. fl.BoolVar(&s.Pretty, "pretty", false, "pretty prints json")
  168. return fl, &s
  169. },
  170. Callback: func(fs any, a []string, w commands.StringWriter) error {
  171. return sshPrintRelays(f, fs, a, w)
  172. },
  173. })
  174. reg.Register(&commands.Command{
  175. Name: commands.ChangeRemote,
  176. ShortDescription: "Changes the remote address used in the tunnel for the provided vpn addr",
  177. Flags: func() (*flag.FlagSet, any) {
  178. fl := flag.NewFlagSet("", flag.ContinueOnError)
  179. s := sshChangeRemoteFlags{}
  180. fl.StringVar(&s.Address, "address", "", "The new remote address, ip:port")
  181. return fl, &s
  182. },
  183. Callback: func(fs any, a []string, w commands.StringWriter) error {
  184. return sshChangeRemote(f, fs, a, w)
  185. },
  186. })
  187. reg.Register(&commands.Command{
  188. Name: commands.CloseTunnel,
  189. ShortDescription: "Closes a tunnel for the provided vpn addr",
  190. Flags: func() (*flag.FlagSet, any) {
  191. fl := flag.NewFlagSet("", flag.ContinueOnError)
  192. s := sshCloseTunnelFlags{}
  193. fl.BoolVar(&s.LocalOnly, "local-only", false, "Disables notifying the remote that the tunnel is shutting down")
  194. return fl, &s
  195. },
  196. Callback: func(fs any, a []string, w commands.StringWriter) error {
  197. return sshCloseTunnel(f, fs, a, w)
  198. },
  199. })
  200. reg.Register(&commands.Command{
  201. Name: commands.CreateTunnel,
  202. ShortDescription: "Creates a tunnel for the provided vpn address",
  203. Help: "The lighthouses will be queried for real addresses but you can provide one as well.",
  204. Flags: func() (*flag.FlagSet, any) {
  205. fl := flag.NewFlagSet("", flag.ContinueOnError)
  206. s := sshCreateTunnelFlags{}
  207. fl.StringVar(&s.Address, "address", "", "Optionally provide a real remote address, ip:port ")
  208. return fl, &s
  209. },
  210. Callback: func(fs any, a []string, w commands.StringWriter) error {
  211. return sshCreateTunnel(f, fs, a, w)
  212. },
  213. })
  214. reg.Register(&commands.Command{
  215. Name: commands.QueryLighthouse,
  216. ShortDescription: "Query the lighthouses for the provided vpn address",
  217. Help: "This command is asynchronous. Only currently known udp addresses will be printed.",
  218. Callback: func(fs any, a []string, w commands.StringWriter) error {
  219. return sshQueryLighthouse(f, fs, a, w)
  220. },
  221. })
  222. }
  223. func sshListHostMap(hl controlHostLister, a any, w commands.StringWriter) error {
  224. fs, ok := a.(*sshListHostMapFlags)
  225. if !ok {
  226. return nil
  227. }
  228. var hm []ControlHostInfo
  229. if fs.ByIndex {
  230. hm = listHostMapIndexes(hl)
  231. } else {
  232. hm = listHostMapHosts(hl)
  233. }
  234. sort.Slice(hm, func(i, j int) bool {
  235. return hm[i].VpnAddrs[0].Compare(hm[j].VpnAddrs[0]) < 0
  236. })
  237. if fs.Json || fs.Pretty {
  238. js := json.NewEncoder(w.GetWriter())
  239. if fs.Pretty {
  240. js.SetIndent("", " ")
  241. }
  242. err := js.Encode(hm)
  243. if err != nil {
  244. return nil
  245. }
  246. } else {
  247. for _, v := range hm {
  248. err := w.WriteLine(fmt.Sprintf("%s: %s", v.VpnAddrs, v.RemoteAddrs))
  249. if err != nil {
  250. return err
  251. }
  252. }
  253. }
  254. return nil
  255. }
  256. func sshListLighthouseMap(lightHouse *LightHouse, a any, w commands.StringWriter) error {
  257. fs, ok := a.(*sshListHostMapFlags)
  258. if !ok {
  259. return nil
  260. }
  261. type lighthouseInfo struct {
  262. VpnAddr string `json:"vpnAddr"`
  263. Addrs *CacheMap `json:"addrs"`
  264. }
  265. lightHouse.RLock()
  266. addrMap := make([]lighthouseInfo, len(lightHouse.addrMap))
  267. x := 0
  268. for k, v := range lightHouse.addrMap {
  269. addrMap[x] = lighthouseInfo{
  270. VpnAddr: k.String(),
  271. Addrs: v.CopyCache(),
  272. }
  273. x++
  274. }
  275. lightHouse.RUnlock()
  276. sort.Slice(addrMap, func(i, j int) bool {
  277. return strings.Compare(addrMap[i].VpnAddr, addrMap[j].VpnAddr) < 0
  278. })
  279. if fs.Json || fs.Pretty {
  280. js := json.NewEncoder(w.GetWriter())
  281. if fs.Pretty {
  282. js.SetIndent("", " ")
  283. }
  284. err := js.Encode(addrMap)
  285. if err != nil {
  286. return nil
  287. }
  288. } else {
  289. for _, v := range addrMap {
  290. b, err := json.Marshal(v.Addrs)
  291. if err != nil {
  292. return err
  293. }
  294. err = w.WriteLine(fmt.Sprintf("%s: %s", v.VpnAddr, string(b)))
  295. if err != nil {
  296. return err
  297. }
  298. }
  299. }
  300. return nil
  301. }
  302. func sshVersion(ifce *Interface, fs any, a []string, w commands.StringWriter) error {
  303. return w.WriteLine(fmt.Sprintf("%s", ifce.version))
  304. }
  305. func sshQueryLighthouse(ifce *Interface, fs any, a []string, w commands.StringWriter) error {
  306. if len(a) == 0 {
  307. return w.WriteLine("No vpn address was provided")
  308. }
  309. vpnAddr, err := netip.ParseAddr(a[0])
  310. if err != nil {
  311. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  312. }
  313. if !vpnAddr.IsValid() {
  314. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  315. }
  316. var cm *CacheMap
  317. rl := ifce.lightHouse.Query(vpnAddr)
  318. if rl != nil {
  319. cm = rl.CopyCache()
  320. }
  321. return json.NewEncoder(w.GetWriter()).Encode(cm)
  322. }
  323. func sshCloseTunnel(ifce *Interface, fs any, a []string, w commands.StringWriter) error {
  324. flags, ok := fs.(*sshCloseTunnelFlags)
  325. if !ok {
  326. return nil
  327. }
  328. if len(a) == 0 {
  329. return w.WriteLine("No vpn address was provided")
  330. }
  331. vpnAddr, err := netip.ParseAddr(a[0])
  332. if err != nil {
  333. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  334. }
  335. if !vpnAddr.IsValid() {
  336. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  337. }
  338. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  339. if hostInfo == nil {
  340. return w.WriteLine(fmt.Sprintf("Could not find tunnel for vpn address: %v", a[0]))
  341. }
  342. if !flags.LocalOnly {
  343. ifce.send(
  344. header.CloseTunnel,
  345. 0,
  346. hostInfo.ConnectionState,
  347. hostInfo,
  348. []byte{},
  349. make([]byte, 12, 12),
  350. make([]byte, mtu),
  351. )
  352. }
  353. ifce.closeTunnel(hostInfo)
  354. return w.WriteLine("Closed")
  355. }
  356. func sshCreateTunnel(ifce *Interface, fs any, a []string, w commands.StringWriter) error {
  357. flags, ok := fs.(*sshCreateTunnelFlags)
  358. if !ok {
  359. return nil
  360. }
  361. if len(a) == 0 {
  362. return w.WriteLine("No vpn address was provided")
  363. }
  364. vpnAddr, err := netip.ParseAddr(a[0])
  365. if err != nil {
  366. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  367. }
  368. if !vpnAddr.IsValid() {
  369. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  370. }
  371. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  372. if hostInfo != nil {
  373. return w.WriteLine(fmt.Sprintf("Tunnel already exists"))
  374. }
  375. hostInfo = ifce.handshakeManager.QueryVpnAddr(vpnAddr)
  376. if hostInfo != nil {
  377. return w.WriteLine(fmt.Sprintf("Tunnel already handshaking"))
  378. }
  379. var addr netip.AddrPort
  380. if flags.Address != "" {
  381. addr, err = netip.ParseAddrPort(flags.Address)
  382. if err != nil {
  383. return w.WriteLine("Address could not be parsed")
  384. }
  385. }
  386. // Set the remote from the callback, which StartHandshake runs under the handshake manager lock
  387. // before the hostinfo reaches the handshake loop or the static-host trigger, so the address is
  388. // in the RemoteList before the first handshake is sent.
  389. ifce.handshakeManager.StartHandshake(vpnAddr, func(hh *HandshakeHostInfo) {
  390. if addr.IsValid() {
  391. hh.hostinfo.SetRemote(addr)
  392. }
  393. })
  394. return w.WriteLine("Created")
  395. }
  396. func sshChangeRemote(ifce *Interface, fs any, a []string, w commands.StringWriter) error {
  397. flags, ok := fs.(*sshChangeRemoteFlags)
  398. if !ok {
  399. return nil
  400. }
  401. if len(a) == 0 {
  402. return w.WriteLine("No vpn address was provided")
  403. }
  404. if flags.Address == "" {
  405. return w.WriteLine("No address was provided")
  406. }
  407. addr, err := netip.ParseAddrPort(flags.Address)
  408. if err != nil {
  409. return w.WriteLine("Address could not be parsed")
  410. }
  411. vpnAddr, err := netip.ParseAddr(a[0])
  412. if err != nil {
  413. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  414. }
  415. if !vpnAddr.IsValid() {
  416. return w.WriteLine(fmt.Sprintf("The provided vpn address could not be parsed: %s", a[0]))
  417. }
  418. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  419. if hostInfo == nil {
  420. return w.WriteLine(fmt.Sprintf("Could not find tunnel for vpn address: %v", a[0]))
  421. }
  422. hostInfo.SetRemote(addr)
  423. return w.WriteLine("Changed")
  424. }
  425. func sshLogLevel(l *slog.Logger, fs any, a []string, w commands.StringWriter) error {
  426. ctrl, ok := l.Handler().(interface {
  427. GetLevel() slog.Level
  428. SetLevel(slog.Level)
  429. })
  430. if !ok {
  431. return w.WriteLine("Log level is not reconfigurable on this logger")
  432. }
  433. if len(a) == 0 {
  434. return w.WriteLine(fmt.Sprintf("Log level is: %s", logging.LevelName(ctrl.GetLevel())))
  435. }
  436. level, err := logging.ParseLevel(strings.ToLower(a[0]))
  437. if err != nil {
  438. return w.WriteLine(fmt.Sprintf("Unknown log level %s. Possible log levels: trace, debug, info, warn, error", a))
  439. }
  440. ctrl.SetLevel(level)
  441. return w.WriteLine(fmt.Sprintf("Log level is: %s", logging.LevelName(ctrl.GetLevel())))
  442. }
  443. func sshLogFormat(l *slog.Logger, fs any, a []string, w commands.StringWriter) error {
  444. ctrl, ok := l.Handler().(interface {
  445. GetFormat() string
  446. SetFormat(string) error
  447. })
  448. if !ok {
  449. return w.WriteLine("Log format is not reconfigurable on this logger")
  450. }
  451. if len(a) == 0 {
  452. return w.WriteLine(fmt.Sprintf("Log format is: %s", ctrl.GetFormat()))
  453. }
  454. if err := ctrl.SetFormat(strings.ToLower(a[0])); err != nil {
  455. return err
  456. }
  457. return w.WriteLine(fmt.Sprintf("Log format is: %s", ctrl.GetFormat()))
  458. }
  459. func sshPrintCert(ifce *Interface, fs any, a []string, w commands.StringWriter) error {
  460. args, ok := fs.(*sshPrintCertFlags)
  461. if !ok {
  462. return nil
  463. }
  464. cert := ifce.pki.getCertState().GetDefaultCertificate()
  465. if len(a) > 0 {
  466. vpnAddr, err := netip.ParseAddr(a[0])
  467. if err != nil {
  468. return w.WriteLine(fmt.Sprintf("The provided vpn addr could not be parsed: %s", a[0]))
  469. }
  470. if !vpnAddr.IsValid() {
  471. return w.WriteLine(fmt.Sprintf("The provided vpn addr could not be parsed: %s", a[0]))
  472. }
  473. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  474. if hostInfo == nil {
  475. return w.WriteLine(fmt.Sprintf("Could not find tunnel for vpn addr: %v", a[0]))
  476. }
  477. cert = hostInfo.GetCert().Certificate
  478. }
  479. if args.Json || args.Pretty {
  480. b, err := cert.MarshalJSON()
  481. if err != nil {
  482. return nil
  483. }
  484. if args.Pretty {
  485. buf := new(bytes.Buffer)
  486. err := json.Indent(buf, b, "", " ")
  487. b = buf.Bytes()
  488. if err != nil {
  489. return nil
  490. }
  491. }
  492. return w.WriteBytes(b)
  493. }
  494. if args.Raw {
  495. b, err := cert.MarshalPEM()
  496. if err != nil {
  497. return nil
  498. }
  499. return w.WriteBytes(b)
  500. }
  501. return w.WriteLine(cert.String())
  502. }
  503. func sshPrintRelays(ifce *Interface, fs any, a []string, w commands.StringWriter) error {
  504. args, ok := fs.(*sshPrintTunnelFlags)
  505. if !ok {
  506. w.WriteLine(fmt.Sprintf("sshPrintRelays failed to convert args type"))
  507. return nil
  508. }
  509. relays := map[uint32]*HostInfo{}
  510. ifce.hostMap.Lock()
  511. maps.Copy(relays, ifce.hostMap.Relays)
  512. ifce.hostMap.Unlock()
  513. type RelayFor struct {
  514. Error error
  515. Type string
  516. State string
  517. PeerAddr netip.Addr
  518. LocalIndex uint32
  519. RemoteIndex uint32
  520. RelayedThrough []netip.Addr
  521. }
  522. type RelayOutput struct {
  523. NebulaAddr netip.Addr
  524. RelayForAddrs []RelayFor
  525. }
  526. type CmdOutput struct {
  527. Relays []*RelayOutput
  528. }
  529. co := CmdOutput{}
  530. enc := json.NewEncoder(w.GetWriter())
  531. if args.Pretty {
  532. enc.SetIndent("", " ")
  533. }
  534. for k, v := range relays {
  535. ro := RelayOutput{NebulaAddr: v.vpnAddrs[0]}
  536. co.Relays = append(co.Relays, &ro)
  537. relayHI := ifce.hostMap.QueryVpnAddr(v.vpnAddrs[0])
  538. if relayHI == nil {
  539. ro.RelayForAddrs = append(ro.RelayForAddrs, RelayFor{Error: errors.New("could not find hostinfo")})
  540. continue
  541. }
  542. for _, vpnAddr := range relayHI.relayState.CopyRelayForIps() {
  543. rf := RelayFor{Error: nil}
  544. r, ok := relayHI.relayState.GetRelayForByAddr(vpnAddr)
  545. if ok {
  546. t := ""
  547. switch r.Type {
  548. case ForwardingType:
  549. t = "forwarding"
  550. case TerminalType:
  551. t = "terminal"
  552. default:
  553. t = "unknown"
  554. }
  555. s := ""
  556. switch r.State {
  557. case Requested:
  558. s = "requested"
  559. case Established:
  560. s = "established"
  561. default:
  562. s = "unknown"
  563. }
  564. rf.LocalIndex = r.LocalIndex
  565. rf.RemoteIndex = r.RemoteIndex
  566. rf.PeerAddr = r.PeerAddr
  567. rf.Type = t
  568. rf.State = s
  569. if rf.LocalIndex != k {
  570. rf.Error = fmt.Errorf("hostmap LocalIndex '%v' does not match RelayState LocalIndex", k)
  571. }
  572. }
  573. relayedHI := ifce.hostMap.QueryVpnAddr(vpnAddr)
  574. if relayedHI != nil {
  575. rf.RelayedThrough = append(rf.RelayedThrough, relayedHI.relayState.CopyRelayIps()...)
  576. }
  577. ro.RelayForAddrs = append(ro.RelayForAddrs, rf)
  578. }
  579. }
  580. err := enc.Encode(co)
  581. if err != nil {
  582. return err
  583. }
  584. return nil
  585. }
  586. func sshPrintTunnel(ifce *Interface, fs any, a []string, w commands.StringWriter) error {
  587. args, ok := fs.(*sshPrintTunnelFlags)
  588. if !ok {
  589. return nil
  590. }
  591. if len(a) == 0 {
  592. return w.WriteLine("No vpn address was provided")
  593. }
  594. vpnAddr, err := netip.ParseAddr(a[0])
  595. if err != nil {
  596. return w.WriteLine(fmt.Sprintf("The provided vpn addr could not be parsed: %s", a[0]))
  597. }
  598. if !vpnAddr.IsValid() {
  599. return w.WriteLine(fmt.Sprintf("The provided vpn addr could not be parsed: %s", a[0]))
  600. }
  601. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  602. if hostInfo == nil {
  603. return w.WriteLine(fmt.Sprintf("Could not find tunnel for vpn addr: %v", a[0]))
  604. }
  605. enc := json.NewEncoder(w.GetWriter())
  606. if args.Pretty {
  607. enc.SetIndent("", " ")
  608. }
  609. return enc.Encode(copyHostInfo(hostInfo, ifce.hostMap.GetPreferredRanges()))
  610. }
  611. func sshDeviceInfo(ifce *Interface, fs any, w commands.StringWriter) error {
  612. data := struct {
  613. Name string `json:"name"`
  614. Cidr []netip.Prefix `json:"cidr"`
  615. }{
  616. Name: ifce.inside.Name(),
  617. Cidr: make([]netip.Prefix, len(ifce.inside.Networks())),
  618. }
  619. copy(data.Cidr, ifce.inside.Networks())
  620. flags, ok := fs.(*sshDeviceInfoFlags)
  621. if !ok {
  622. return fmt.Errorf("internal error: expected flags to be sshDeviceInfoFlags but was %+v", fs)
  623. }
  624. if flags.Json || flags.Pretty {
  625. js := json.NewEncoder(w.GetWriter())
  626. if flags.Pretty {
  627. js.SetIndent("", " ")
  628. }
  629. return js.Encode(data)
  630. } else {
  631. return w.WriteLine(fmt.Sprintf("name=%v cidr=%v", data.Name, data.Cidr))
  632. }
  633. }
  634. // sshReload reloads the config and writes the result to the client. It reloads even if the client has gone away
  635. func sshReload(l *slog.Logger, c *config.C, w commands.StringWriter) error {
  636. writeErr := w.WriteLine("Reloading config")
  637. res := c.ReloadConfig()
  638. res.Log(l)
  639. if err := w.WriteLine(res.String()); writeErr == nil {
  640. writeErr = err
  641. }
  642. return writeErr
  643. }