Add bridge and driver code for FreeBSD specifically

This commit is contained in:
Hongjiang Zhang 2017-05-25 13:19:22 +08:00 committed by R. Tyler Croy
parent 3ed6a3a883
commit 027fc3483b
No known key found for this signature in database
GPG Key ID: 1426C7DC3F51E16F
6 changed files with 1866 additions and 1 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,384 @@
// +build freebsd
package bridge
import (
"encoding/json"
"fmt"
"net"
"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/discoverapi"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/types"
)
const (
// network config prefix was not specific enough.
// To be backward compatible, need custom endpoint
// prefix with different root
bridgePrefix = "bridge"
bridgeEndpointPrefix = "bridge-endpoint"
)
func (d *driver) initStore(option map[string]interface{}) error {
if data, ok := option[netlabel.LocalKVClient]; ok {
var err error
dsc, ok := data.(discoverapi.DatastoreConfigData)
if !ok {
return types.InternalErrorf("incorrect data in datastore configuration: %v", data)
}
d.store, err = datastore.NewDataStoreFromConfig(dsc)
if err != nil {
return types.InternalErrorf("bridge driver failed to initialize data store: %v", err)
}
err = d.populateNetworks()
if err != nil {
return err
}
err = d.populateEndpoints()
if err != nil {
return err
}
}
return nil
}
func (d *driver) populateNetworks() error {
kvol, err := d.store.List(datastore.Key(bridgePrefix), &networkConfiguration{})
if err != nil && err != datastore.ErrKeyNotFound {
return fmt.Errorf("failed to get bridge network configurations from store: %v", err)
}
// It's normal for network configuration state to be empty. Just return.
if err == datastore.ErrKeyNotFound {
return nil
}
for _, kvo := range kvol {
ncfg := kvo.(*networkConfiguration)
if err = d.createNetwork(ncfg); err != nil {
logrus.Warnf("could not create bridge network for id %s bridge name %s while booting up from persistent state: %v", ncfg.ID, ncfg.BridgeName, err)
}
logrus.Debugf("Network (%s) restored", ncfg.ID[0:7])
}
return nil
}
func (d *driver) populateEndpoints() error {
kvol, err := d.store.List(datastore.Key(bridgeEndpointPrefix), &bridgeEndpoint{})
if err != nil && err != datastore.ErrKeyNotFound {
return fmt.Errorf("failed to get bridge endpoints from store: %v", err)
}
if err == datastore.ErrKeyNotFound {
return nil
}
for _, kvo := range kvol {
ep := kvo.(*bridgeEndpoint)
n, ok := d.networks[ep.nid]
if !ok {
logrus.Debugf("Network (%s) not found for restored bridge endpoint (%s)", ep.nid[0:7], ep.id[0:7])
logrus.Debugf("Deleting stale bridge endpoint (%s) from store", ep.nid[0:7])
if err := d.storeDelete(ep); err != nil {
logrus.Debugf("Failed to delete stale bridge endpoint (%s) from store", ep.nid[0:7])
}
continue
}
n.endpoints[ep.id] = ep
n.restorePortAllocations(ep)
logrus.Debugf("Endpoint (%s) restored to network (%s)", ep.id[0:7], ep.nid[0:7])
}
return nil
}
func (d *driver) storeUpdate(kvObject datastore.KVObject) error {
if d.store == nil {
logrus.Warnf("bridge store not initialized. kv object %s is not added to the store", datastore.Key(kvObject.Key()...))
return nil
}
if err := d.store.PutObjectAtomic(kvObject); err != nil {
return fmt.Errorf("failed to update bridge store for object type %T: %v", kvObject, err)
}
return nil
}
func (d *driver) storeDelete(kvObject datastore.KVObject) error {
if d.store == nil {
logrus.Debugf("bridge store not initialized. kv object %s is not deleted from store", datastore.Key(kvObject.Key()...))
return nil
}
retry:
if err := d.store.DeleteObjectAtomic(kvObject); err != nil {
if err == datastore.ErrKeyModified {
if err := d.store.GetObject(datastore.Key(kvObject.Key()...), kvObject); err != nil {
return fmt.Errorf("could not update the kvobject to latest when trying to delete: %v", err)
}
goto retry
}
return err
}
return nil
}
func (ncfg *networkConfiguration) MarshalJSON() ([]byte, error) {
nMap := make(map[string]interface{})
nMap["ID"] = ncfg.ID
nMap["BridgeName"] = ncfg.BridgeName
nMap["BridgeNameInternal"] = ncfg.BridgeNameInternal
nMap["EnableIPv6"] = ncfg.EnableIPv6
nMap["EnableIPMasquerade"] = ncfg.EnableIPMasquerade
nMap["EnableICC"] = ncfg.EnableICC
nMap["Mtu"] = ncfg.Mtu
nMap["Internal"] = ncfg.Internal
nMap["DefaultBridge"] = ncfg.DefaultBridge
nMap["DefaultBindingIP"] = ncfg.DefaultBindingIP.String()
nMap["DefaultBindingIntf"] = ncfg.DefaultBindingIntf
nMap["DefaultGatewayIPv4"] = ncfg.DefaultGatewayIPv4.String()
nMap["DefaultGatewayIPv6"] = ncfg.DefaultGatewayIPv6.String()
if ncfg.AddressIPv4 != nil {
nMap["AddressIPv4"] = ncfg.AddressIPv4.String()
}
if ncfg.AddressIPv6 != nil {
nMap["AddressIPv6"] = ncfg.AddressIPv6.String()
}
return json.Marshal(nMap)
}
func (ncfg *networkConfiguration) UnmarshalJSON(b []byte) error {
var (
err error
nMap map[string]interface{}
)
if err = json.Unmarshal(b, &nMap); err != nil {
return err
}
if v, ok := nMap["AddressIPv4"]; ok {
if ncfg.AddressIPv4, err = types.ParseCIDR(v.(string)); err != nil {
return types.InternalErrorf("failed to decode bridge network address IPv4 after json unmarshal: %s", v.(string))
}
}
if v, ok := nMap["AddressIPv6"]; ok {
if ncfg.AddressIPv6, err = types.ParseCIDR(v.(string)); err != nil {
return types.InternalErrorf("failed to decode bridge network address IPv6 after json unmarshal: %s", v.(string))
}
}
ncfg.DefaultBridge = nMap["DefaultBridge"].(bool)
ncfg.DefaultBindingIP = net.ParseIP(nMap["DefaultBindingIP"].(string))
ncfg.DefaultBindingIntf = nMap["DefaultBindingIntf"].(string)
ncfg.DefaultGatewayIPv4 = net.ParseIP(nMap["DefaultGatewayIPv4"].(string))
ncfg.DefaultGatewayIPv6 = net.ParseIP(nMap["DefaultGatewayIPv6"].(string))
ncfg.ID = nMap["ID"].(string)
ncfg.BridgeName = nMap["BridgeName"].(string)
ncfg.BridgeNameInternal = nMap["BridgeNameInternal"].(string)
ncfg.EnableIPv6 = nMap["EnableIPv6"].(bool)
ncfg.EnableIPMasquerade = nMap["EnableIPMasquerade"].(bool)
ncfg.EnableICC = nMap["EnableICC"].(bool)
ncfg.Mtu = int(nMap["Mtu"].(float64))
if v, ok := nMap["Internal"]; ok {
ncfg.Internal = v.(bool)
}
return nil
}
func (ncfg *networkConfiguration) Key() []string {
return []string{bridgePrefix, ncfg.ID}
}
func (ncfg *networkConfiguration) KeyPrefix() []string {
return []string{bridgePrefix}
}
func (ncfg *networkConfiguration) Value() []byte {
b, err := json.Marshal(ncfg)
if err != nil {
return nil
}
return b
}
func (ncfg *networkConfiguration) SetValue(value []byte) error {
return json.Unmarshal(value, ncfg)
}
func (ncfg *networkConfiguration) Index() uint64 {
return ncfg.dbIndex
}
func (ncfg *networkConfiguration) SetIndex(index uint64) {
ncfg.dbIndex = index
ncfg.dbExists = true
}
func (ncfg *networkConfiguration) Exists() bool {
return ncfg.dbExists
}
func (ncfg *networkConfiguration) Skip() bool {
return false
}
func (ncfg *networkConfiguration) New() datastore.KVObject {
return &networkConfiguration{}
}
func (ncfg *networkConfiguration) CopyTo(o datastore.KVObject) error {
dstNcfg := o.(*networkConfiguration)
*dstNcfg = *ncfg
return nil
}
func (ncfg *networkConfiguration) DataScope() string {
return datastore.LocalScope
}
func (ep *bridgeEndpoint) MarshalJSON() ([]byte, error) {
epMap := make(map[string]interface{})
epMap["id"] = ep.id
epMap["nid"] = ep.nid
epMap["SrcName"] = ep.srcName
epMap["MacAddress"] = ep.macAddress.String()
epMap["Addr"] = ep.addr.String()
if ep.addrv6 != nil {
epMap["Addrv6"] = ep.addrv6.String()
}
epMap["Config"] = ep.config
epMap["ContainerConfig"] = ep.containerConfig
epMap["ExternalConnConfig"] = ep.extConnConfig
epMap["PortMapping"] = ep.portMapping
return json.Marshal(epMap)
}
func (ep *bridgeEndpoint) UnmarshalJSON(b []byte) error {
var (
err error
epMap map[string]interface{}
)
if err = json.Unmarshal(b, &epMap); err != nil {
return fmt.Errorf("Failed to unmarshal to bridge endpoint: %v", err)
}
if v, ok := epMap["MacAddress"]; ok {
if ep.macAddress, err = net.ParseMAC(v.(string)); err != nil {
return types.InternalErrorf("failed to decode bridge endpoint MAC address (%s) after json unmarshal: %v", v.(string), err)
}
}
if v, ok := epMap["Addr"]; ok {
if ep.addr, err = types.ParseCIDR(v.(string)); err != nil {
return types.InternalErrorf("failed to decode bridge endpoint IPv4 address (%s) after json unmarshal: %v", v.(string), err)
}
}
if v, ok := epMap["Addrv6"]; ok {
if ep.addrv6, err = types.ParseCIDR(v.(string)); err != nil {
return types.InternalErrorf("failed to decode bridge endpoint IPv6 address (%s) after json unmarshal: %v", v.(string), err)
}
}
ep.id = epMap["id"].(string)
ep.nid = epMap["nid"].(string)
ep.srcName = epMap["SrcName"].(string)
d, _ := json.Marshal(epMap["Config"])
if err := json.Unmarshal(d, &ep.config); err != nil {
logrus.Warnf("Failed to decode endpoint config %v", err)
}
d, _ = json.Marshal(epMap["ContainerConfig"])
if err := json.Unmarshal(d, &ep.containerConfig); err != nil {
logrus.Warnf("Failed to decode endpoint container config %v", err)
}
d, _ = json.Marshal(epMap["ExternalConnConfig"])
if err := json.Unmarshal(d, &ep.extConnConfig); err != nil {
logrus.Warnf("Failed to decode endpoint external connectivity configuration %v", err)
}
d, _ = json.Marshal(epMap["PortMapping"])
if err := json.Unmarshal(d, &ep.portMapping); err != nil {
logrus.Warnf("Failed to decode endpoint port mapping %v", err)
}
return nil
}
func (ep *bridgeEndpoint) Key() []string {
return []string{bridgeEndpointPrefix, ep.id}
}
func (ep *bridgeEndpoint) KeyPrefix() []string {
return []string{bridgeEndpointPrefix}
}
func (ep *bridgeEndpoint) Value() []byte {
b, err := json.Marshal(ep)
if err != nil {
return nil
}
return b
}
func (ep *bridgeEndpoint) SetValue(value []byte) error {
return json.Unmarshal(value, ep)
}
func (ep *bridgeEndpoint) Index() uint64 {
return ep.dbIndex
}
func (ep *bridgeEndpoint) SetIndex(index uint64) {
ep.dbIndex = index
ep.dbExists = true
}
func (ep *bridgeEndpoint) Exists() bool {
return ep.dbExists
}
func (ep *bridgeEndpoint) Skip() bool {
return false
}
func (ep *bridgeEndpoint) New() datastore.KVObject {
return &bridgeEndpoint{}
}
func (ep *bridgeEndpoint) CopyTo(o datastore.KVObject) error {
dstEp := o.(*bridgeEndpoint)
*dstEp = *ep
return nil
}
func (ep *bridgeEndpoint) DataScope() string {
return datastore.LocalScope
}
func (n *bridgeNetwork) restorePortAllocations(ep *bridgeEndpoint) {
if ep.extConnConfig == nil ||
ep.extConnConfig.ExposedPorts == nil ||
ep.extConnConfig.PortBindings == nil {
return
}
tmp := ep.extConnConfig.PortBindings
ep.extConnConfig.PortBindings = ep.portMapping
_, err := n.allocatePorts(ep, n.config.DefaultBindingIntf, n.config.DefaultBindingIP, n.driver.config.EnableUserlandProxy)
if err != nil {
logrus.Warnf("Failed to reserve existing port mapping for endpoint %s:%v", ep.id[0:7], err)
}
ep.extConnConfig.PortBindings = tmp
}

