226 lines
5.2 KiB
Go
226 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"strings"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"path/filepath"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/vishvananda/netlink"
|
|
"github.com/vishvananda/netns"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
type stringSlice []string
|
|
|
|
func (s *stringSlice) String() string {
|
|
return strings.Join(*s, ",")
|
|
}
|
|
|
|
func (s *stringSlice) Set(value string) error {
|
|
*s = append(*s, value)
|
|
return nil
|
|
}
|
|
|
|
|
|
func main() {
|
|
|
|
var interfaces stringSlice
|
|
flag.Var(&interfaces, "iface", "Interface(s) to filter on (can be specified multiple times)")
|
|
flag.Parse()
|
|
|
|
// Build lookup map
|
|
filterSet := make(map[string]struct{})
|
|
for _, name := range interfaces {
|
|
filterSet[name] = struct{}{}
|
|
}
|
|
|
|
// Determine if we are filtering at all
|
|
filtering := len(filterSet) > 0
|
|
|
|
headerStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("63"))
|
|
ipStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("203"))
|
|
upStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("42"))
|
|
downStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("196"))
|
|
addrStyle := lipgloss.NewStyle().Faint(true)
|
|
infoStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("33"))
|
|
|
|
origNS, err := netns.Get()
|
|
if err != nil {
|
|
fmt.Println("Error getting current namespace:", err)
|
|
return
|
|
}
|
|
defer origNS.Close()
|
|
|
|
links, err := netlink.LinkList()
|
|
if err != nil {
|
|
fmt.Println("Error listing links:", err)
|
|
return
|
|
}
|
|
|
|
|
|
for _, link := range links {
|
|
attrs := link.Attrs()
|
|
if attrs == nil {
|
|
continue
|
|
}
|
|
|
|
if filtering {
|
|
if _, ok := filterSet[attrs.Name]; !ok {
|
|
continue
|
|
}
|
|
}
|
|
state := downStyle.Render("DOWN")
|
|
if attrs.Flags&net.FlagUp != 0 {
|
|
state = upStyle.Render("UP")
|
|
}
|
|
|
|
fmt.Print(headerStyle.Render(attrs.Name), " ")
|
|
fmt.Printf(addrStyle.Render(" %s"), attrs.HardwareAddr)
|
|
fmt.Printf(addrStyle.Render(" %d"), attrs.MTU)
|
|
fmt.Printf(" %s\n", state)
|
|
|
|
addrs, err := netlink.AddrList(link, unix.AF_INET)
|
|
if err == nil {
|
|
for _, addr := range addrs {
|
|
fmt.Println(" ", ipStyle.Render(addr.IPNet.String()))
|
|
}
|
|
}
|
|
|
|
if link.Type() != "veth" {
|
|
fmt.Println()
|
|
continue
|
|
}
|
|
|
|
fmt.Println(" ", infoStyle.Render("Veth interface"))
|
|
|
|
peerFound := false
|
|
netnsDir := "/var/run/netns/"
|
|
entries, err := ioutil.ReadDir(netnsDir)
|
|
if err != nil {
|
|
fmt.Println(" ", infoStyle.Render("Could not scan netns dir"))
|
|
} else {
|
|
for _, entry := range entries {
|
|
nsPath := filepath.Join(netnsDir, entry.Name())
|
|
|
|
nsHandle, err := netns.GetFromPath(nsPath)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if err := netns.Set(nsHandle); err != nil {
|
|
nsHandle.Close()
|
|
continue
|
|
}
|
|
|
|
remoteLinks, err := netlink.LinkList()
|
|
if err == nil {
|
|
for _, rl := range remoteLinks {
|
|
if rl.Type() == "veth" && rl.Attrs().ParentIndex == attrs.Index {
|
|
fmt.Println(" ", infoStyle.Render("Peer in namespace:"), entry.Name(), "as", rl.Attrs().Name)
|
|
|
|
// Show IPs on peer
|
|
peerAddrs, err := netlink.AddrList(rl, unix.AF_INET)
|
|
if err == nil {
|
|
for _, pa := range peerAddrs {
|
|
fmt.Println(" ", ipStyle.Render(pa.IPNet.String()))
|
|
}
|
|
}
|
|
|
|
fmt.Println(" ", infoStyle.Render("Interfaces in namespace:"), entry.Name())
|
|
for _, intf := range remoteLinks {
|
|
intfState := downStyle.Render("DOWN")
|
|
if intf.Attrs().Flags&net.FlagUp != 0 {
|
|
intfState = upStyle.Render("UP")
|
|
}
|
|
fmt.Printf(" %s %s\n", headerStyle.Render(intf.Attrs().Name), intfState)
|
|
|
|
nsAddrs, err := netlink.AddrList(intf, unix.AF_INET)
|
|
if err == nil {
|
|
for _, addr := range nsAddrs {
|
|
fmt.Println(" ", ipStyle.Render(addr.IPNet.String()))
|
|
}
|
|
}
|
|
}
|
|
|
|
peerFound = true
|
|
routes, _ := netlink.RouteList(nil, unix.AF_INET)
|
|
if len(routes) > 0 {
|
|
fmt.Println(" ", infoStyle.Render("Routes in namespace:"), entry.Name())
|
|
for _, route := range routes {
|
|
dst := "default"
|
|
if route.Dst != nil {
|
|
dst = route.Dst.String()
|
|
}
|
|
via := ""
|
|
if route.Gw != nil {
|
|
via = "via " + route.Gw.String()
|
|
}
|
|
dev := ""
|
|
if link, err := netlink.LinkByIndex(route.LinkIndex); err == nil {
|
|
dev = "dev " + link.Attrs().Name
|
|
}
|
|
src := ""
|
|
if route.Src != nil {
|
|
src = "src " + route.Src.String()
|
|
}
|
|
fmt.Printf(" %s %s %s %s\n", dst, via, dev, src)
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
nsHandle.Close()
|
|
netns.Set(origNS)
|
|
|
|
|
|
if peerFound {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if !peerFound {
|
|
fmt.Println(" ", infoStyle.Render("Peer not found — may be deleted or hidden"))
|
|
}
|
|
|
|
fmt.Println()
|
|
}
|
|
fmt.Println(headerStyle.Render("Routes in main namespace:"))
|
|
mainRoutes, err := netlink.RouteList(nil, unix.AF_INET)
|
|
if err == nil {
|
|
for _, route := range mainRoutes {
|
|
var dst string
|
|
if route.Dst == nil {
|
|
dst = "default"
|
|
} else {
|
|
dst = route.Dst.String()
|
|
}
|
|
|
|
via := ""
|
|
if route.Gw != nil {
|
|
via = "via " + route.Gw.String()
|
|
}
|
|
|
|
dev := ""
|
|
if link, err := netlink.LinkByIndex(route.LinkIndex); err == nil {
|
|
dev = "dev " + link.Attrs().Name
|
|
}
|
|
src := ""
|
|
if route.Src != nil {
|
|
src = "src " + route.Src.String()
|
|
}
|
|
|
|
fmt.Printf(" %s %s %s %s\n", dst, via, dev, src)
|
|
}
|
|
}
|
|
fmt.Println()
|
|
|
|
}
|
|
|