mirror of https://github.com/xSmurf/oz.git
parent
33484fd11b
commit
3b6169d8df
@ -0,0 +1,200 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import(
|
||||||
|
//Builtin
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
//"math"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
//"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
//Internal
|
||||||
|
|
||||||
|
//External
|
||||||
|
"github.com/milosgajdos83/tenus"
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func BridgeInit(log *logging.Logger) (*HostNetwork, error) {
|
||||||
|
htn := new(HostNetwork)
|
||||||
|
|
||||||
|
if os.Getpid() == 1 {
|
||||||
|
panic(errors.New("Cannot use netinit from child."))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the bridge interface by ifname
|
||||||
|
brL, err := net.InterfaceByName(ozDefaultInterfaceBridge)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Bridge not found, attempting to create a new one")
|
||||||
|
|
||||||
|
_, err = createNewBridge(log)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to create bridge %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the new interface
|
||||||
|
brL, _ = net.InterfaceByName(ozDefaultInterfaceBridge)
|
||||||
|
} else {
|
||||||
|
log.Info("Bridge already exists attempting to reuse it")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup the bridge ip addresses
|
||||||
|
addrs, _ := brL.Addrs()
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
return nil, errors.New("Host bridge does not have an IP address assigned")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to build the network config from the bridge's address
|
||||||
|
addrIndex := -1
|
||||||
|
for i, addr := range addrs {
|
||||||
|
bIP, brIP, _ := net.ParseCIDR(addr.String())
|
||||||
|
|
||||||
|
// Discard IPv6 (TODO...)
|
||||||
|
if bIP.To4() != nil {
|
||||||
|
addrIndex = i
|
||||||
|
|
||||||
|
bMask := []byte(brIP.Mask)
|
||||||
|
|
||||||
|
htn.netmask = net.IPv4(bMask[0], bMask[1], bMask[2], bMask[3])
|
||||||
|
htn.gateway = net.ParseIP(strings.Split(addr.String(), "/")[0])
|
||||||
|
htn.class = strings.Split(addr.String(), "/")[1]
|
||||||
|
htn.broadcast = net_getbroadcast(bIP, brIP.Mask)
|
||||||
|
|
||||||
|
htn.min = inet_aton(bIP)
|
||||||
|
htn.min++
|
||||||
|
|
||||||
|
htn.max = inet_aton(htn.broadcast)
|
||||||
|
htn.max--
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if addrIndex < 0 {
|
||||||
|
return nil, errors.New("Could not find IPv4 for bridge interface")
|
||||||
|
}
|
||||||
|
|
||||||
|
return htn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Create a new bridge on the host
|
||||||
|
// Assign it an unused range
|
||||||
|
// And bring the interface up
|
||||||
|
func createNewBridge(log *logging.Logger) (tenus.Bridger, error) {
|
||||||
|
if os.Getpid() == 1 {
|
||||||
|
panic(errors.New("Cannot use netinit from child."))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the bridge
|
||||||
|
br, err := tenus.NewBridgeWithName(ozDefaultInterfaceBridge)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup an empty ip range
|
||||||
|
brIp, brIpNet, err := findEmptyRange(log)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Could not find an ip range to assign to the bridge")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the bridge's address
|
||||||
|
if err := br.SetLinkIp(brIp, brIpNet); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bridge the interface up
|
||||||
|
if err = br.SetLinkUp(); err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to bring bridge '%+v' up: %+v", ozDefaultInterfaceBridge, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return br, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Look at all the assigned IP address and try to find an available range
|
||||||
|
// Returns a ip range in the CIDR form if found or an empty string
|
||||||
|
func findEmptyRange(log *logging.Logger) (net.IP, *net.IPNet, error) {
|
||||||
|
type localNet struct {
|
||||||
|
min uint64
|
||||||
|
max uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
localNets []localNet
|
||||||
|
availableRange string
|
||||||
|
)
|
||||||
|
|
||||||
|
// List all the available interfaces and their addresses
|
||||||
|
// and calulate their network's min and max values
|
||||||
|
ifs, _ := net.Interfaces()
|
||||||
|
for _, netif := range ifs {
|
||||||
|
// Disable loopback and our bridge
|
||||||
|
if netif.Name == ozDefaultInterfaceBridge ||
|
||||||
|
strings.HasPrefix(netif.Name, "lo") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through each address on the interface
|
||||||
|
addrs, _ := netif.Addrs()
|
||||||
|
for _, addr := range addrs {
|
||||||
|
bIP, brIP, _ := net.ParseCIDR(addr.String())
|
||||||
|
|
||||||
|
// Discard non IPv4 addresses
|
||||||
|
if bIP.To4() != nil {
|
||||||
|
min := inet_aton(brIP.IP)
|
||||||
|
min++
|
||||||
|
|
||||||
|
max := inet_aton(net_getbroadcast(bIP, brIP.Mask))
|
||||||
|
max--
|
||||||
|
|
||||||
|
localNets = append(localNets, localNet{min: min, max: max})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through the list of private network ranges and
|
||||||
|
// look for one in which we cannot find a local network
|
||||||
|
for _, ipRange := range privateNetworkRanges {
|
||||||
|
bIP, brIP, err := net.ParseCIDR(ipRange)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bMin := inet_aton(bIP)
|
||||||
|
bMax := inet_aton(net_getbroadcast(bIP, brIP.Mask))
|
||||||
|
|
||||||
|
alreadyUsed := false
|
||||||
|
for _, add := range localNets {
|
||||||
|
if add.min >= bMin && add.min < bMax &&
|
||||||
|
add.max > bMin && add.max <= bMax {
|
||||||
|
alreadyUsed = true
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the range is available, grab a small slice
|
||||||
|
if alreadyUsed == false {
|
||||||
|
bRange := bMax - bMin
|
||||||
|
if bRange > 0xFF {
|
||||||
|
bMin = bMax - 0xFE
|
||||||
|
}
|
||||||
|
|
||||||
|
availableRange = inet_ntoa(bMin).String() + "/24"
|
||||||
|
|
||||||
|
log.Info("Found available range: %+v", availableRange)
|
||||||
|
|
||||||
|
return net.ParseCIDR(availableRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil, errors.New("Could not find an available range")
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
//Builtin
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ozDefaultInterfaceBridgeBase = "oz"
|
||||||
|
ozDefaultInterfaceBridge = ozDefaultInterfaceBridgeBase + "0"
|
||||||
|
ozDefaultInterfacePrefix = "veth"
|
||||||
|
ozDefaultInterfaceInternal = "eth0"
|
||||||
|
ozMaxRandTries = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
type HostNetwork struct {
|
||||||
|
// Host bridge IP address
|
||||||
|
hostip net.IP
|
||||||
|
// Gateway ip (bridge ip)
|
||||||
|
gateway net.IP
|
||||||
|
// Bridge netmask
|
||||||
|
netmask net.IP
|
||||||
|
// Broadcast ip
|
||||||
|
broadcast net.IP
|
||||||
|
// IP class (ie: /24)
|
||||||
|
class string
|
||||||
|
// Minimum longip available ip
|
||||||
|
min uint64
|
||||||
|
// Maximum longip available ip
|
||||||
|
max uint64
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type SandboxNetwork struct {
|
||||||
|
// Name of the veth in the host
|
||||||
|
vethHost string
|
||||||
|
// Temporary name of the guest' veth in the host
|
||||||
|
vethGuest string
|
||||||
|
// Guest ip address
|
||||||
|
ip string
|
||||||
|
|
||||||
|
host *HostNetwork
|
||||||
|
}
|
||||||
|
|
||||||
|
var privateNetworkRanges []string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
privateNetworkRanges = []string{
|
||||||
|
// RFC1918 Private ranges
|
||||||
|
"10.0.0.0/8",
|
||||||
|
"172.16.0.0/12",
|
||||||
|
"192.168.0.0/16",
|
||||||
|
// Documentation / Testnet 2
|
||||||
|
"192.51.100.0/24",
|
||||||
|
// Documentation/ Testnet
|
||||||
|
"192.0.2.0/24",
|
||||||
|
// Inter-network communication
|
||||||
|
"192.18.0.0/15",
|
||||||
|
// Documentation / Testnet 3
|
||||||
|
"203.0.113.0/24",
|
||||||
|
// Carrier grade NAT
|
||||||
|
"100.64.0.0/10",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Convert longip to net.IP
|
||||||
|
func inet_ntoa(ipnr uint64) net.IP {
|
||||||
|
var bytes [4]byte
|
||||||
|
bytes[0] = byte(ipnr & 0xFF)
|
||||||
|
bytes[1] = byte((ipnr >> 8) & 0xFF)
|
||||||
|
bytes[2] = byte((ipnr >> 16) & 0xFF)
|
||||||
|
bytes[3] = byte((ipnr >> 24) & 0xFF)
|
||||||
|
|
||||||
|
return net.IPv4(bytes[3], bytes[2], bytes[1], bytes[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert net.IP to longip
|
||||||
|
func inet_aton(ipnr net.IP) uint64 {
|
||||||
|
bits := strings.Split(ipnr.String(), ".")
|
||||||
|
|
||||||
|
b0, _ := strconv.Atoi(bits[0])
|
||||||
|
b1, _ := strconv.Atoi(bits[1])
|
||||||
|
b2, _ := strconv.Atoi(bits[2])
|
||||||
|
b3, _ := strconv.Atoi(bits[3])
|
||||||
|
|
||||||
|
var sum uint64
|
||||||
|
|
||||||
|
sum += uint64(b0) << 24
|
||||||
|
sum += uint64(b1) << 16
|
||||||
|
sum += uint64(b2) << 8
|
||||||
|
sum += uint64(b3)
|
||||||
|
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
func net_getbroadcast(bIP net.IP, ipMask net.IPMask) net.IP {
|
||||||
|
bMask := []byte(ipMask)
|
||||||
|
byteIP := bIP.To4()
|
||||||
|
|
||||||
|
return net.IPv4(
|
||||||
|
byteIP[0]|(bMask[0]^0xFF),
|
||||||
|
byteIP[1]|(bMask[1]^0xFF),
|
||||||
|
byteIP[2]|(bMask[2]^0xFF),
|
||||||
|
byteIP[3]|(bMask[3]^0xFF))
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,216 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
//Builtin
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
//"math"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
//External
|
||||||
|
"github.com/j-keck/arping"
|
||||||
|
"github.com/milosgajdos83/tenus"
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NetInit(log *logging.Logger, htn *HostNetwork, childPid int) (*SandboxNetwork, error) {
|
||||||
|
if os.Getpid() == 1 {
|
||||||
|
panic(errors.New("Cannot use netSetup from child."))
|
||||||
|
}
|
||||||
|
|
||||||
|
stn := new(SandboxNetwork)
|
||||||
|
|
||||||
|
stn.vethHost = tenus.MakeNetInterfaceName(ozDefaultInterfacePrefix)
|
||||||
|
stn.vethGuest = stn.vethHost + "1"
|
||||||
|
|
||||||
|
// Seed random number generator (poorly but we're not doing crypto)
|
||||||
|
rand.Seed(time.Now().Unix() ^ int64((os.Getpid() + childPid)))
|
||||||
|
|
||||||
|
log.Info("Configuring host veth pair: %s", stn.vethHost)
|
||||||
|
|
||||||
|
// Fetch the bridge from the ifname
|
||||||
|
br, err := tenus.BridgeFromName(ozDefaultInterfaceBridge)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to attach to bridge interface %, %s.", ozDefaultInterfaceBridge, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the veth pair
|
||||||
|
veth, err := tenus.NewVethPairWithOptions(stn.vethHost, tenus.VethOptions{PeerName: stn.vethGuest})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to create veth pair %s, %s.", stn.vethHost, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the newly created hostside veth
|
||||||
|
vethIf, err := net.InterfaceByName(stn.vethHost)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to fetch veth pair %s, %s.", stn.vethHost, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the host side veth to the bridge
|
||||||
|
if err = br.AddSlaveIfc(vethIf); err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to add veth pair %s to bridge, %s.", stn.vethHost, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring the host side veth interface up
|
||||||
|
if err = veth.SetLinkUp(); err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to bring veth pair %s up, %s.", stn.vethHost, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign the veth path to the namespace
|
||||||
|
pid := childPid
|
||||||
|
if err := veth.SetPeerLinkNsPid(pid); err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to add veth pair %s to namespace, %s.", stn.vethHost, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate a new IP address
|
||||||
|
stn.ip = getFreshIP(htn.min, htn.max, log)
|
||||||
|
if stn.ip == "" {
|
||||||
|
return nil, errors.New("Unable to acquire random IP")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the ip/class into the the appropriate formats
|
||||||
|
vethGuestIp, vethGuestIpNet, err := net.ParseCIDR(stn.ip + "/" + htn.class)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to parse ip %s, %s.", stn.ip, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set interface address in the namespace
|
||||||
|
if err := veth.SetPeerLinkNetInNs(pid, vethGuestIp, vethGuestIpNet, nil); err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to parse ip link in namespace, %s.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stn, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the networking inside the child
|
||||||
|
// Namely setup the loopback interface
|
||||||
|
// and the veth interface if requested
|
||||||
|
func NetSetup(stn *SandboxNetwork, htn *HostNetwork) error {
|
||||||
|
if os.Getpid() != 1 {
|
||||||
|
panic(errors.New("Cannot use netChildSetup from child."))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring loopback interface up
|
||||||
|
lo, err := tenus.NewLinkFrom("lo")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to fetch loopback interface, %s.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring the link up
|
||||||
|
err = lo.SetLinkUp()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to bring loopback interface up, %s.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If required configure veth
|
||||||
|
if stn.vethGuest != "" {
|
||||||
|
ifc, err := tenus.NewLinkFrom(stn.vethGuest)
|
||||||
|
if err == nil {
|
||||||
|
// Bring the link down to prepare for renaming
|
||||||
|
if err = ifc.SetLinkDown(); err != nil {
|
||||||
|
return fmt.Errorf("Unable to bring interface %s down, %s.", stn.vethGuest, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename the interface to a standard eth0 (not really a necessity)
|
||||||
|
if err = tenus.RenameInterface(stn.vethGuest, ozDefaultInterfaceInternal); err != nil {
|
||||||
|
return fmt.Errorf("Unable to rename interface %s, %s.", stn.vethGuest, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refetch the interface again as it has changed
|
||||||
|
ifc, err = tenus.NewLinkFrom(ozDefaultInterfaceInternal)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to fetch interface %s, %s.", ozDefaultInterfaceInternal, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring the link back up
|
||||||
|
if err = ifc.SetLinkUp(); err != nil {
|
||||||
|
return fmt.Errorf("Unable to bring interface %s up, %s.", ozDefaultInterfaceInternal, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the link's default gateway
|
||||||
|
if err = ifc.SetLinkDefaultGw(&htn.gateway); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set default route %s.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Unable to fetch inteface %s, %s.", stn.vethGuest, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find an unassigned IP address
|
||||||
|
// Do this by first trying two random IPs or, if that fails, sequentially
|
||||||
|
func getFreshIP(min, max uint64, log *logging.Logger) string {
|
||||||
|
var newIP string
|
||||||
|
|
||||||
|
for i := 0; i < ozMaxRandTries; i++ {
|
||||||
|
newIP = getRandIP(min, max)
|
||||||
|
if newIP != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if newIP == "" {
|
||||||
|
log.Notice("Random IP lookup failed %d times, reverting to sequential select", ozMaxRandTries)
|
||||||
|
|
||||||
|
newIP = getScanIP(min, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newIP
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a random ip and arping it to see if it is available
|
||||||
|
// Returns the ip on success or an ip string is the ip is already taken
|
||||||
|
func getRandIP(min, max uint64) string {
|
||||||
|
if min > max {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
dstIP := inet_ntoa(uint64(rand.Int63n(int64(max-min))) + min)
|
||||||
|
|
||||||
|
arping.SetTimeout(time.Millisecond * 150)
|
||||||
|
|
||||||
|
_, _, err := arping.PingOverIfaceByName(dstIP, ozDefaultInterfaceBridge)
|
||||||
|
|
||||||
|
if err == arping.ErrTimeout {
|
||||||
|
return dstIP.String()
|
||||||
|
} else if err != nil {
|
||||||
|
return dstIP.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through all possible ips between min and max
|
||||||
|
// and arping each one until a free one is found
|
||||||
|
func getScanIP(min, max uint64) string {
|
||||||
|
if min > max {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := min; i < max; i++ {
|
||||||
|
dstIP := inet_ntoa(i)
|
||||||
|
|
||||||
|
arping.SetTimeout(time.Millisecond * 150)
|
||||||
|
|
||||||
|
_, _, err := arping.PingOverIfaceByName(dstIP, ozDefaultInterfaceBridge)
|
||||||
|
|
||||||
|
if err == arping.ErrTimeout {
|
||||||
|
return dstIP.String()
|
||||||
|
} else if err != nil {
|
||||||
|
return dstIP.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in new issue