View File

@ -0,0 +1,120 @@
// +build freebsd
package bridge
import "fmt"
// ErrInvalidEndpointConfig error is returned when an endpoint create is attempted with an invalid endpoint configuration.
type ErrInvalidEndpointConfig struct{}
func (eiec *ErrInvalidEndpointConfig) Error() string {
return "trying to create an endpoint with an invalid endpoint configuration"
}
// BadRequest denotes the type of this error
func (eiec *ErrInvalidEndpointConfig) BadRequest() {}
// ErrNoIPAddr error is returned when bridge has no IPv4 address configured.
type ErrNoIPAddr struct{}
func (enip *ErrNoIPAddr) Error() string {
return "bridge has no IPv4 address configured"
}
// InternalError denotes the type of this error
func (enip *ErrNoIPAddr) InternalError() {}
// ErrInvalidGateway is returned when the user provided default gateway (v4/v6) is not not valid.
type ErrInvalidGateway struct{}
func (eig *ErrInvalidGateway) Error() string {
return "default gateway ip must be part of the network"
}
// BadRequest denotes the type of this error
func (eig *ErrInvalidGateway) BadRequest() {}
// ErrInvalidMtu is returned when the user provided MTU is not valid.
type ErrInvalidMtu int
func (eim ErrInvalidMtu) Error() string {
return fmt.Sprintf("invalid MTU number: %d", int(eim))
}
// BadRequest denotes the type of this error
func (eim ErrInvalidMtu) BadRequest() {}
// ErrUnsupportedAddressType is returned when the specified address type is not supported.
type ErrUnsupportedAddressType string
func (uat ErrUnsupportedAddressType) Error() string {
return fmt.Sprintf("unsupported address type: %s", string(uat))
}
// BadRequest denotes the type of this error
func (uat ErrUnsupportedAddressType) BadRequest() {}
// ActiveEndpointsError is returned when there are
// still active endpoints in the network being deleted.
type ActiveEndpointsError string
func (aee ActiveEndpointsError) Error() string {
return fmt.Sprintf("network %s has active endpoint", string(aee))
}
// Forbidden denotes the type of this error
func (aee ActiveEndpointsError) Forbidden() {}
// InvalidNetworkIDError is returned when the passed
// network id for an existing network is not a known id.
type InvalidNetworkIDError string
func (inie InvalidNetworkIDError) Error() string {
return fmt.Sprintf("invalid network id %s", string(inie))
}
// NotFound denotes the type of this error
func (inie InvalidNetworkIDError) NotFound() {}
// InvalidEndpointIDError is returned when the passed
// endpoint id is not valid.
type InvalidEndpointIDError string
func (ieie InvalidEndpointIDError) Error() string {
return fmt.Sprintf("invalid endpoint id: %s", string(ieie))
}
// BadRequest denotes the type of this error
func (ieie InvalidEndpointIDError) BadRequest() {}
// EndpointNotFoundError is returned when the no endpoint
// with the passed endpoint id is found.
type EndpointNotFoundError string
func (enfe EndpointNotFoundError) Error() string {
return fmt.Sprintf("endpoint not found: %s", string(enfe))
}
// NotFound denotes the type of this error
func (enfe EndpointNotFoundError) NotFound() {}
// NonDefaultBridgeExistError is returned when a non-default
// bridge config is passed but it does not already exist.
type NonDefaultBridgeExistError string
func (ndbee NonDefaultBridgeExistError) Error() string {
return fmt.Sprintf("bridge device with non default name %s must be created manually", string(ndbee))
}
// Forbidden denotes the type of this error
func (ndbee NonDefaultBridgeExistError) Forbidden() {}
// NonDefaultBridgeNeedsIPError is returned when a non-default
// bridge config is passed but it has no ip configured
type NonDefaultBridgeNeedsIPError string
func (ndbee NonDefaultBridgeNeedsIPError) Error() string {
return fmt.Sprintf("bridge device with non default name %s must have a valid IP address", string(ndbee))
}
// Forbidden denotes the type of this error
func (ndbee NonDefaultBridgeNeedsIPError) Forbidden() {}

