//go:build linux package network import ( "fmt" "net" "strings" "github.com/vishvananda/netlink" ) // InterfaceInfo contains basic information about a network interface. type InterfaceInfo struct { Name string Index int } // NetworkOps abstracts the low-level netlink operations. type NetworkOps interface { LinkList() ([]netlink.Link, error) LinkByName(name string) (netlink.Link, error) LinkSetMTU(link netlink.Link, mtu int) error LinkSetUp(link netlink.Link) error AddrAdd(link netlink.Link, addr *netlink.Addr) error RouteAdd(route *netlink.Route) error RouteReplace(route *netlink.Route) error } // realNetworkOps is the production implementation using netlink. type realNetworkOps struct{} func (r *realNetworkOps) LinkList() ([]netlink.Link, error) { return netlink.LinkList() } func (r *realNetworkOps) LinkByName(name string) (netlink.Link, error) { return netlink.LinkByName(name) } func (r *realNetworkOps) LinkSetMTU(link netlink.Link, mtu int) error { return netlink.LinkSetMTU(link, mtu) } func (r *realNetworkOps) LinkSetUp(link netlink.Link) error { return netlink.LinkSetUp(link) } func (r *realNetworkOps) AddrAdd(link netlink.Link, addr *netlink.Addr) error { return netlink.AddrAdd(link, addr) } func (r *realNetworkOps) RouteAdd(route *netlink.Route) error { return netlink.RouteAdd(route) } func (r *realNetworkOps) RouteReplace(route *netlink.Route) error { return netlink.RouteReplace(route) } // DefaultNetworkOps is the global instance used by the package functions. // It can be replaced during tests. var DefaultNetworkOps NetworkOps = &realNetworkOps{} // NetworkManager coordinates network configuration within a namespace. type NetworkManager struct { Ops NetworkOps } // NewNetworkManager creates a new NetworkManager with production defaults. func NewNetworkManager() *NetworkManager { return &NetworkManager{ Ops: DefaultNetworkOps, } } // ListInterfaces returns a list of all network interfaces present in the current namespace. func (nm *NetworkManager) ListInterfaces() ([]InterfaceInfo, error) { links, err := nm.Ops.LinkList() if err != nil { return nil, fmt.Errorf("failed to list interfaces: %w", err) } var interfaces []InterfaceInfo for _, link := range links { interfaces = append(interfaces, InterfaceInfo{ Name: link.Attrs().Name, Index: link.Attrs().Index, }) } return interfaces, nil } // ConfigureInterface sets the MTU, brings the interface up, assigns an IP address, // and configures the default route. func (nm *NetworkManager) ConfigureInterface(name, address string, mtu int) error { link, err := nm.Ops.LinkByName(name) if err != nil { return fmt.Errorf("failed to find link %s: %w", name, err) } if err := nm.Ops.LinkSetMTU(link, mtu); err != nil { return fmt.Errorf("failed to set MTU %d on link %s: %w", mtu, name, err) } if err := nm.Ops.LinkSetUp(link); err != nil { return fmt.Errorf("failed to bring up link %s: %w", name, err) } addr, err := netlink.ParseAddr(address) if err != nil { return fmt.Errorf("invalid IP address %s: %w", address, err) } if err := nm.Ops.AddrAdd(link, addr); err != nil { if !strings.Contains(err.Error(), "file exists") { return fmt.Errorf("failed to add address %s to link %s: %w", address, name, err) } } var dst *net.IPNet if addr.IP.To4() != nil { _, dst, _ = net.ParseCIDR("0.0.0.0/0") } else { _, dst, _ = net.ParseCIDR("::/0") } route := &netlink.Route{ Scope: netlink.SCOPE_UNIVERSE, LinkIndex: link.Attrs().Index, Dst: dst, } if err := nm.Ops.RouteAdd(route); err != nil { if err := nm.Ops.RouteReplace(route); err != nil { return fmt.Errorf("failed to configure default route via %s: %w", name, err) } } return nil } // ListInterfaces returns a list of all network interfaces present in the current namespace. func ListInterfaces() ([]InterfaceInfo, error) { return NewNetworkManager().ListInterfaces() } // ConfigureInterface sets the MTU, brings the interface up, assigns an IP address, // and configures the default route. func ConfigureInterface(name, address string, mtu int) error { return NewNetworkManager().ConfigureInterface(name, address, mtu) }