From 691f7cb42c6136af58be7cc41b1d9296e8b3892f Mon Sep 17 00:00:00 2001 From: Josh Curl Date: Wed, 19 Oct 2016 16:21:35 -0700 Subject: [PATCH] Move in code from netconf and docker-from-scratch --- cmd/cloudinitsave/cloudinitsave.go | 6 +- cmd/cloudinitsave/packet.go | 28 +- cmd/network/network.go | 2 +- config/types.go | 44 +- dfs/one.go | 24 + dfs/scratch.go | 727 +++++++++++++++++++++++++++++ init/bootstrap.go | 4 +- init/init.go | 16 +- init/root.go | 4 +- main.go | 4 +- netconf/bonding.go | 143 ++++++ netconf/bridge.go | 48 ++ netconf/ipv4ll_linux.go | 75 +++ netconf/netconf_linux.go | 396 ++++++++++++++++ netconf/vlan.go | 79 ++++ selinux/selinux_linux.go | 5 + 16 files changed, 1567 insertions(+), 38 deletions(-) create mode 100644 dfs/one.go create mode 100644 dfs/scratch.go create mode 100644 netconf/bonding.go create mode 100644 netconf/bridge.go create mode 100644 netconf/ipv4ll_linux.go create mode 100644 netconf/netconf_linux.go create mode 100644 netconf/vlan.go diff --git a/cmd/cloudinitsave/cloudinitsave.go b/cmd/cloudinitsave/cloudinitsave.go index f1b0503a..5ced8b93 100644 --- a/cmd/cloudinitsave/cloudinitsave.go +++ b/cmd/cloudinitsave/cloudinitsave.go @@ -36,9 +36,9 @@ import ( "github.com/coreos/coreos-cloudinit/datasource/proc_cmdline" "github.com/coreos/coreos-cloudinit/datasource/url" "github.com/coreos/coreos-cloudinit/pkg" - "github.com/rancher/netconf" "github.com/rancher/os/cmd/cloudinitsave/gce" rancherConfig "github.com/rancher/os/config" + "github.com/rancher/os/netconf" "github.com/rancher/os/util" ) @@ -231,8 +231,8 @@ func getDatasources(cfg *rancherConfig.CloudConfig) []datasource.Datasource { } func enableDoLinkLocal() { - err := netconf.ApplyNetworkConfigs(&netconf.NetworkConfig{ - Interfaces: map[string]netconf.InterfaceConfig{ + err := netconf.ApplyNetworkConfigs(&rancherConfig.NetworkConfig{ + Interfaces: map[string]rancherConfig.InterfaceConfig{ "eth0": { IPV4LL: true, }, diff --git a/cmd/cloudinitsave/packet.go b/cmd/cloudinitsave/packet.go index 6e093907..b11df9e1 100644 --- a/cmd/cloudinitsave/packet.go +++ b/cmd/cloudinitsave/packet.go @@ -12,11 +12,11 @@ import ( "github.com/Sirupsen/logrus" "github.com/packethost/packngo/metadata" - "github.com/rancher/netconf" - rancherConfig "github.com/rancher/os/config" + "github.com/rancher/os/config" + "github.com/rancher/os/netconf" ) -func enablePacketNetwork(cfg *rancherConfig.RancherConfig) { +func enablePacketNetwork(cfg *config.RancherConfig) { bootStrapped := false for _, v := range cfg.Network.Interfaces { if v.Address != "" { @@ -40,7 +40,7 @@ func enablePacketNetwork(cfg *rancherConfig.RancherConfig) { return } - bondCfg := netconf.InterfaceConfig{ + bondCfg := config.InterfaceConfig{ Addresses: []string{}, BondOpts: map[string]string{ "lacp_rate": "1", @@ -51,11 +51,11 @@ func enablePacketNetwork(cfg *rancherConfig.RancherConfig) { "mode": "4", }, } - netCfg := netconf.NetworkConfig{ - Interfaces: map[string]netconf.InterfaceConfig{}, + netCfg := config.NetworkConfig{ + Interfaces: map[string]config.InterfaceConfig{}, } for _, iface := range m.Network.Interfaces { - netCfg.Interfaces["mac="+iface.Mac] = netconf.InterfaceConfig{ + netCfg.Interfaces["mac="+iface.Mac] = config.InterfaceConfig{ Bond: "bond0", } } @@ -80,24 +80,24 @@ func enablePacketNetwork(cfg *rancherConfig.RancherConfig) { b, _ := yaml.Marshal(netCfg) logrus.Debugf("Generated network config: %s", string(b)) - cc := rancherConfig.CloudConfig{ - Rancher: rancherConfig.RancherConfig{ + cc := config.CloudConfig{ + Rancher: config.RancherConfig{ Network: netCfg, }, } // Post to phone home URL on first boot - if _, err = os.Stat(rancherConfig.CloudConfigNetworkFile); err != nil { + if _, err = os.Stat(config.CloudConfigNetworkFile); err != nil { if _, err = http.Post(m.PhoneHomeURL, "application/json", bytes.NewReader([]byte{})); err != nil { logrus.Errorf("Failed to post to Packet phone home URL: %v", err) } } - if err := os.MkdirAll(path.Dir(rancherConfig.CloudConfigNetworkFile), 0700); err != nil { - logrus.Errorf("Failed to create directory for file %s: %v", rancherConfig.CloudConfigNetworkFile, err) + if err := os.MkdirAll(path.Dir(config.CloudConfigNetworkFile), 0700); err != nil { + logrus.Errorf("Failed to create directory for file %s: %v", config.CloudConfigNetworkFile, err) } - if err := rancherConfig.WriteToFile(cc, rancherConfig.CloudConfigNetworkFile); err != nil { - logrus.Errorf("Failed to save config file %s: %v", rancherConfig.CloudConfigNetworkFile, err) + if err := config.WriteToFile(cc, config.CloudConfigNetworkFile); err != nil { + logrus.Errorf("Failed to save config file %s: %v", config.CloudConfigNetworkFile, err) } } diff --git a/cmd/network/network.go b/cmd/network/network.go index 87a8d3d6..1e26b395 100644 --- a/cmd/network/network.go +++ b/cmd/network/network.go @@ -9,10 +9,10 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/libnetwork/resolvconf" - "github.com/rancher/netconf" "github.com/rancher/os/config" "github.com/rancher/os/docker" "github.com/rancher/os/hostname" + "github.com/rancher/os/netconf" ) var ( diff --git a/config/types.go b/config/types.go index 12504af8..142c6388 100644 --- a/config/types.go +++ b/config/types.go @@ -7,7 +7,6 @@ import ( "github.com/coreos/coreos-cloudinit/config" "github.com/docker/engine-api/types" composeConfig "github.com/docker/libcompose/config" - "github.com/rancher/netconf" ) const ( @@ -113,8 +112,8 @@ type RancherConfig struct { Disable []string `yaml:"disable,omitempty"` ServicesInclude map[string]bool `yaml:"services_include,omitempty"` Modules []string `yaml:"modules,omitempty"` - Network netconf.NetworkConfig `yaml:"network,omitempty"` - DefaultNetwork netconf.NetworkConfig `yaml:"default_network,omitempty"` + Network NetworkConfig `yaml:"network,omitempty"` + DefaultNetwork NetworkConfig `yaml:"default_network,omitempty"` Repositories Repositories `yaml:"repositories,omitempty"` Ssh SshConfig `yaml:"ssh,omitempty"` State StateConfig `yaml:"state,omitempty"` @@ -169,6 +168,39 @@ type DockerConfig struct { Exec bool `yaml:"exec,omitempty"` } +type NetworkConfig struct { + PreCmds []string `yaml:"pre_cmds,omitempty"` + Dns DnsConfig `yaml:"dns,omitempty"` + Interfaces map[string]InterfaceConfig `yaml:"interfaces,omitempty"` + PostCmds []string `yaml:"post_cmds,omitempty"` + HttpProxy string `yaml:"http_proxy,omitempty"` + HttpsProxy string `yaml:"https_proxy,omitempty"` + NoProxy string `yaml:"no_proxy,omitempty"` +} + +type InterfaceConfig struct { + Match string `yaml:"match,omitempty"` + DHCP bool `yaml:"dhcp,omitempty"` + DHCPArgs string `yaml:"dhcp_args,omitempty"` + Address string `yaml:"address,omitempty"` + Addresses []string `yaml:"addresses,omitempty"` + IPV4LL bool `yaml:"ipv4ll,omitempty"` + Gateway string `yaml:"gateway,omitempty"` + GatewayIpv6 string `yaml:"gateway_ipv6,omitempty"` + MTU int `yaml:"mtu,omitempty"` + Bridge string `yaml:"bridge,omitempty"` + Bond string `yaml:"bond,omitempty"` + BondOpts map[string]string `yaml:"bond_opts,omitempty"` + PostUp []string `yaml:"post_up,omitempty"` + PreUp []string `yaml:"pre_up,omitempty"` + Vlans string `yaml:"vlans,omitempty"` +} + +type DnsConfig struct { + Nameservers []string `yaml:"nameservers,flow,omitempty"` + Search []string `yaml:"search,flow,omitempty"` +} + type SshConfig struct { Keys map[string]string `yaml:"keys,omitempty"` } @@ -191,9 +223,9 @@ type CloudInit struct { } type Defaults struct { - Hostname string `yaml:"hostname,omitempty"` - Docker DockerConfig `yaml:"docker,omitempty"` - Network netconf.NetworkConfig `yaml:"network,omitempty"` + Hostname string `yaml:"hostname,omitempty"` + Docker DockerConfig `yaml:"docker,omitempty"` + Network NetworkConfig `yaml:"network,omitempty"` } func (r Repositories) ToArray() []string { diff --git a/dfs/one.go b/dfs/one.go new file mode 100644 index 00000000..72e4ec58 --- /dev/null +++ b/dfs/one.go @@ -0,0 +1,24 @@ +// +build linux + +package dfs + +import ( + "os" + "os/signal" + "syscall" +) + +func PidOne() error { + c := make(chan os.Signal, 2048) + signal.Notify(c, syscall.SIGCHLD) + + for range c { + for { + if pid, err := syscall.Wait4(-1, nil, syscall.WNOHANG, nil); err != nil || pid <= 0 { + break + } + } + } + + return nil +} diff --git a/dfs/scratch.go b/dfs/scratch.go new file mode 100644 index 00000000..d57d80aa --- /dev/null +++ b/dfs/scratch.go @@ -0,0 +1,727 @@ +package dfs + +import ( + "bufio" + "io" + "io/ioutil" + "os" + "os/exec" + "path" + "strconv" + "strings" + "syscall" + + log "github.com/Sirupsen/logrus" + "github.com/docker/libnetwork/resolvconf" + "github.com/rancher/os/config" + "github.com/rancher/os/netconf" + "github.com/rancher/os/selinux" + "github.com/rancher/os/util" +) + +const ( + defaultPrefix = "/usr" + iptables = "/sbin/iptables" + modprobe = "/sbin/modprobe" + distSuffix = ".dist" +) + +var ( + mounts = [][]string{ + {"devtmpfs", "/dev", "devtmpfs", ""}, + {"none", "/dev/pts", "devpts", ""}, + {"shm", "/dev/shm", "tmpfs", "rw,nosuid,nodev,noexec,relatime,size=65536k"}, + {"mqueue", "/dev/mqueue", "mqueue", "rw,nosuid,nodev,noexec,relatime"}, + {"none", "/proc", "proc", ""}, + {"none", "/run", "tmpfs", ""}, + {"none", "/sys", "sysfs", ""}, + {"none", "/sys/fs/cgroup", "tmpfs", ""}, + } + optionalMounts = [][]string{ + {"none", "/sys/fs/selinux", "selinuxfs", ""}, + } +) + +type Config struct { + Fork bool + PidOne bool + CommandName string + DnsConfig config.DnsConfig + BridgeName string + BridgeAddress string + BridgeMtu int + CgroupHierarchy map[string]string + LogFile string + NoLog bool + NoFiles uint64 + Environment []string + GraphDirectory string + DaemonConfig string +} + +func createMounts(mounts ...[]string) error { + for _, mount := range mounts { + log.Debugf("Mounting %s %s %s %s", mount[0], mount[1], mount[2], mount[3]) + err := util.Mount(mount[0], mount[1], mount[2], mount[3]) + if err != nil { + return err + } + } + + return nil +} + +func createOptionalMounts(mounts ...[]string) { + for _, mount := range mounts { + log.Debugf("Mounting %s %s %s %s", mount[0], mount[1], mount[2], mount[3]) + err := util.Mount(mount[0], mount[1], mount[2], mount[3]) + if err != nil { + log.Debugf("Unable to mount %s %s %s %s: %s", mount[0], mount[1], mount[2], mount[3], err) + } + } +} + +func createDirs(dirs ...string) error { + for _, dir := range dirs { + if _, err := os.Stat(dir); os.IsNotExist(err) { + log.Debugf("Creating %s", dir) + err = os.MkdirAll(dir, 0755) + if err != nil { + return err + } + } + } + + return nil +} + +func mountCgroups(hierarchyConfig map[string]string) error { + f, err := os.Open("/proc/cgroups") + if err != nil { + return err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + + hierarchies := make(map[string][]string) + + for scanner.Scan() { + text := scanner.Text() + log.Debugf("/proc/cgroups: %s", text) + fields := strings.Split(text, "\t") + cgroup := fields[0] + if cgroup == "" || cgroup[0] == '#' || (len(fields) > 3 && fields[3] == "0") { + continue + } + + hierarchy := hierarchyConfig[cgroup] + if hierarchy == "" { + hierarchy = fields[1] + } + + if hierarchy == "0" { + hierarchy = cgroup + } + + hierarchies[hierarchy] = append(hierarchies[hierarchy], cgroup) + } + + for _, hierarchy := range hierarchies { + if err := mountCgroup(strings.Join(hierarchy, ",")); err != nil { + return err + } + } + + if err = scanner.Err(); err != nil { + return err + } + + log.Debug("Done mouting cgroupfs") + return nil +} + +func CreateSymlinks(pathSets [][]string) error { + for _, paths := range pathSets { + if err := CreateSymlink(paths[0], paths[1]); err != nil { + return err + } + } + + return nil +} + +func CreateSymlink(src, dest string) error { + if _, err := os.Lstat(dest); os.IsNotExist(err) { + log.Debugf("Symlinking %s => %s", dest, src) + if err = os.Symlink(src, dest); err != nil { + return err + } + } + + return nil +} + +func mountCgroup(cgroup string) error { + if err := createDirs("/sys/fs/cgroup/" + cgroup); err != nil { + return err + } + + if err := createMounts([][]string{{"none", "/sys/fs/cgroup/" + cgroup, "cgroup", cgroup}}...); err != nil { + return err + } + + parts := strings.Split(cgroup, ",") + if len(parts) > 1 { + for _, part := range parts { + if err := CreateSymlink("/sys/fs/cgroup/"+cgroup, "/sys/fs/cgroup/"+part); err != nil { + return err + } + } + } + + return nil +} + +func execDocker(config *Config, docker, cmd string, args []string) (*exec.Cmd, error) { + if len(args) > 0 && args[0] == "docker" { + args = args[1:] + } + log.Debugf("Launching Docker %s %s %v", docker, cmd, args) + + env := os.Environ() + if len(config.Environment) != 0 { + env = append(env, config.Environment...) + } + + if config.Fork { + cmd := exec.Command(docker, args...) + if !config.NoLog { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } + cmd.Env = env + err := cmd.Start() + if err != nil { + return cmd, err + } + if config.PidOne { + PidOne() + } + return cmd, err + } else { + err := syscall.Exec(expand(docker), append([]string{cmd}, args...), env) + return nil, err + } +} + +func copyDefault(folder, name string) error { + defaultFile := path.Join(defaultPrefix, folder, name) + if err := CopyFile(defaultFile, folder, name); err != nil { + return err + } + + return nil +} + +func copyDefaultFolder(folder string) error { + log.Debugf("Copying folder %s", folder) + defaultFolder := path.Join(defaultPrefix, folder) + files, _ := ioutil.ReadDir(defaultFolder) + for _, file := range files { + var err error + if file.IsDir() { + err = copyDefaultFolder(path.Join(folder, file.Name())) + } else { + err = copyDefault(folder, file.Name()) + } + if err != nil { + return err + } + } + + return nil +} + +func defaultFiles(files ...string) error { + for _, file := range files { + dir := path.Dir(file) + name := path.Base(file) + if err := copyDefault(dir, name); err != nil { + return err + } + } + + return nil +} + +func defaultFolders(folders ...string) error { + for _, folder := range folders { + if err := copyDefaultFolder(folder); err != nil { + return err + } + } + + return nil +} + +func CopyFile(src, folder, name string) error { + if _, err := os.Lstat(src); os.IsNotExist(err) { + log.Debugf("Not copying %s, does not exists", src) + return nil + } + + dst := path.Join(folder, name) + if _, err := os.Lstat(dst); err == nil { + log.Debugf("Not copying %s => %s already exists", src, dst) + return nil + } + + if err := createDirs(folder); err != nil { + return err + } + + stat, err := os.Lstat(src) + if err != nil { + return err + } + + if stat.Mode()&os.ModeSymlink != 0 { + symDst, err := os.Readlink(src) + if err != nil { + log.Errorf("Failed to readlink: %v", err) + return err + } + // file is a symlink + log.Debugf("Symlinking %s => %s", dst, symDst) + return os.Symlink(symDst, dst) + } + + srcFile, err := os.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + + dstFile, err := os.Create(dst) + if err != nil { + return err + } + defer dstFile.Close() + + log.Debugf("Copying %s => %s", src, dst) + _, err = io.Copy(dstFile, srcFile) + return err +} + +func tryCreateFile(name, content string) error { + if _, err := os.Stat(name); err == nil { + return nil + } + + if err := createDirs(path.Dir(name)); err != nil { + return err + } + + return ioutil.WriteFile(name, []byte(content), 0644) +} + +func createPasswd() error { + return tryCreateFile("/etc/passwd", "root:x:0:0:root:/root:/bin/sh\n") +} + +func createGroup() error { + return tryCreateFile("/etc/group", "root:x:0:\n") +} + +func setupNetworking(cfg *Config) error { + if cfg == nil { + return nil + } + + hostname, err := os.Hostname() + if err != nil { + return err + } + tryCreateFile("/etc/hosts", `127.0.0.1 localhost +::1 localhost ip6-localhost ip6-loopback +fe00::0 ip6-localnet +ff00::0 ip6-mcastprefix +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters + +127.0.1.1 `+hostname) + + if len(cfg.DnsConfig.Nameservers) != 0 { + if _, err := resolvconf.Build("/etc/resolv.conf", cfg.DnsConfig.Nameservers, cfg.DnsConfig.Search, nil); err != nil { + return err + } + } + + if cfg.BridgeName != "" && cfg.BridgeName != "none" { + log.Debugf("Creating bridge %s (%s)", cfg.BridgeName, cfg.BridgeAddress) + if err := netconf.ApplyNetworkConfigs(&config.NetworkConfig{ + Interfaces: map[string]config.InterfaceConfig{ + cfg.BridgeName: { + Address: cfg.BridgeAddress, + MTU: cfg.BridgeMtu, + Bridge: "true", + }, + }, + }); err != nil { + return err + } + } + + return nil +} + +func GetValue(index int, args []string) string { + val := args[index] + parts := strings.SplitN(val, "=", 2) + if len(parts) == 1 { + if len(args) > index+1 { + return args[index+1] + } else { + return "" + } + } else { + return parts[1] + } +} + +func ParseConfig(config *Config, args ...string) []string { + for i, arg := range args { + if strings.HasPrefix(arg, "--bip") { + config.BridgeAddress = GetValue(i, args) + } else if strings.HasPrefix(arg, "--fixed-cidr") { + config.BridgeAddress = GetValue(i, args) + } else if strings.HasPrefix(arg, "-b") || strings.HasPrefix(arg, "--bridge") { + config.BridgeName = GetValue(i, args) + } else if strings.HasPrefix(arg, "--config-file") { + config.DaemonConfig = GetValue(i, args) + } else if strings.HasPrefix(arg, "--mtu") { + mtu, err := strconv.Atoi(GetValue(i, args)) + if err != nil { + config.BridgeMtu = mtu + } + } else if strings.HasPrefix(arg, "-g") || strings.HasPrefix(arg, "--graph") { + config.GraphDirectory = GetValue(i, args) + } + } + + if config.BridgeName != "" && config.BridgeAddress != "" { + newArgs := []string{} + skip := false + for _, arg := range args { + if skip { + skip = false + continue + } + + if arg == "--bip" { + skip = true + continue + } else if strings.HasPrefix(arg, "--bip=") { + continue + } + + newArgs = append(newArgs, arg) + } + + args = newArgs + } + + return args +} + +func PrepareFs(config *Config) error { + if err := createMounts(mounts...); err != nil { + return err + } + + createOptionalMounts(optionalMounts...) + + if err := mountCgroups(config.CgroupHierarchy); err != nil { + return err + } + + if err := createLayout(config); err != nil { + return err + } + + if err := firstPrepare(); err != nil { + return err + } + + return nil +} + +func touchSocket(path string) error { + if err := syscall.Unlink(path); err != nil && !os.IsNotExist(err) { + return err + } + return ioutil.WriteFile(path, []byte{}, 0700) +} + +func touchSockets(args ...string) error { + touched := false + + for i, arg := range args { + if strings.HasPrefix(arg, "-H") { + val := GetValue(i, args) + if strings.HasPrefix(val, "unix://") { + val = val[len("unix://"):] + log.Debugf("Creating temp file at %s", val) + if err := touchSocket(val); err != nil { + return err + } + touched = true + } + } + } + + if !touched { + return touchSocket("/var/run/docker.sock") + } + + return nil +} + +func createDaemonConfig(config *Config) error { + if config.DaemonConfig == "" { + return nil + } + + if _, err := os.Stat(config.DaemonConfig); os.IsNotExist(err) { + if err := os.MkdirAll(path.Dir(config.DaemonConfig), 0755); err != nil { + return err + } + + return ioutil.WriteFile(config.DaemonConfig, []byte("{}"), 0600) + } + + return nil +} + +func cleanupFiles(graphDirectory string) { + zeroFiles := []string{ + "/etc/docker/key.json", + "/etc/docker/daemon.json", + "/etc/docker/system-daemon.json", + path.Join(graphDirectory, "image/overlay/repositories.json"), + } + + for _, file := range zeroFiles { + if stat, err := os.Stat(file); err == nil { + if stat.Size() < 2 { + log.Warnf("Deleting invalid json file: %s", file) + os.Remove(file) + } + } + } +} + +func createLayout(config *Config) error { + if err := createDirs("/tmp", "/root/.ssh", "/var", "/usr/lib"); err != nil { + return err + } + + graphDirectory := config.GraphDirectory + + if config.GraphDirectory == "" { + graphDirectory = "/var/lib/docker" + } + + if err := createDirs(graphDirectory); err != nil { + return err + } + + if err := createDaemonConfig(config); err != nil { + return err + } + + cleanupFiles(graphDirectory) + + selinux.SetFileContext(graphDirectory, "system_u:object_r:var_lib_t:s0") + + return CreateSymlinks([][]string{ + {"usr/lib", "/lib"}, + {"usr/sbin", "/sbin"}, + {"../run", "/var/run"}, + }) +} + +func firstPrepare() error { + os.Setenv("PATH", "/sbin:/usr/sbin:/usr/bin") + + if err := defaultFiles( + "/etc/ssl/certs/ca-certificates.crt", + "/etc/passwd", + "/etc/group", + ); err != nil { + return err + } + + if err := defaultFolders( + "/etc/docker", + "/etc/selinux", + "/etc/selinux/ros", + "/etc/selinux/ros/policy", + "/etc/selinux/ros/contexts", + "/var/lib/cni", + ); err != nil { + return err + } + + if err := createPasswd(); err != nil { + return err + } + + if err := createGroup(); err != nil { + return err + } + + return nil +} + +func secondPrepare(config *Config, docker string, args ...string) error { + + if err := setupNetworking(config); err != nil { + return err + } + + if err := touchSockets(args...); err != nil { + return err + } + + if err := setupLogging(config); err != nil { + return err + } + + for _, i := range []string{docker, iptables, modprobe} { + if err := setupBin(config, i); err != nil { + return err + } + } + + if err := setUlimit(config); err != nil { + return err + } + + ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1"), 0655) + + return nil +} + +func expand(bin string) string { + expanded, err := exec.LookPath(bin) + if err == nil { + return expanded + } + return bin +} + +func setupBin(config *Config, bin string) error { + expanded, err := exec.LookPath(bin) + if err == nil { + return nil + } + + expanded, err = exec.LookPath(bin + distSuffix) + if err != nil { + // Purposely not returning error + return nil + } + + return CreateSymlink(expanded, expanded[:len(expanded)-len(distSuffix)]) +} + +func setupLogging(config *Config) error { + if config.LogFile == "" { + return nil + } + + if err := createDirs(path.Dir(config.LogFile)); err != nil { + return err + } + + output, err := os.OpenFile(config.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + return err + } + + syscall.Dup3(int(output.Fd()), int(os.Stdout.Fd()), 0) + syscall.Dup3(int(output.Fd()), int(os.Stderr.Fd()), 0) + + return nil +} + +func setUlimit(cfg *Config) error { + var rLimit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil { + return err + } + if cfg.NoFiles == 0 { + rLimit.Max = 1000000 + } else { + rLimit.Max = cfg.NoFiles + } + rLimit.Cur = rLimit.Max + return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) +} + +func runOrExec(config *Config, docker string, args ...string) (*exec.Cmd, error) { + if err := secondPrepare(config, docker, args...); err != nil { + return nil, err + } + + cmd := path.Base(docker) + if config != nil && config.CommandName != "" { + cmd = config.CommandName + } + + if cmd == "dockerd" && len(args) > 1 && args[0] == "daemon" { + args = args[1:] + } + + return execDocker(config, docker, cmd, args) +} + +func LaunchDocker(config *Config, docker string, args ...string) (*exec.Cmd, error) { + if err := PrepareFs(config); err != nil { + return nil, err + } + + return runOrExec(config, docker, args...) +} + +func Main() { + if os.Getenv("DOCKER_LAUNCH_DEBUG") == "true" { + log.SetLevel(log.DebugLevel) + } + + if len(os.Args) < 2 { + log.Fatalf("Usage Example: %s /usr/bin/docker -d -D", os.Args[0]) + } + + args := []string{} + if len(os.Args) > 1 { + args = os.Args[2:] + } + + var config Config + args = ParseConfig(&config, args...) + + if os.Getenv("DOCKER_LAUNCH_REAP") == "true" { + config.Fork = true + config.PidOne = true + } + + log.Debugf("Launch config %#v", config) + + _, err := LaunchDocker(&config, os.Args[1], args...) + if err != nil { + log.Fatal(err) + } +} diff --git a/init/bootstrap.go b/init/bootstrap.go index 9b4f35f1..4ab02dd3 100644 --- a/init/bootstrap.go +++ b/init/bootstrap.go @@ -6,9 +6,9 @@ import ( "strings" log "github.com/Sirupsen/logrus" - "github.com/rancher/docker-from-scratch" "github.com/rancher/os/compose" "github.com/rancher/os/config" + "github.com/rancher/os/dfs" "github.com/rancher/os/util" ) @@ -36,7 +36,7 @@ func startDocker(cfg *config.CloudConfig) (chan interface{}, error) { launchConfig.LogFile = "" launchConfig.NoLog = true - cmd, err := dockerlaunch.LaunchDocker(launchConfig, config.SYSTEM_DOCKER_BIN, args...) + cmd, err := dfs.LaunchDocker(launchConfig, config.SYSTEM_DOCKER_BIN, args...) if err != nil { return nil, err } diff --git a/init/init.go b/init/init.go index 56f1fe7f..befd6924 100644 --- a/init/init.go +++ b/init/init.go @@ -12,8 +12,8 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/mount" - "github.com/rancher/docker-from-scratch" "github.com/rancher/os/config" + "github.com/rancher/os/dfs" "github.com/rancher/os/util" "github.com/rancher/os/util/network" ) @@ -27,7 +27,7 @@ const ( ) var ( - mountConfig = dockerlaunch.Config{ + mountConfig = dfs.Config{ CgroupHierarchy: map[string]string{ "cpu": "cpu", "cpuacct": "cpu", @@ -162,10 +162,10 @@ func tryMountAndBootstrap(cfg *config.CloudConfig) (*config.CloudConfig, error) return mountOem(cfg) } -func getLaunchConfig(cfg *config.CloudConfig, dockerCfg *config.DockerConfig) (*dockerlaunch.Config, []string) { - var launchConfig dockerlaunch.Config +func getLaunchConfig(cfg *config.CloudConfig, dockerCfg *config.DockerConfig) (*dfs.Config, []string) { + var launchConfig dfs.Config - args := dockerlaunch.ParseConfig(&launchConfig, dockerCfg.FullArgs()...) + args := dfs.ParseConfig(&launchConfig, dockerCfg.FullArgs()...) launchConfig.DnsConfig.Nameservers = cfg.Rancher.Defaults.Network.Dns.Nameservers launchConfig.DnsConfig.Search = cfg.Rancher.Defaults.Network.Dns.Search @@ -220,7 +220,7 @@ func RunInit() error { boot2DockerEnvironment := false initFuncs := []config.CfgFunc{ func(c *config.CloudConfig) (*config.CloudConfig, error) { - return c, dockerlaunch.PrepareFs(&mountConfig) + return c, dfs.PrepareFs(&mountConfig) }, mountOem, func(_ *config.CloudConfig) (*config.CloudConfig, error) { @@ -280,7 +280,7 @@ func RunInit() error { }, loadModules, func(c *config.CloudConfig) (*config.CloudConfig, error) { - return c, dockerlaunch.PrepareFs(&mountConfig) + return c, dfs.PrepareFs(&mountConfig) }, func(c *config.CloudConfig) (*config.CloudConfig, error) { network.SetProxyEnvironmentVariables(c) @@ -300,7 +300,7 @@ func RunInit() error { launchConfig.Fork = !cfg.Rancher.SystemDocker.Exec log.Info("Launching System Docker") - _, err = dockerlaunch.LaunchDocker(launchConfig, config.SYSTEM_DOCKER_BIN, args...) + _, err = dfs.LaunchDocker(launchConfig, config.SYSTEM_DOCKER_BIN, args...) if err != nil { return err } diff --git a/init/root.go b/init/root.go index 03c0e397..cf82408d 100644 --- a/init/root.go +++ b/init/root.go @@ -10,8 +10,8 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/archive" - "github.com/rancher/docker-from-scratch" "github.com/rancher/os/config" + "github.com/rancher/os/dfs" ) func cleanupTarget(rootfs, targetUsr, usr, usrVer, tmpDir string) (bool, error) { @@ -21,7 +21,7 @@ func cleanupTarget(rootfs, targetUsr, usr, usrVer, tmpDir string) (bool, error) return false, err } - if err := dockerlaunch.CreateSymlink(usrVer, path.Join(rootfs, "usr")); err != nil { + if err := dfs.CreateSymlink(usrVer, path.Join(rootfs, "usr")); err != nil { return false, err } diff --git a/main.go b/main.go index c225e282..b7fd0cd2 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "github.com/docker/docker/docker" "github.com/docker/docker/pkg/reexec" "github.com/rancher/cniglue" - "github.com/rancher/docker-from-scratch" "github.com/rancher/os/cmd/cloudinitexecute" "github.com/rancher/os/cmd/cloudinitsave" "github.com/rancher/os/cmd/console" @@ -20,6 +19,7 @@ import ( "github.com/rancher/os/cmd/systemdocker" "github.com/rancher/os/cmd/userdocker" "github.com/rancher/os/cmd/wait" + "github.com/rancher/os/dfs" osInit "github.com/rancher/os/init" ) @@ -30,7 +30,7 @@ var entrypoints = map[string]func(){ "console.sh": console.Main, "docker": docker.Main, "docker-init": dockerinit.Main, - "dockerlaunch": dockerlaunch.Main, + "dockerlaunch": dfs.Main, "halt": power.Halt, "init": osInit.MainInit, "netconf": network.Main, diff --git a/netconf/bonding.go b/netconf/bonding.go new file mode 100644 index 00000000..5f92c56d --- /dev/null +++ b/netconf/bonding.go @@ -0,0 +1,143 @@ +package netconf + +import ( + "io/ioutil" + "os" + "os/exec" + "strings" + "time" + + "github.com/Sirupsen/logrus" + "github.com/vishvananda/netlink" +) + +const ( + base = "/sys/class/net/" + bondingMasters = "/sys/class/net/bonding_masters" +) + +type Bonding struct { + name string +} + +func (b *Bonding) init() error { + _, err := os.Stat(bondingMasters) + if os.IsNotExist(err) { + logrus.Info("Loading bonding kernel module") + cmd := exec.Command("modprobe", "bonding") + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdin + err = cmd.Run() + if err != nil { + for i := 0; i < 30; i++ { + if _, err = os.Stat(bondingMasters); err == nil { + break + } + time.Sleep(100 * time.Millisecond) + } + } + } + _, err = os.Stat(bondingMasters) + return err +} + +func contains(file, word string) (bool, error) { + words, err := ioutil.ReadFile(file) + if err != nil { + return false, err + } + + for _, s := range strings.Split(strings.TrimSpace(string(words)), " ") { + if s == strings.TrimSpace(word) { + return true, nil + } + } + + return false, nil +} + +func (b *Bonding) linkDown() error { + link, err := netlink.LinkByName(b.name) + if err != nil { + return err + } + + return netlink.LinkSetDown(link) +} + +func (b *Bonding) ListSlaves() ([]string, error) { + file := base + b.name + "/bonding/slaves" + words, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + + result := []string{} + for _, s := range strings.Split(strings.TrimSpace(string(words)), " ") { + if s != "" { + result = append(result, s) + } + } + return result, nil +} + +func (b *Bonding) RemoveSlave(slave string) error { + if ok, err := contains(base+b.name+"/bonding/slaves", slave); err != nil { + return err + } else if !ok { + return nil + } + + p := base + b.name + "/bonding/slaves" + logrus.Infof("Removing slave %s from master %s", slave, b.name) + return ioutil.WriteFile(p, []byte("-"+slave), 0644) +} + +func (b *Bonding) AddSlave(slave string) error { + if ok, err := contains(base+b.name+"/bonding/slaves", slave); err != nil { + return err + } else if ok { + return nil + } + + p := base + b.name + "/bonding/slaves" + logrus.Infof("Adding slave %s to master %s", slave, b.name) + return ioutil.WriteFile(p, []byte("+"+slave), 0644) +} + +func (b *Bonding) Opt(key, value string) error { + if key == "mode" { + // Don't care about errors here + b.linkDown() + slaves, _ := b.ListSlaves() + for _, slave := range slaves { + b.RemoveSlave(slave) + } + } + + p := base + b.name + "/bonding/" + key + if err := ioutil.WriteFile(p, []byte(value), 0644); err != nil { + logrus.Errorf("Failed to set %s=%s on %s: %v", key, value, b.name, err) + return err + } else { + logrus.Infof("Set %s=%s on %s", key, value, b.name) + } + + return nil +} + +func Bond(name string) (*Bonding, error) { + b := &Bonding{name: name} + if err := b.init(); err != nil { + return nil, err + } + + if ok, err := contains(bondingMasters, name); err != nil { + return nil, err + } else if ok { + return b, nil + } + + logrus.Infof("Creating bond %s", name) + return b, ioutil.WriteFile(bondingMasters, []byte("+"+name), 0644) +} diff --git a/netconf/bridge.go b/netconf/bridge.go new file mode 100644 index 00000000..48604c3f --- /dev/null +++ b/netconf/bridge.go @@ -0,0 +1,48 @@ +package netconf + +import ( + "fmt" + + "github.com/vishvananda/netlink" +) + +type Bridge struct { + name string +} + +func NewBridge(name string) (*Bridge, error) { + b := &Bridge{name: name} + return b, b.init() +} + +func (b *Bridge) init() error { + link, err := netlink.LinkByName(b.name) + if err == nil { + if _, ok := link.(*netlink.Bridge); !ok { + return fmt.Errorf("%s is not a bridge device", b.name) + } + return nil + } + + bridge := netlink.Bridge{} + bridge.LinkAttrs.Name = b.name + + return netlink.LinkAdd(&bridge) +} + +func (b *Bridge) AddLink(link netlink.Link) error { + existing, err := netlink.LinkByName(b.name) + if err != nil { + return err + } + + if bridge, ok := existing.(*netlink.Bridge); ok { + if link.Attrs().MasterIndex != bridge.Index { + return netlink.LinkSetMaster(link, bridge) + } + } else { + return fmt.Errorf("%s is not a bridge", b.name) + } + + return nil +} diff --git a/netconf/ipv4ll_linux.go b/netconf/ipv4ll_linux.go new file mode 100644 index 00000000..b7ed8ec3 --- /dev/null +++ b/netconf/ipv4ll_linux.go @@ -0,0 +1,75 @@ +package netconf + +import ( + "encoding/binary" + "fmt" + "math/rand" + "net" + + log "github.com/Sirupsen/logrus" + + "github.com/j-keck/arping" + "github.com/vishvananda/netlink" +) + +func AssignLinkLocalIP(link netlink.Link) error { + ifaceName := link.Attrs().Name + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + log.Error("could not get information about interface") + return err + } + addrs, err := iface.Addrs() + if err != nil { + log.Error("Error fetching existing ip on interface") + } + for _, addr := range addrs { + if addr.String()[:7] == "169.254" { + log.Info("Link Local IP already set on interface") + return nil + } + } + randSource, err := getPseudoRandomGenerator(link.Attrs().HardwareAddr) + if err != nil { + return err + } + // try a random address upto 10 times + for i := 0; i < 10; i++ { + randGenerator := rand.New(*randSource) + randomNum := randGenerator.Uint32() + dstIP := getNewIPV4LLAddr(randomNum) + if dstIP[2] == 0 || dstIP[2] == 255 { + i-- + continue + } + _, _, err := arping.PingOverIfaceByName(dstIP, ifaceName) + if err != nil { + // this ip is not being used + addr, err := netlink.ParseAddr(dstIP.String() + "/16") + if err != nil { + log.Errorf("error while parsing ipv4ll addr, err = %v", err) + return err + } + if err := netlink.AddrAdd(link, addr); err != nil { + log.Error("ipv4ll addr add failed") + return err + } + log.Infof("Set %s on %s", dstIP.String(), link.Attrs().Name) + return nil + } + } + log.Error("Could not find a suitable ipv4ll") + return fmt.Errorf("Could not find a suitable ipv4ll") +} + +func getNewIPV4LLAddr(randomNum uint32) net.IP { + byte1 := randomNum & 255 // use least significant 8 bits + byte2 := randomNum >> 24 // use most significant 8 bits + return []byte{169, 254, byte(byte1), byte(byte2)} +} + +func getPseudoRandomGenerator(haAddr []byte) (*rand.Source, error) { + seed, _ := binary.Varint(haAddr) + src := rand.NewSource(seed) + return &src, nil +} diff --git a/netconf/netconf_linux.go b/netconf/netconf_linux.go new file mode 100644 index 00000000..57537f25 --- /dev/null +++ b/netconf/netconf_linux.go @@ -0,0 +1,396 @@ +package netconf + +import ( + "bytes" + "errors" + "net" + "os" + "os/exec" + "strings" + "sync" + "syscall" + + log "github.com/Sirupsen/logrus" + "github.com/flynn/go-shlex" + + "github.com/rancher/os/config" + "github.com/ryanuber/go-glob" + "github.com/vishvananda/netlink" +) + +const ( + CONF = "/var/lib/rancher/conf" + MODE = "mode" +) + +var ( + defaultDhcpArgs = []string{"dhcpcd", "-MA4"} +) + +func createInterfaces(netCfg *config.NetworkConfig) { + configured := map[string]bool{} + + for name, iface := range netCfg.Interfaces { + if iface.Bridge == "true" { + if _, err := NewBridge(name); err != nil { + log.Errorf("Failed to create bridge %s: %v", name, err) + } + } else if iface.Bridge != "" { + if _, err := NewBridge(iface.Bridge); err != nil { + log.Errorf("Failed to create bridge %s: %v", iface.Bridge, err) + } + } else if iface.Bond != "" { + bond, err := Bond(iface.Bond) + if err != nil { + log.Errorf("Failed to create bond %s: %v", iface.Bond, err) + continue + } + + if !configured[iface.Bond] { + if bondIface, ok := netCfg.Interfaces[iface.Bond]; ok { + // Other settings depends on mode, so set it first + if v, ok := bondIface.BondOpts[MODE]; ok { + bond.Opt(MODE, v) + } + + for k, v := range bondIface.BondOpts { + if k != MODE { + bond.Opt(k, v) + } + } + configured[iface.Bond] = true + } + } + } + } +} + +func createSlaveInterfaces(netCfg *config.NetworkConfig) { + links, err := netlink.LinkList() + if err != nil { + log.Errorf("Failed to list links: %v", err) + return + } + + for _, link := range links { + match, ok := findMatch(link, netCfg) + if !ok { + continue + } + + vlanDefs, err := ParseVlanDefinitions(match.Vlans) + if err != nil { + log.Errorf("Failed to create vlans on device %s: %v", link.Attrs().Name, err) + continue + } + + for _, vlanDef := range vlanDefs { + if _, err = NewVlan(link, vlanDef.Name, vlanDef.Id); err != nil { + log.Errorf("Failed to create vlans on device %s, id %d: %v", link.Attrs().Name, vlanDef.Id, err) + } + } + } +} + +func findMatch(link netlink.Link, netCfg *config.NetworkConfig) (config.InterfaceConfig, bool) { + linkName := link.Attrs().Name + var match config.InterfaceConfig + exactMatch := false + found := false + + for key, netConf := range netCfg.Interfaces { + if netConf.Match == "" { + netConf.Match = key + } + + if netConf.Match == "" { + continue + } + + if strings.HasPrefix(netConf.Match, "mac") { + haAddr, err := net.ParseMAC(netConf.Match[4:]) + if err != nil { + log.Errorf("Failed to parse mac %s: %v", netConf.Match[4:], err) + continue + } + + // Don't match mac address of the bond because it is the same as the slave + if bytes.Compare(haAddr, link.Attrs().HardwareAddr) == 0 && link.Attrs().Name != netConf.Bond { + // MAC address match is used over all other matches + return netConf, true + } + } + + if !exactMatch && glob.Glob(netConf.Match, linkName) { + match = netConf + found = true + } + + if netConf.Match == linkName { + // Found exact match, use it over wildcard match + match = netConf + exactMatch = true + } + } + + return match, exactMatch || found +} + +func populateDefault(netCfg *config.NetworkConfig) { + if netCfg.Interfaces == nil { + netCfg.Interfaces = map[string]config.InterfaceConfig{} + } + + if len(netCfg.Interfaces) == 0 { + netCfg.Interfaces["eth*"] = config.InterfaceConfig{ + DHCP: true, + } + } + + if _, ok := netCfg.Interfaces["lo"]; !ok { + netCfg.Interfaces["lo"] = config.InterfaceConfig{ + Address: "127.0.0.1/8", + } + } +} + +func ApplyNetworkConfigs(netCfg *config.NetworkConfig) error { + populateDefault(netCfg) + + log.Debugf("Config: %#v", netCfg) + runCmds(netCfg.PreCmds, "") + + createInterfaces(netCfg) + + createSlaveInterfaces(netCfg) + + links, err := netlink.LinkList() + if err != nil { + return err + } + + //apply network config + for _, link := range links { + linkName := link.Attrs().Name + if match, ok := findMatch(link, netCfg); ok && !match.DHCP { + if err := applyInterfaceConfig(link, match); err != nil { + log.Errorf("Failed to apply settings to %s : %v", linkName, err) + } + } + } + + runCmds(netCfg.PostCmds, "") + return err +} + +func RunDhcp(netCfg *config.NetworkConfig, setHostname, setDns bool) error { + populateDefault(netCfg) + + links, err := netlink.LinkList() + if err != nil { + return err + } + + dhcpLinks := map[string]string{} + for _, link := range links { + if match, ok := findMatch(link, netCfg); ok && match.DHCP { + dhcpLinks[link.Attrs().Name] = match.DHCPArgs + } + } + + //run dhcp + wg := sync.WaitGroup{} + for iface, args := range dhcpLinks { + wg.Add(1) + go func(iface, args string) { + runDhcp(netCfg, iface, args, setHostname, setDns) + wg.Done() + }(iface, args) + } + wg.Wait() + + return err +} + +func runDhcp(netCfg *config.NetworkConfig, iface string, argstr string, setHostname, setDns bool) { + log.Infof("Running DHCP on %s", iface) + args := []string{} + if argstr != "" { + var err error + args, err = shlex.Split(argstr) + if err != nil { + log.Errorf("Failed to parse [%s]: %v", argstr, err) + } + } + if len(args) == 0 { + args = defaultDhcpArgs + } + + if setHostname { + args = append(args, "-e", "force_hostname=true") + } + + if !setDns { + args = append(args, "--nohook", "resolv.conf") + } + + args = append(args, iface) + cmd := exec.Command(args[0], args[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + log.Error(err) + } +} + +func linkUp(link netlink.Link, netConf config.InterfaceConfig) error { + if err := netlink.LinkSetUp(link); err != nil { + log.Errorf("failed to setup link: %v", err) + return err + } + + return nil +} + +func applyAddress(address string, link netlink.Link, netConf config.InterfaceConfig) error { + addr, err := netlink.ParseAddr(address) + if err != nil { + return err + } + if err := netlink.AddrAdd(link, addr); err == syscall.EEXIST { + //Ignore this error + } else if err != nil { + log.Errorf("addr add failed: %v", err) + } else { + log.Infof("Set %s on %s", netConf.Address, link.Attrs().Name) + } + + return nil +} + +func setGateway(gateway string) error { + if gateway == "" { + return nil + } + + gatewayIp := net.ParseIP(gateway) + if gatewayIp == nil { + return errors.New("Invalid gateway address " + gateway) + } + + route := netlink.Route{ + Scope: netlink.SCOPE_UNIVERSE, + Gw: gatewayIp, + } + + if err := netlink.RouteAdd(&route); err == syscall.EEXIST { + //Ignore this error + } else if err != nil { + log.Errorf("gateway set failed: %v", err) + return err + } + + log.Infof("Set default gateway %s", gateway) + return nil +} + +func applyInterfaceConfig(link netlink.Link, netConf config.InterfaceConfig) error { + if netConf.Bond != "" { + if err := netlink.LinkSetDown(link); err != nil { + return err + } + + b, err := Bond(netConf.Bond) + if err != nil { + return err + } + if err := b.AddSlave(link.Attrs().Name); err != nil { + return err + } + return nil + } + + if netConf.Bridge != "" && netConf.Bridge != "true" { + b, err := NewBridge(netConf.Bridge) + if err != nil { + return err + } + if err := b.AddLink(link); err != nil { + return err + } + return linkUp(link, netConf) + } + + if netConf.IPV4LL { + if err := AssignLinkLocalIP(link); err != nil { + log.Errorf("IPV4LL set failed: %v", err) + return err + } + } else { + addresses := []string{} + + if netConf.Address != "" { + addresses = append(addresses, netConf.Address) + } + + if len(netConf.Addresses) > 0 { + addresses = append(addresses, netConf.Addresses...) + } + + for _, address := range addresses { + err := applyAddress(address, link, netConf) + if err != nil { + log.Errorf("Failed to apply address %s to %s: %v", address, link.Attrs().Name, err) + } + } + } + + if netConf.MTU > 0 { + if err := netlink.LinkSetMTU(link, netConf.MTU); err != nil { + log.Errorf("set MTU Failed: %v", err) + return err + } + } + + runCmds(netConf.PreUp, link.Attrs().Name) + + if err := linkUp(link, netConf); err != nil { + return err + } + + if err := setGateway(netConf.Gateway); err != nil { + log.Errorf("Fail to set gateway %s", netConf.Gateway) + } + + if err := setGateway(netConf.GatewayIpv6); err != nil { + log.Errorf("Fail to set gateway %s", netConf.GatewayIpv6) + } + + runCmds(netConf.PostUp, link.Attrs().Name) + + return nil +} + +func runCmds(cmds []string, iface string) { + for _, cmd := range cmds { + cmd = strings.TrimSpace(cmd) + if cmd == "" { + continue + } + + args, err := shlex.Split(strings.Replace(cmd, "$iface", iface, -1)) + if err != nil { + log.Errorf("Failed to parse command [%s]: %v", cmd, err) + continue + } + + log.Infof("Running command %s %v", args[0], args[1:]) + cmd := exec.Command(args[0], args[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + log.Errorf("Failed to run command [%v]: %v", cmd, err) + continue + } + } +} diff --git a/netconf/vlan.go b/netconf/vlan.go new file mode 100644 index 00000000..75821d82 --- /dev/null +++ b/netconf/vlan.go @@ -0,0 +1,79 @@ +package netconf + +import ( + "fmt" + "strconv" + "strings" + + "github.com/vishvananda/netlink" +) + +type VlanDefinition struct { + Id int + Name string +} + +type Vlan struct { + name string + link netlink.Link + id int +} + +func NewVlan(link netlink.Link, name string, id int) (*Vlan, error) { + if name == "" { + name = fmt.Sprintf("%s.%d", link.Attrs().Name, id) + } + + v := &Vlan{ + name: name, + link: link, + id: id, + } + return v, v.init() +} + +func (v *Vlan) init() error { + link, err := netlink.LinkByName(v.name) + if err == nil { + if _, ok := link.(*netlink.Vlan); !ok { + return fmt.Errorf("%s is not a VLAN device", v.name) + } + return nil + } + + vlan := netlink.Vlan{} + vlan.ParentIndex = v.link.Attrs().Index + vlan.Name = v.name + vlan.VlanId = v.id + + return netlink.LinkAdd(&vlan) +} + +func ParseVlanDefinitions(vlans string) ([]VlanDefinition, error) { + vlans = strings.TrimSpace(vlans) + if vlans == "" { + return nil, nil + } + + result := []VlanDefinition{} + + for _, vlan := range strings.Split(vlans, ",") { + idName := strings.SplitN(strings.TrimSpace(vlan), ":", 2) + id, err := strconv.Atoi(idName[0]) + if err != nil { + return nil, fmt.Errorf("Invalid format in %s: %v", vlans, err) + } + + def := VlanDefinition{ + Id: id, + } + + if len(idName) > 1 { + def.Name = idName[1] + } + + result = append(result, def) + } + + return result, nil +} diff --git a/selinux/selinux_linux.go b/selinux/selinux_linux.go index e999ca33..c1550e6e 100644 --- a/selinux/selinux_linux.go +++ b/selinux/selinux_linux.go @@ -9,3 +9,8 @@ func InitializeSelinux() (int, error) { ret, err := C.selinux_init_load_policy(&enforce) return int(ret), err } + +func SetFileContext(path string, context string) (int, error) { + ret, err := C.setfilecon(C.CString(path), C.CString(context)) + return int(ret), err +}