View File

@ -0,0 +1,235 @@
// +build freebsd
package bridge
import (
"bytes"
"errors"
"fmt"
"net"
//"os"
"os/exec"
"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/types"
)
var (
defaultBindingIP = net.IPv4(0, 0, 0, 0)
)
const (
maxAllocatePortAttempts = 10
)
func addPFRules(epid, bindIntf string, bs []types.PortBinding) {
/*
var id string
if len(epid) > 12 {
id = epid[:12]
} else {
id = epid
}
fname := "/var/lib/docker/network/files/pf." + id
f, err := os.OpenFile(fname,
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
if err != nil {
logrus.Warn("cannot open temp pf file")
return
}
for _, b := range bs {
r := fmt.Sprintf(
"pass in on %s proto %s from any to (%s) "+
"port %d rdr-to %s port %d\n", bindIntf,
b.Proto.String(), bindIntf, b.HostPort,
b.IP.String(), b.Port)
_, err = f.WriteString(r)
if err != nil {
logrus.Warnf("cannot write firewall rules to %s: %v", fname, err)
}
}
f.Close()
anchor := fmt.Sprintf("_auto/docker/ep%s", id)
err = exec.Command("/usr/sbin/pfctl", "-a", anchor, "-f", fname).Run()
if err != nil {
logrus.Warnf("failed to add firewall rules: %v", err)
}
os.Remove(fname)
*/
}
func removePFRules(epid string) {
var id string
if len(epid) > 12 {
id = epid[:12]
} else {
id = epid
}
anchor := fmt.Sprintf("_auto/docker/ep%s", id)
err := exec.Command("/usr/sbin/pfctl", "-a", anchor, "-F", "all").Run()
if err != nil {
logrus.Warnf("failed to remove firewall rules: %v", err)
}
}
func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, bindIntf string, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
if ep.extConnConfig == nil || ep.extConnConfig.PortBindings == nil {
return nil, nil
}
defHostIP := defaultBindingIP
if reqDefBindIP != nil {
defHostIP = reqDefBindIP
}
bs, err := n.allocatePortsInternal(ep.extConnConfig.PortBindings, bindIntf, ep.addr.IP, defHostIP, ulPxyEnabled)
if err != nil {
return nil, err
}
// Add PF rules for port bindings, if any
if len(bs) > 0 {
addPFRules(ep.id, bindIntf, bs)
}
return bs, err
}
func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, bindIntf string, containerIP, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
bs := make([]types.PortBinding, 0, len(bindings))
for _, c := range bindings {
b := c.GetCopy()
if err := n.allocatePort(&b, containerIP, defHostIP); err != nil {
// On allocation failure,release previously
// allocated ports. On cleanup error, just log
// a warning message
if cuErr := n.releasePortsInternal(bs); cuErr != nil {
logrus.Warnf("Upon allocation failure "+
"for %v, failed to clear previously "+
"allocated port bindings: %v", b, cuErr)
}
return nil, err
}
bs = append(bs, b)
}
return bs, nil
}
func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, containerIP, defHostIP net.IP) error {
var (
host net.Addr
err error
)
// Store the container interface address in the operational binding
bnd.IP = containerIP
// Adjust the host address in the operational binding
if len(bnd.HostIP) == 0 {
bnd.HostIP = defHostIP
}
// Adjust HostPortEnd if this is not a range.
if bnd.HostPortEnd == 0 {
bnd.HostPortEnd = bnd.HostPort
}
/* TODO
// Construct the container side transport address
container, err := bnd.ContainerAddr()
if err != nil {
return err
}
*/
// Try up to maxAllocatePortAttempts times to get a port that's
// not already allocated.
for i := 0; i < maxAllocatePortAttempts; i++ {
/*
TODO
if host, err = n.portMapper.MapRange(container, bnd.HostIP,
int(bnd.HostPort), int(bnd.HostPortEnd), false); err == nil {
break
}
*/
// There is no point in immediately retrying to map an
// explicitly chosen port.
if bnd.HostPort != 0 {
logrus.Warnf(
"Failed to allocate and map port %d-%d: %s",
bnd.HostPort, bnd.HostPortEnd, err)
break
}
logrus.Warnf("Failed to allocate and map port: %s, retry: %d",
err, i+1)
}
if err != nil {
return err
}
// Save the host port (regardless it was or not specified in the
// binding)
switch netAddr := host.(type) {
case *net.TCPAddr:
bnd.HostPort = uint16(host.(*net.TCPAddr).Port)
return nil
case *net.UDPAddr:
bnd.HostPort = uint16(host.(*net.UDPAddr).Port)
return nil
default:
// For completeness
return ErrUnsupportedAddressType(fmt.Sprintf("%T", netAddr))
}
}
func (n *bridgeNetwork) releasePorts(ep *bridgeEndpoint) error {
err := n.releasePortsInternal(ep.portMapping)
if err != nil {
return nil
}
// remove rules if there are any port mappings
if len(ep.portMapping) > 0 {
removePFRules(ep.id)
}
return nil
}
func (n *bridgeNetwork) releasePortsInternal(bindings []types.PortBinding) error {
var errorBuf bytes.Buffer
// Attempt to release all port bindings, do not stop on failure
for _, m := range bindings {
if err := n.releasePort(m); err != nil {
errorBuf.WriteString(
fmt.Sprintf(
"\ncould not release %v because of %v",
m, err))
}
}
if errorBuf.Len() != 0 {
return errors.New(errorBuf.String())
}
return nil
}
func (n *bridgeNetwork) releasePort(bnd types.PortBinding) error {
/*
// Construct the host side transport address
host, err := bnd.HostAddr()
if err != nil {
return err
}
// TODO
return n.portMapper.Unmap(host)
*/
return nil
}

