From 67e3e2044db48de3425cdf7246142d7128d91415 Mon Sep 17 00:00:00 2001 From: Luke Rindels Date: Thu, 21 Sep 2023 12:13:04 -0600 Subject: [PATCH] Add client to any server (#25) * Allow additional clients to attach to any server --- README.md | 28 +++++++++++++++++++ src/cmd/add_client.go | 62 ++++++++++++++++++++++++++++++++++++++----- src/cmd/add_server.go | 59 ++++++++++++++++++++++++---------------- src/cmd/status.go | 6 ++--- 4 files changed, 123 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index f497e72..8c4a6c6 100644 --- a/README.md +++ b/README.md @@ -617,3 +617,31 @@ Finally, run Wiretap with the forwarded local port as your endpoint on the serve ```bash WIRETAP_RELAY_INTERFACE_PRIVATEKEY= WIRETAP_RELAY_PEER_PUBLICKEY= WIRETAP_E2EE_INTERFACE_PRIVATEKEY= WIRETAP_E2EE_PEER_PUBLICKEY= WIRETAP_E2EE_PEER_ENDPOINT=172.16.0.1:51821 ./wiretap serve --endpoint localhost:51821 ``` + +### Add Clients To Any Server + +> **Note** +> Clients added to arbitrary servers do not currently have the same capabilities as clients added to first-hop servers (the default) + +Clients can be attached to any server in the network by using the `--server-address ` argument when running `wiretap add client`. This allows a client on a different network than the first client to still gain access to all of the Wiretap network's routes. But this has some limitations. + +In this example, a new client is added to the second server in the right branch of a Wiretap network. This client will only be able to access routes via the right branch of the network and not the left branch because the branches are only joined through an existing client, which does not route traffic from other clients: + +``` + ┌─────┐ + │ C │ + └┬───┬┘ + │ │ + ┌────┴┐ ┌┴────┐ + │ S │ │ S │ + └──┬──┘ └──┬──┘ + │ │ + ┌──┴──┐ ┌──┴──┐ + │ S │ │ S ◄───────┐ + └─────┘ └─────┘ │ + ┌──┴─┐ + │ C │ + └────┘ +``` + +You may also need to manually edit the resulting `wiretap.conf` for the new client to remove any `AllowedIPs` entries that already exist in the new client's host routing table. If the server that the client is attaching to has a route for 10.2.0.0/16, but the Client already has that route (because that's where it lives), then remove the `10.2.0.0/16` entry from the `wiretap.conf` file before importing into WireGuard. Leave the API address and any other routes you wish to access. \ No newline at end of file diff --git a/src/cmd/add_client.go b/src/cmd/add_client.go index b91c2c9..d619f39 100644 --- a/src/cmd/add_client.go +++ b/src/cmd/add_client.go @@ -1,6 +1,7 @@ package cmd import ( + "errors" "fmt" "log" "net" @@ -21,6 +22,7 @@ type addClientCmdConfig struct { inputConfigFileE2EE string outputConfigFileRelay string outputConfigFileE2EE string + serverAddress string mtu int } @@ -29,6 +31,7 @@ var addClientCmdArgs = addClientCmdConfig{ inputConfigFileE2EE: ConfigE2EE, outputConfigFileRelay: ConfigRelay, outputConfigFileE2EE: ConfigE2EE, + serverAddress: "", mtu: MTU, } @@ -49,6 +52,7 @@ func init() { addClientCmd.Flags().StringVarP(&addClientCmdArgs.outputConfigFileE2EE, "e2ee-output", "", addClientCmdArgs.outputConfigFileE2EE, "filename of output E2EE config file") addClientCmd.Flags().StringVarP(&addClientCmdArgs.inputConfigFileRelay, "relay-input", "", addClientCmdArgs.inputConfigFileRelay, "filename of input relay config file") addClientCmd.Flags().StringVarP(&addClientCmdArgs.inputConfigFileE2EE, "e2ee-input", "", addClientCmdArgs.inputConfigFileE2EE, "filename of input E2EE config file") + addClientCmd.Flags().StringVarP(&addClientCmdArgs.serverAddress, "server-address", "s", addClientCmdArgs.serverAddress, "API address of server that new client will connect to. By default new clients connect to existing relay servers") addClientCmd.Flags().IntVarP(&addClientCmdArgs.mtu, "mtu", "m", addClientCmdArgs.mtu, "tunnel MTU") addClientCmd.Flags().SortFlags = false @@ -71,7 +75,7 @@ func (c addClientCmdConfig) Run() { check("failed to retrieve address allocation from server", err) disableV6 := false - if len(baseConfigE2EE.GetPeers()[0].GetAllowedIPs()) < 3 { + if len(baseConfigE2EE.GetAddresses()) == 1 { disableV6 = true } @@ -98,15 +102,45 @@ func (c addClientCmdConfig) Run() { check("failed to generate relay e2ee config", err) // Copy peers. - for _, p := range baseConfigRelay.GetPeers() { - clientConfigRelay.AddPeer(p) + leafAddr := baseConfigRelay.GetAddresses()[0].IP + if c.serverAddress == "" { + for _, p := range baseConfigRelay.GetPeers() { + clientConfigRelay.AddPeer(p) + } + } else { + // Get leaf server info + leafApiAddr, err := netip.ParseAddr(c.serverAddress) + check("invalid server address", err) + leafApiAddrPort := netip.AddrPortFrom(leafApiAddr, uint16(ApiPort)) + leafServerConfigRelay, _, err := api.ServerInfo(leafApiAddrPort) + check("failed to get leaf server info", err) + leafServerPeerConfigRelay, err := leafServerConfigRelay.AsPeer() + check("failed to parse client server config as peer", err) + + // Search base relay config for this server's relay peer and copy routes. + out: + for _, p := range baseConfigRelay.GetPeers() { + for _, a := range p.GetAllowedIPs() { + if a.Contains(leafServerConfigRelay.GetAddresses()[0].IP) { + for _, aip := range p.GetAllowedIPs() { + err = leafServerPeerConfigRelay.AddAllowedIPs(aip.String()) + check("failed to copy routes from leaf server", err) + } + break out + } + } + } + + clientConfigRelay.AddPeer(leafServerPeerConfigRelay) + + leafAddr = leafServerConfigRelay.GetAddresses()[0].IP } for _, p := range baseConfigE2EE.GetPeers() { clientConfigE2EE.AddPeer(p) } // Push new client peer to all servers. - // Relay nodes need a new relay peeer on top of the e2ee peer. + // Relay nodes need a new relay peer on top of the e2ee peer. // Relay nodes have a relay peer that matches our baseConfig public key. clientPubKey, err := wgtypes.ParseKey(baseConfigRelay.GetPublicKey()) check("failed to get client public key", err) @@ -151,7 +185,7 @@ func (c addClientCmdConfig) Run() { }) check("failed to parse client as peer", err) - for _, p := range baseConfigE2EE.GetPeers() { + for _, p := range clientConfigE2EE.GetPeers() { apiAddrPort := netip.AddrPortFrom(p.GetApiAddr(), uint16(ApiPort)) relay, _, err := api.ServerInfo(apiAddrPort) if err != nil { @@ -164,9 +198,25 @@ func (c addClientCmdConfig) Run() { check("failed to add peer", err) // This is a relay node. - if relay.GetPeer(clientPubKey) != nil { + if (relay.GetPeer(clientPubKey) != nil && c.serverAddress == "") || (c.serverAddress == p.GetApiAddr().String()) { err = api.AddRelayPeer(apiAddrPort, clientPeerConfigRelay) check("failed to add peer", err) + } else { + // This is an e2ee node. Add client IP to client/leaf-facing relay peer. + // Find client-facing relay peer. + outer: + for i, rp := range relay.GetPeers() { + for _, ap := range rp.GetAllowedIPs() { + if ap.Contains(leafAddr) { + err = api.AddAllowedIPs(apiAddrPort, rp.GetPublicKey(), clientPeerConfigRelay.GetAllowedIPs()) + check("failed to add new client IP to peer", err) + break outer + } + } + if i == len(relay.GetPeers())-1 { + check("failed to find client-facing peer", errors.New("peer's relay interface has no client-facing route")) + } + } } } diff --git a/src/cmd/add_server.go b/src/cmd/add_server.go index 0695ee1..9bc52cc 100644 --- a/src/cmd/add_server.go +++ b/src/cmd/add_server.go @@ -3,6 +3,7 @@ package cmd import ( "errors" "fmt" + "log" "net" "net/netip" "os" @@ -26,7 +27,7 @@ type addServerCmdConfig struct { } var addServerCmdArgs = addServerCmdConfig{ - allowedIPs: []string{ClientRelaySubnet4.String(), ClientRelaySubnet6.String()}, + allowedIPs: []string{}, serverAddress: "", configFileRelay: ConfigRelay, configFileE2EE: ConfigE2EE, @@ -210,12 +211,20 @@ func (c addServerCmdConfig) Run() { check("failed to set endpoint", err) } } - relayAddrs := []string{ClientRelaySubnet4.String()} - if !disableV6 { - relayAddrs = append(relayAddrs, ClientRelaySubnet6.String()) + + // Make allowed IPs all of current peer's allowed IPs: + relayAddrs := []string{} + for _, p := range leafServerConfigRelay.GetPeers() { + for _, aip := range p.GetAllowedIPs() { + relayAddrs = append(relayAddrs, aip.String()) + } + } + for _, a := range leafServerConfigRelay.GetAddresses() { + relayAddrs = append(relayAddrs, a.String()) } err = leafServerPeerConfigRelay.SetAllowedIPs(relayAddrs) check("failed to set allowedIPs", err) + serverConfigRelay.AddPeer(leafServerPeerConfigRelay) serverConfigE2EE.AddPeer(clientPeerConfigE2EE) @@ -263,30 +272,34 @@ func (c addServerCmdConfig) Run() { err = serverConfigE2EE.SetAddresses([]string{fmt.Sprintf("%s/%d", addresses.ApiAddr.String(), addresses.ApiAddr.BitLen())}) check("failed to set addresses", err) - // Update routes for every node in path to new server (after getting addresses) - serverApi := apiAddrPort - outer: - for serverApi != leafApiAddrPort { - relay, _, err := api.ServerInfo(serverApi) - check("failed to get server info from intermediate node", err) + // Push new route to every server. + for _, p := range clientConfigE2EE.GetPeers() { + apiAddrPort := netip.AddrPortFrom(p.GetApiAddr(), uint16(ApiPort)) + // Skip leaf and new peer. + if apiAddrPort == leafApiAddrPort || p.GetApiAddr() == serverPeerConfigE2EE.GetApiAddr() { + continue + } - for _, p := range relay.GetPeers() { - for _, ap := range p.GetAllowedIPs() { + relay, _, err := api.ServerInfo(apiAddrPort) + if err != nil { + log.Println("failed to query server info:", err) + continue + } + + // Find leaf-facing relay peer and push route. + outer: + for i, rp := range relay.GetPeers() { + for _, ap := range rp.GetAllowedIPs() { if ap.Contains(leafServerConfigRelay.GetAddresses()[0].IP) { - err = api.AddAllowedIPs(serverApi, p.GetPublicKey(), serverConfigRelay.GetAddresses()) - check("failed to add allowedips", err) - // Find which of our E2EE peers has an endpoint that matches the first Allowed IP of this peer: - for _, e2ee_p := range clientConfigE2EE.GetPeers() { - if p.GetAllowedIPs()[0].Contains(e2ee_p.GetEndpoint().IP) { - aa := e2ee_p.GetApiAddr() - serverApi = netip.MustParseAddrPort(net.JoinHostPort(aa.String(), fmt.Sprint(ApiPort))) - continue outer - } - } + err = api.AddAllowedIPs(apiAddrPort, rp.GetPublicKey(), serverPeerConfigRelay.GetAllowedIPs()) + check("failed to add new client IP to peer", err) + break outer } } + if i == len(relay.GetPeers())-1 { + check("failed to find leaf-facing peer", errors.New("peer's relay interface has no leaf-facing route")) + } } - check("", errors.New("could not update routes along path, peer not found")) } // Leaf server is the relay peer for the new server. diff --git a/src/cmd/status.go b/src/cmd/status.go index e510471..b2b2484 100644 --- a/src/cmd/status.go +++ b/src/cmd/status.go @@ -1,7 +1,6 @@ package cmd import ( - "errors" "fmt" "net/netip" "strings" @@ -93,14 +92,15 @@ func (c statusCmdConfig) Run() { for _, rp := range current.relayConfig.GetPeers() { // Skip client-facing peers. for _, ip := range rp.GetAllowedIPs() { - if ClientRelaySubnet4.Contains(netip.MustParseAddr(ip.IP.String())) || ClientRelaySubnet6.Contains(netip.MustParseAddr(ip.IP.String())) { + if clientConfigRelay.GetAddresses()[0].Contains(ip.IP) { continue outer } } next, ok := nodes[rp.GetPublicKey().String()] + // Not a peer we know about. Could be another client or an error. if !ok { - check("failed to find relay peer", errors.New("public key not returned by any node")) + continue } current.children = append(current.children, &next) findChildren(&next)