commands.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. package nebula
  2. import (
  3. "encoding/json"
  4. "flag"
  5. "fmt"
  6. "io"
  7. "log/slog"
  8. "maps"
  9. "net/netip"
  10. "sort"
  11. "strings"
  12. "github.com/slackhq/nebula/cert"
  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; the ssh debug server is a consumer of it, not its owner, which is why this
  20. // is not ssh.go. See the commands package for what a Command is.
  21. type listHostMapFlags struct {
  22. ByIndex bool
  23. }
  24. type printCertFlags struct {
  25. Raw bool
  26. // Address is the -address form of the vpn addr, which the info API needs since a
  27. // query string has no operands; see identityFlags for why presence is recorded.
  28. Address string
  29. addressSet bool
  30. }
  31. func (f *printCertFlags) setAddress(v string) error {
  32. f.Address = v
  33. f.addressSet = true
  34. return nil
  35. }
  36. type changeRemoteFlags struct {
  37. Address string
  38. }
  39. type closeTunnelFlags struct {
  40. LocalOnly bool
  41. }
  42. type createTunnelFlags struct {
  43. Address string
  44. }
  45. // lighthouseInfo is one entry of list-lighthouse-addrmap.
  46. type lighthouseInfo struct {
  47. VpnAddr string `json:"vpnAddr"`
  48. Addrs *CacheMap `json:"addrs"`
  49. }
  50. // deviceInfo is the result of device-info.
  51. type deviceInfo struct {
  52. Name string `json:"name"`
  53. Cidr []netip.Prefix `json:"cidr"`
  54. }
  55. // certPEM is the result of print-cert -raw. The PEM form is a different thing to emit,
  56. // not a different formatting of the certificate, so it is its own shape.
  57. type certPEM struct {
  58. PEM string `json:"pem"`
  59. }
  60. // Result shapes for print-relays. Field names are the JSON keys; changing them would
  61. // change the output.
  62. type relayFor struct {
  63. Error string `json:",omitempty"`
  64. Type string
  65. State string
  66. PeerAddr netip.Addr
  67. LocalIndex uint32
  68. RemoteIndex uint32
  69. RelayedThrough []netip.Addr
  70. }
  71. type relayOutput struct {
  72. NebulaAddr netip.Addr
  73. RelayForAddrs []relayFor
  74. }
  75. type relaysOutput struct {
  76. Relays []*relayOutput
  77. }
  78. type versionInfo struct {
  79. Version string `json:"version"`
  80. }
  81. type logLevelInfo struct {
  82. Level string `json:"level"`
  83. }
  84. type logFormatInfo struct {
  85. Format string `json:"format"`
  86. }
  87. // defineListHostMapFlags declares the flags list-hostmap and list-pending-hostmap share.
  88. func defineListHostMapFlags(fl *flag.FlagSet, s *listHostMapFlags) {
  89. fl.BoolVar(&s.ByIndex, "by-index", false, "gets all hosts in the hostmap from the index table")
  90. }
  91. func attachCommands(l *slog.Logger, c *config.C, reg *commands.Registry, f *Interface) {
  92. reg.Register(commands.Flagged(&commands.Command{
  93. Name: commands.ListHostmap,
  94. ShortDescription: "List all known previously connected hosts",
  95. Text: commands.Typed(textListHostMap),
  96. }, defineListHostMapFlags, func(s *listHostMapFlags, a []string) (commands.Result, error) {
  97. return runListHostMap(f.hostMap, s)
  98. }))
  99. reg.Register(commands.Flagged(&commands.Command{
  100. Name: commands.ListPendingHostmap,
  101. ShortDescription: "List all handshaking hosts",
  102. Text: commands.Typed(textListHostMap),
  103. }, defineListHostMapFlags, func(s *listHostMapFlags, a []string) (commands.Result, error) {
  104. return runListHostMap(f.handshakeManager, s)
  105. }))
  106. reg.Register(&commands.Command{
  107. Name: commands.ListLighthouseAddrmap,
  108. ShortDescription: "List all lighthouse map entries",
  109. Run: func(fs any, a []string) (commands.Result, error) {
  110. return runListLighthouseMap(f.lightHouse)
  111. },
  112. Text: commands.Typed(textListLighthouseMap),
  113. })
  114. reg.Register(&commands.Command{
  115. Name: commands.Reload,
  116. ShortDescription: "Reloads configuration from disk, same as sending HUP to the process",
  117. // The line goes out before the reload: a reload that restarts the ssh server ends
  118. // the session that asked, and this is what it sees.
  119. Run: func(fs any, a []string) (commands.Result, error) {
  120. return commands.Did("Reloading config"), nil
  121. },
  122. Then: func() commands.Result {
  123. return runReload(l, c)
  124. },
  125. })
  126. reg.Register(&commands.Command{
  127. Name: commands.LogLevel,
  128. ShortDescription: "Gets or sets the current log level",
  129. Run: func(fs any, a []string) (commands.Result, error) {
  130. return runLogLevel(l, a)
  131. },
  132. Text: commands.Typed(textLogLevel),
  133. })
  134. reg.Register(&commands.Command{
  135. Name: commands.LogFormat,
  136. ShortDescription: "Gets or sets the current log format",
  137. Run: func(fs any, a []string) (commands.Result, error) {
  138. return runLogFormat(l, a)
  139. },
  140. Text: commands.Typed(textLogFormat),
  141. })
  142. reg.Register(&commands.Command{
  143. Name: commands.Version,
  144. ShortDescription: "Prints the currently running version of nebula",
  145. Run: func(fs any, a []string) (commands.Result, error) {
  146. return commands.Value(versionInfo{Version: f.version}), nil
  147. },
  148. Text: commands.Typed(textVersion),
  149. })
  150. reg.Register(&commands.Command{
  151. Name: commands.DeviceInfo,
  152. ShortDescription: "Prints information about the network device.",
  153. Run: func(fs any, a []string) (commands.Result, error) {
  154. return commands.Value(runDeviceInfo(f)), nil
  155. },
  156. Text: commands.Typed(textDeviceInfo),
  157. })
  158. reg.Register(commands.Flagged(&commands.Command{
  159. Name: commands.PrintCert,
  160. ShortDescription: "Prints the current certificate being used or the certificate for the provided vpn addr",
  161. Help: "The vpn addr is an operand or -address, which may carry a port so a connection's remote address can be passed through as is. For a peer it is the certificate nebula verified when the tunnel came up; with no address it is this node's own, the v2 certificate when both versions are loaded. -raw prints the PEM.",
  162. Identity: true,
  163. Text: textPrintCert,
  164. }, func(fl *flag.FlagSet, s *printCertFlags) {
  165. fl.BoolVar(&s.Raw, "raw", false, "prints the PEM encoded certificate; with -json, an object holding it")
  166. fl.Func("address", "The vpn addr to look up, optionally with a port", s.setAddress)
  167. }, func(s *printCertFlags, a []string) (commands.Result, error) {
  168. return runPrintCert(f, s, a)
  169. }))
  170. reg.Register(commands.Flagged(&commands.Command{
  171. Name: commands.Identity,
  172. ShortDescription: "Prints the certificate identity of the host owning the provided vpn addr",
  173. Help: "Identity is what the certificate says: name, vpn addrs, networks, groups, fingerprint, issuer and validity. The address may carry a port, so a connection's remote address can be passed through as is. For this node's own identity use whoami.",
  174. Identity: true,
  175. Text: commands.Typed(textIdentity),
  176. }, func(fl *flag.FlagSet, s *identityFlags) {
  177. fl.Func("address", "The vpn addr to look up, optionally with a port", s.setAddress)
  178. }, func(s *identityFlags, a []string) (commands.Result, error) {
  179. return runIdentity(f, s, a)
  180. }))
  181. reg.Register(&commands.Command{
  182. Name: commands.Whoami,
  183. ShortDescription: "Prints the certificate identity of this node",
  184. Help: "The fields identity prints, read from this node's own certificate.",
  185. Identity: true,
  186. Text: commands.Typed(textIdentity),
  187. Run: func(fs any, a []string) (commands.Result, error) {
  188. if len(a) > 0 {
  189. return commands.Failed("whoami takes no arguments"), nil
  190. }
  191. return runWhoami(f)
  192. },
  193. })
  194. reg.Register(&commands.Command{
  195. Name: commands.PrintTunnel,
  196. ShortDescription: "Prints json details about a tunnel for the provided vpn addr",
  197. Run: func(fs any, a []string) (commands.Result, error) {
  198. return runPrintTunnel(f, a)
  199. },
  200. // No Text on purpose: nothing human readable exists yet, and shipping an interim
  201. // form would invite scripts against it. ssh defaults these to JSON; ctl asks.
  202. JSONOnly: true,
  203. })
  204. reg.Register(&commands.Command{
  205. Name: commands.PrintRelays,
  206. ShortDescription: "Prints json details about all relay info",
  207. Run: func(fs any, a []string) (commands.Result, error) {
  208. return commands.Value(runPrintRelays(f)), nil
  209. },
  210. // No Text on purpose; see print-tunnel.
  211. JSONOnly: true,
  212. })
  213. reg.Register(commands.Flagged(&commands.Command{
  214. Name: commands.ChangeRemote,
  215. ShortDescription: "Changes the remote address used in the tunnel for the provided vpn addr",
  216. }, func(fl *flag.FlagSet, s *changeRemoteFlags) {
  217. fl.StringVar(&s.Address, "address", "", "The new remote address, ip:port")
  218. }, func(s *changeRemoteFlags, a []string) (commands.Result, error) {
  219. return runChangeRemote(f, s, a)
  220. }))
  221. reg.Register(commands.Flagged(&commands.Command{
  222. Name: commands.CloseTunnel,
  223. ShortDescription: "Closes a tunnel for the provided vpn addr",
  224. }, func(fl *flag.FlagSet, s *closeTunnelFlags) {
  225. fl.BoolVar(&s.LocalOnly, "local-only", false, "Disables notifying the remote that the tunnel is shutting down")
  226. }, func(s *closeTunnelFlags, a []string) (commands.Result, error) {
  227. return runCloseTunnel(f, s, a)
  228. }))
  229. reg.Register(commands.Flagged(&commands.Command{
  230. Name: commands.CreateTunnel,
  231. ShortDescription: "Creates a tunnel for the provided vpn address",
  232. Help: "The lighthouses will be queried for real addresses but you can provide one as well.",
  233. }, func(fl *flag.FlagSet, s *createTunnelFlags) {
  234. fl.StringVar(&s.Address, "address", "", "Optionally provide a real remote address, ip:port ")
  235. }, func(s *createTunnelFlags, a []string) (commands.Result, error) {
  236. return runCreateTunnel(f, s, a)
  237. }))
  238. reg.Register(&commands.Command{
  239. Name: commands.QueryLighthouse,
  240. ShortDescription: "Query the lighthouses for the provided vpn address",
  241. Help: "This command is asynchronous. Only currently known udp addresses will be printed.",
  242. Run: func(fs any, a []string) (commands.Result, error) {
  243. return runQueryLighthouse(f, a)
  244. },
  245. // No Text on purpose; see print-tunnel.
  246. JSONOnly: true,
  247. })
  248. }
  249. // runListHostMap computes the hostmap listing. Presentation is Render's job.
  250. func runListHostMap(hl controlHostLister, fs *listHostMapFlags) (commands.Result, error) {
  251. var hm []ControlHostInfo
  252. if fs.ByIndex {
  253. hm = listHostMapIndexes(hl)
  254. } else {
  255. hm = listHostMapHosts(hl)
  256. }
  257. sort.Slice(hm, func(i, j int) bool {
  258. return hm[i].VpnAddrs[0].Compare(hm[j].VpnAddrs[0]) < 0
  259. })
  260. return commands.Value(hm), nil
  261. }
  262. // textListHostMap is the human rendering, shared by every surface.
  263. func textListHostMap(hm []ControlHostInfo, w io.Writer) error {
  264. for _, h := range hm {
  265. if _, err := fmt.Fprintf(w, "%s: %s\n", h.VpnAddrs, h.RemoteAddrs); err != nil {
  266. return err
  267. }
  268. }
  269. return nil
  270. }
  271. func runListLighthouseMap(lightHouse *LightHouse) (commands.Result, error) {
  272. lightHouse.RLock()
  273. addrMap := make([]lighthouseInfo, len(lightHouse.addrMap))
  274. x := 0
  275. for k, v := range lightHouse.addrMap {
  276. addrMap[x] = lighthouseInfo{
  277. VpnAddr: k.String(),
  278. Addrs: v.CopyCache(),
  279. }
  280. x++
  281. }
  282. lightHouse.RUnlock()
  283. sort.Slice(addrMap, func(i, j int) bool {
  284. return strings.Compare(addrMap[i].VpnAddr, addrMap[j].VpnAddr) < 0
  285. })
  286. return commands.Value(addrMap), nil
  287. }
  288. func textListLighthouseMap(addrMap []lighthouseInfo, w io.Writer) error {
  289. for _, e := range addrMap {
  290. b, err := json.Marshal(e.Addrs)
  291. if err != nil {
  292. return err
  293. }
  294. if _, err := fmt.Fprintf(w, "%s: %s\n", e.VpnAddr, string(b)); err != nil {
  295. return err
  296. }
  297. }
  298. return nil
  299. }
  300. func textVersion(vi versionInfo, w io.Writer) error {
  301. _, err := fmt.Fprintf(w, "%s\n", vi.Version)
  302. return err
  303. }
  304. // vpnAddrOperand parses the vpn address a command takes as its first operand. noun is
  305. // how the command's messages name it, "vpn address" or "vpn addr", which the output
  306. // golden pins per command. With no operand, or one that does not parse, ok is false and
  307. // res is the refusal to return.
  308. func vpnAddrOperand(a []string, noun string) (vpnAddr netip.Addr, res commands.Result, ok bool) {
  309. if len(a) == 0 {
  310. return netip.Addr{}, commands.Failed("No vpn address was provided"), false
  311. }
  312. vpnAddr, err := netip.ParseAddr(a[0])
  313. if err != nil {
  314. return netip.Addr{}, commands.Failed("The provided %s could not be parsed: %s", noun, a[0]), false
  315. }
  316. return vpnAddr, commands.Result{}, true
  317. }
  318. // tunnelOperand is vpnAddrOperand followed by the lookup of the tunnel it names.
  319. func tunnelOperand(ifce *Interface, a []string, noun string) (*HostInfo, commands.Result, bool) {
  320. vpnAddr, res, ok := vpnAddrOperand(a, noun)
  321. if !ok {
  322. return nil, res, false
  323. }
  324. hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)
  325. if hostInfo == nil {
  326. return nil, commands.Failed("Could not find tunnel for %s: %v", noun, a[0]), false
  327. }
  328. return hostInfo, commands.Result{}, true
  329. }
  330. func runQueryLighthouse(ifce *Interface, a []string) (commands.Result, error) {
  331. vpnAddr, res, ok := vpnAddrOperand(a, "vpn address")
  332. if !ok {
  333. return res, nil
  334. }
  335. var cm *CacheMap
  336. rl := ifce.lightHouse.Query(vpnAddr)
  337. if rl != nil {
  338. cm = rl.CopyCache()
  339. }
  340. return commands.Value(cm), nil
  341. }
  342. func runCloseTunnel(ifce *Interface, flags *closeTunnelFlags, a []string) (commands.Result, error) {
  343. hostInfo, res, ok := tunnelOperand(ifce, a, "vpn address")
  344. if !ok {
  345. return res, nil
  346. }
  347. if !flags.LocalOnly {
  348. ifce.send(
  349. header.CloseTunnel,
  350. 0,
  351. hostInfo.ConnectionState,
  352. hostInfo,
  353. []byte{},
  354. make([]byte, 12, 12),
  355. make([]byte, mtu),
  356. )
  357. }
  358. ifce.closeTunnel(hostInfo)
  359. return commands.Did("Closed"), nil
  360. }
  361. func runCreateTunnel(ifce *Interface, flags *createTunnelFlags, a []string) (commands.Result, error) {
  362. vpnAddr, res, ok := vpnAddrOperand(a, "vpn address")
  363. if !ok {
  364. return res, nil
  365. }
  366. if hostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr); hostInfo != nil {
  367. return commands.Failed("Tunnel already exists"), nil
  368. }
  369. if hostInfo := ifce.handshakeManager.QueryVpnAddr(vpnAddr); hostInfo != nil {
  370. return commands.Failed("Tunnel already handshaking"), nil
  371. }
  372. var addr netip.AddrPort
  373. if flags.Address != "" {
  374. var err error
  375. addr, err = netip.ParseAddrPort(flags.Address)
  376. if err != nil {
  377. return commands.Failed("Address could not be parsed"), nil
  378. }
  379. }
  380. // Set the remote from the callback, which StartHandshake runs under the handshake manager lock
  381. // before the hostinfo reaches the handshake loop or the static-host trigger, so the address is
  382. // in the RemoteList before the first handshake is sent.
  383. ifce.handshakeManager.StartHandshake(vpnAddr, func(hh *HandshakeHostInfo) {
  384. if addr.IsValid() {
  385. hh.hostinfo.SetRemote(addr)
  386. }
  387. })
  388. return commands.Did("Created"), nil
  389. }
  390. func runChangeRemote(ifce *Interface, flags *changeRemoteFlags, a []string) (commands.Result, error) {
  391. if len(a) == 0 {
  392. return commands.Failed("No vpn address was provided"), nil
  393. }
  394. if flags.Address == "" {
  395. return commands.Failed("No address was provided"), nil
  396. }
  397. addr, err := netip.ParseAddrPort(flags.Address)
  398. if err != nil {
  399. return commands.Failed("Address could not be parsed"), nil
  400. }
  401. hostInfo, res, ok := tunnelOperand(ifce, a, "vpn address")
  402. if !ok {
  403. return res, nil
  404. }
  405. hostInfo.SetRemote(addr)
  406. return commands.Did("Changed"), nil
  407. }
  408. func runLogLevel(l *slog.Logger, a []string) (commands.Result, error) {
  409. ctrl, ok := l.Handler().(interface {
  410. GetLevel() slog.Level
  411. SetLevel(slog.Level)
  412. })
  413. if !ok {
  414. return commands.Failed("Log level is not reconfigurable on this logger"), nil
  415. }
  416. if len(a) > 0 {
  417. level, err := logging.ParseLevel(strings.ToLower(a[0]))
  418. if err != nil {
  419. return commands.Failed("Unknown log level %s. Possible log levels: trace, debug, info, warn, error", a), nil
  420. }
  421. ctrl.SetLevel(level)
  422. }
  423. return commands.Value(logLevelInfo{Level: logging.LevelName(ctrl.GetLevel())}), nil
  424. }
  425. func textLogLevel(li logLevelInfo, w io.Writer) error {
  426. _, err := fmt.Fprintf(w, "Log level is: %s\n", li.Level)
  427. return err
  428. }
  429. func runLogFormat(l *slog.Logger, a []string) (commands.Result, error) {
  430. ctrl, ok := l.Handler().(interface {
  431. GetFormat() string
  432. SetFormat(string) error
  433. })
  434. if !ok {
  435. return commands.Failed("Log format is not reconfigurable on this logger"), nil
  436. }
  437. if len(a) > 0 {
  438. if err := ctrl.SetFormat(strings.ToLower(a[0])); err != nil {
  439. return commands.Failed("Unable to set log format: %s", err), nil
  440. }
  441. }
  442. return commands.Value(logFormatInfo{Format: ctrl.GetFormat()}), nil
  443. }
  444. func textLogFormat(fi logFormatInfo, w io.Writer) error {
  445. _, err := fmt.Fprintf(w, "Log format is: %s\n", fi.Format)
  446. return err
  447. }
  448. // runPrintCert prints a certificate: this node's own, or the one the host owning a vpn
  449. // addr presented. The addr is an operand, as it always was over ssh, or -address, which
  450. // is how the info API names it, and an address of our own answers with our own
  451. // certificate as identity does. A tunnel still handshaking, or one being torn down, has
  452. // no certificate and is reported as not found.
  453. func runPrintCert(ifce *Interface, args *printCertFlags, a []string) (commands.Result, error) {
  454. if len(a) > 0 && args.addressSet {
  455. return commands.Failed("print-cert takes one vpn addr, as -address or as an operand, not both"), nil
  456. }
  457. cs := ifce.pki.getCertState()
  458. var c cert.Certificate
  459. switch {
  460. case len(a) > 0:
  461. vpnAddr, res, ok := vpnAddrOperand(a, "vpn addr")
  462. if !ok {
  463. return res, nil
  464. }
  465. if c, _ = findCertificateForVpnAddr(cs, ifce.hostMap, vpnAddr); c == nil {
  466. return commands.NotFound("Could not find tunnel for vpn addr: %v", a[0]), nil
  467. }
  468. case args.addressSet:
  469. vpnAddr, err := parseVpnAddrWithOptionalPort(args.Address)
  470. if err != nil {
  471. return commands.Failed("The provided vpn addr could not be parsed: %s", args.Address), nil
  472. }
  473. if c, _ = findCertificateForVpnAddr(cs, ifce.hostMap, vpnAddr); c == nil {
  474. return commands.NotFound("Could not find tunnel for vpn addr: %v", args.Address), nil
  475. }
  476. default:
  477. if c = ownCertificate(cs); c == nil {
  478. return commands.Failed("No certificate is loaded"), nil
  479. }
  480. }
  481. if args.Raw {
  482. b, err := c.MarshalPEM()
  483. if err != nil {
  484. return commands.Failed("Unable to marshal the certificate: %s", err), nil
  485. }
  486. return commands.Value(certPEM{PEM: string(b)}), nil
  487. }
  488. return commands.Value(c), nil
  489. }
  490. // textPrintCert takes the value untyped: print-cert returns a certificate or, with -raw,
  491. // its PEM, and those are two things to emit rather than two renderings of one.
  492. func textPrintCert(v any, w io.Writer) error {
  493. switch c := v.(type) {
  494. case certPEM:
  495. _, err := io.WriteString(w, c.PEM)
  496. return err
  497. case cert.Certificate:
  498. _, err := io.WriteString(w, c.String()+"\n")
  499. return err
  500. default:
  501. return fmt.Errorf("expected a certificate, got %T", v)
  502. }
  503. }
  504. func runPrintRelays(ifce *Interface) any {
  505. ifce.hostMap.RLock()
  506. relays := maps.Clone(ifce.hostMap.Relays)
  507. ifce.hostMap.RUnlock()
  508. co := relaysOutput{}
  509. for k, v := range relays {
  510. ro := relayOutput{NebulaAddr: v.vpnAddrs[0]}
  511. co.Relays = append(co.Relays, &ro)
  512. relayHI := ifce.hostMap.QueryVpnAddr(v.vpnAddrs[0])
  513. if relayHI == nil {
  514. ro.RelayForAddrs = append(ro.RelayForAddrs, relayFor{Error: "could not find hostinfo"})
  515. continue
  516. }
  517. for _, vpnAddr := range relayHI.relayState.CopyRelayForIps() {
  518. rf := relayFor{}
  519. r, ok := relayHI.relayState.GetRelayForByAddr(vpnAddr)
  520. if ok {
  521. t := ""
  522. switch r.Type {
  523. case ForwardingType:
  524. t = "forwarding"
  525. case TerminalType:
  526. t = "terminal"
  527. default:
  528. t = "unknown"
  529. }
  530. st := ""
  531. switch r.State {
  532. case Requested:
  533. st = "requested"
  534. case Established:
  535. st = "established"
  536. default:
  537. st = "unknown"
  538. }
  539. rf.LocalIndex = r.LocalIndex
  540. rf.RemoteIndex = r.RemoteIndex
  541. rf.PeerAddr = r.PeerAddr
  542. rf.Type = t
  543. rf.State = st
  544. if rf.LocalIndex != k {
  545. rf.Error = fmt.Sprintf("hostmap LocalIndex '%v' does not match RelayState LocalIndex", k)
  546. }
  547. }
  548. relayedHI := ifce.hostMap.QueryVpnAddr(vpnAddr)
  549. if relayedHI != nil {
  550. rf.RelayedThrough = append(rf.RelayedThrough, relayedHI.relayState.CopyRelayIps()...)
  551. }
  552. ro.RelayForAddrs = append(ro.RelayForAddrs, rf)
  553. }
  554. }
  555. return co
  556. }
  557. func runPrintTunnel(ifce *Interface, a []string) (commands.Result, error) {
  558. hostInfo, res, ok := tunnelOperand(ifce, a, "vpn addr")
  559. if !ok {
  560. return res, nil
  561. }
  562. return commands.Value(copyHostInfo(hostInfo, ifce.hostMap.GetPreferredRanges())), nil
  563. }
  564. func runDeviceInfo(ifce *Interface) deviceInfo {
  565. d := deviceInfo{
  566. Name: ifce.inside.Name(),
  567. Cidr: make([]netip.Prefix, len(ifce.inside.Networks())),
  568. }
  569. copy(d.Cidr, ifce.inside.Networks())
  570. return d
  571. }
  572. func textDeviceInfo(d deviceInfo, w io.Writer) error {
  573. _, err := fmt.Fprintf(w, "name=%v cidr=%v\n", d.Name, d.Cidr)
  574. return err
  575. }
  576. // runReload reloads the config and reports how it went. A reload that failed is a
  577. // refusal, exit 1 over ssh; one that merely needs a restart is not.
  578. func runReload(l *slog.Logger, c *config.C) commands.Result {
  579. res := c.ReloadConfig()
  580. res.Log(l)
  581. if res.Err != nil || res.Failed {
  582. return commands.Failed("%s", res.String())
  583. }
  584. return commands.Did("%s", res.String())
  585. }