Merge pull request #124 from crawford/networkd
feat(networkd): Adding support for debian-interface-to-networkd conversion
This commit is contained in:
		| @@ -1,23 +1,21 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path" | ||||
|  | ||||
| 	"github.com/coreos/coreos-cloudinit/datasource" | ||||
| 	"github.com/coreos/coreos-cloudinit/initialize" | ||||
| 	"github.com/coreos/coreos-cloudinit/network" | ||||
| 	"github.com/coreos/coreos-cloudinit/system" | ||||
| ) | ||||
|  | ||||
| const version = "0.7.3+git" | ||||
|  | ||||
| func init() { | ||||
| 	//Removes timestamp since it is displayed already during booting | ||||
| 	log.SetFlags(0) | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	var printVersion bool | ||||
| 	flag.BoolVar(&printVersion, "version", false, "Print the version and exit") | ||||
| @@ -28,12 +26,18 @@ func main() { | ||||
| 	var file string | ||||
| 	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 | ||||
| 	flag.StringVar(&url, "from-url", "", "Download user-data from provided url") | ||||
|  | ||||
| 	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)) | ||||
|  | ||||
| 	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 | ||||
| 	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) | ||||
| 	} else if url != "" { | ||||
| 		ds = datasource.NewMetadataService(url) | ||||
| 	} else if configdrive != "" { | ||||
| 		ds = datasource.NewConfigDrive(configdrive) | ||||
| 	} else if useProcCmdline { | ||||
| 		ds = datasource.NewProcCmdline() | ||||
| 	} 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) | ||||
| 	} | ||||
|  | ||||
| 	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() | ||||
| 	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 { | ||||
| 			os.Exit(0) | ||||
| 		} else { | ||||
| @@ -70,29 +89,41 @@ func main() { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(userdataBytes) == 0 { | ||||
| 		log.Printf("No user data to handle, exiting.") | ||||
| 		os.Exit(0) | ||||
| 	env := initialize.NewEnvironment("/", workspace) | ||||
| 	if len(userdataBytes) > 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) | ||||
|  | ||||
| 	parsed, err := initialize.ParseUserData(userdata) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Failed parsing user-data: %v", err) | ||||
| 		if ignoreFailure { | ||||
| 			os.Exit(0) | ||||
| 		} else { | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		fmt.Printf("Failed parsing user-data: %v\n", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	err = initialize.PrepWorkspace(env.Workspace()) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed preparing workspace: %v", err) | ||||
| 		fmt.Printf("Failed preparing workspace: %v\n", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	switch t := parsed.(type) { | ||||
| @@ -104,11 +135,54 @@ func main() { | ||||
| 		if err == nil { | ||||
| 			var name string | ||||
| 			name, err = system.ExecuteScript(path) | ||||
| 			initialize.PersistUnitNameInWorkspace(name, workspace) | ||||
| 			initialize.PersistUnitNameInWorkspace(name, env.Workspace()) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed resolving user-data: %v", err) | ||||
| 	} | ||||
| 	return 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 | ||||
|  | ||||
| declare -a TESTPKGS=(initialize system datasource pkg) | ||||
| declare -a TESTPKGS=(initialize system datasource pkg network) | ||||
|  | ||||
| if [ -z "$PKG" ]; then | ||||
| 	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 | ||||
|  | ||||
| # Watch for configs at a couple common paths | ||||
| Requires=user-cloudinit@media-configdrive-openstack-latest-user_data.path | ||||
| After=user-cloudinit@media-configdrive-openstack-latest-user_data.path | ||||
| Requires=user-configdrive.path | ||||
| After=user-configdrive.path | ||||
| Requires=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] | ||||
| Description=Load cloud-config from %f | ||||
| Description=Load cloud-config from /media/configdrive | ||||
| Requires=coreos-setup-environment.service | ||||
| After=coreos-setup-environment.service | ||||
| Before=user-config.target | ||||
| ConditionFileNotEmpty=%f | ||||
| 
 | ||||
| # HACK: work around ordering between config drive and ec2 metadata It is | ||||
| # possible for OpenStack style systems to provide both the metadata service | ||||
| @@ -21,4 +20,4 @@ After=ec2-cloudinit.service | ||||
| Type=oneshot | ||||
| RemainAfterExit=yes | ||||
| EnvironmentFile=-/etc/environment | ||||
| ExecStart=/usr/bin/coreos-cloudinit --from-file=%f | ||||
| ExecStart=/usr/bin/coreos-cloudinit --from-configdrive=/media/configdrive | ||||
		Reference in New Issue
	
	Block a user