View File

@ -1,12 +1,14 @@
package libnetwork
import (
"github.com/docker/libnetwork/drivers/freebsd/bridge"
"github.com/docker/libnetwork/drivers/null"
"github.com/docker/libnetwork/drivers/remote"
)
func getInitializers(experimental bool) []initializer {
return []initializer{
{bridge.Init, "bridge"},
{null.Init, "null"},
{remote.Init, "remote"},
}

View File

@ -1,7 +1,10 @@
package netutils
import (
"fmt"
"net"
"os/exec"
"strings"
"github.com/docker/libnetwork/types"
)
@ -19,5 +22,32 @@ func ElectInterfaceAddresses(name string) ([]*net.IPNet, []*net.IPNet, error) {
// FindAvailableNetwork returns a network from the passed list which does not
// overlap with existing interfaces in the system
func FindAvailableNetwork(list []*net.IPNet) (*net.IPNet, error) {
return nil, types.NotImplementedErrorf("not supported on freebsd")
for _, avail := range list {
cidr := strings.Split(avail.String(), "/")
ipitems := strings.Split(cidr[0], ".")
ip := ipitems[0] + "." +
ipitems[1] + "." +
ipitems[2] + "." + "1"
out, err := exec.Command("/sbin/route", "get", ip).Output()
if err != nil {
fmt.Println("failed to run route get command")
return nil, err
}
lines := strings.Split(string(out), "\n")
for _, l := range lines {
s := strings.Split(string(l), ":")
if len(s) == 2 {
k, v := s[0], strings.TrimSpace(s[1])
if k == "destination" {
if v == "default" {
return avail, nil
}
break
}
}
}
}
return nil, fmt.Errorf("no available network")
//types.NotImplementedErrorf("not supported on freebsd")
}