natc.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // The natc command is a work-in-progress implementation of a NAT based
  4. // connector for Tailscale. It is intended to be used to route traffic to a
  5. // specific domain through a specific node.
  6. package main
  7. import (
  8. "context"
  9. "encoding/json"
  10. "errors"
  11. "expvar"
  12. "flag"
  13. "fmt"
  14. "log"
  15. "math/rand/v2"
  16. "net"
  17. "net/http"
  18. "net/netip"
  19. "os"
  20. "path/filepath"
  21. "strings"
  22. "time"
  23. "github.com/gaissmai/bart"
  24. "github.com/hashicorp/raft"
  25. "github.com/inetaf/tcpproxy"
  26. "github.com/peterbourgon/ff/v3"
  27. "go4.org/netipx"
  28. "golang.org/x/net/dns/dnsmessage"
  29. "tailscale.com/client/local"
  30. "tailscale.com/client/tailscale/apitype"
  31. "tailscale.com/cmd/natc/ippool"
  32. "tailscale.com/envknob"
  33. "tailscale.com/hostinfo"
  34. "tailscale.com/ipn"
  35. "tailscale.com/net/netutil"
  36. "tailscale.com/tsnet"
  37. "tailscale.com/tsweb"
  38. "tailscale.com/util/mak"
  39. "tailscale.com/util/must"
  40. "tailscale.com/wgengine/netstack"
  41. )
  42. func main() {
  43. hostinfo.SetApp("natc")
  44. if !envknob.UseWIPCode() {
  45. log.Fatal("cmd/natc is a work in progress and has not been security reviewed;\nits use requires TAILSCALE_USE_WIP_CODE=1 be set in the environment for now.")
  46. }
  47. // Parse flags
  48. fs := flag.NewFlagSet("natc", flag.ExitOnError)
  49. var (
  50. debugPort = fs.Int("debug-port", 8893, "Listening port for debug/metrics endpoint")
  51. hostname = fs.String("hostname", "", "Hostname to register the service under")
  52. siteID = fs.Uint("site-id", 1, "an integer site ID to use for the ULA prefix which allows for multiple proxies to act in a HA configuration")
  53. v4PfxStr = fs.String("v4-pfx", "100.64.1.0/24", "comma-separated list of IPv4 prefixes to advertise")
  54. dnsServers = fs.String("dns-servers", "", "comma separated list of upstream DNS to use, including host and port (use system if empty)")
  55. verboseTSNet = fs.Bool("verbose-tsnet", false, "enable verbose logging in tsnet")
  56. printULA = fs.Bool("print-ula", false, "print the ULA prefix and exit")
  57. ignoreDstPfxStr = fs.String("ignore-destinations", "", "comma-separated list of prefixes to ignore")
  58. wgPort = fs.Uint("wg-port", 0, "udp port for wireguard and peer to peer traffic")
  59. clusterTag = fs.String("cluster-tag", "", "optionally run in a consensus cluster with other nodes with this tag")
  60. server = fs.String("login-server", ipn.DefaultControlURL, "the base URL of control server")
  61. stateDir = fs.String("state-dir", "", "path to directory in which to store app state")
  62. clusterFollowOnly = fs.Bool("follow-only", false, "Try to find a leader with the cluster tag or exit.")
  63. clusterAdminPort = fs.Int("cluster-admin-port", 8081, "Port on localhost for the cluster admin HTTP API")
  64. )
  65. ff.Parse(fs, os.Args[1:], ff.WithEnvVarPrefix("TS_NATC"))
  66. if *printULA {
  67. fmt.Println(ula(uint16(*siteID)))
  68. return
  69. }
  70. ctx, cancel := context.WithCancel(context.Background())
  71. defer cancel()
  72. if *siteID == 0 {
  73. log.Fatalf("site-id must be set")
  74. } else if *siteID > 0xffff {
  75. log.Fatalf("site-id must be in the range [0, 65535]")
  76. }
  77. var ignoreDstTable *bart.Table[bool]
  78. for s := range strings.SplitSeq(*ignoreDstPfxStr, ",") {
  79. s := strings.TrimSpace(s)
  80. if s == "" {
  81. continue
  82. }
  83. if ignoreDstTable == nil {
  84. ignoreDstTable = &bart.Table[bool]{}
  85. }
  86. pfx, err := netip.ParsePrefix(s)
  87. if err != nil {
  88. log.Fatalf("unable to parse prefix: %v", err)
  89. }
  90. if pfx.Masked() != pfx {
  91. log.Fatalf("prefix %v is not normalized (bits are set outside the mask)", pfx)
  92. }
  93. ignoreDstTable.Insert(pfx, true)
  94. }
  95. ts := &tsnet.Server{
  96. Hostname: *hostname,
  97. Dir: *stateDir,
  98. }
  99. ts.ControlURL = *server
  100. if *wgPort != 0 {
  101. if *wgPort >= 1<<16 {
  102. log.Fatalf("wg-port must be in the range [0, 65535]")
  103. }
  104. ts.Port = uint16(*wgPort)
  105. }
  106. defer ts.Close()
  107. if *verboseTSNet {
  108. ts.Logf = log.Printf
  109. }
  110. // Start special-purpose listeners: dns, http promotion, debug server
  111. if *debugPort != 0 {
  112. mux := http.NewServeMux()
  113. tsweb.Debugger(mux)
  114. dln, err := ts.Listen("tcp", fmt.Sprintf(":%d", *debugPort))
  115. if err != nil {
  116. log.Fatalf("failed listening on debug port: %v", err)
  117. }
  118. defer dln.Close()
  119. go func() {
  120. log.Fatalf("debug serve: %v", http.Serve(dln, mux))
  121. }()
  122. }
  123. if err := ts.Start(); err != nil {
  124. log.Fatalf("ts.Start: %v", err)
  125. }
  126. // TODO(raggi): this is not a public interface or guarantee.
  127. ns := ts.Sys().Netstack.Get().(*netstack.Impl)
  128. if *debugPort != 0 {
  129. expvar.Publish("netstack", ns.ExpVar())
  130. }
  131. lc, err := ts.LocalClient()
  132. if err != nil {
  133. log.Fatalf("LocalClient() failed: %v", err)
  134. }
  135. if _, err := ts.Up(ctx); err != nil {
  136. log.Fatalf("ts.Up: %v", err)
  137. }
  138. var prefixes []netip.Prefix
  139. for _, s := range strings.Split(*v4PfxStr, ",") {
  140. p := netip.MustParsePrefix(strings.TrimSpace(s))
  141. if p.Masked() != p {
  142. log.Fatalf("v4 prefix %v is not a masked prefix", p)
  143. }
  144. prefixes = append(prefixes, p)
  145. }
  146. routes, dnsAddr, addrPool := calculateAddresses(prefixes)
  147. v6ULA := ula(uint16(*siteID))
  148. var ipp ippool.IPPool
  149. if *clusterTag != "" {
  150. cipp := ippool.NewConsensusIPPool(addrPool)
  151. clusterStateDir, err := getClusterStatePath(*stateDir)
  152. if err != nil {
  153. log.Fatalf("Creating cluster state dir failed: %v", err)
  154. }
  155. err = cipp.StartConsensus(ctx, ts, ippool.ClusterOpts{
  156. Tag: *clusterTag,
  157. StateDir: clusterStateDir,
  158. FollowOnly: *clusterFollowOnly,
  159. })
  160. if err != nil {
  161. log.Fatalf("StartConsensus: %v", err)
  162. }
  163. defer func() {
  164. err := cipp.StopConsensus(ctx)
  165. if err != nil {
  166. log.Printf("Error stopping consensus: %v", err)
  167. }
  168. }()
  169. ipp = cipp
  170. go func() {
  171. // This listens on localhost only, so that only those with access to the host machine
  172. // can remove servers from the cluster config.
  173. log.Print(http.ListenAndServe(fmt.Sprintf("127.0.0.1:%d", *clusterAdminPort), httpClusterAdmin(cipp)))
  174. }()
  175. } else {
  176. ipp = &ippool.SingleMachineIPPool{IPSet: addrPool}
  177. }
  178. c := &connector{
  179. ts: ts,
  180. whois: lc,
  181. v6ULA: v6ULA,
  182. ignoreDsts: ignoreDstTable,
  183. ipPool: ipp,
  184. routes: routes,
  185. dnsAddr: dnsAddr,
  186. resolver: getResolver(*dnsServers),
  187. }
  188. c.run(ctx, lc)
  189. }
  190. // getResolver parses serverFlag and returns either the default resolver, or a
  191. // resolver that uses the provided comma-separated DNS server AddrPort's, or
  192. // panics.
  193. func getResolver(serverFlag string) lookupNetIPer {
  194. if serverFlag == "" {
  195. return net.DefaultResolver
  196. }
  197. var addrs []string
  198. for s := range strings.SplitSeq(serverFlag, ",") {
  199. s = strings.TrimSpace(s)
  200. addr, err := netip.ParseAddrPort(s)
  201. if err != nil {
  202. log.Fatalf("dns server provided: %q does not parse: %v", s, err)
  203. }
  204. addrs = append(addrs, addr.String())
  205. }
  206. return &net.Resolver{
  207. PreferGo: true,
  208. Dial: func(ctx context.Context, network string, address string) (net.Conn, error) {
  209. var dialer net.Dialer
  210. // TODO(raggi): perhaps something other than random?
  211. return dialer.DialContext(ctx, network, addrs[rand.N(len(addrs))])
  212. },
  213. }
  214. }
  215. func calculateAddresses(prefixes []netip.Prefix) (*netipx.IPSet, netip.Addr, *netipx.IPSet) {
  216. var ipsb netipx.IPSetBuilder
  217. for _, p := range prefixes {
  218. ipsb.AddPrefix(p)
  219. }
  220. routesToAdvertise := must.Get(ipsb.IPSet())
  221. dnsAddr := routesToAdvertise.Ranges()[0].From()
  222. ipsb.Remove(dnsAddr)
  223. addrPool := must.Get(ipsb.IPSet())
  224. return routesToAdvertise, dnsAddr, addrPool
  225. }
  226. type lookupNetIPer interface {
  227. LookupNetIP(ctx context.Context, net, host string) ([]netip.Addr, error)
  228. }
  229. type whoiser interface {
  230. WhoIs(ctx context.Context, remoteAddr string) (*apitype.WhoIsResponse, error)
  231. }
  232. type connector struct {
  233. // ts is the tsnet.Server used to host the connector.
  234. ts *tsnet.Server
  235. // whois is the local.Client used to interact with the tsnet.Server hosting this
  236. // connector.
  237. whois whoiser
  238. // dnsAddr is the IPv4 address to listen on for DNS requests. It is used to
  239. // prevent the app connector from assigning it to a domain.
  240. dnsAddr netip.Addr
  241. // routes is the set of IPv4 ranges advertised to the tailnet, or ipset with
  242. // the dnsAddr removed.
  243. routes *netipx.IPSet
  244. // v6ULA is the ULA prefix used by the app connector to assign IPv6 addresses.
  245. v6ULA netip.Prefix
  246. // ignoreDsts is initialized at start up with the contents of --ignore-destinations (if none it is nil)
  247. // It is never mutated, only used for lookups.
  248. // Users who want to natc a DNS wildcard but not every address record in that domain can supply the
  249. // exceptions in --ignore-destinations. When we receive a dns request we will look up the fqdn
  250. // and if any of the ip addresses in response to the lookup match any 'ignore destinations' prefix we will
  251. // return a dns response that contains the ip addresses we discovered with the lookup (ie not the
  252. // natc behavior, which would return a dummy ip address pointing at natc).
  253. ignoreDsts *bart.Table[bool]
  254. // ipPool contains the per-peer IPv4 address assignments.
  255. ipPool ippool.IPPool
  256. // resolver is used to lookup IP addresses for DNS queries.
  257. resolver lookupNetIPer
  258. }
  259. // v6ULA is the ULA prefix used by the app connector to assign IPv6 addresses.
  260. // The 8th and 9th bytes are used to encode the site ID which allows for
  261. // multiple proxies to act in a HA configuration.
  262. // mnemonic: a99c = appc
  263. var v6ULA = netip.MustParsePrefix("fd7a:115c:a1e0:a99c::/64")
  264. func ula(siteID uint16) netip.Prefix {
  265. as16 := v6ULA.Addr().As16()
  266. as16[8] = byte(siteID >> 8)
  267. as16[9] = byte(siteID)
  268. return netip.PrefixFrom(netip.AddrFrom16(as16), 64+16)
  269. }
  270. // run runs the connector.
  271. //
  272. // The passed in context is only used for the initial setup. The connector runs
  273. // forever.
  274. func (c *connector) run(ctx context.Context, lc *local.Client) {
  275. if _, err := lc.EditPrefs(ctx, &ipn.MaskedPrefs{
  276. AdvertiseRoutesSet: true,
  277. Prefs: ipn.Prefs{
  278. AdvertiseRoutes: append(c.routes.Prefixes(), c.v6ULA),
  279. },
  280. }); err != nil {
  281. log.Fatalf("failed to advertise routes: %v", err)
  282. }
  283. c.ts.RegisterFallbackTCPHandler(c.handleTCPFlow)
  284. c.serveDNS()
  285. }
  286. func (c *connector) serveDNS() {
  287. pc, err := c.ts.ListenPacket("udp", net.JoinHostPort(c.dnsAddr.String(), "53"))
  288. if err != nil {
  289. log.Fatalf("failed listening on port 53: %v", err)
  290. }
  291. defer pc.Close()
  292. log.Printf("Listening for DNS on %s", pc.LocalAddr().String())
  293. for {
  294. buf := make([]byte, 1500)
  295. n, addr, err := pc.ReadFrom(buf)
  296. if err != nil {
  297. if errors.Is(err, net.ErrClosed) {
  298. return
  299. }
  300. log.Printf("serveDNS.ReadFrom failed: %v", err)
  301. continue
  302. }
  303. go c.handleDNS(pc, buf[:n], addr.(*net.UDPAddr))
  304. }
  305. }
  306. // handleDNS handles a DNS request to the app connector.
  307. // It generates a response based on the request and the node that sent it.
  308. //
  309. // Each node is assigned a unique pair of IP addresses for each domain it
  310. // queries. This assignment is done lazily and is not persisted across restarts.
  311. // A per-peer assignment allows the connector to reuse a limited number of IP
  312. // addresses across multiple nodes and domains. It also allows for clear
  313. // failover behavior when an app connector is restarted.
  314. //
  315. // This assignment later allows the connector to determine where to forward
  316. // traffic based on the destination IP address.
  317. func (c *connector) handleDNS(pc net.PacketConn, buf []byte, remoteAddr *net.UDPAddr) {
  318. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  319. defer cancel()
  320. who, err := c.whois.WhoIs(ctx, remoteAddr.String())
  321. if err != nil {
  322. log.Printf("HandleDNS(remote=%s): WhoIs failed: %v\n", remoteAddr.String(), err)
  323. return
  324. }
  325. var msg dnsmessage.Message
  326. err = msg.Unpack(buf)
  327. if err != nil {
  328. log.Printf("HandleDNS(remote=%s): dnsmessage unpack failed: %v\n", remoteAddr.String(), err)
  329. return
  330. }
  331. var resolves map[string][]netip.Addr
  332. var addrQCount int
  333. for _, q := range msg.Questions {
  334. if q.Type != dnsmessage.TypeA && q.Type != dnsmessage.TypeAAAA {
  335. continue
  336. }
  337. addrQCount++
  338. if _, ok := resolves[q.Name.String()]; !ok {
  339. addrs, err := c.resolver.LookupNetIP(ctx, "ip", q.Name.String())
  340. var dnsErr *net.DNSError
  341. if errors.As(err, &dnsErr) && dnsErr.IsNotFound {
  342. continue
  343. }
  344. if err != nil {
  345. log.Printf("HandleDNS(remote=%s): lookup destination failed: %v\n", remoteAddr.String(), err)
  346. return
  347. }
  348. // Note: If _any_ destination is ignored, pass through all of the resolved
  349. // addresses as-is.
  350. //
  351. // This could result in some odd split-routing if there was a mix of
  352. // ignored and non-ignored addresses, but it's currently the user
  353. // preferred behavior.
  354. if !c.ignoreDestination(addrs) {
  355. addr, err := c.ipPool.IPForDomain(who.Node.ID, q.Name.String())
  356. if err != nil {
  357. log.Printf("HandleDNS(remote=%s): lookup destination failed: %v\n", remoteAddr.String(), err)
  358. return
  359. }
  360. addrs = []netip.Addr{addr, v6ForV4(c.v6ULA.Addr(), addr)}
  361. }
  362. mak.Set(&resolves, q.Name.String(), addrs)
  363. }
  364. }
  365. rcode := dnsmessage.RCodeSuccess
  366. if addrQCount > 0 && len(resolves) == 0 {
  367. rcode = dnsmessage.RCodeNameError
  368. }
  369. b := dnsmessage.NewBuilder(nil,
  370. dnsmessage.Header{
  371. ID: msg.Header.ID,
  372. Response: true,
  373. Authoritative: true,
  374. RCode: rcode,
  375. })
  376. b.EnableCompression()
  377. if err := b.StartQuestions(); err != nil {
  378. log.Printf("HandleDNS(remote=%s): dnsmessage start questions failed: %v\n", remoteAddr.String(), err)
  379. return
  380. }
  381. for _, q := range msg.Questions {
  382. b.Question(q)
  383. }
  384. if err := b.StartAnswers(); err != nil {
  385. log.Printf("HandleDNS(remote=%s): dnsmessage start answers failed: %v\n", remoteAddr.String(), err)
  386. return
  387. }
  388. for _, q := range msg.Questions {
  389. switch q.Type {
  390. case dnsmessage.TypeSOA:
  391. if err := b.SOAResource(
  392. dnsmessage.ResourceHeader{Name: q.Name, Class: q.Class, TTL: 120},
  393. dnsmessage.SOAResource{NS: q.Name, MBox: tsMBox, Serial: 2023030600,
  394. Refresh: 120, Retry: 120, Expire: 120, MinTTL: 60},
  395. ); err != nil {
  396. log.Printf("HandleDNS(remote=%s): dnsmessage SOA resource failed: %v\n", remoteAddr.String(), err)
  397. return
  398. }
  399. case dnsmessage.TypeNS:
  400. if err := b.NSResource(
  401. dnsmessage.ResourceHeader{Name: q.Name, Class: q.Class, TTL: 120},
  402. dnsmessage.NSResource{NS: tsMBox},
  403. ); err != nil {
  404. log.Printf("HandleDNS(remote=%s): dnsmessage NS resource failed: %v\n", remoteAddr.String(), err)
  405. return
  406. }
  407. case dnsmessage.TypeAAAA:
  408. for _, addr := range resolves[q.Name.String()] {
  409. if !addr.Is6() {
  410. continue
  411. }
  412. if err := b.AAAAResource(
  413. dnsmessage.ResourceHeader{Name: q.Name, Class: q.Class, TTL: 120},
  414. dnsmessage.AAAAResource{AAAA: addr.As16()},
  415. ); err != nil {
  416. log.Printf("HandleDNS(remote=%s): dnsmessage AAAA resource failed: %v\n", remoteAddr.String(), err)
  417. return
  418. }
  419. }
  420. case dnsmessage.TypeA:
  421. for _, addr := range resolves[q.Name.String()] {
  422. if !addr.Is4() {
  423. continue
  424. }
  425. if err := b.AResource(
  426. dnsmessage.ResourceHeader{Name: q.Name, Class: q.Class, TTL: 120},
  427. dnsmessage.AResource{A: addr.As4()},
  428. ); err != nil {
  429. log.Printf("HandleDNS(remote=%s): dnsmessage A resource failed: %v\n", remoteAddr.String(), err)
  430. return
  431. }
  432. }
  433. }
  434. }
  435. out, err := b.Finish()
  436. if err != nil {
  437. log.Printf("HandleDNS(remote=%s): dnsmessage finish failed: %v\n", remoteAddr.String(), err)
  438. return
  439. }
  440. _, err = pc.WriteTo(out, remoteAddr)
  441. if err != nil {
  442. log.Printf("HandleDNS(remote=%s): write failed: %v\n", remoteAddr.String(), err)
  443. }
  444. }
  445. func v6ForV4(ula netip.Addr, v4 netip.Addr) netip.Addr {
  446. as16 := ula.As16()
  447. as4 := v4.As4()
  448. copy(as16[12:], as4[:])
  449. return netip.AddrFrom16(as16)
  450. }
  451. func v4ForV6(v6 netip.Addr) netip.Addr {
  452. as16 := v6.As16()
  453. var as4 [4]byte
  454. copy(as4[:], as16[12:])
  455. return netip.AddrFrom4(as4)
  456. }
  457. // tsMBox is the mailbox used in SOA records.
  458. // The convention is to replace the @ symbol with a dot.
  459. // So in this case, the mailbox is support.tailscale.com. with the trailing dot
  460. // to indicate that it is a fully qualified domain name.
  461. var tsMBox = dnsmessage.MustNewName("support.tailscale.com.")
  462. // handleTCPFlow handles a TCP flow from the given source to the given
  463. // destination. It uses the source address to determine the node that sent the
  464. // request and the destination address to determine the domain that the request
  465. // is for based on the IP address assigned to the destination in the DNS
  466. // response.
  467. func (c *connector) handleTCPFlow(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) {
  468. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  469. defer cancel()
  470. who, err := c.whois.WhoIs(ctx, src.Addr().String())
  471. cancel()
  472. if err != nil {
  473. log.Printf("HandleTCPFlow: WhoIs failed: %v\n", err)
  474. return nil, false
  475. }
  476. dstAddr := dst.Addr()
  477. if dstAddr.Is6() {
  478. dstAddr = v4ForV6(dstAddr)
  479. }
  480. domain, ok := c.ipPool.DomainForIP(who.Node.ID, dstAddr, time.Now())
  481. if !ok {
  482. return nil, false
  483. }
  484. return func(conn net.Conn) {
  485. proxyTCPConn(conn, domain, c)
  486. }, true
  487. }
  488. // ignoreDestination reports whether any of the provided dstAddrs match the prefixes configured
  489. // in --ignore-destinations
  490. func (c *connector) ignoreDestination(dstAddrs []netip.Addr) bool {
  491. if c.ignoreDsts == nil {
  492. return false
  493. }
  494. for _, a := range dstAddrs {
  495. if _, ok := c.ignoreDsts.Lookup(a); ok {
  496. return true
  497. }
  498. }
  499. return false
  500. }
  501. func proxyTCPConn(c net.Conn, dest string, ctor *connector) {
  502. if c.RemoteAddr() == nil {
  503. log.Printf("proxyTCPConn: nil RemoteAddr")
  504. c.Close()
  505. return
  506. }
  507. laddr, err := netip.ParseAddrPort(c.LocalAddr().String())
  508. if err != nil {
  509. log.Printf("proxyTCPConn: ParseAddrPort failed: %v", err)
  510. c.Close()
  511. return
  512. }
  513. daddrs, err := ctor.resolver.LookupNetIP(context.TODO(), "ip", dest)
  514. if err != nil {
  515. log.Printf("proxyTCPConn: LookupNetIP failed: %v", err)
  516. c.Close()
  517. return
  518. }
  519. if len(daddrs) == 0 {
  520. log.Printf("proxyTCPConn: no IP addresses found for %s", dest)
  521. c.Close()
  522. return
  523. }
  524. if ctor.ignoreDestination(daddrs) {
  525. log.Printf("proxyTCPConn: closing connection to ignored destination %s (%v)", dest, daddrs)
  526. c.Close()
  527. return
  528. }
  529. p := &tcpproxy.Proxy{
  530. ListenFunc: func(net, laddr string) (net.Listener, error) {
  531. return netutil.NewOneConnListener(c, nil), nil
  532. },
  533. }
  534. // TODO(raggi): more code could avoid this shuffle, but avoiding allocations
  535. // for now most of the time daddrs will be short.
  536. rand.Shuffle(len(daddrs), func(i, j int) {
  537. daddrs[i], daddrs[j] = daddrs[j], daddrs[i]
  538. })
  539. daddr := daddrs[0]
  540. // Try to match the upstream and downstream protocols (v4/v6)
  541. if laddr.Addr().Is6() {
  542. for _, addr := range daddrs {
  543. if addr.Is6() {
  544. daddr = addr
  545. break
  546. }
  547. }
  548. } else {
  549. for _, addr := range daddrs {
  550. if addr.Is4() {
  551. daddr = addr
  552. break
  553. }
  554. }
  555. }
  556. // TODO(raggi): drop this library, it ends up being allocation and
  557. // indirection heavy and really doesn't help us here.
  558. dsockaddrs := netip.AddrPortFrom(daddr, laddr.Port()).String()
  559. p.AddRoute(dsockaddrs, &tcpproxy.DialProxy{
  560. Addr: dsockaddrs,
  561. })
  562. p.Start()
  563. }
  564. func getClusterStatePath(stateDirFlag string) (string, error) {
  565. var dirPath string
  566. if stateDirFlag != "" {
  567. dirPath = stateDirFlag
  568. } else {
  569. confDir, err := os.UserConfigDir()
  570. if err != nil {
  571. return "", err
  572. }
  573. dirPath = filepath.Join(confDir, "nat-connector-state")
  574. }
  575. dirPath = filepath.Join(dirPath, "cluster")
  576. if err := os.MkdirAll(dirPath, 0700); err != nil {
  577. return "", err
  578. }
  579. if fi, err := os.Stat(dirPath); err != nil {
  580. return "", err
  581. } else if !fi.IsDir() {
  582. return "", fmt.Errorf("%v is not a directory", dirPath)
  583. }
  584. return dirPath, nil
  585. }
  586. func httpClusterAdmin(ipp *ippool.ConsensusIPPool) http.Handler {
  587. mux := http.NewServeMux()
  588. mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
  589. c, err := ipp.GetClusterConfiguration()
  590. if err != nil {
  591. log.Printf("cluster admin http: error getClusterConfig: %v", err)
  592. http.Error(w, "", http.StatusInternalServerError)
  593. return
  594. }
  595. if err := json.NewEncoder(w).Encode(c); err != nil {
  596. log.Printf("cluster admin http: error encoding raft configuration: %v", err)
  597. }
  598. })
  599. mux.HandleFunc("DELETE /{id}", func(w http.ResponseWriter, r *http.Request) {
  600. idString := r.PathValue("id")
  601. id := raft.ServerID(idString)
  602. idx, err := ipp.DeleteClusterServer(id)
  603. if err != nil {
  604. http.Error(w, err.Error(), http.StatusInternalServerError)
  605. return
  606. }
  607. if err := json.NewEncoder(w).Encode(idx); err != nil {
  608. log.Printf("cluster admin http: error encoding delete index: %v", err)
  609. return
  610. }
  611. })
  612. return mux
  613. }