Merge pull request #124 from crawford/networkd
feat(networkd): Adding support for debian-interface-to-networkd conversion
This commit is contained in:
commit
38c8fda0d1
@ -1,23 +1,21 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/datasource"
|
"github.com/coreos/coreos-cloudinit/datasource"
|
||||||
"github.com/coreos/coreos-cloudinit/initialize"
|
"github.com/coreos/coreos-cloudinit/initialize"
|
||||||
|
"github.com/coreos/coreos-cloudinit/network"
|
||||||
"github.com/coreos/coreos-cloudinit/system"
|
"github.com/coreos/coreos-cloudinit/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
const version = "0.7.3+git"
|
const version = "0.7.3+git"
|
||||||
|
|
||||||
func init() {
|
|
||||||
//Removes timestamp since it is displayed already during booting
|
|
||||||
log.SetFlags(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var printVersion bool
|
var printVersion bool
|
||||||
flag.BoolVar(&printVersion, "version", false, "Print the version and exit")
|
flag.BoolVar(&printVersion, "version", false, "Print the version and exit")
|
||||||
@ -28,12 +26,18 @@ func main() {
|
|||||||
var file string
|
var file string
|
||||||
flag.StringVar(&file, "from-file", "", "Read user-data from provided file")
|
flag.StringVar(&file, "from-file", "", "Read user-data from provided file")
|
||||||
|
|
||||||
|
var configdrive string
|
||||||
|
flag.StringVar(&configdrive, "from-configdrive", "", "Read user-data from provided cloud-drive directory")
|
||||||
|
|
||||||
var url string
|
var url string
|
||||||
flag.StringVar(&url, "from-url", "", "Download user-data from provided url")
|
flag.StringVar(&url, "from-url", "", "Download user-data from provided url")
|
||||||
|
|
||||||
var useProcCmdline bool
|
var useProcCmdline bool
|
||||||
flag.BoolVar(&useProcCmdline, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", datasource.ProcCmdlineLocation, datasource.ProcCmdlineCloudConfigFlag))
|
flag.BoolVar(&useProcCmdline, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", datasource.ProcCmdlineLocation, datasource.ProcCmdlineCloudConfigFlag))
|
||||||
|
|
||||||
|
var convertNetconf string
|
||||||
|
flag.StringVar(&convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files (requires the -from-configdrive flag)")
|
||||||
|
|
||||||
var workspace string
|
var workspace string
|
||||||
flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
|
flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
|
||||||
|
|
||||||
@ -52,17 +56,32 @@ func main() {
|
|||||||
ds = datasource.NewLocalFile(file)
|
ds = datasource.NewLocalFile(file)
|
||||||
} else if url != "" {
|
} else if url != "" {
|
||||||
ds = datasource.NewMetadataService(url)
|
ds = datasource.NewMetadataService(url)
|
||||||
|
} else if configdrive != "" {
|
||||||
|
ds = datasource.NewConfigDrive(configdrive)
|
||||||
} else if useProcCmdline {
|
} else if useProcCmdline {
|
||||||
ds = datasource.NewProcCmdline()
|
ds = datasource.NewProcCmdline()
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Provide one of --from-file, --from-url or --from-proc-cmdline")
|
fmt.Println("Provide one of --from-file, --from-configdrive, --from-url or --from-proc-cmdline")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Fetching user-data from datasource of type %q", ds.Type())
|
if convertNetconf != "" && configdrive == "" {
|
||||||
|
fmt.Println("-convert-netconf flag requires -from-configdrive")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch convertNetconf {
|
||||||
|
case "":
|
||||||
|
case "debian":
|
||||||
|
default:
|
||||||
|
fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian'\n", convertNetconf)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Fetching user-data from datasource of type %q\n", ds.Type())
|
||||||
userdataBytes, err := ds.Fetch()
|
userdataBytes, err := ds.Fetch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed fetching user-data from datasource: %v", err)
|
fmt.Printf("Failed fetching user-data from datasource: %v\n", err)
|
||||||
if ignoreFailure {
|
if ignoreFailure {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
} else {
|
} else {
|
||||||
@ -70,29 +89,41 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(userdataBytes) == 0 {
|
env := initialize.NewEnvironment("/", workspace)
|
||||||
log.Printf("No user data to handle, exiting.")
|
if len(userdataBytes) > 0 {
|
||||||
os.Exit(0)
|
if err := processUserdata(string(userdataBytes), env); err != nil {
|
||||||
|
fmt.Printf("Failed resolving user-data: %v\n", err)
|
||||||
|
if !ignoreFailure {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("No user data to handle.")
|
||||||
}
|
}
|
||||||
|
|
||||||
env := initialize.NewEnvironment("/", workspace)
|
if convertNetconf != "" {
|
||||||
|
if err := processNetconf(convertNetconf, configdrive); err != nil {
|
||||||
|
fmt.Printf("Failed to process network config: %v\n", err)
|
||||||
|
if !ignoreFailure {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
userdata := string(userdataBytes)
|
func processUserdata(userdata string, env *initialize.Environment) error {
|
||||||
userdata = env.Apply(userdata)
|
userdata = env.Apply(userdata)
|
||||||
|
|
||||||
parsed, err := initialize.ParseUserData(userdata)
|
parsed, err := initialize.ParseUserData(userdata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed parsing user-data: %v", err)
|
fmt.Printf("Failed parsing user-data: %v\n", err)
|
||||||
if ignoreFailure {
|
return err
|
||||||
os.Exit(0)
|
|
||||||
} else {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = initialize.PrepWorkspace(env.Workspace())
|
err = initialize.PrepWorkspace(env.Workspace())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed preparing workspace: %v", err)
|
fmt.Printf("Failed preparing workspace: %v\n", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := parsed.(type) {
|
switch t := parsed.(type) {
|
||||||
@ -104,11 +135,54 @@ func main() {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
var name string
|
var name string
|
||||||
name, err = system.ExecuteScript(path)
|
name, err = system.ExecuteScript(path)
|
||||||
initialize.PersistUnitNameInWorkspace(name, workspace)
|
initialize.PersistUnitNameInWorkspace(name, env.Workspace())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
return err
|
||||||
log.Fatalf("Failed resolving user-data: %v", err)
|
}
|
||||||
}
|
|
||||||
|
func processNetconf(convertNetconf, configdrive string) error {
|
||||||
|
openstackRoot := path.Join(configdrive, "openstack")
|
||||||
|
metadataFilename := path.Join(openstackRoot, "latest", "meta_data.json")
|
||||||
|
metadataBytes, err := ioutil.ReadFile(metadataFilename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata struct {
|
||||||
|
NetworkConfig struct {
|
||||||
|
ContentPath string `json:"content_path"`
|
||||||
|
} `json:"network_config"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configPath := metadata.NetworkConfig.ContentPath
|
||||||
|
if configPath == "" {
|
||||||
|
fmt.Printf("No network config specified in %q.\n", metadataFilename)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
netconfBytes, err := ioutil.ReadFile(path.Join(openstackRoot, configPath))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var interfaces []network.InterfaceGenerator
|
||||||
|
switch convertNetconf {
|
||||||
|
case "debian":
|
||||||
|
interfaces, err = network.ProcessDebianNetconf(string(netconfBytes))
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unsupported network config format %q", convertNetconf)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := system.WriteNetworkdConfigs(interfaces); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return system.RestartNetwork(interfaces)
|
||||||
}
|
}
|
||||||
|
27
datasource/configdrive.go
Normal file
27
datasource/configdrive.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package datasource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
type configDrive struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfigDrive(path string) *configDrive {
|
||||||
|
return &configDrive{path}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *configDrive) Fetch() ([]byte, error) {
|
||||||
|
data, err := ioutil.ReadFile(path.Join(self.path, "openstack", "latest", "user_data"))
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *configDrive) Type() string {
|
||||||
|
return "cloud-drive"
|
||||||
|
}
|
193
network/interface.go
Normal file
193
network/interface.go
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InterfaceGenerator interface {
|
||||||
|
Name() string
|
||||||
|
Netdev() string
|
||||||
|
Link() string
|
||||||
|
Network() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type logicalInterface struct {
|
||||||
|
name string
|
||||||
|
config configMethod
|
||||||
|
children []InterfaceGenerator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *logicalInterface) Network() string {
|
||||||
|
config := fmt.Sprintf("[Match]\nName=%s\n\n[Network]\n", i.name)
|
||||||
|
|
||||||
|
for _, child := range i.children {
|
||||||
|
switch iface := child.(type) {
|
||||||
|
case *vlanInterface:
|
||||||
|
config += fmt.Sprintf("VLAN=%s\n", iface.name)
|
||||||
|
case *bondInterface:
|
||||||
|
config += fmt.Sprintf("Bond=%s\n", iface.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch conf := i.config.(type) {
|
||||||
|
case configMethodStatic:
|
||||||
|
for _, nameserver := range conf.nameservers {
|
||||||
|
config += fmt.Sprintf("DNS=%s\n", nameserver)
|
||||||
|
}
|
||||||
|
if conf.address.IP != nil {
|
||||||
|
config += fmt.Sprintf("\n[Address]\nAddress=%s\n", conf.address.String())
|
||||||
|
}
|
||||||
|
for _, route := range conf.routes {
|
||||||
|
config += fmt.Sprintf("\n[Route]\nDestination=%s\nGateway=%s\n", route.destination.String(), route.gateway)
|
||||||
|
}
|
||||||
|
case configMethodDHCP:
|
||||||
|
config += "DHCP=true\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
type physicalInterface struct {
|
||||||
|
logicalInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *physicalInterface) Name() string {
|
||||||
|
return p.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *physicalInterface) Netdev() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *physicalInterface) Link() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type bondInterface struct {
|
||||||
|
logicalInterface
|
||||||
|
slaves []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bondInterface) Name() string {
|
||||||
|
return b.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bondInterface) Netdev() string {
|
||||||
|
return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bondInterface) Link() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type vlanInterface struct {
|
||||||
|
logicalInterface
|
||||||
|
id int
|
||||||
|
rawDevice string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *vlanInterface) Name() string {
|
||||||
|
return v.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *vlanInterface) Netdev() string {
|
||||||
|
return fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n\n[VLAN]\nId=%d\n", v.name, v.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *vlanInterface) Link() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator {
|
||||||
|
bondStanzas := make(map[string]*stanzaInterface)
|
||||||
|
physicalStanzas := make(map[string]*stanzaInterface)
|
||||||
|
vlanStanzas := make(map[string]*stanzaInterface)
|
||||||
|
for _, iface := range stanzas {
|
||||||
|
switch iface.kind {
|
||||||
|
case interfaceBond:
|
||||||
|
bondStanzas[iface.name] = iface
|
||||||
|
case interfacePhysical:
|
||||||
|
physicalStanzas[iface.name] = iface
|
||||||
|
case interfaceVLAN:
|
||||||
|
vlanStanzas[iface.name] = iface
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
physicals := make(map[string]*physicalInterface)
|
||||||
|
for _, p := range physicalStanzas {
|
||||||
|
if _, ok := p.configMethod.(configMethodLoopback); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
physicals[p.name] = &physicalInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: p.name,
|
||||||
|
config: p.configMethod,
|
||||||
|
children: []InterfaceGenerator{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bonds := make(map[string]*bondInterface)
|
||||||
|
for _, b := range bondStanzas {
|
||||||
|
bonds[b.name] = &bondInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: b.name,
|
||||||
|
config: b.configMethod,
|
||||||
|
children: []InterfaceGenerator{},
|
||||||
|
},
|
||||||
|
b.options["slaves"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vlans := make(map[string]*vlanInterface)
|
||||||
|
for _, v := range vlanStanzas {
|
||||||
|
var rawDevice string
|
||||||
|
id, _ := strconv.Atoi(v.options["id"][0])
|
||||||
|
if device := v.options["raw_device"]; len(device) == 1 {
|
||||||
|
rawDevice = device[0]
|
||||||
|
}
|
||||||
|
vlans[v.name] = &vlanInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: v.name,
|
||||||
|
config: v.configMethod,
|
||||||
|
children: []InterfaceGenerator{},
|
||||||
|
},
|
||||||
|
id,
|
||||||
|
rawDevice,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, vlan := range vlans {
|
||||||
|
if physical, ok := physicals[vlan.rawDevice]; ok {
|
||||||
|
physical.children = append(physical.children, vlan)
|
||||||
|
}
|
||||||
|
if bond, ok := bonds[vlan.rawDevice]; ok {
|
||||||
|
bond.children = append(bond.children, vlan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bond := range bonds {
|
||||||
|
for _, slave := range bond.slaves {
|
||||||
|
if physical, ok := physicals[slave]; ok {
|
||||||
|
physical.children = append(physical.children, bond)
|
||||||
|
}
|
||||||
|
if pBond, ok := bonds[slave]; ok {
|
||||||
|
pBond.children = append(pBond.children, bond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interfaces := make([]InterfaceGenerator, 0, len(physicals)+len(bonds)+len(vlans))
|
||||||
|
for _, physical := range physicals {
|
||||||
|
interfaces = append(interfaces, physical)
|
||||||
|
}
|
||||||
|
for _, bond := range bonds {
|
||||||
|
interfaces = append(interfaces, bond)
|
||||||
|
}
|
||||||
|
for _, vlan := range vlans {
|
||||||
|
interfaces = append(interfaces, vlan)
|
||||||
|
}
|
||||||
|
|
||||||
|
return interfaces
|
||||||
|
}
|
321
network/interface_test.go
Normal file
321
network/interface_test.go
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPhysicalInterfaceName(t *testing.T) {
|
||||||
|
p := physicalInterface{logicalInterface{name: "testname"}}
|
||||||
|
if p.Name() != "testname" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPhysicalInterfaceNetdev(t *testing.T) {
|
||||||
|
p := physicalInterface{}
|
||||||
|
if p.Netdev() != "" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPhysicalInterfaceLink(t *testing.T) {
|
||||||
|
p := physicalInterface{}
|
||||||
|
if p.Link() != "" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPhysicalInterfaceNetwork(t *testing.T) {
|
||||||
|
p := physicalInterface{logicalInterface{
|
||||||
|
name: "testname",
|
||||||
|
children: []InterfaceGenerator{
|
||||||
|
&bondInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "testbond1",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
&vlanInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "testvlan1",
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
&vlanInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "testvlan2",
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
network := `[Match]
|
||||||
|
Name=testname
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
Bond=testbond1
|
||||||
|
VLAN=testvlan1
|
||||||
|
VLAN=testvlan2
|
||||||
|
`
|
||||||
|
if p.Network() != network {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBondInterfaceName(t *testing.T) {
|
||||||
|
b := bondInterface{logicalInterface{name: "testname"}, nil}
|
||||||
|
if b.Name() != "testname" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBondInterfaceNetdev(t *testing.T) {
|
||||||
|
b := bondInterface{logicalInterface{name: "testname"}, nil}
|
||||||
|
netdev := `[NetDev]
|
||||||
|
Kind=bond
|
||||||
|
Name=testname
|
||||||
|
`
|
||||||
|
if b.Netdev() != netdev {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBondInterfaceLink(t *testing.T) {
|
||||||
|
b := bondInterface{}
|
||||||
|
if b.Link() != "" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBondInterfaceNetwork(t *testing.T) {
|
||||||
|
b := bondInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "testname",
|
||||||
|
config: configMethodDHCP{},
|
||||||
|
children: []InterfaceGenerator{
|
||||||
|
&bondInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "testbond1",
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
&vlanInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "testvlan1",
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
&vlanInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "testvlan2",
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
}
|
||||||
|
network := `[Match]
|
||||||
|
Name=testname
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
Bond=testbond1
|
||||||
|
VLAN=testvlan1
|
||||||
|
VLAN=testvlan2
|
||||||
|
DHCP=true
|
||||||
|
`
|
||||||
|
if b.Network() != network {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVLANInterfaceName(t *testing.T) {
|
||||||
|
v := vlanInterface{logicalInterface{name: "testname"}, 1, ""}
|
||||||
|
if v.Name() != "testname" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVLANInterfaceNetdev(t *testing.T) {
|
||||||
|
v := vlanInterface{logicalInterface{name: "testname"}, 1, ""}
|
||||||
|
netdev := `[NetDev]
|
||||||
|
Kind=vlan
|
||||||
|
Name=testname
|
||||||
|
|
||||||
|
[VLAN]
|
||||||
|
Id=1
|
||||||
|
`
|
||||||
|
if v.Netdev() != netdev {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVLANInterfaceLink(t *testing.T) {
|
||||||
|
v := vlanInterface{}
|
||||||
|
if v.Link() != "" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVLANInterfaceNetwork(t *testing.T) {
|
||||||
|
v := vlanInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "testname",
|
||||||
|
config: configMethodStatic{
|
||||||
|
address: net.IPNet{
|
||||||
|
IP: []byte{192, 168, 1, 100},
|
||||||
|
Mask: []byte{255, 255, 255, 0},
|
||||||
|
},
|
||||||
|
nameservers: []net.IP{
|
||||||
|
[]byte{8, 8, 8, 8},
|
||||||
|
},
|
||||||
|
routes: []route{
|
||||||
|
route{
|
||||||
|
destination: net.IPNet{
|
||||||
|
IP: []byte{0, 0, 0, 0},
|
||||||
|
Mask: []byte{0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
gateway: []byte{1, 2, 3, 4},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
network := `[Match]
|
||||||
|
Name=testname
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
DNS=8.8.8.8
|
||||||
|
|
||||||
|
[Address]
|
||||||
|
Address=192.168.1.100/24
|
||||||
|
|
||||||
|
[Route]
|
||||||
|
Destination=0.0.0.0/0
|
||||||
|
Gateway=1.2.3.4
|
||||||
|
`
|
||||||
|
if v.Network() != network {
|
||||||
|
t.Log(v.Network())
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildInterfacesLo(t *testing.T) {
|
||||||
|
stanzas := []*stanzaInterface{
|
||||||
|
&stanzaInterface{
|
||||||
|
name: "lo",
|
||||||
|
kind: interfacePhysical,
|
||||||
|
auto: false,
|
||||||
|
configMethod: configMethodLoopback{},
|
||||||
|
options: map[string][]string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
interfaces := buildInterfaces(stanzas)
|
||||||
|
if len(interfaces) != 0 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildInterfaces(t *testing.T) {
|
||||||
|
stanzas := []*stanzaInterface{
|
||||||
|
&stanzaInterface{
|
||||||
|
name: "eth0",
|
||||||
|
kind: interfacePhysical,
|
||||||
|
auto: false,
|
||||||
|
configMethod: configMethodManual{},
|
||||||
|
options: map[string][]string{},
|
||||||
|
},
|
||||||
|
&stanzaInterface{
|
||||||
|
name: "bond0",
|
||||||
|
kind: interfaceBond,
|
||||||
|
auto: false,
|
||||||
|
configMethod: configMethodManual{},
|
||||||
|
options: map[string][]string{
|
||||||
|
"slaves": []string{"eth0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&stanzaInterface{
|
||||||
|
name: "bond1",
|
||||||
|
kind: interfaceBond,
|
||||||
|
auto: false,
|
||||||
|
configMethod: configMethodManual{},
|
||||||
|
options: map[string][]string{
|
||||||
|
"slaves": []string{"bond0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&stanzaInterface{
|
||||||
|
name: "vlan0",
|
||||||
|
kind: interfaceVLAN,
|
||||||
|
auto: false,
|
||||||
|
configMethod: configMethodManual{},
|
||||||
|
options: map[string][]string{
|
||||||
|
"id": []string{"0"},
|
||||||
|
"raw_device": []string{"eth0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&stanzaInterface{
|
||||||
|
name: "vlan1",
|
||||||
|
kind: interfaceVLAN,
|
||||||
|
auto: false,
|
||||||
|
configMethod: configMethodManual{},
|
||||||
|
options: map[string][]string{
|
||||||
|
"id": []string{"1"},
|
||||||
|
"raw_device": []string{"bond0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
interfaces := buildInterfaces(stanzas)
|
||||||
|
vlan1 := &vlanInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "vlan1",
|
||||||
|
config: configMethodManual{},
|
||||||
|
children: []InterfaceGenerator{},
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
"bond0",
|
||||||
|
}
|
||||||
|
vlan0 := &vlanInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "vlan0",
|
||||||
|
config: configMethodManual{},
|
||||||
|
children: []InterfaceGenerator{},
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
"eth0",
|
||||||
|
}
|
||||||
|
bond1 := &bondInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "bond1",
|
||||||
|
config: configMethodManual{},
|
||||||
|
children: []InterfaceGenerator{},
|
||||||
|
},
|
||||||
|
[]string{"bond0"},
|
||||||
|
}
|
||||||
|
bond0 := &bondInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "bond0",
|
||||||
|
config: configMethodManual{},
|
||||||
|
children: []InterfaceGenerator{vlan1, bond1},
|
||||||
|
},
|
||||||
|
[]string{"eth0"},
|
||||||
|
}
|
||||||
|
eth0 := &physicalInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "eth0",
|
||||||
|
config: configMethodManual{},
|
||||||
|
children: []InterfaceGenerator{vlan0, bond0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
expect := []InterfaceGenerator{eth0, bond0, bond1, vlan0, vlan1}
|
||||||
|
if !reflect.DeepEqual(interfaces, expect) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
45
network/network.go
Normal file
45
network/network.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ProcessDebianNetconf(config string) ([]InterfaceGenerator, error) {
|
||||||
|
lines := formatConfig(config)
|
||||||
|
stanzas, err := parseStanzas(lines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
interfaces := make([]*stanzaInterface, 0, len(stanzas))
|
||||||
|
for _, stanza := range stanzas {
|
||||||
|
switch s := stanza.(type) {
|
||||||
|
case *stanzaInterface:
|
||||||
|
interfaces = append(interfaces, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildInterfaces(interfaces), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatConfig(config string) []string {
|
||||||
|
lines := []string{}
|
||||||
|
config = strings.Replace(config, "\\\n", "", -1)
|
||||||
|
for config != "" {
|
||||||
|
split := strings.SplitN(config, "\n", 2)
|
||||||
|
line := strings.TrimSpace(split[0])
|
||||||
|
|
||||||
|
if len(split) == 2 {
|
||||||
|
config = split[1]
|
||||||
|
} else {
|
||||||
|
config = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(line, "#") || line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, line)
|
||||||
|
}
|
||||||
|
return lines
|
||||||
|
}
|
42
network/network_test.go
Normal file
42
network/network_test.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormatConfigs(t *testing.T) {
|
||||||
|
for in, n := range map[string]int{
|
||||||
|
"": 0,
|
||||||
|
"line1\\\nis long": 1,
|
||||||
|
"#comment": 0,
|
||||||
|
"#comment\\\ncomment": 0,
|
||||||
|
" #comment \\\n comment\nline 1\nline 2\\\n is long": 2,
|
||||||
|
} {
|
||||||
|
lines := formatConfig(in)
|
||||||
|
if len(lines) != n {
|
||||||
|
t.Fatalf("bad number of lines for config %q: got %d, want %d", in, len(lines), n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessDebianNetconf(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
in string
|
||||||
|
fail bool
|
||||||
|
n int
|
||||||
|
}{
|
||||||
|
{"", false, 0},
|
||||||
|
{"iface", true, -1},
|
||||||
|
{"auto eth1\nauto eth2", false, 0},
|
||||||
|
{"iface eth1 inet manual", false, 1},
|
||||||
|
} {
|
||||||
|
interfaces, err := ProcessDebianNetconf(tt.in)
|
||||||
|
failed := err != nil
|
||||||
|
if tt.fail != failed {
|
||||||
|
t.Fatalf("bad failure state for %q: got %b, want %b", failed, tt.fail)
|
||||||
|
}
|
||||||
|
if tt.n != -1 && tt.n != len(interfaces) {
|
||||||
|
t.Fatalf("bad number of interfaces for %q: got %d, want %q", tt.in, len(interfaces), tt.n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
295
network/stanza.go
Normal file
295
network/stanza.go
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stanza interface{}
|
||||||
|
|
||||||
|
type stanzaAuto struct {
|
||||||
|
interfaces []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type stanzaInterface struct {
|
||||||
|
name string
|
||||||
|
kind interfaceKind
|
||||||
|
auto bool
|
||||||
|
configMethod configMethod
|
||||||
|
options map[string][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type interfaceKind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
interfaceBond = interfaceKind(iota)
|
||||||
|
interfacePhysical
|
||||||
|
interfaceVLAN
|
||||||
|
)
|
||||||
|
|
||||||
|
type route struct {
|
||||||
|
destination net.IPNet
|
||||||
|
gateway net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
type configMethod interface{}
|
||||||
|
|
||||||
|
type configMethodStatic struct {
|
||||||
|
address net.IPNet
|
||||||
|
nameservers []net.IP
|
||||||
|
routes []route
|
||||||
|
}
|
||||||
|
|
||||||
|
type configMethodLoopback struct{}
|
||||||
|
|
||||||
|
type configMethodManual struct{}
|
||||||
|
|
||||||
|
type configMethodDHCP struct{}
|
||||||
|
|
||||||
|
func parseStanzas(lines []string) (stanzas []stanza, err error) {
|
||||||
|
rawStanzas, err := splitStanzas(lines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stanzas = make([]stanza, 0, len(rawStanzas))
|
||||||
|
for _, rawStanza := range rawStanzas {
|
||||||
|
if stanza, err := parseStanza(rawStanza); err == nil {
|
||||||
|
stanzas = append(stanzas, stanza)
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
autos := make([]string, 0)
|
||||||
|
interfaceMap := make(map[string]*stanzaInterface)
|
||||||
|
for _, stanza := range stanzas {
|
||||||
|
switch c := stanza.(type) {
|
||||||
|
case *stanzaAuto:
|
||||||
|
autos = append(autos, c.interfaces...)
|
||||||
|
case *stanzaInterface:
|
||||||
|
interfaceMap[c.name] = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the auto attribute
|
||||||
|
for _, auto := range autos {
|
||||||
|
if iface, ok := interfaceMap[auto]; ok {
|
||||||
|
iface.auto = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stanzas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitStanzas(lines []string) ([][]string, error) {
|
||||||
|
var curStanza []string
|
||||||
|
stanzas := make([][]string, 0)
|
||||||
|
for _, line := range lines {
|
||||||
|
if isStanzaStart(line) {
|
||||||
|
if curStanza != nil {
|
||||||
|
stanzas = append(stanzas, curStanza)
|
||||||
|
}
|
||||||
|
curStanza = []string{line}
|
||||||
|
} else if curStanza != nil {
|
||||||
|
curStanza = append(curStanza, line)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("missing stanza start '%s'", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if curStanza != nil {
|
||||||
|
stanzas = append(stanzas, curStanza)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stanzas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isStanzaStart(line string) bool {
|
||||||
|
switch strings.Split(line, " ")[0] {
|
||||||
|
case "auto":
|
||||||
|
fallthrough
|
||||||
|
case "iface":
|
||||||
|
fallthrough
|
||||||
|
case "mapping":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(line, "allow-") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseStanza(rawStanza []string) (stanza, error) {
|
||||||
|
if len(rawStanza) == 0 {
|
||||||
|
panic("empty stanza")
|
||||||
|
}
|
||||||
|
tokens := strings.Fields(rawStanza[0])
|
||||||
|
if len(tokens) < 2 {
|
||||||
|
return nil, fmt.Errorf("malformed stanza start %q", rawStanza[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
kind := tokens[0]
|
||||||
|
attributes := tokens[1:]
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case "auto":
|
||||||
|
return parseAutoStanza(attributes, rawStanza[1:])
|
||||||
|
case "iface":
|
||||||
|
return parseInterfaceStanza(attributes, rawStanza[1:])
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown stanza '%s'", kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAutoStanza(attributes []string, options []string) (*stanzaAuto, error) {
|
||||||
|
return &stanzaAuto{interfaces: attributes}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterface, error) {
|
||||||
|
if len(attributes) != 3 {
|
||||||
|
return nil, fmt.Errorf("incorrect number of attributes")
|
||||||
|
}
|
||||||
|
|
||||||
|
iface := attributes[0]
|
||||||
|
confMethod := attributes[2]
|
||||||
|
|
||||||
|
optionMap := make(map[string][]string, 0)
|
||||||
|
for _, option := range options {
|
||||||
|
if strings.HasPrefix(option, "post-up") {
|
||||||
|
tokens := strings.SplitAfterN(option, " ", 2)
|
||||||
|
if len(tokens) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v, ok := optionMap["post-up"]; ok {
|
||||||
|
optionMap["post-up"] = append(v, tokens[1])
|
||||||
|
} else {
|
||||||
|
optionMap["post-up"] = []string{tokens[1]}
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(option, "pre-down") {
|
||||||
|
tokens := strings.SplitAfterN(option, " ", 2)
|
||||||
|
if len(tokens) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v, ok := optionMap["pre-down"]; ok {
|
||||||
|
optionMap["pre-down"] = append(v, tokens[1])
|
||||||
|
} else {
|
||||||
|
optionMap["pre-down"] = []string{tokens[1]}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tokens := strings.Fields(option)
|
||||||
|
optionMap[tokens[0]] = tokens[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var conf configMethod
|
||||||
|
switch confMethod {
|
||||||
|
case "static":
|
||||||
|
config := configMethodStatic{
|
||||||
|
routes: make([]route, 0),
|
||||||
|
nameservers: make([]net.IP, 0),
|
||||||
|
}
|
||||||
|
if addresses, ok := optionMap["address"]; ok {
|
||||||
|
if len(addresses) == 1 {
|
||||||
|
config.address.IP = net.ParseIP(addresses[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if netmasks, ok := optionMap["netmask"]; ok {
|
||||||
|
if len(netmasks) == 1 {
|
||||||
|
config.address.Mask = net.IPMask(net.ParseIP(netmasks[0]).To4())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if config.address.IP == nil || config.address.Mask == nil {
|
||||||
|
return nil, fmt.Errorf("malformed static network config for '%s'", iface)
|
||||||
|
}
|
||||||
|
if gateways, ok := optionMap["gateway"]; ok {
|
||||||
|
if len(gateways) == 1 {
|
||||||
|
config.routes = append(config.routes, route{
|
||||||
|
destination: net.IPNet{
|
||||||
|
IP: net.IPv4(0, 0, 0, 0),
|
||||||
|
Mask: net.IPv4Mask(0, 0, 0, 0),
|
||||||
|
},
|
||||||
|
gateway: net.ParseIP(gateways[0]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, nameserver := range optionMap["dns-nameservers"] {
|
||||||
|
config.nameservers = append(config.nameservers, net.ParseIP(nameserver))
|
||||||
|
}
|
||||||
|
for _, postup := range optionMap["post-up"] {
|
||||||
|
if strings.HasPrefix(postup, "route add") {
|
||||||
|
route := route{}
|
||||||
|
fields := strings.Fields(postup)
|
||||||
|
for i, field := range fields[:len(fields)-1] {
|
||||||
|
switch field {
|
||||||
|
case "-net":
|
||||||
|
route.destination.IP = net.ParseIP(fields[i+1])
|
||||||
|
case "netmask":
|
||||||
|
route.destination.Mask = net.IPMask(net.ParseIP(fields[i+1]).To4())
|
||||||
|
case "gw":
|
||||||
|
route.gateway = net.ParseIP(fields[i+1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if route.destination.IP != nil && route.destination.Mask != nil && route.gateway != nil {
|
||||||
|
config.routes = append(config.routes, route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conf = config
|
||||||
|
case "loopback":
|
||||||
|
conf = configMethodLoopback{}
|
||||||
|
case "manual":
|
||||||
|
conf = configMethodManual{}
|
||||||
|
case "dhcp":
|
||||||
|
conf = configMethodDHCP{}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid config method '%s'", confMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := optionMap["vlan_raw_device"]; ok {
|
||||||
|
return parseVLANStanza(iface, conf, attributes, optionMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(iface, ".") {
|
||||||
|
return parseVLANStanza(iface, conf, attributes, optionMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := optionMap["bond-slaves"]; ok {
|
||||||
|
return parseBondStanza(iface, conf, attributes, optionMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsePhysicalStanza(iface, conf, attributes, optionMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBondStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
|
||||||
|
options["slaves"] = options["bond-slaves"]
|
||||||
|
return &stanzaInterface{name: iface, kind: interfaceBond, configMethod: conf, options: options}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePhysicalStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
|
||||||
|
return &stanzaInterface{name: iface, kind: interfacePhysical, configMethod: conf, options: options}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseVLANStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
|
||||||
|
var id string
|
||||||
|
if strings.Contains(iface, ".") {
|
||||||
|
tokens := strings.Split(iface, ".")
|
||||||
|
id = tokens[len(tokens)-1]
|
||||||
|
} else if strings.HasPrefix(iface, "vlan") {
|
||||||
|
id = strings.TrimPrefix(iface, "vlan")
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("malformed vlan name %s", iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := strconv.Atoi(id); err != nil {
|
||||||
|
return nil, fmt.Errorf("malformed vlan name %s", iface)
|
||||||
|
}
|
||||||
|
options["id"] = []string{id}
|
||||||
|
options["raw_device"] = options["vlan_raw_device"]
|
||||||
|
|
||||||
|
return &stanzaInterface{name: iface, kind: interfaceVLAN, configMethod: conf, options: options}, nil
|
||||||
|
}
|
502
network/stanza_test.go
Normal file
502
network/stanza_test.go
Normal file
@ -0,0 +1,502 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSplitStanzasNoParent(t *testing.T) {
|
||||||
|
in := []string{"test"}
|
||||||
|
e := "missing stanza start"
|
||||||
|
_, err := splitStanzas(in)
|
||||||
|
if err == nil || !strings.HasPrefix(err.Error(), e) {
|
||||||
|
t.Fatalf("bad error for splitStanzas(%q): got %q, want %q", in, err, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadParseStanzas(t *testing.T) {
|
||||||
|
for in, e := range map[string]string{
|
||||||
|
"": "missing stanza start",
|
||||||
|
"iface": "malformed stanza start",
|
||||||
|
"allow-?? unknown": "unknown stanza",
|
||||||
|
} {
|
||||||
|
_, err := parseStanzas([]string{in})
|
||||||
|
if err == nil || !strings.HasPrefix(err.Error(), e) {
|
||||||
|
t.Fatalf("bad error for parseStanzas(%q): got %q, want %q", in, err, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadParseInterfaceStanza(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
in []string
|
||||||
|
opts []string
|
||||||
|
e string
|
||||||
|
}{
|
||||||
|
{[]string{}, nil, "incorrect number of attributes"},
|
||||||
|
{[]string{"eth", "inet", "invalid"}, nil, "invalid config method"},
|
||||||
|
{[]string{"eth", "inet", "static"}, []string{"address 192.168.1.100"}, "malformed static network config"},
|
||||||
|
{[]string{"eth", "inet", "static"}, []string{"netmask 255.255.255.0"}, "malformed static network config"},
|
||||||
|
{[]string{"eth", "inet", "static"}, []string{"address invalid", "netmask 255.255.255.0"}, "malformed static network config"},
|
||||||
|
{[]string{"eth", "inet", "static"}, []string{"address 192.168.1.100", "netmask invalid"}, "malformed static network config"},
|
||||||
|
} {
|
||||||
|
_, err := parseInterfaceStanza(tt.in, tt.opts)
|
||||||
|
if err == nil || !strings.HasPrefix(err.Error(), tt.e) {
|
||||||
|
t.Fatalf("bad error parsing interface stanza %q: got %q, want %q", tt.in, err.Error(), tt.e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadParseVLANStanzas(t *testing.T) {
|
||||||
|
conf := configMethodManual{}
|
||||||
|
options := map[string][]string{}
|
||||||
|
for _, in := range []string{"myvlan", "eth.vlan"} {
|
||||||
|
_, err := parseVLANStanza(in, conf, nil, options)
|
||||||
|
if err == nil || !strings.HasPrefix(err.Error(), "malformed vlan name") {
|
||||||
|
t.Fatalf("did not error on bad vlan %q", in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitStanzas(t *testing.T) {
|
||||||
|
expect := [][]string{
|
||||||
|
{"auto lo"},
|
||||||
|
{"iface eth1", "option: 1"},
|
||||||
|
{"mapping"},
|
||||||
|
{"allow-"},
|
||||||
|
}
|
||||||
|
lines := make([]string, 0, 5)
|
||||||
|
for _, stanza := range expect {
|
||||||
|
for _, line := range stanza {
|
||||||
|
lines = append(lines, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stanzas, err := splitStanzas(lines)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
for i, stanza := range stanzas {
|
||||||
|
if len(stanza) != len(expect[i]) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
for j, line := range stanza {
|
||||||
|
if line != expect[i][j] {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseStanzaNil(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r == nil {
|
||||||
|
t.Fatal("parseStanza(nil) did not panic")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
parseStanza(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseStanzaSuccess(t *testing.T) {
|
||||||
|
for _, in := range []string{
|
||||||
|
"auto a",
|
||||||
|
"iface a inet manual",
|
||||||
|
} {
|
||||||
|
if _, err := parseStanza([]string{in}); err != nil {
|
||||||
|
t.Fatalf("unexpected error parsing stanza %q: %s", in, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseAutoStanza(t *testing.T) {
|
||||||
|
interfaces := []string{"test", "attribute"}
|
||||||
|
stanza, err := parseAutoStanza(interfaces, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error parsing auto stanza %q: %s", interfaces, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(stanza.interfaces, interfaces) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseBondStanzaNoSlaves(t *testing.T) {
|
||||||
|
bond, err := parseBondStanza("", nil, nil, map[string][]string{})
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if bond.options["slaves"] != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseBondStanza(t *testing.T) {
|
||||||
|
conf := configMethodManual{}
|
||||||
|
options := map[string][]string{
|
||||||
|
"bond-slaves": []string{"1", "2"},
|
||||||
|
}
|
||||||
|
bond, err := parseBondStanza("test", conf, nil, options)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if bond.name != "test" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if bond.kind != interfaceBond {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if bond.configMethod != conf {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(bond.options["slaves"], options["bond-slaves"]) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePhysicalStanza(t *testing.T) {
|
||||||
|
conf := configMethodManual{}
|
||||||
|
options := map[string][]string{
|
||||||
|
"a": []string{"1", "2"},
|
||||||
|
"b": []string{"1"},
|
||||||
|
}
|
||||||
|
physical, err := parsePhysicalStanza("test", conf, nil, options)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if physical.name != "test" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if physical.kind != interfacePhysical {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if physical.configMethod != conf {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(physical.options, options) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseVLANStanzas(t *testing.T) {
|
||||||
|
conf := configMethodManual{}
|
||||||
|
options := map[string][]string{}
|
||||||
|
for _, in := range []string{"vlan25", "eth.25"} {
|
||||||
|
vlan, err := parseVLANStanza(in, conf, nil, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error from parseVLANStanza(%q): %s", in, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(vlan.options["id"], []string{"25"}) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaceStanzaStaticAddress(t *testing.T) {
|
||||||
|
options := []string{"address 192.168.1.100", "netmask 255.255.255.0"}
|
||||||
|
expect := net.IPNet{
|
||||||
|
IP: net.IPv4(192, 168, 1, 100),
|
||||||
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
static, ok := iface.configMethod.(configMethodStatic)
|
||||||
|
if !ok {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(static.address, expect) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaceStanzaStaticGateway(t *testing.T) {
|
||||||
|
options := []string{"address 192.168.1.100", "netmask 255.255.255.0", "gateway 192.168.1.1"}
|
||||||
|
expect := []route{
|
||||||
|
{
|
||||||
|
destination: net.IPNet{
|
||||||
|
IP: net.IPv4(0, 0, 0, 0),
|
||||||
|
Mask: net.IPv4Mask(0, 0, 0, 0),
|
||||||
|
},
|
||||||
|
gateway: net.IPv4(192, 168, 1, 1),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
static, ok := iface.configMethod.(configMethodStatic)
|
||||||
|
if !ok {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(static.routes, expect) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaceStanzaStaticDNS(t *testing.T) {
|
||||||
|
options := []string{"address 192.168.1.100", "netmask 255.255.255.0", "dns-nameservers 192.168.1.10 192.168.1.11 192.168.1.12"}
|
||||||
|
expect := []net.IP{
|
||||||
|
net.IPv4(192, 168, 1, 10),
|
||||||
|
net.IPv4(192, 168, 1, 11),
|
||||||
|
net.IPv4(192, 168, 1, 12),
|
||||||
|
}
|
||||||
|
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
static, ok := iface.configMethod.(configMethodStatic)
|
||||||
|
if !ok {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(static.nameservers, expect) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadParseInterfaceStanzasStaticPostUp(t *testing.T) {
|
||||||
|
for _, in := range []string{
|
||||||
|
"post-up invalid",
|
||||||
|
"post-up route add",
|
||||||
|
"post-up route add -net",
|
||||||
|
"post-up route add gw",
|
||||||
|
"post-up route add netmask",
|
||||||
|
"gateway",
|
||||||
|
"gateway 192.168.1.1 192.168.1.2",
|
||||||
|
} {
|
||||||
|
options := []string{"address 192.168.1.100", "netmask 255.255.255.0", in}
|
||||||
|
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parseInterfaceStanza with options %s got unexpected error", options)
|
||||||
|
}
|
||||||
|
static, ok := iface.configMethod.(configMethodStatic)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("parseInterfaceStanza with options %s did not return configMethodStatic", options)
|
||||||
|
}
|
||||||
|
if len(static.routes) != 0 {
|
||||||
|
t.Fatalf("parseInterfaceStanza with options %s did not return zero-length static routes", options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaceStanzaStaticPostUp(t *testing.T) {
|
||||||
|
options := []string{
|
||||||
|
"address 192.168.1.100",
|
||||||
|
"netmask 255.255.255.0",
|
||||||
|
"post-up route add gw 192.168.1.1 -net 192.168.1.0 netmask 255.255.255.0",
|
||||||
|
}
|
||||||
|
expect := []route{
|
||||||
|
{
|
||||||
|
destination: net.IPNet{
|
||||||
|
IP: net.IPv4(192, 168, 1, 0),
|
||||||
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||||
|
},
|
||||||
|
gateway: net.IPv4(192, 168, 1, 1),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
static, ok := iface.configMethod.(configMethodStatic)
|
||||||
|
if !ok {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(static.routes, expect) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaceStanzaLoopback(t *testing.T) {
|
||||||
|
iface, err := parseInterfaceStanza([]string{"eth", "inet", "loopback"}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if _, ok := iface.configMethod.(configMethodLoopback); !ok {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaceStanzaManual(t *testing.T) {
|
||||||
|
iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if _, ok := iface.configMethod.(configMethodManual); !ok {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaceStanzaDHCP(t *testing.T) {
|
||||||
|
iface, err := parseInterfaceStanza([]string{"eth", "inet", "dhcp"}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if _, ok := iface.configMethod.(configMethodDHCP); !ok {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaceStanzaPostUpOption(t *testing.T) {
|
||||||
|
options := []string{
|
||||||
|
"post-up",
|
||||||
|
"post-up 1 2",
|
||||||
|
"post-up 3 4",
|
||||||
|
}
|
||||||
|
iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(iface.options["post-up"], []string{"1 2", "3 4"}) {
|
||||||
|
t.Log(iface.options["post-up"])
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaceStanzaPreDownOption(t *testing.T) {
|
||||||
|
options := []string{
|
||||||
|
"pre-down",
|
||||||
|
"pre-down 3",
|
||||||
|
"pre-down 4",
|
||||||
|
}
|
||||||
|
iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(iface.options["pre-down"], []string{"3", "4"}) {
|
||||||
|
t.Log(iface.options["pre-down"])
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaceStanzaEmptyOption(t *testing.T) {
|
||||||
|
options := []string{
|
||||||
|
"test",
|
||||||
|
}
|
||||||
|
iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(iface.options["test"], []string{}) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaceStanzaOptions(t *testing.T) {
|
||||||
|
options := []string{
|
||||||
|
"test1 1",
|
||||||
|
"test2 2 3",
|
||||||
|
"test1 5 6",
|
||||||
|
}
|
||||||
|
iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(iface.options["test1"], []string{"5", "6"}) {
|
||||||
|
t.Log(iface.options["test1"])
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(iface.options["test2"], []string{"2", "3"}) {
|
||||||
|
t.Log(iface.options["test2"])
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaceStazaBond(t *testing.T) {
|
||||||
|
iface, err := parseInterfaceStanza([]string{"mybond", "inet", "manual"}, []string{"bond-slaves eth"})
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if iface.kind != interfaceBond {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaceStazaVLANName(t *testing.T) {
|
||||||
|
iface, err := parseInterfaceStanza([]string{"eth0.1", "inet", "manual"}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if iface.kind != interfaceVLAN {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInterfaceStazaVLANOption(t *testing.T) {
|
||||||
|
iface, err := parseInterfaceStanza([]string{"vlan1", "inet", "manual"}, []string{"vlan_raw_device eth"})
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if iface.kind != interfaceVLAN {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseStanzasNone(t *testing.T) {
|
||||||
|
stanzas, err := parseStanzas(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if len(stanzas) != 0 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseStanzas(t *testing.T) {
|
||||||
|
lines := []string{
|
||||||
|
"auto lo",
|
||||||
|
"iface lo inet loopback",
|
||||||
|
"iface eth1 inet manual",
|
||||||
|
"iface eth2 inet manual",
|
||||||
|
"iface eth3 inet manual",
|
||||||
|
"auto eth1 eth3",
|
||||||
|
}
|
||||||
|
expect := []stanza{
|
||||||
|
&stanzaAuto{
|
||||||
|
interfaces: []string{"lo"},
|
||||||
|
},
|
||||||
|
&stanzaInterface{
|
||||||
|
name: "lo",
|
||||||
|
kind: interfacePhysical,
|
||||||
|
auto: true,
|
||||||
|
configMethod: configMethodLoopback{},
|
||||||
|
options: map[string][]string{},
|
||||||
|
},
|
||||||
|
&stanzaInterface{
|
||||||
|
name: "eth1",
|
||||||
|
kind: interfacePhysical,
|
||||||
|
auto: true,
|
||||||
|
configMethod: configMethodManual{},
|
||||||
|
options: map[string][]string{},
|
||||||
|
},
|
||||||
|
&stanzaInterface{
|
||||||
|
name: "eth2",
|
||||||
|
kind: interfacePhysical,
|
||||||
|
auto: false,
|
||||||
|
configMethod: configMethodManual{},
|
||||||
|
options: map[string][]string{},
|
||||||
|
},
|
||||||
|
&stanzaInterface{
|
||||||
|
name: "eth3",
|
||||||
|
kind: interfacePhysical,
|
||||||
|
auto: true,
|
||||||
|
configMethod: configMethodManual{},
|
||||||
|
options: map[string][]string{},
|
||||||
|
},
|
||||||
|
&stanzaAuto{
|
||||||
|
interfaces: []string{"eth1", "eth3"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
stanzas, err := parseStanzas(lines)
|
||||||
|
if err != err {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(stanzas, expect) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
89
system/networkd.go
Normal file
89
system/networkd.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/network"
|
||||||
|
"github.com/coreos/coreos-cloudinit/third_party/github.com/dotcloud/docker/pkg/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
runtimeNetworkPath = "/run/systemd/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RestartNetwork(interfaces []network.InterfaceGenerator) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if e := restartNetworkd(); e != nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = downNetworkInterfaces(interfaces); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = probe8012q(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func downNetworkInterfaces(interfaces []network.InterfaceGenerator) error {
|
||||||
|
sysInterfaceMap := make(map[string]*net.Interface)
|
||||||
|
if systemInterfaces, err := net.Interfaces(); err == nil {
|
||||||
|
for _, iface := range systemInterfaces {
|
||||||
|
sysInterfaceMap[iface.Name] = &iface
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
if systemInterface, ok := sysInterfaceMap[iface.Name()]; ok {
|
||||||
|
if err := netlink.NetworkLinkDown(systemInterface); err != nil {
|
||||||
|
fmt.Printf("Error while downing interface %q (%s). Continuing...\n", systemInterface.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func probe8012q() error {
|
||||||
|
return exec.Command("modprobe", "8021q").Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func restartNetworkd() error {
|
||||||
|
_, err := RunUnitCommand("restart", "systemd-networkd.service")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteNetworkdConfigs(interfaces []network.InterfaceGenerator) error {
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
filename := path.Join(runtimeNetworkPath, fmt.Sprintf("%s.netdev", iface.Name()))
|
||||||
|
if err := writeConfig(filename, iface.Netdev()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.link", iface.Name()))
|
||||||
|
if err := writeConfig(filename, iface.Link()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.network", iface.Name()))
|
||||||
|
if err := writeConfig(filename, iface.Network()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeConfig(filename string, config string) error {
|
||||||
|
if config == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ioutil.WriteFile(filename, []byte(config), 0444)
|
||||||
|
}
|
2
test
2
test
@ -13,7 +13,7 @@ COVER=${COVER:-"-cover"}
|
|||||||
|
|
||||||
source ./build
|
source ./build
|
||||||
|
|
||||||
declare -a TESTPKGS=(initialize system datasource pkg)
|
declare -a TESTPKGS=(initialize system datasource pkg network)
|
||||||
|
|
||||||
if [ -z "$PKG" ]; then
|
if [ -z "$PKG" ]; then
|
||||||
GOFMTPATH="$TESTPKGS coreos-cloudinit.go"
|
GOFMTPATH="$TESTPKGS coreos-cloudinit.go"
|
||||||
|
2
third_party/github.com/dotcloud/docker/pkg/netlink/MAINTAINERS
vendored
Normal file
2
third_party/github.com/dotcloud/docker/pkg/netlink/MAINTAINERS
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||||
|
Guillaume J. Charmes <guillaume@docker.com> (@creack)
|
23
third_party/github.com/dotcloud/docker/pkg/netlink/netlink.go
vendored
Normal file
23
third_party/github.com/dotcloud/docker/pkg/netlink/netlink.go
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Packet netlink provide access to low level Netlink sockets and messages.
|
||||||
|
//
|
||||||
|
// Actual implementations are in:
|
||||||
|
// netlink_linux.go
|
||||||
|
// netlink_darwin.go
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrWrongSockType = errors.New("Wrong socket type")
|
||||||
|
ErrShortResponse = errors.New("Got short response from netlink")
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Route is a subnet associated with the interface to reach it.
|
||||||
|
type Route struct {
|
||||||
|
*net.IPNet
|
||||||
|
Iface *net.Interface
|
||||||
|
Default bool
|
||||||
|
}
|
891
third_party/github.com/dotcloud/docker/pkg/netlink/netlink_linux.go
vendored
Normal file
891
third_party/github.com/dotcloud/docker/pkg/netlink/netlink_linux.go
vendored
Normal file
@ -0,0 +1,891 @@
|
|||||||
|
// +build amd64
|
||||||
|
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
IFNAMSIZ = 16
|
||||||
|
DEFAULT_CHANGE = 0xFFFFFFFF
|
||||||
|
IFLA_INFO_KIND = 1
|
||||||
|
IFLA_INFO_DATA = 2
|
||||||
|
VETH_INFO_PEER = 1
|
||||||
|
IFLA_NET_NS_FD = 28
|
||||||
|
SIOC_BRADDBR = 0x89a0
|
||||||
|
SIOC_BRADDIF = 0x89a2
|
||||||
|
)
|
||||||
|
|
||||||
|
var nextSeqNr int
|
||||||
|
|
||||||
|
type ifreqHwaddr struct {
|
||||||
|
IfrnName [16]byte
|
||||||
|
IfruHwaddr syscall.RawSockaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
type ifreqIndex struct {
|
||||||
|
IfrnName [16]byte
|
||||||
|
IfruIndex int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func nativeEndian() binary.ByteOrder {
|
||||||
|
var x uint32 = 0x01020304
|
||||||
|
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
||||||
|
return binary.BigEndian
|
||||||
|
}
|
||||||
|
return binary.LittleEndian
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSeq() int {
|
||||||
|
nextSeqNr = nextSeqNr + 1
|
||||||
|
return nextSeqNr
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIpFamily(ip net.IP) int {
|
||||||
|
if len(ip) <= net.IPv4len {
|
||||||
|
return syscall.AF_INET
|
||||||
|
}
|
||||||
|
if ip.To4() != nil {
|
||||||
|
return syscall.AF_INET
|
||||||
|
}
|
||||||
|
return syscall.AF_INET6
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetlinkRequestData interface {
|
||||||
|
Len() int
|
||||||
|
ToWireFormat() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type IfInfomsg struct {
|
||||||
|
syscall.IfInfomsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIfInfomsg(family int) *IfInfomsg {
|
||||||
|
return &IfInfomsg{
|
||||||
|
IfInfomsg: syscall.IfInfomsg{
|
||||||
|
Family: uint8(family),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIfInfomsgChild(parent *RtAttr, family int) *IfInfomsg {
|
||||||
|
msg := newIfInfomsg(family)
|
||||||
|
parent.children = append(parent.children, msg)
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *IfInfomsg) ToWireFormat() []byte {
|
||||||
|
native := nativeEndian()
|
||||||
|
|
||||||
|
length := syscall.SizeofIfInfomsg
|
||||||
|
b := make([]byte, length)
|
||||||
|
b[0] = msg.Family
|
||||||
|
b[1] = 0
|
||||||
|
native.PutUint16(b[2:4], msg.Type)
|
||||||
|
native.PutUint32(b[4:8], uint32(msg.Index))
|
||||||
|
native.PutUint32(b[8:12], msg.Flags)
|
||||||
|
native.PutUint32(b[12:16], msg.Change)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *IfInfomsg) Len() int {
|
||||||
|
return syscall.SizeofIfInfomsg
|
||||||
|
}
|
||||||
|
|
||||||
|
type IfAddrmsg struct {
|
||||||
|
syscall.IfAddrmsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIfAddrmsg(family int) *IfAddrmsg {
|
||||||
|
return &IfAddrmsg{
|
||||||
|
IfAddrmsg: syscall.IfAddrmsg{
|
||||||
|
Family: uint8(family),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *IfAddrmsg) ToWireFormat() []byte {
|
||||||
|
native := nativeEndian()
|
||||||
|
|
||||||
|
length := syscall.SizeofIfAddrmsg
|
||||||
|
b := make([]byte, length)
|
||||||
|
b[0] = msg.Family
|
||||||
|
b[1] = msg.Prefixlen
|
||||||
|
b[2] = msg.Flags
|
||||||
|
b[3] = msg.Scope
|
||||||
|
native.PutUint32(b[4:8], msg.Index)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *IfAddrmsg) Len() int {
|
||||||
|
return syscall.SizeofIfAddrmsg
|
||||||
|
}
|
||||||
|
|
||||||
|
type RtMsg struct {
|
||||||
|
syscall.RtMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRtMsg(family int) *RtMsg {
|
||||||
|
return &RtMsg{
|
||||||
|
RtMsg: syscall.RtMsg{
|
||||||
|
Family: uint8(family),
|
||||||
|
Table: syscall.RT_TABLE_MAIN,
|
||||||
|
Scope: syscall.RT_SCOPE_UNIVERSE,
|
||||||
|
Protocol: syscall.RTPROT_BOOT,
|
||||||
|
Type: syscall.RTN_UNICAST,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *RtMsg) ToWireFormat() []byte {
|
||||||
|
native := nativeEndian()
|
||||||
|
|
||||||
|
length := syscall.SizeofRtMsg
|
||||||
|
b := make([]byte, length)
|
||||||
|
b[0] = msg.Family
|
||||||
|
b[1] = msg.Dst_len
|
||||||
|
b[2] = msg.Src_len
|
||||||
|
b[3] = msg.Tos
|
||||||
|
b[4] = msg.Table
|
||||||
|
b[5] = msg.Protocol
|
||||||
|
b[6] = msg.Scope
|
||||||
|
b[7] = msg.Type
|
||||||
|
native.PutUint32(b[8:12], msg.Flags)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *RtMsg) Len() int {
|
||||||
|
return syscall.SizeofRtMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtaAlignOf(attrlen int) int {
|
||||||
|
return (attrlen + syscall.RTA_ALIGNTO - 1) & ^(syscall.RTA_ALIGNTO - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RtAttr struct {
|
||||||
|
syscall.RtAttr
|
||||||
|
Data []byte
|
||||||
|
children []NetlinkRequestData
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRtAttr(attrType int, data []byte) *RtAttr {
|
||||||
|
return &RtAttr{
|
||||||
|
RtAttr: syscall.RtAttr{
|
||||||
|
Type: uint16(attrType),
|
||||||
|
},
|
||||||
|
children: []NetlinkRequestData{},
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRtAttrChild(parent *RtAttr, attrType int, data []byte) *RtAttr {
|
||||||
|
attr := newRtAttr(attrType, data)
|
||||||
|
parent.children = append(parent.children, attr)
|
||||||
|
return attr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RtAttr) Len() int {
|
||||||
|
l := 0
|
||||||
|
for _, child := range a.children {
|
||||||
|
l += child.Len() + syscall.SizeofRtAttr
|
||||||
|
}
|
||||||
|
if l == 0 {
|
||||||
|
l++
|
||||||
|
}
|
||||||
|
return rtaAlignOf(l + len(a.Data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RtAttr) ToWireFormat() []byte {
|
||||||
|
native := nativeEndian()
|
||||||
|
|
||||||
|
length := a.Len()
|
||||||
|
buf := make([]byte, rtaAlignOf(length+syscall.SizeofRtAttr))
|
||||||
|
|
||||||
|
if a.Data != nil {
|
||||||
|
copy(buf[4:], a.Data)
|
||||||
|
} else {
|
||||||
|
next := 4
|
||||||
|
for _, child := range a.children {
|
||||||
|
childBuf := child.ToWireFormat()
|
||||||
|
copy(buf[next:], childBuf)
|
||||||
|
next += rtaAlignOf(len(childBuf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if l := uint16(rtaAlignOf(length)); l != 0 {
|
||||||
|
native.PutUint16(buf[0:2], l+1)
|
||||||
|
}
|
||||||
|
native.PutUint16(buf[2:4], a.Type)
|
||||||
|
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetlinkRequest struct {
|
||||||
|
syscall.NlMsghdr
|
||||||
|
Data []NetlinkRequestData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *NetlinkRequest) ToWireFormat() []byte {
|
||||||
|
native := nativeEndian()
|
||||||
|
|
||||||
|
length := rr.Len
|
||||||
|
dataBytes := make([][]byte, len(rr.Data))
|
||||||
|
for i, data := range rr.Data {
|
||||||
|
dataBytes[i] = data.ToWireFormat()
|
||||||
|
length += uint32(len(dataBytes[i]))
|
||||||
|
}
|
||||||
|
b := make([]byte, length)
|
||||||
|
native.PutUint32(b[0:4], length)
|
||||||
|
native.PutUint16(b[4:6], rr.Type)
|
||||||
|
native.PutUint16(b[6:8], rr.Flags)
|
||||||
|
native.PutUint32(b[8:12], rr.Seq)
|
||||||
|
native.PutUint32(b[12:16], rr.Pid)
|
||||||
|
|
||||||
|
next := 16
|
||||||
|
for _, data := range dataBytes {
|
||||||
|
copy(b[next:], data)
|
||||||
|
next += len(data)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *NetlinkRequest) AddData(data NetlinkRequestData) {
|
||||||
|
if data != nil {
|
||||||
|
rr.Data = append(rr.Data, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNetlinkRequest(proto, flags int) *NetlinkRequest {
|
||||||
|
return &NetlinkRequest{
|
||||||
|
NlMsghdr: syscall.NlMsghdr{
|
||||||
|
Len: uint32(syscall.NLMSG_HDRLEN),
|
||||||
|
Type: uint16(proto),
|
||||||
|
Flags: syscall.NLM_F_REQUEST | uint16(flags),
|
||||||
|
Seq: uint32(getSeq()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetlinkSocket struct {
|
||||||
|
fd int
|
||||||
|
lsa syscall.SockaddrNetlink
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNetlinkSocket() (*NetlinkSocket, error) {
|
||||||
|
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_ROUTE)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s := &NetlinkSocket{
|
||||||
|
fd: fd,
|
||||||
|
}
|
||||||
|
s.lsa.Family = syscall.AF_NETLINK
|
||||||
|
if err := syscall.Bind(fd, &s.lsa); err != nil {
|
||||||
|
syscall.Close(fd)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NetlinkSocket) Close() {
|
||||||
|
syscall.Close(s.fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NetlinkSocket) Send(request *NetlinkRequest) error {
|
||||||
|
if err := syscall.Sendto(s.fd, request.ToWireFormat(), 0, &s.lsa); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NetlinkSocket) Receive() ([]syscall.NetlinkMessage, error) {
|
||||||
|
rb := make([]byte, syscall.Getpagesize())
|
||||||
|
nr, _, err := syscall.Recvfrom(s.fd, rb, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if nr < syscall.NLMSG_HDRLEN {
|
||||||
|
return nil, ErrShortResponse
|
||||||
|
}
|
||||||
|
rb = rb[:nr]
|
||||||
|
return syscall.ParseNetlinkMessage(rb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NetlinkSocket) GetPid() (uint32, error) {
|
||||||
|
lsa, err := syscall.Getsockname(s.fd)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
switch v := lsa.(type) {
|
||||||
|
case *syscall.SockaddrNetlink:
|
||||||
|
return v.Pid, nil
|
||||||
|
}
|
||||||
|
return 0, ErrWrongSockType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NetlinkSocket) HandleAck(seq uint32) error {
|
||||||
|
native := nativeEndian()
|
||||||
|
|
||||||
|
pid, err := s.GetPid()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
for {
|
||||||
|
msgs, err := s.Receive()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, m := range msgs {
|
||||||
|
if m.Header.Seq != seq {
|
||||||
|
return fmt.Errorf("Wrong Seq nr %d, expected %d", m.Header.Seq, seq)
|
||||||
|
}
|
||||||
|
if m.Header.Pid != pid {
|
||||||
|
return fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
|
||||||
|
}
|
||||||
|
if m.Header.Type == syscall.NLMSG_DONE {
|
||||||
|
break done
|
||||||
|
}
|
||||||
|
if m.Header.Type == syscall.NLMSG_ERROR {
|
||||||
|
error := int32(native.Uint32(m.Data[0:4]))
|
||||||
|
if error == 0 {
|
||||||
|
break done
|
||||||
|
}
|
||||||
|
return syscall.Errno(-error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new default gateway. Identical to:
|
||||||
|
// ip route add default via $ip
|
||||||
|
func AddDefaultGw(ip net.IP) error {
|
||||||
|
s, err := getNetlinkSocket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
family := getIpFamily(ip)
|
||||||
|
|
||||||
|
wb := newNetlinkRequest(syscall.RTM_NEWROUTE, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
|
||||||
|
|
||||||
|
msg := newRtMsg(family)
|
||||||
|
wb.AddData(msg)
|
||||||
|
|
||||||
|
var ipData []byte
|
||||||
|
if family == syscall.AF_INET {
|
||||||
|
ipData = ip.To4()
|
||||||
|
} else {
|
||||||
|
ipData = ip.To16()
|
||||||
|
}
|
||||||
|
|
||||||
|
gateway := newRtAttr(syscall.RTA_GATEWAY, ipData)
|
||||||
|
|
||||||
|
wb.AddData(gateway)
|
||||||
|
|
||||||
|
if err := s.Send(wb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.HandleAck(wb.Seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring up a particular network interface
|
||||||
|
func NetworkLinkUp(iface *net.Interface) error {
|
||||||
|
s, err := getNetlinkSocket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_ACK)
|
||||||
|
|
||||||
|
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
||||||
|
msg.Change = syscall.IFF_UP
|
||||||
|
msg.Flags = syscall.IFF_UP
|
||||||
|
msg.Index = int32(iface.Index)
|
||||||
|
wb.AddData(msg)
|
||||||
|
|
||||||
|
if err := s.Send(wb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.HandleAck(wb.Seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkDown(iface *net.Interface) error {
|
||||||
|
s, err := getNetlinkSocket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_ACK)
|
||||||
|
|
||||||
|
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
||||||
|
msg.Change = syscall.IFF_UP
|
||||||
|
msg.Flags = 0 & ^syscall.IFF_UP
|
||||||
|
msg.Index = int32(iface.Index)
|
||||||
|
wb.AddData(msg)
|
||||||
|
|
||||||
|
if err := s.Send(wb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.HandleAck(wb.Seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetMTU(iface *net.Interface, mtu int) error {
|
||||||
|
s, err := getNetlinkSocket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
|
||||||
|
|
||||||
|
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
||||||
|
msg.Type = syscall.RTM_SETLINK
|
||||||
|
msg.Flags = syscall.NLM_F_REQUEST
|
||||||
|
msg.Index = int32(iface.Index)
|
||||||
|
msg.Change = DEFAULT_CHANGE
|
||||||
|
wb.AddData(msg)
|
||||||
|
|
||||||
|
var (
|
||||||
|
b = make([]byte, 4)
|
||||||
|
native = nativeEndian()
|
||||||
|
)
|
||||||
|
native.PutUint32(b, uint32(mtu))
|
||||||
|
|
||||||
|
data := newRtAttr(syscall.IFLA_MTU, b)
|
||||||
|
wb.AddData(data)
|
||||||
|
|
||||||
|
if err := s.Send(wb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.HandleAck(wb.Seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
// same as ip link set $name master $master
|
||||||
|
func NetworkSetMaster(iface, master *net.Interface) error {
|
||||||
|
s, err := getNetlinkSocket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
|
||||||
|
|
||||||
|
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
||||||
|
msg.Type = syscall.RTM_SETLINK
|
||||||
|
msg.Flags = syscall.NLM_F_REQUEST
|
||||||
|
msg.Index = int32(iface.Index)
|
||||||
|
msg.Change = DEFAULT_CHANGE
|
||||||
|
wb.AddData(msg)
|
||||||
|
|
||||||
|
var (
|
||||||
|
b = make([]byte, 4)
|
||||||
|
native = nativeEndian()
|
||||||
|
)
|
||||||
|
native.PutUint32(b, uint32(master.Index))
|
||||||
|
|
||||||
|
data := newRtAttr(syscall.IFLA_MASTER, b)
|
||||||
|
wb.AddData(data)
|
||||||
|
|
||||||
|
if err := s.Send(wb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.HandleAck(wb.Seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetNsPid(iface *net.Interface, nspid int) error {
|
||||||
|
s, err := getNetlinkSocket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
|
||||||
|
|
||||||
|
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
||||||
|
msg.Type = syscall.RTM_SETLINK
|
||||||
|
msg.Flags = syscall.NLM_F_REQUEST
|
||||||
|
msg.Index = int32(iface.Index)
|
||||||
|
msg.Change = DEFAULT_CHANGE
|
||||||
|
wb.AddData(msg)
|
||||||
|
|
||||||
|
var (
|
||||||
|
b = make([]byte, 4)
|
||||||
|
native = nativeEndian()
|
||||||
|
)
|
||||||
|
native.PutUint32(b, uint32(nspid))
|
||||||
|
|
||||||
|
data := newRtAttr(syscall.IFLA_NET_NS_PID, b)
|
||||||
|
wb.AddData(data)
|
||||||
|
|
||||||
|
if err := s.Send(wb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.HandleAck(wb.Seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetNsFd(iface *net.Interface, fd int) error {
|
||||||
|
s, err := getNetlinkSocket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
|
||||||
|
|
||||||
|
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
||||||
|
msg.Type = syscall.RTM_SETLINK
|
||||||
|
msg.Flags = syscall.NLM_F_REQUEST
|
||||||
|
msg.Index = int32(iface.Index)
|
||||||
|
msg.Change = DEFAULT_CHANGE
|
||||||
|
wb.AddData(msg)
|
||||||
|
|
||||||
|
var (
|
||||||
|
b = make([]byte, 4)
|
||||||
|
native = nativeEndian()
|
||||||
|
)
|
||||||
|
native.PutUint32(b, uint32(fd))
|
||||||
|
|
||||||
|
data := newRtAttr(IFLA_NET_NS_FD, b)
|
||||||
|
wb.AddData(data)
|
||||||
|
|
||||||
|
if err := s.Send(wb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.HandleAck(wb.Seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an Ip address to an interface. This is identical to:
|
||||||
|
// ip addr add $ip/$ipNet dev $iface
|
||||||
|
func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
||||||
|
s, err := getNetlinkSocket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
family := getIpFamily(ip)
|
||||||
|
|
||||||
|
wb := newNetlinkRequest(syscall.RTM_NEWADDR, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
|
||||||
|
|
||||||
|
msg := newIfAddrmsg(family)
|
||||||
|
msg.Index = uint32(iface.Index)
|
||||||
|
prefixLen, _ := ipNet.Mask.Size()
|
||||||
|
msg.Prefixlen = uint8(prefixLen)
|
||||||
|
wb.AddData(msg)
|
||||||
|
|
||||||
|
var ipData []byte
|
||||||
|
if family == syscall.AF_INET {
|
||||||
|
ipData = ip.To4()
|
||||||
|
} else {
|
||||||
|
ipData = ip.To16()
|
||||||
|
}
|
||||||
|
|
||||||
|
localData := newRtAttr(syscall.IFA_LOCAL, ipData)
|
||||||
|
wb.AddData(localData)
|
||||||
|
|
||||||
|
addrData := newRtAttr(syscall.IFA_ADDRESS, ipData)
|
||||||
|
wb.AddData(addrData)
|
||||||
|
|
||||||
|
if err := s.Send(wb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.HandleAck(wb.Seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func zeroTerminated(s string) []byte {
|
||||||
|
return []byte(s + "\000")
|
||||||
|
}
|
||||||
|
|
||||||
|
func nonZeroTerminated(s string) []byte {
|
||||||
|
return []byte(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new network link of a specified type. This is identical to
|
||||||
|
// running: ip add link $name type $linkType
|
||||||
|
func NetworkLinkAdd(name string, linkType string) error {
|
||||||
|
s, err := getNetlinkSocket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
|
||||||
|
|
||||||
|
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
||||||
|
wb.AddData(msg)
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name))
|
||||||
|
wb.AddData(nameData)
|
||||||
|
}
|
||||||
|
|
||||||
|
kindData := newRtAttr(IFLA_INFO_KIND, nonZeroTerminated(linkType))
|
||||||
|
|
||||||
|
infoData := newRtAttr(syscall.IFLA_LINKINFO, kindData.ToWireFormat())
|
||||||
|
wb.AddData(infoData)
|
||||||
|
|
||||||
|
if err := s.Send(wb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.HandleAck(wb.Seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an array of IPNet for all the currently routed subnets on ipv4
|
||||||
|
// This is similar to the first column of "ip route" output
|
||||||
|
func NetworkGetRoutes() ([]Route, error) {
|
||||||
|
native := nativeEndian()
|
||||||
|
|
||||||
|
s, err := getNetlinkSocket()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
wb := newNetlinkRequest(syscall.RTM_GETROUTE, syscall.NLM_F_DUMP)
|
||||||
|
|
||||||
|
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
||||||
|
wb.AddData(msg)
|
||||||
|
|
||||||
|
if err := s.Send(wb); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pid, err := s.GetPid()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]Route, 0)
|
||||||
|
|
||||||
|
done:
|
||||||
|
for {
|
||||||
|
msgs, err := s.Receive()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, m := range msgs {
|
||||||
|
if m.Header.Seq != wb.Seq {
|
||||||
|
return nil, fmt.Errorf("Wrong Seq nr %d, expected 1", m.Header.Seq)
|
||||||
|
}
|
||||||
|
if m.Header.Pid != pid {
|
||||||
|
return nil, fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
|
||||||
|
}
|
||||||
|
if m.Header.Type == syscall.NLMSG_DONE {
|
||||||
|
break done
|
||||||
|
}
|
||||||
|
if m.Header.Type == syscall.NLMSG_ERROR {
|
||||||
|
error := int32(native.Uint32(m.Data[0:4]))
|
||||||
|
if error == 0 {
|
||||||
|
break done
|
||||||
|
}
|
||||||
|
return nil, syscall.Errno(-error)
|
||||||
|
}
|
||||||
|
if m.Header.Type != syscall.RTM_NEWROUTE {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var r Route
|
||||||
|
|
||||||
|
msg := (*RtMsg)(unsafe.Pointer(&m.Data[0:syscall.SizeofRtMsg][0]))
|
||||||
|
|
||||||
|
if msg.Flags&syscall.RTM_F_CLONED != 0 {
|
||||||
|
// Ignore cloned routes
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.Table != syscall.RT_TABLE_MAIN {
|
||||||
|
// Ignore non-main tables
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.Family != syscall.AF_INET {
|
||||||
|
// Ignore non-ipv4 routes
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.Dst_len == 0 {
|
||||||
|
// Default routes
|
||||||
|
r.Default = true
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs, err := syscall.ParseNetlinkRouteAttr(&m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, attr := range attrs {
|
||||||
|
switch attr.Attr.Type {
|
||||||
|
case syscall.RTA_DST:
|
||||||
|
ip := attr.Value
|
||||||
|
r.IPNet = &net.IPNet{
|
||||||
|
IP: ip,
|
||||||
|
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(ip)),
|
||||||
|
}
|
||||||
|
case syscall.RTA_OIF:
|
||||||
|
index := int(native.Uint32(attr.Value[0:4]))
|
||||||
|
r.Iface, _ = net.InterfaceByIndex(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.Default || r.IPNet != nil {
|
||||||
|
res = append(res, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIfSocket() (fd int, err error) {
|
||||||
|
for _, socket := range []int{
|
||||||
|
syscall.AF_INET,
|
||||||
|
syscall.AF_PACKET,
|
||||||
|
syscall.AF_INET6,
|
||||||
|
} {
|
||||||
|
if fd, err = syscall.Socket(socket, syscall.SOCK_DGRAM, 0); err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
return fd, nil
|
||||||
|
}
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkChangeName(iface *net.Interface, newName string) error {
|
||||||
|
fd, err := getIfSocket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer syscall.Close(fd)
|
||||||
|
|
||||||
|
data := [IFNAMSIZ * 2]byte{}
|
||||||
|
// the "-1"s here are very important for ensuring we get proper null
|
||||||
|
// termination of our new C strings
|
||||||
|
copy(data[:IFNAMSIZ-1], iface.Name)
|
||||||
|
copy(data[IFNAMSIZ:IFNAMSIZ*2-1], newName)
|
||||||
|
|
||||||
|
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&data[0]))); errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkCreateVethPair(name1, name2 string) error {
|
||||||
|
s, err := getNetlinkSocket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
|
||||||
|
|
||||||
|
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
||||||
|
wb.AddData(msg)
|
||||||
|
|
||||||
|
nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name1))
|
||||||
|
wb.AddData(nameData)
|
||||||
|
|
||||||
|
nest1 := newRtAttr(syscall.IFLA_LINKINFO, nil)
|
||||||
|
newRtAttrChild(nest1, IFLA_INFO_KIND, zeroTerminated("veth"))
|
||||||
|
nest2 := newRtAttrChild(nest1, IFLA_INFO_DATA, nil)
|
||||||
|
nest3 := newRtAttrChild(nest2, VETH_INFO_PEER, nil)
|
||||||
|
|
||||||
|
newIfInfomsgChild(nest3, syscall.AF_UNSPEC)
|
||||||
|
newRtAttrChild(nest3, syscall.IFLA_IFNAME, zeroTerminated(name2))
|
||||||
|
|
||||||
|
wb.AddData(nest1)
|
||||||
|
|
||||||
|
if err := s.Send(wb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.HandleAck(wb.Seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the actual bridge device. This is more backward-compatible than
|
||||||
|
// netlink.NetworkLinkAdd and works on RHEL 6.
|
||||||
|
func CreateBridge(name string, setMacAddr bool) error {
|
||||||
|
s, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_IP)
|
||||||
|
if err != nil {
|
||||||
|
// ipv6 issue, creating with ipv4
|
||||||
|
s, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_IP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer syscall.Close(s)
|
||||||
|
|
||||||
|
nameBytePtr, err := syscall.BytePtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), SIOC_BRADDBR, uintptr(unsafe.Pointer(nameBytePtr))); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if setMacAddr {
|
||||||
|
return setBridgeMacAddress(s, name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a slave to abridge device. This is more backward-compatible than
|
||||||
|
// netlink.NetworkSetMaster and works on RHEL 6.
|
||||||
|
func AddToBridge(iface, master *net.Interface) error {
|
||||||
|
s, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_IP)
|
||||||
|
if err != nil {
|
||||||
|
// ipv6 issue, creating with ipv4
|
||||||
|
s, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_IP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer syscall.Close(s)
|
||||||
|
|
||||||
|
ifr := ifreqIndex{}
|
||||||
|
copy(ifr.IfrnName[:], master.Name)
|
||||||
|
ifr.IfruIndex = int32(iface.Index)
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), SIOC_BRADDIF, uintptr(unsafe.Pointer(&ifr))); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setBridgeMacAddress(s int, name string) error {
|
||||||
|
ifr := ifreqHwaddr{}
|
||||||
|
ifr.IfruHwaddr.Family = syscall.ARPHRD_ETHER
|
||||||
|
copy(ifr.IfrnName[:], name)
|
||||||
|
|
||||||
|
for i := 0; i < 6; i++ {
|
||||||
|
ifr.IfruHwaddr.Data[i] = int8(rand.Intn(255))
|
||||||
|
}
|
||||||
|
|
||||||
|
ifr.IfruHwaddr.Data[0] &^= 0x1 // clear multicast bit
|
||||||
|
ifr.IfruHwaddr.Data[0] |= 0x2 // set local assignment bit (IEEE802)
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), syscall.SIOCSIFHWADDR, uintptr(unsafe.Pointer(&ifr))); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
69
third_party/github.com/dotcloud/docker/pkg/netlink/netlink_unsupported.go
vendored
Normal file
69
third_party/github.com/dotcloud/docker/pkg/netlink/netlink_unsupported.go
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// +build !linux !amd64
|
||||||
|
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotImplemented = errors.New("not implemented")
|
||||||
|
)
|
||||||
|
|
||||||
|
func NetworkGetRoutes() ([]Route, error) {
|
||||||
|
return nil, ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkAdd(name string, linkType string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkUp(iface *net.Interface) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddDefaultGw(ip net.IP) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetMTU(iface *net.Interface, mtu int) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkCreateVethPair(name1, name2 string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkChangeName(iface *net.Interface, newName string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetNsFd(iface *net.Interface, fd int) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetNsPid(iface *net.Interface, nspid int) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetMaster(iface, master *net.Interface) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkDown(iface *net.Interface) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateBridge(name string, setMacAddr bool) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddToBridge(iface, master *net.Interface) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
@ -4,8 +4,8 @@ Requires=system-config.target
|
|||||||
After=system-config.target
|
After=system-config.target
|
||||||
|
|
||||||
# Watch for configs at a couple common paths
|
# Watch for configs at a couple common paths
|
||||||
Requires=user-cloudinit@media-configdrive-openstack-latest-user_data.path
|
Requires=user-configdrive.path
|
||||||
After=user-cloudinit@media-configdrive-openstack-latest-user_data.path
|
After=user-configdrive.path
|
||||||
Requires=user-cloudinit@var-lib-coreos\x2dinstall-user_data.path
|
Requires=user-cloudinit@var-lib-coreos\x2dinstall-user_data.path
|
||||||
After=user-cloudinit@var-lib-coreos\x2dinstall-user_data.path
|
After=user-cloudinit@var-lib-coreos\x2dinstall-user_data.path
|
||||||
|
|
||||||
|
5
units/user-configdrive.path
Normal file
5
units/user-configdrive.path
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Watch for a cloud-config at /media/configdrive
|
||||||
|
|
||||||
|
[Path]
|
||||||
|
DirectoryNotEmpty=/media/configdrive
|
@ -1,9 +1,8 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Load cloud-config from %f
|
Description=Load cloud-config from /media/configdrive
|
||||||
Requires=coreos-setup-environment.service
|
Requires=coreos-setup-environment.service
|
||||||
After=coreos-setup-environment.service
|
After=coreos-setup-environment.service
|
||||||
Before=user-config.target
|
Before=user-config.target
|
||||||
ConditionFileNotEmpty=%f
|
|
||||||
|
|
||||||
# HACK: work around ordering between config drive and ec2 metadata It is
|
# HACK: work around ordering between config drive and ec2 metadata It is
|
||||||
# possible for OpenStack style systems to provide both the metadata service
|
# possible for OpenStack style systems to provide both the metadata service
|
||||||
@ -21,4 +20,4 @@ After=ec2-cloudinit.service
|
|||||||
Type=oneshot
|
Type=oneshot
|
||||||
RemainAfterExit=yes
|
RemainAfterExit=yes
|
||||||
EnvironmentFile=-/etc/environment
|
EnvironmentFile=-/etc/environment
|
||||||
ExecStart=/usr/bin/coreos-cloudinit --from-file=%f
|
ExecStart=/usr/bin/coreos-cloudinit --from-configdrive=/media/configdrive
|
Loading…
Reference in New Issue
Block a user