blob: 009568042d2406d1a03b83e791ffe7d5cc85751f [file] [log] [blame] [edit]
// 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"
"github.com/google/netstack/tcpip"
"app/context"
"fidl/netstack"
"fidl/wlan_service"
)
type netstackClientApp struct {
ctx *context.Context
netstack *netstack.NetstackInterface
wlan *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 (a *netstackClientApp) getIfaceByName(name string) *netstack.NetInterface {
ifaces, err := a.netstack.GetInterfaces()
if err != nil {
fmt.Print("ifconfig: failed to fetch interfaces\n")
return nil
}
for _, iface := range ifaces {
if iface.Name == name {
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\n", iface.Name, hwAddrToString(iface.Hwaddr))
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, netSubnet, err := net.ParseCIDR(os.Args[3])
if err != nil {
fmt.Printf("Error parsing CIDR notation: %s, error: %v\n", os.Args[3], err)
usage()
}
prefixLen, _ := netSubnet.Mask.Size()
result, _ := a.netstack.SetInterfaceAddress(iface.Id, toNetAddress(netAddr), uint64(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 = toNetAddress(net.ParseIP(val))
case "iface":
iface := a.getIfaceByName(val)
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 = toNetAddress(dstAddr)
route.Netmask = toNetAddress(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 {
rs, err := a.netstack.GetRouteTable()
if err != nil {
return fmt.Errorf("Could not get route table from netstack: %s", err)
}
return a.netstack.SetRouteTable(append(rs, r))
}
func (a *netstackClientApp) bridge(ifNames []string) error {
ifs := make([]*netstack.NetInterface, len(ifNames))
nicIDs := make([]uint32, len(ifNames))
// first, validate that all interfaces exist
for i, ifName := range ifNames {
iface := a.getIfaceByName(ifName)
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", result, ifs)
}
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 != wlan_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 wlan_service.State) string {
switch state {
case wlan_service.StateBss:
return "starting-bss"
case wlan_service.StateQuerying:
return "querying"
case wlan_service.StateScanning:
return "scanning"
case wlan_service.StateJoining:
return "joining"
case wlan_service.StateAuthenticating:
return "authenticating"
case wlan_service.StateAssociating:
return "associating"
case wlan_service.StateAssociated:
return "associated"
default:
return "unknown"
}
}
func hwAddrToString(hwaddr []uint8) string {
str := ""
for i := 0; i < len(hwaddr); i++ {
if i > 0 {
str += ":"
}
str += fmt.Sprintf("%x", hwaddr[i])
}
return str
}
func netAddrToString(addr netstack.NetAddress) string {
switch addr.Family {
case netstack.NetAddressFamilyIpv4:
a := tcpip.Address(addr.Ipv4.Addr[:])
return fmt.Sprintf("%s", a)
case netstack.NetAddressFamilyIpv6:
a := tcpip.Address(addr.Ipv6.Addr[:])
return fmt.Sprintf("%s", a)
}
return ""
}
func flagsToString(flags uint32) string {
str := ""
if flags&netstack.NetInterfaceFlagUp != 0 {
str += "UP"
}
return str
}
func isIPv4(ip net.IP) bool {
return ip.DefaultMask() != nil
}
func toNetAddress(addr net.IP) netstack.NetAddress {
out := netstack.NetAddress{Family: netstack.NetAddressFamilyUnspecified}
if isIPv4(addr) {
out.Family = netstack.NetAddressFamilyIpv4
out.Ipv4 = &netstack.Ipv4Address{Addr: [4]uint8{}}
copy(out.Ipv4.Addr[:], addr[len(addr)-4:])
} else {
out.Family = netstack.NetAddressFamilyIpv6
out.Ipv6 = &netstack.Ipv6Address{Addr: [16]uint8{}}
copy(out.Ipv6.Addr[:], addr[:])
}
return out
}
func isWLAN(features uint32) bool {
return features&uint32(netstack.InterfaceFeatureWlan) != 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>]\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 := wlan_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":
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)
}
switch op := os.Args[2]; op {
case "add":
err = a.addRoute(r)
if err != nil {
fmt.Printf("Error adding route to route table: %s", err)
}
case "del":
fmt.Printf("Deleting routes from the route table is not yet supported.\n")
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)
}
// fmt.Printf("Created virtual nic %s", bridge)
fmt.Printf("Bridged interfaces %s\n", ifaces)
return
default:
iface = a.getIfaceByName(os.Args[1])
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":
fmt.Printf("Deleting addresses from an interface is not yet supported.\n")
usage()
case "dhcp":
a.setDHCP(*iface, os.Args[3])
default:
usage()
}
default:
usage()
}
}