diff --git a/README.md b/README.md index 9816f42..7a440c9 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,9 @@ Once the client configs have been imported and Wireguard is started, you can vie ╰─────────────────────╯ ``` +> [!TIP] +> Add the `--network-info` flag to this command to get a list of each Server host's network interfaces and associated CIDR addresses. + ## Add Server (Optional)
diff --git a/src/api/api.go b/src/api/api.go index e1d85ef..7e0008a 100644 --- a/src/api/api.go +++ b/src/api/api.go @@ -26,6 +26,9 @@ type request struct { Body []byte } +// Re-export the server api struct so files importing this package can access it +type HostInterface serverapi.HostInterface + // MakeRequest attempts to send an API query to the Wiretap server. func makeRequest(req request) ([]byte, error) { client := &http.Client{Timeout: 3 * time.Second} @@ -89,6 +92,24 @@ func ServerInfo(apiAddr netip.AddrPort) (peer.Config, peer.Config, error) { return *configs.RelayConfig, *configs.E2EEConfig, nil } +func ServerInterfaces(apiAddr netip.AddrPort) ([]HostInterface, error) { + body, err := makeRequest(request{ + URL: makeUrl(apiAddr, "serverinterfaces", []string{}), + Method: "GET", + }) + if err != nil { + return nil, err + } + + var interfaces []HostInterface + err = json.Unmarshal(body, &interfaces) + if err != nil { + return nil, err + } + + return interfaces, nil +} + func AllocateNode(apiAddr netip.AddrPort, peerType peer.PeerType) (serverapi.NetworkState, error) { body, err := makeRequest(request{ URL: makeUrl(apiAddr, "allocate", []string{fmt.Sprintf("type=%d", peerType)}), diff --git a/src/cmd/status.go b/src/cmd/status.go index de0540e..7415ad3 100644 --- a/src/cmd/status.go +++ b/src/cmd/status.go @@ -14,6 +14,7 @@ import ( ) type statusCmdConfig struct { + networkInfo bool configFileRelay string configFileE2EE string } @@ -21,6 +22,7 @@ type statusCmdConfig struct { // Defaults for status command. // See root command for shared defaults. var statusCmd = statusCmdConfig{ + networkInfo: false, configFileRelay: ConfigRelay, configFileE2EE: ConfigE2EE, } @@ -39,6 +41,7 @@ func init() { rootCmd.AddCommand(cmd) + cmd.Flags().BoolVarP(&statusCmd.networkInfo, "network-info", "n", statusCmd.networkInfo, "Display network info for each online server node") cmd.Flags().StringVarP(&statusCmd.configFileRelay, "relay", "1", statusCmd.configFileRelay, "wireguard relay config input filename") cmd.Flags().StringVarP(&statusCmd.configFileE2EE, "e2ee", "2", statusCmd.configFileE2EE, "wireguard E2EE config input filename") @@ -46,22 +49,23 @@ func init() { } // Run attempts to parse config files into a network diagram. -func (c statusCmdConfig) Run() { +func (cc statusCmdConfig) Run() { // Start building tree. type Node struct { peerConfig peer.PeerConfig relayConfig peer.Config e2eeConfig peer.Config children []*Node + interfaces []api.HostInterface error string } var err error // Parse the relay and e2ee config files - clientConfigRelay, err := peer.ParseConfig(c.configFileRelay) + clientConfigRelay, err := peer.ParseConfig(cc.configFileRelay) check("failed to parse relay config file", err) - clientConfigE2EE, err := peer.ParseConfig(c.configFileE2EE) + clientConfigE2EE, err := peer.ParseConfig(cc.configFileE2EE) check("failed to parse e2ee config file", err) client := Node{ @@ -81,15 +85,26 @@ func (c statusCmdConfig) Run() { relayConfig, e2eeConfig, err := api.ServerInfo(netip.AddrPortFrom(ep.GetApiAddr(), uint16(ApiPort))) if err != nil { errorNodes = append(errorNodes, Node{ - peerConfig: ep, - error: err.Error(), + peerConfig: ep, + error: err.Error(), }) - + } else { + var interfaces []api.HostInterface + if cc.networkInfo { + interfaces, err = api.ServerInterfaces(netip.AddrPortFrom(ep.GetApiAddr(), uint16(ApiPort))) + if err != nil { + interfaces = append(interfaces, api.HostInterface{ + Name: "ERROR: " + err.Error(), + }) + } + } + nodes[relayConfig.GetPublicKey()] = Node{ peerConfig: ep, relayConfig: relayConfig, e2eeConfig: e2eeConfig, + interfaces: interfaces, } } } @@ -131,19 +146,43 @@ func (c statusCmdConfig) Run() { ips := []string{} var api string for j, a := range c.peerConfig.GetAllowedIPs() { - if j == len(c.peerConfig.GetAllowedIPs()) - 1 { + if j == len(c.peerConfig.GetAllowedIPs())-1 { api = a.IP.String() } else { ips = append(ips, a.String()) } } - t.AddChild(tree.NodeString(fmt.Sprintf(`server + + nodeString := fmt.Sprintf( +`server nickname: %v relay: %v... e2ee: %v... api: %v - routes: %v `, c.peerConfig.GetNickname(), c.relayConfig.GetPublicKey()[:8], c.e2eeConfig.GetPublicKey()[:8], api, strings.Join(ips, ",")))) + routes: %v `, + c.peerConfig.GetNickname(), + c.relayConfig.GetPublicKey()[:8], + c.e2eeConfig.GetPublicKey()[:8], + api, + strings.Join(ips, ","), + ) + + if cc.networkInfo { + nodeString += ` + +Network Interfaces: +------------------- +` + for _, ifx := range c.interfaces { + nodeString += fmt.Sprintf("%v:\n", ifx.Name) + for _, a := range ifx.Addrs { + nodeString += strings.Repeat(" ", 2) + a.String() + "\n" + } + } + } + + t.AddChild(tree.NodeString(nodeString)) child, err := t.Child(0) check("could not build tree", err) treeTraversal(node.children[i], child) @@ -156,31 +195,39 @@ func (c statusCmdConfig) Run() { fmt.Println() fmt.Fprintln(color.Output, WhiteBold(t)) fmt.Println() - + if len(errorNodes) > 0 { // Display known peers that we had issues connecting to fmt.Fprintln(color.Output, WhiteBold("Peers with Errors:")) fmt.Println() - + for _, node := range errorNodes { ips := []string{} var api string for j, a := range node.peerConfig.GetAllowedIPs() { - if j == len(node.peerConfig.GetAllowedIPs()) - 1 { + if j == len(node.peerConfig.GetAllowedIPs())-1 { api = a.IP.String() } else { ips = append(ips, a.String()) } } - - t = tree.NewTree(tree.NodeString(fmt.Sprintf(`server - nickname: %v - e2ee: %v... - api: %v - routes: %v + nodeString := fmt.Sprintf( +`server - error: %v`, node.peerConfig.GetNickname(), node.peerConfig.GetPublicKey().String()[:8], api, strings.Join(ips, ","), errorWrap(node.error, 80)))) + nickname: %v + e2ee: %v... + api: %v + routes: %v + + error: %v`, + node.peerConfig.GetNickname(), + node.peerConfig.GetPublicKey().String()[:8], + api, strings.Join(ips, ","), + errorWrap(node.error, 80), + ) + + t = tree.NewTree(tree.NodeString(nodeString)) fmt.Fprintln(color.Output, WhiteBold(t)) } } diff --git a/src/transport/api/api.go b/src/transport/api/api.go index 9760cc8..e911b22 100644 --- a/src/transport/api/api.go +++ b/src/transport/api/api.go @@ -58,6 +58,11 @@ type NetworkState struct { ServerRelaySubnet6 netip.Addr } +type HostInterface struct { + Name string + Addrs []net.IPNet +} + type AddAllowedIPsRequest struct { PublicKey wgtypes.Key AllowedIPs []net.IPNet @@ -129,6 +134,7 @@ func Handle(tnet *netstack.Net, devRelay *device.Device, devE2EE *device.Device, http.HandleFunc("/ping", wrapApi(handlePing())) http.HandleFunc("/serverinfo", wrapApi(handleServerInfo(configs))) + http.HandleFunc("/serverinterfaces", wrapApi(handleServerInterfaces())) http.HandleFunc("/addpeer", wrapApi(handleAddPeer(devRelay, devE2EE, configs))) http.HandleFunc("/allocate", wrapApi(handleAllocate(ns))) http.HandleFunc("/addallowedips", wrapApi(handleAddAllowedIPs(devRelay, configs))) @@ -189,6 +195,53 @@ func handleServerInfo(configs ServerConfigs) http.HandlerFunc { } } +// handleServerInterfaces responds with network interface information for this server. +func handleServerInterfaces() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + var interfaces []HostInterface + ifs, err := net.Interfaces() + if err != nil { + log.Printf("API Error: %v", err) + } + + for _, ifx := range ifs { + newIF := HostInterface{} + newIF.Name = ifx.Name + + addrs, err := ifx.Addrs() + if err != nil { + log.Printf("API Error: %v", err) + } + + for _, a := range addrs { + _, cidr, err := net.ParseCIDR(a.String()) + if err != nil { + log.Printf("API Error: %v", err) + } + newIF.Addrs = append(newIF.Addrs, *cidr) + } + + interfaces = append(interfaces, newIF) + } + + body, err := json.Marshal(interfaces) + if err != nil { + writeErr(w, err) + return + } + + _, err = w.Write(body) + if err != nil { + log.Printf("API Error: %v", err) + } + } +} + // handleAddPeer adds a peer to either this server's relay or e2ee device. func handleAddPeer(devRelay *device.Device, devE2EE *device.Device, config ServerConfigs) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -321,7 +374,7 @@ func handleAllocate(ns *NetworkState) http.HandlerFunc { } } -// handleAddAllowedIPs adds new route to a specfied peer. +// handleAddAllowedIPs adds new route to a specified peer. func handleAddAllowedIPs(devRelay *device.Device, config ServerConfigs) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" {