blob: 1215318785a83c75b319774e9217cf19d52dd035 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// +build !build_with_native_toolchain
package ifconfig
import (
"context"
"fmt"
"net"
"os"
"strconv"
"strings"
"syscall/zx"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/fidlconv"
"go.fuchsia.dev/fuchsia/src/lib/component"
netfidl "fidl/fuchsia/net"
"fidl/fuchsia/net/dhcp"
"fidl/fuchsia/netstack"
"gvisor.dev/gvisor/pkg/tcpip"
)
type netstackClientApp struct {
ctx *component.Context
netstack *netstack.NetstackWithCtxInterface
}
func (a *netstackClientApp) printAll() {
ifaces, err := a.netstack.GetInterfaces2(context.Background())
if err != nil {
fmt.Print("ifconfig: failed to fetch interfaces\n")
return
}
for _, iface := range ifaces {
a.printIface(iface)
}
}
func getIfaceByNameFromIfaces(name string, ifaces []netstack.NetInterface2) *netstack.NetInterface2 {
var candidate *netstack.NetInterface2
for i, iface := range ifaces {
if strings.HasPrefix(iface.Name, name) {
if candidate != nil {
return nil
}
candidate = &ifaces[i]
}
}
return candidate
}
func getIfaceByIdFromIfaces(id uint32, ifaces []netstack.NetInterface2) *netstack.NetInterface2 {
for _, iface := range ifaces {
if iface.Id == id {
return &iface
}
}
return nil
}
func (a *netstackClientApp) printIface(iface netstack.NetInterface2) {
fmt.Printf("%s\tHWaddr %s Id:%d\n", iface.Name, hwAddrToString(iface.Hwaddr), iface.Id)
fmt.Printf("\tinet addr:%s Bcast:%s Mask:%s\n", netAddrToString(iface.Addr), netAddrToString(iface.Broadaddr), netAddrToString(iface.Netmask))
for _, addr := range iface.Ipv6addrs {
// TODO: scopes
fmt.Printf("\tinet6 addr: %s/%d Scope:Link\n", netAddrToString(addr.Addr), addr.PrefixLen)
}
fmt.Printf("\tmetric:%d\n", iface.Metric)
fmt.Printf("\t%s\n", iface.Flags)
}
func (a *netstackClientApp) setStatus(iface netstack.NetInterface2, up bool) {
a.netstack.SetInterfaceStatus(context.Background(), iface.Id, up)
}
func (a *netstackClientApp) addIfaceAddress(iface netstack.NetInterface2, cidr string) {
netAddr, prefixLen := validateCidr(os.Args[3])
result, _ := a.netstack.SetInterfaceAddress(context.Background(), iface.Id, netAddr, prefixLen)
if result.Status != netstack.StatusOk {
fmt.Printf("Error setting interface address: %s\n", result.Message)
}
}
func (a *netstackClientApp) removeIfaceAddress(iface netstack.NetInterface2, cidr string) {
netAddr, prefixLen := validateCidr(os.Args[3])
result, _ := a.netstack.RemoveInterfaceAddress(context.Background(), iface.Id, netAddr, prefixLen)
if result.Status != netstack.StatusOk {
fmt.Printf("Error setting interface address: %s\n", result.Message)
}
}
func (a *netstackClientApp) parseRouteAttribute(in *netstack.RouteTableEntry2, args []string) (remaining []string, err error) {
if len(args) < 2 {
return args, fmt.Errorf("not enough args to make attribute")
}
var attr, val string
switch attr, val, remaining = args[0], args[1], args[2:]; attr {
case "gateway":
gateway := toIpAddress(net.ParseIP(val))
in.Gateway = &gateway
case "metric":
m, err := strconv.ParseUint(val, 10, 32)
if err != nil {
return remaining, fmt.Errorf("metric value '%s' is not uint32: %s", val, err)
}
in.Metric = uint32(m)
case "iface":
ifaces, err := a.netstack.GetInterfaces2(context.Background())
if err != nil {
return remaining, err
}
iface := getIfaceByNameFromIfaces(val, ifaces)
if err != nil {
return remaining, err
}
if iface == nil {
return remaining, fmt.Errorf("no such interface '%s'\n", val)
}
in.Nicid = iface.Id
default:
return remaining, fmt.Errorf("unknown route attribute: %s %s", attr, val)
}
return remaining, nil
}
func (a *netstackClientApp) newRouteFromArgs(args []string) (route netstack.RouteTableEntry2, err error) {
destination, remaining := args[0], args[1:]
_, dstSubnet, err := net.ParseCIDR(destination)
if err != nil {
return route, fmt.Errorf("invalid destination (destination must be provided in CIDR format): %s", destination)
}
route.Destination = toIpAddress(dstSubnet.IP)
route.Netmask = toIpAddress(net.IP(dstSubnet.Mask))
for len(remaining) > 0 {
remaining, err = a.parseRouteAttribute(&route, remaining)
if err != nil {
return route, err
}
}
if len(remaining) != 0 {
return route, fmt.Errorf("could not parse all route arguments. remaining: %s", remaining)
}
return route, nil
}
func (a *netstackClientApp) addRoute(r netstack.RouteTableEntry2) error {
if r.Gateway == nil && r.Nicid == 0 {
return fmt.Errorf("either gateway or iface must be provided when adding a route")
}
req, transactionInterface, err := netstack.NewRouteTableTransactionWithCtxInterfaceRequest()
if err != nil {
return fmt.Errorf("could not make a new route table transaction: %s", err)
}
defer req.Close()
status, err := a.netstack.StartRouteTableTransaction(context.Background(), req)
if err != nil || zx.Status(status) != zx.ErrOk {
return fmt.Errorf("could not start a route table transaction: %s (%s)", err, zx.Status(status))
}
status, err = transactionInterface.AddRoute(context.Background(), r)
if err != nil {
return fmt.Errorf("could not add route due to transaction interface error: %s", err)
}
if zx.Status(status) != zx.ErrOk {
return fmt.Errorf("could not add route in netstack: %s", zx.Status(status))
}
return nil
}
func (a *netstackClientApp) deleteRoute(r netstack.RouteTableEntry2) error {
req, transactionInterface, err := netstack.NewRouteTableTransactionWithCtxInterfaceRequest()
if err != nil {
return fmt.Errorf("could not make a new route table transaction: %s", err)
}
defer req.Close()
status, err := a.netstack.StartRouteTableTransaction(context.Background(), req)
if err != nil || zx.Status(status) != zx.ErrOk {
return fmt.Errorf("could not start a route table transaction (maybe the route table is locked?): %s", err)
}
status, err = transactionInterface.DelRoute(context.Background(), r)
if err != nil {
return fmt.Errorf("could not delete route due to transaction interface error: %s", err)
}
if zx.Status(status) != zx.ErrOk {
return fmt.Errorf("could not delete route in netstack: %s", zx.Status(status))
}
return nil
}
func routeTableEntryToString(r netstack.RouteTableEntry2, ifaces []netstack.NetInterface2) string {
iface := getIfaceByIdFromIfaces(r.Nicid, ifaces)
var ifaceName string
if iface == nil {
ifaceName = fmt.Sprintf("Nicid:%d", r.Nicid)
} else {
ifaceName = iface.Name
}
var netAndMask net.IPNet
switch r.Destination.Which() {
case netfidl.IpAddressIpv4:
netAndMask = net.IPNet{IP: r.Destination.Ipv4.Addr[:], Mask: r.Netmask.Ipv4.Addr[:]}
case netfidl.IpAddressIpv6:
netAndMask = net.IPNet{IP: r.Destination.Ipv6.Addr[:], Mask: r.Netmask.Ipv6.Addr[:]}
}
if r.Gateway != nil {
return fmt.Sprintf("%s via %s %s metric %v", netAndMask.String(), netAddrToString(*r.Gateway), ifaceName, r.Metric)
}
return fmt.Sprintf("%s via %s metric %v", netAndMask.String(), ifaceName, r.Metric)
}
func (a *netstackClientApp) showRoutes() error {
rs, err := a.netstack.GetRouteTable2(context.Background())
if err != nil {
return fmt.Errorf("Could not get route table from netstack: %s", err)
}
ifaces, err := a.netstack.GetInterfaces2(context.Background())
if err != nil {
return err
}
for _, r := range rs {
fmt.Printf("%s\n", routeTableEntryToString(r, ifaces))
}
return nil
}
func (a *netstackClientApp) bridge(ifNames []string) (uint32, error) {
ifs := make([]*netstack.NetInterface2, len(ifNames))
nicIDs := make([]uint32, len(ifNames))
// first, validate that all interfaces exist
ifaces, err := a.netstack.GetInterfaces2(context.Background())
if err != nil {
return 0, err
}
for i, ifName := range ifNames {
iface := getIfaceByNameFromIfaces(ifName, ifaces)
if iface == nil {
return 0, fmt.Errorf("no such interface '%s'\n", ifName)
}
ifs[i] = iface
nicIDs[i] = iface.Id
}
result, nicid, _ := a.netstack.BridgeInterfaces(context.Background(), nicIDs)
if result.Status != netstack.StatusOk {
return 0, fmt.Errorf("error bridging interfaces: %s, result: %s", ifNames, result)
}
return nicid, nil
}
func (a *netstackClientApp) setDHCP(iface netstack.NetInterface2, startStop string) error {
req, client, err := dhcp.NewClientWithCtxInterfaceRequest()
if err != nil {
return err
}
res, err := a.netstack.GetDhcpClient(context.Background(), iface.Id, req)
if err != nil {
return err
}
switch res.Which() {
case netstack.NetstackGetDhcpClientResultErr:
return fmt.Errorf("failed to get DHCP client controller for interface %d: %s", iface.Id, zx.Status(res.Err))
case netstack.NetstackGetDhcpClientResultResponse:
}
switch startStop {
case "start":
res, err := client.Start(context.Background())
if err != nil {
return fmt.Errorf("failed to start client: %w", err)
}
switch res.Which() {
case dhcp.ClientStartResultErr:
return &zx.Error{Text: "dhcp client.Start()", Status: zx.Status(res.Err)}
case dhcp.ClientStartResultResponse:
}
case "stop":
res, err := client.Stop(context.Background())
if err != nil {
return fmt.Errorf("failed to stop client: %w", err)
}
switch res.Which() {
case dhcp.ClientStopResultErr:
return &zx.Error{Text: "dhcp client.Stop()", Status: zx.Status(res.Err)}
case dhcp.ClientStopResultResponse:
}
default:
usage()
}
return nil
}
func hwAddrToString(hwaddr []uint8) string {
var b strings.Builder
for i, d := range hwaddr {
if i > 0 {
b.WriteByte(':')
}
fmt.Fprintf(&b, "%02x", d)
}
return b.String()
}
func netAddrToString(addr netfidl.IpAddress) string {
return fidlconv.ToTCPIPAddress(addr).String()
}
func toIpAddress(addr net.IP) netfidl.IpAddress {
// TODO(eyalsoha): this doesn't correctly handle IPv6-mapped IPv4 addresses.
if ipv4 := addr.To4(); ipv4 != nil {
addr = ipv4
}
return fidlconv.ToNetIpAddress(tcpip.Address(addr))
}
func validateCidr(cidr string) (address netfidl.IpAddress, prefixLength uint8) {
netAddr, netSubnet, err := net.ParseCIDR(cidr)
if err != nil {
fmt.Printf("Error parsing CIDR notation: %s, error: %v\n", cidr, err)
usage()
}
prefixLen, _ := netSubnet.Mask.Size()
return toIpAddress(netAddr), uint8(prefixLen)
}
// bytesToString returns a human-friendly display of the given byte count.
// Values over 1K have an approximation appended in parentheses.
// bytesToString(42) => "42"
// bytesToString(42*1024 + 200) => "43208 (42.2 KB)"
func bytesToString(bytes uint64) string {
if bytes == 0 {
return "0"
}
units := "BKMGT"
unitSize := uint64(1)
unit := "B"
for i := 0; i+1 < len(units) && bytes >= unitSize*1024; i++ {
unitSize *= 1024
unit = string(units[i+1]) + "B"
}
if unitSize == 1 {
return fmt.Sprintf("%d", bytes)
} else {
value := float32(bytes) / float32(unitSize)
return fmt.Sprintf("%d (%.1f %s)", bytes, value, unit)
}
}
func usage() {
fmt.Printf("Usage:\n")
fmt.Printf(" %s [<interface>]\n", os.Args[0])
fmt.Printf(" %s <interface> {up|down}\n", os.Args[0])
fmt.Printf(" %s <interface> {add|del} <address>/<mask>\n", os.Args[0])
fmt.Printf(" %s <interface> metric <metric>\n", os.Args[0])
fmt.Printf(" %s <interface> dhcp {start|stop}\n", os.Args[0])
fmt.Printf(" %s route {add|del} <address>/<mask> [iface <name>] [gateway <address>] [metric <metric>]\n", os.Args[0])
fmt.Printf(" %s route show\n", os.Args[0])
fmt.Printf(" %s bridge [<interface>]+\n", os.Args[0])
os.Exit(1)
}
func Main() {
a := &netstackClientApp{ctx: component.NewContextFromStartupInfo()}
req, pxy, err := netstack.NewNetstackWithCtxInterfaceRequest()
if err != nil {
panic(err.Error())
}
a.netstack = pxy
defer a.netstack.Close()
a.ctx.ConnectToEnvService(req)
if len(os.Args) == 1 {
a.printAll()
return
}
var iface *netstack.NetInterface2
switch os.Args[1] {
case "route":
if len(os.Args) == 2 || os.Args[2] == "show" {
err = a.showRoutes()
if err != nil {
fmt.Printf("Error showing routes: %s\n", err)
}
return
}
if len(os.Args) < 4 {
fmt.Printf("Not enough arguments to `ifconfig route`; at least a destination and one of iface name or gateway must be provided\n")
usage()
}
routeFlags := os.Args[3:]
r, err := a.newRouteFromArgs(routeFlags)
if err != nil {
fmt.Printf("Error parsing route from args: %s, error: %s\n", routeFlags, err)
usage()
}
op := os.Args[2]
switch op {
case "add":
if err = a.addRoute(r); err != nil {
fmt.Printf("Error adding route to route table: %s\n", err)
usage()
}
case "del":
err = a.deleteRoute(r)
if err != nil {
fmt.Printf("Error deleting route from route table: %s\n", err)
usage()
}
default:
fmt.Printf("Unknown route operation: %s\n", op)
usage()
}
return
case "bridge":
ifaces := os.Args[2:]
nicid, err := a.bridge(ifaces)
if err != nil {
fmt.Printf("error creating bridge: %s\n", err)
} else {
interfaces, _ := a.netstack.GetInterfaces2(context.Background())
bridge := getIfaceByIdFromIfaces(uint32(nicid), interfaces)
fmt.Printf("Bridged interfaces %s.\nInterface '%s' created.\nPlease run `ifconfig %[2]s up` to enable it.\n", ifaces, bridge.Name)
}
return
case "help":
usage()
return
default:
ifaces, err := a.netstack.GetInterfaces2(context.Background())
if err != nil {
fmt.Printf("Error finding interface name: %s\n", err)
return
}
iface = getIfaceByNameFromIfaces(os.Args[1], ifaces)
if err != nil {
fmt.Printf("Error finding interface name: %s\n", err)
return
}
if iface == nil {
fmt.Printf("ifconfig: no such interface '%s'\n", os.Args[1])
return
}
}
switch len(os.Args) {
case 2:
a.printIface(*iface)
case 3:
switch os.Args[2] {
case "up":
a.setStatus(*iface, true)
case "down":
a.setStatus(*iface, false)
default:
usage()
}
case 4:
switch os.Args[2] {
case "add":
a.addIfaceAddress(*iface, os.Args[3])
case "del":
a.removeIfaceAddress(*iface, os.Args[3])
case "dhcp":
if err := a.setDHCP(*iface, os.Args[3]); err != nil {
fmt.Printf("ifconfig: a.setDHCP(%+v, %s) failed: %s", *iface, os.Args[3], err)
}
case "metric":
metric, err := strconv.ParseUint(os.Args[3], 10, 32)
if err != nil {
fmt.Printf("ifconfig: metric value '%s' is not uint32: %s\n", os.Args[3], err)
return
}
a.netstack.SetInterfaceMetric(context.Background(), iface.Id, uint32(metric))
default:
usage()
}
default:
usage()
}
}