blob: 6287aa2b83248e8fde6c2a6a8ec815d0a20980cd [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.
package main
import (
"fmt"
"net"
"os"
"strings"
"syscall/zx"
"app/context"
"netstack/fidlconv"
"fidl/fuchsia/hardware/ethernet"
netfidl "fidl/fuchsia/net"
"fidl/fuchsia/netstack"
"fidl/fuchsia/wlan/service"
"github.com/google/netstack/tcpip"
)
type netstackClientApp struct {
ctx *context.Context
netstack *netstack.NetstackInterface
wlan *service.WlanInterface
}
func (a *netstackClientApp) printAll() {
ifaces, err := a.netstack.GetInterfaces()
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.NetInterface) *netstack.NetInterface {
var candidate *netstack.NetInterface
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.NetInterface) *netstack.NetInterface {
for _, iface := range ifaces {
if iface.Id == id {
return &iface
}
}
return nil
}
func (a *netstackClientApp) printIface(iface netstack.NetInterface) {
stats, err := a.netstack.GetStats(iface.Id)
if err != nil {
fmt.Printf("ifconfig: failed to fetch stats for '%v'\n\n", iface.Name)
return
}
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("\t%s\n", flagsToString(iface.Flags))
if isWLAN(iface.Features) {
fmt.Printf("\tWLAN Status: %s\n", a.wlanStatus())
}
fmt.Printf("\tRX packets:%d\n", stats.Rx.PktsTotal)
fmt.Printf("\tTX packets:%d\n", stats.Tx.PktsTotal)
fmt.Printf("\tRX bytes:%s TX bytes:%s\n",
bytesToString(stats.Rx.BytesTotal), bytesToString(stats.Tx.BytesTotal))
fmt.Printf("\n")
// TODO: more stats. MTU, RX/TX errors
}
func (a *netstackClientApp) setStatus(iface netstack.NetInterface, up bool) {
a.netstack.SetInterfaceStatus(iface.Id, up)
}
func (a *netstackClientApp) addIfaceAddress(iface netstack.NetInterface, cidr string) {
netAddr, prefixLen := validateCidr(os.Args[3])
result, _ := a.netstack.SetInterfaceAddress(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.NetInterface, cidr string) {
netAddr, prefixLen := validateCidr(os.Args[3])
result, _ := a.netstack.RemoveInterfaceAddress(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.RouteTableEntry, 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":
in.Gateway = toIpAddress(net.ParseIP(val))
case "iface":
ifaces, err := a.netstack.GetInterfaces()
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.RouteTableEntry, err error) {
destination, remaining := args[0], args[1:]
dstAddr, 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(dstAddr)
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.RouteTableEntry) error {
if (r.Gateway == netfidl.IpAddress{}) && r.Nicid == 0 {
return fmt.Errorf("either gateway or iface must be provided when adding a route")
}
req, transactionInterface, err := netstack.NewRouteTableTransactionInterfaceRequest()
if err != nil {
return fmt.Errorf("could not make a new route table transaction: %s", err)
}
defer req.Close()
status, err := a.netstack.StartRouteTableTransaction(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))
}
rs, err := transactionInterface.GetRouteTable()
if err != nil {
return fmt.Errorf("could not get route table from netstack: %s", err)
}
err = transactionInterface.SetRouteTable(append(rs, r))
if err != nil {
return fmt.Errorf("could not set route table in netstack: %s", err)
}
status, err = transactionInterface.Commit()
if err != nil || zx.Status(status) != zx.ErrOk {
return fmt.Errorf("could not commit route table in netstack: %s (%s)", err, zx.Status(status))
}
return nil
}
func equalIpAddress(a netfidl.IpAddress, b netfidl.IpAddress) bool {
if a.Which() != b.Which() {
return false
}
switch a.Which() {
case netfidl.IpAddressIpv4:
return a.Ipv4.Addr == b.Ipv4.Addr
case netfidl.IpAddressIpv6:
return a.Ipv6.Addr == b.Ipv6.Addr
default:
return false
}
}
// Returns true if the target matches the source. If the target is
// missing the gateway or nicid then those are considered to be
// matching.
func matchRoute(target netstack.RouteTableEntry, source netstack.RouteTableEntry) bool {
if !equalIpAddress(target.Destination, source.Destination) {
return false
}
if !equalIpAddress(target.Netmask, source.Netmask) {
return false
}
if target.Gateway.Which() != 0 &&
!equalIpAddress(source.Gateway, target.Gateway) {
// The gateway is neither wildcard nor a match.
return false
}
if target.Nicid != 0 && source.Nicid != target.Nicid {
// The Nicid is neither wildcard nor a match.
return false
}
return true
}
func (a *netstackClientApp) deleteRoute(target netstack.RouteTableEntry) error {
req, transactionInterface, err := netstack.NewRouteTableTransactionInterfaceRequest()
if err != nil {
return fmt.Errorf("could not make a new route table transaction: %s", err)
}
defer req.Close()
status, err := a.netstack.StartRouteTableTransaction(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)
}
rs, err := transactionInterface.GetRouteTable()
if err != nil {
return fmt.Errorf("could not get route table from netstack: %s", err)
}
for i, r := range rs {
if matchRoute(target, r) {
transactionInterface.SetRouteTable(append(rs[:i], rs[i+1:]...))
_, err = transactionInterface.Commit()
if err != nil {
return fmt.Errorf("could not commit route table in netstack: %s", err)
}
return nil
}
}
return fmt.Errorf("could not find route to delete in route table")
}
func routeTableEntryToString(r netstack.RouteTableEntry, ifaces []netstack.NetInterface) 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[:]}
}
return fmt.Sprintf("%s via %s %s", netAndMask.String(), netAddrToString(r.Gateway), ifaceName)
}
func (a *netstackClientApp) showRoutes() error {
rs, err := a.netstack.GetRouteTable()
if err != nil {
return fmt.Errorf("Could not get route table from netstack: %s", err)
}
ifaces, err := a.netstack.GetInterfaces()
if err != nil {
return err
}
for _, r := range rs {
fmt.Printf("%s\n", routeTableEntryToString(r, ifaces))
}
return nil
}
func (a *netstackClientApp) bridge(ifNames []string) error {
ifs := make([]*netstack.NetInterface, len(ifNames))
nicIDs := make([]uint32, len(ifNames))
// first, validate that all interfaces exist
ifaces, err := a.netstack.GetInterfaces()
if err != nil {
return err
}
for i, ifName := range ifNames {
iface := getIfaceByNameFromIfaces(ifName, ifaces)
if iface == nil {
return fmt.Errorf("no such interface '%s'\n", ifName)
}
ifs[i] = iface
nicIDs[i] = iface.Id
}
result, _ := a.netstack.BridgeInterfaces(nicIDs)
if result.Status != netstack.StatusOk {
return fmt.Errorf("error bridging interfaces: %s, result: %s", ifNames, result)
}
return nil
}
func (a *netstackClientApp) setDHCP(iface netstack.NetInterface, startStop string) {
switch startStop {
case "start":
a.netstack.SetDhcpClientStatus(iface.Id, true)
case "stop":
a.netstack.SetDhcpClientStatus(iface.Id, false)
default:
usage()
}
}
func (a *netstackClientApp) wlanStatus() string {
if a.wlan == nil {
return "failed to query (FIDL service unintialized)"
}
res, err := a.wlan.Status()
if err != nil {
return fmt.Sprintf("failed to query (error: %v)", err)
} else if res.Error.Code != service.ErrCodeOk {
return fmt.Sprintf("failed to query (err: code(%v) desc(%v)", res.Error.Code, res.Error.Description)
} else {
status := wlanStateToStr(res.State)
if res.CurrentAp != nil {
ap := res.CurrentAp
isSecureStr := ""
if ap.IsSecure {
isSecureStr = "*"
}
status += fmt.Sprintf(" BSSID: %x SSID: %q Security: %v RSSI: %d",
ap.Bssid, ap.Ssid, isSecureStr, ap.RssiDbm)
}
return status
}
}
func wlanStateToStr(state service.State) string {
switch state {
case service.StateBss:
return "starting-bss"
case service.StateQuerying:
return "querying"
case service.StateScanning:
return "scanning"
case service.StateJoining:
return "joining"
case service.StateAuthenticating:
return "authenticating"
case service.StateAssociating:
return "associating"
case service.StateAssociated:
return "associated"
default:
return "unknown"
}
}
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 flagsToString(flags uint32) string {
var b strings.Builder
if flags&netstack.NetInterfaceFlagUp != 0 {
b.WriteString("UP")
}
return b.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)
}
func isWLAN(features uint32) bool {
return features&ethernet.InfoFeatureWlan != 0
}
// 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>] dhcp [start|stop]\n", os.Args[0])
fmt.Printf(" %s route [add|del] [<address>/<mask>] [iface <name>] [gateway <address>/<mask>]\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: context.CreateFromStartupInfo()}
req, pxy, err := netstack.NewNetstackInterfaceRequest()
if err != nil {
panic(err.Error())
}
a.netstack = pxy
defer a.netstack.Close()
a.ctx.ConnectToEnvService(req)
reqWlan, pxyWlan, errWlan := service.NewWlanInterfaceRequest()
if errWlan == nil {
a.wlan = pxyWlan
defer a.wlan.Close()
a.ctx.ConnectToEnvService(reqWlan)
}
if len(os.Args) == 1 {
a.printAll()
return
}
var iface *netstack.NetInterface
switch os.Args[1] {
case "route":
if len(os.Args) == 2 {
fmt.Printf("Missing route operation.\n")
usage()
}
op := os.Args[2]
if op == "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()
}
switch op {
case "add":
if err = a.addRoute(r); err != nil {
fmt.Printf("Error adding route to route table: %s", err)
usage()
}
case "del":
err = a.deleteRoute(r)
if err != nil {
fmt.Printf("Error deleting route from route table: %s", err)
usage()
}
default:
fmt.Printf("Unknown route operation: %s", op)
usage()
}
return
case "bridge":
ifaces := os.Args[2:]
err := a.bridge(ifaces)
if err != nil {
fmt.Printf("error creating bridge: %s", err)
} else {
fmt.Printf("Bridged interfaces %s\n", ifaces)
}
return
case "help":
usage()
return
default:
ifaces, err := a.netstack.GetInterfaces()
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, 4:
switch os.Args[2] {
case "up":
a.setStatus(*iface, true)
case "down":
a.setStatus(*iface, false)
case "add":
a.addIfaceAddress(*iface, os.Args[3])
case "del":
a.removeIfaceAddress(*iface, os.Args[3])
case "dhcp":
a.setDHCP(*iface, os.Args[3])
default:
usage()
}
default:
usage()
}
}