Skip to content

Commit

Permalink
Server Network Interface Enumeration (#69)
Browse files Browse the repository at this point in the history
Adds the --network-info (-n) boolean flag to the status command. For reachable servers, this displays a list of network interface names and associated CIDRs on the server host.
  • Loading branch information
Aptimex authored Nov 25, 2024
1 parent a886c71 commit ae063c0
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 20 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,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)

<div align="center">
Expand Down
21 changes: 21 additions & 0 deletions src/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
// Never use a proxy for API requests, they must go direct through the Wiretap network
Expand Down Expand Up @@ -93,6 +96,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)}),
Expand Down
85 changes: 66 additions & 19 deletions src/cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import (
)

type statusCmdConfig struct {
networkInfo bool
configFileRelay string
configFileE2EE string
}

// Defaults for status command.
// See root command for shared defaults.
var statusCmd = statusCmdConfig{
networkInfo: false,
configFileRelay: ConfigRelay,
configFileE2EE: ConfigE2EE,
}
Expand All @@ -39,29 +41,31 @@ 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")

cmd.Flags().SortFlags = false
}

// 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{
Expand All @@ -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,
}
}
}
Expand Down Expand Up @@ -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)
Expand All @@ -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))
}
}
Expand Down
55 changes: 54 additions & 1 deletion src/transport/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)))
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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" {
Expand Down

0 comments on commit ae063c0

Please sign in to comment.