func containerValidConfig(d *Daemon, config map[string]string, profile bool, expanded bool) error { if config == nil { return nil } for k, v := range config { if profile && strings.HasPrefix(k, "volatile.") { return fmt.Errorf("Volatile keys can only be set on containers.") } err := containerValidConfigKey(d, k, v) if err != nil { return err } } _, rawSeccomp := config["raw.seccomp"] _, whitelist := config["security.syscalls.whitelist"] _, blacklist := config["securtiy.syscalls.blacklist"] blacklistDefault := shared.IsTrue(config["security.syscalls.blacklist_default"]) blacklistCompat := shared.IsTrue(config["security.syscalls.blacklist_compat"]) if rawSeccomp && (whitelist || blacklist || blacklistDefault || blacklistCompat) { return fmt.Errorf("raw.seccomp is mutually exclusive with security.syscalls*") } if whitelist && (blacklist || blacklistDefault || blacklistCompat) { return fmt.Errorf("security.syscalls.whitelist is mutually exclusive with security.syscalls.blacklist*") } return nil }
func ContainerNeedsSeccomp(c container) bool { config := c.ExpandedConfig() keys := []string{ "raw.seccomp", "security.syscalls.whitelist", "security.syscalls.blacklist", } for _, k := range keys { _, hasKey := config[k] if hasKey { return true } } compat := config["security.syscalls.blacklist_compat"] if shared.IsTrue(compat) { return true } /* this are enabled by default, so if the keys aren't present, that * means "true" */ default_, ok := config["security.syscalls.blacklist_default"] if !ok || shared.IsTrue(default_) { return true } return false }
func getSeccompProfileContent(c container) (string, error) { config := c.ExpandedConfig() raw := config["raw.seccomp"] if raw != "" { return raw, nil } policy := SECCOMP_HEADER whitelist := config["security.syscalls.whitelist"] if whitelist != "" { policy += "whitelist\n[all]\n" policy += whitelist return policy, nil } policy += "blacklist\n" default_, ok := config["security.syscalls.blacklist_default"] if !ok || shared.IsTrue(default_) { policy += DEFAULT_SECCOMP_POLICY } compat := config["security.syscalls.blacklist_compat"] if shared.IsTrue(compat) { arch, err := shared.ArchitectureName(c.Architecture()) if err != nil { return "", err } policy += fmt.Sprintf(COMPAT_BLOCKING_POLICY, arch) } return policy, nil }
func containersRestart(d *Daemon) error { // Get all the containers result, err := dbContainersList(d.db, cTypeRegular) if err != nil { return err } containers := []container{} for _, name := range result { c, err := containerLoadByName(d, name) if err != nil { return err } containers = append(containers, c) } sort.Sort(containerAutostartList(containers)) // Restart the containers for _, c := range containers { config := c.ExpandedConfig() lastState := config["volatile.last_state.power"] autoStart := config["boot.autostart"] autoStartDelay := config["boot.autostart.delay"] if shared.IsTrue(autoStart) || (autoStart == "" && lastState == "RUNNING") { if c.IsRunning() { continue } c.Start(false) autoStartDelayInt, err := strconv.Atoi(autoStartDelay) if err == nil { time.Sleep(time.Duration(autoStartDelayInt) * time.Second) } } } // Reset the recorded state (to ensure it's up to date) _, err = dbExec(d.db, "DELETE FROM containers_config WHERE key='volatile.last_state.power'") if err != nil { return err } for _, c := range containers { err = c.ConfigKeySet("volatile.last_state.power", c.State()) if err != nil { return err } } return nil }
func (k *daemonConfigKey) GetBool() bool { value := k.currentValue // Get the default value if not set if value == "" { value = k.defaultValue } // Convert to boolean return shared.IsTrue(value) }
func cmdActivateIfNeeded() error { // Don't start a full daemon, we just need DB access d := &Daemon{ imagesDownloading: map[string]chan bool{}, imagesDownloadingLock: sync.RWMutex{}, lxcpath: shared.VarPath("containers"), } err := initializeDbObject(d, shared.VarPath("lxd.db")) if err != nil { return err } /* Load all config values from the database */ err = daemonConfigInit(d.db) if err != nil { return err } // Look for network socket value := daemonConfig["core.https_address"].Get() if value != "" { shared.Debugf("Daemon has core.https_address set, activating...") _, err := lxd.NewClient(&lxd.DefaultConfig, "local") return err } // Look for auto-started or previously started containers d.IdmapSet, err = shared.DefaultIdmapSet() if err != nil { return err } result, err := dbContainersList(d.db, cTypeRegular) if err != nil { return err } for _, name := range result { c, err := containerLoadByName(d, name) if err != nil { return err } config := c.ExpandedConfig() lastState := config["volatile.last_state.power"] autoStart := config["boot.autostart"] if c.IsRunning() { shared.Debugf("Daemon has running containers, activating...") _, err := lxd.NewClient(&lxd.DefaultConfig, "local") return err } if lastState == "RUNNING" || lastState == "Running" || shared.IsTrue(autoStart) { shared.Debugf("Daemon has auto-started containers, activating...") _, err := lxd.NewClient(&lxd.DefaultConfig, "local") return err } } shared.Debugf("No need to start the daemon now.") return nil }
func (n *network) Start() error { // Create directory if !shared.PathExists(shared.VarPath("networks", n.name)) { err := os.MkdirAll(shared.VarPath("networks", n.name), 0700) if err != nil { return err } } // Create the bridge interface if !n.IsRunning() { if n.config["bridge.driver"] == "openvswitch" { err := shared.RunCommand("ovs-vsctl", "add-br", n.name) if err != nil { return err } } else { err := shared.RunCommand("ip", "link", "add", n.name, "type", "bridge") if err != nil { return err } } } // Get a list of tunnels tunnels := networkGetTunnels(n.config) // IPv6 bridge configuration if !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) { err := networkSysctl(fmt.Sprintf("ipv6/conf/%s/autoconf", n.name), "0") if err != nil { return err } err = networkSysctl(fmt.Sprintf("ipv6/conf/%s/accept_dad", n.name), "0") if err != nil { return err } } // Get a list of interfaces ifaces, err := net.Interfaces() if err != nil { return err } // Cleanup any existing tunnel device for _, iface := range ifaces { if strings.HasPrefix(iface.Name, fmt.Sprintf("%s-", n.name)) { err = shared.RunCommand("ip", "link", "del", iface.Name) if err != nil { return err } } } // Set the MTU mtu := "" if n.config["bridge.mtu"] != "" { mtu = n.config["bridge.mtu"] } else if len(tunnels) > 0 { mtu = "1400" } else if n.config["bridge.mode"] == "fan" { if n.config["fan.type"] == "ipip" { mtu = "1480" } else { mtu = "1450" } } // Attempt to add a dummy device to the bridge to force the MTU if mtu != "" && n.config["bridge.driver"] != "openvswitch" { err = shared.RunCommand("ip", "link", "add", fmt.Sprintf("%s-mtu", n.name), "mtu", mtu, "type", "dummy") if err == nil { networkAttachInterface(n.name, fmt.Sprintf("%s-mtu", n.name)) } } // Now, set a default MTU if mtu == "" { mtu = "1500" } err = shared.RunCommand("ip", "link", "set", n.name, "mtu", mtu) if err != nil { return err } // Bring it up err = shared.RunCommand("ip", "link", "set", n.name, "up") if err != nil { return err } // Add any listed existing external interface if n.config["bridge.external_interfaces"] != "" { for _, entry := range strings.Split(n.config["bridge.external_interfaces"], ",") { entry = strings.TrimSpace(entry) iface, err := net.InterfaceByName(entry) if err != nil { continue } addrs, err := iface.Addrs() if err == nil && len(addrs) != 0 { return fmt.Errorf("Only unconfigured network interfaces can be bridged") } err = networkAttachInterface(n.name, entry) if err != nil { return err } } } // Remove any existing IPv4 iptables rules err = networkIptablesClear("ipv4", n.name, "") if err != nil { return err } err = networkIptablesClear("ipv4", n.name, "mangle") if err != nil { return err } err = networkIptablesClear("ipv4", n.name, "nat") if err != nil { return err } // Flush all IPv4 addresses err = shared.RunCommand("ip", "-4", "addr", "flush", "dev", n.name, "scope", "global") if err != nil { return err } // Configure IPv4 firewall (includes fan) if n.config["bridge.mode"] == "fan" || !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) { // Setup basic iptables overrides rules := [][]string{ []string{"ipv4", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "67", "-j", "ACCEPT"}, []string{"ipv4", n.name, "", "INPUT", "-i", n.name, "-p", "tcp", "--dport", "67", "-j", "ACCEPT"}, []string{"ipv4", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "53", "-j", "ACCEPT"}, []string{"ipv4", n.name, "", "INPUT", "-i", n.name, "-p", "tcp", "--dport", "53", "-j", "ACCEPT"}, []string{"ipv4", n.name, "", "OUTPUT", "-o", n.name, "-p", "udp", "--sport", "67", "-j", "ACCEPT"}, []string{"ipv4", n.name, "", "OUTPUT", "-o", n.name, "-p", "tcp", "--sport", "67", "-j", "ACCEPT"}, []string{"ipv4", n.name, "", "OUTPUT", "-o", n.name, "-p", "udp", "--sport", "53", "-j", "ACCEPT"}, []string{"ipv4", n.name, "", "OUTPUT", "-o", n.name, "-p", "tcp", "--sport", "53", "-j", "ACCEPT"}, []string{"ipv4", n.name, "mangle", "POSTROUTING", "-o", n.name, "-p", "udp", "--dport", "68", "-j", "CHECKSUM", "--checksum-fill"}} for _, rule := range rules { err = networkIptablesPrepend(rule[0], rule[1], rule[2], rule[3], rule[4:]...) if err != nil { return err } } // Allow forwarding if n.config["bridge.mode"] == "fan" || n.config["ipv4.routing"] == "" || shared.IsTrue(n.config["ipv4.routing"]) { err = networkSysctl("ipv4/ip_forward", "1") if err != nil { return err } err = networkIptablesPrepend("ipv4", n.name, "", "FORWARD", "-i", n.name, "-j", "ACCEPT") if err != nil { return err } err = networkIptablesPrepend("ipv4", n.name, "", "FORWARD", "-o", n.name, "-j", "ACCEPT") if err != nil { return err } } else { err = networkIptablesPrepend("ipv4", n.name, "", "FORWARD", "-i", n.name, "-j", "REJECT") if err != nil { return err } err = networkIptablesPrepend("ipv4", n.name, "", "FORWARD", "-o", n.name, "-j", "REJECT") if err != nil { return err } } } // Start building the dnsmasq command line dnsmasqCmd := []string{"dnsmasq", "-u", "root", "--strict-order", "--bind-interfaces", fmt.Sprintf("--pid-file=%s", shared.VarPath("networks", n.name, "dnsmasq.pid")), "--except-interface=lo", fmt.Sprintf("--interface=%s", n.name)} // Configure IPv4 if !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) { // Parse the subnet ip, subnet, err := net.ParseCIDR(n.config["ipv4.address"]) if err != nil { return err } // Update the dnsmasq config dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--listen-address=%s", ip.String())) if n.config["ipv4.dhcp"] == "" || shared.IsTrue(n.config["ipv4.dhcp"]) { if !shared.StringInSlice("--dhcp-no-override", dnsmasqCmd) { dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-no-override", "--dhcp-authoritative", fmt.Sprintf("--dhcp-leasefile=%s", shared.VarPath("networks", n.name, "dnsmasq.leases")), fmt.Sprintf("--dhcp-hostsfile=%s", shared.VarPath("networks", n.name, "dnsmasq.hosts"))}...) } if n.config["ipv4.dhcp.ranges"] != "" { for _, dhcpRange := range strings.Split(n.config["ipv4.dhcp.ranges"], ",") { dhcpRange = strings.TrimSpace(dhcpRange) dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", strings.Replace(dhcpRange, "-", ",", -1)}...) } } else { dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("%s,%s", networkGetIP(subnet, 2).String(), networkGetIP(subnet, -2).String())}...) } } // Add the address err = shared.RunCommand("ip", "-4", "addr", "add", "dev", n.name, n.config["ipv4.address"]) if err != nil { return err } // Configure NAT if shared.IsTrue(n.config["ipv4.nat"]) { err = networkIptablesPrepend("ipv4", n.name, "nat", "POSTROUTING", "-s", subnet.String(), "!", "-d", subnet.String(), "-j", "MASQUERADE") if err != nil { return err } } } // Remove any existing IPv6 iptables rules err = networkIptablesClear("ipv6", n.name, "") if err != nil { return err } err = networkIptablesClear("ipv6", n.name, "nat") if err != nil { return err } // Flush all IPv6 addresses err = shared.RunCommand("ip", "-6", "addr", "flush", "dev", n.name, "scope", "global") if err != nil { return err } // Configure IPv6 if !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) { // Enable IPv6 for the subnet err := networkSysctl(fmt.Sprintf("ipv6/conf/%s/disable_ipv6", n.name), "0") if err != nil { return err } // Parse the subnet ip, subnet, err := net.ParseCIDR(n.config["ipv6.address"]) if err != nil { return err } // Update the dnsmasq config dnsmasqCmd = append(dnsmasqCmd, []string{fmt.Sprintf("--listen-address=%s", ip.String()), "--enable-ra"}...) if n.config["ipv6.dhcp"] == "" || shared.IsTrue(n.config["ipv6.dhcp"]) { if !shared.StringInSlice("--dhcp-no-override", dnsmasqCmd) { dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-no-override", "--dhcp-authoritative", fmt.Sprintf("--dhcp-leasefile=%s", shared.VarPath("networks", n.name, "dnsmasq.leases")), fmt.Sprintf("--dhcp-hostsfile=%s", shared.VarPath("networks", n.name, "dnsmasq.hosts"))}...) } if shared.IsTrue(n.config["ipv6.dhcp.stateful"]) { if n.config["ipv6.dhcp.ranges"] != "" { for _, dhcpRange := range strings.Split(n.config["ipv6.dhcp.ranges"], ",") { dhcpRange = strings.TrimSpace(dhcpRange) dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("%s", strings.Replace(dhcpRange, "-", ",", -1))}...) } } else { dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("%s,%s", networkGetIP(subnet, 2), networkGetIP(subnet, -1))}...) } } else { dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("::,constructor:%s,ra-stateless,ra-names", n.name)}...) } } else { dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("::,constructor:%s,ra-only", n.name)}...) } // Setup basic iptables overrides rules := [][]string{ []string{"ipv6", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "546", "-j", "ACCEPT"}, []string{"ipv6", n.name, "", "INPUT", "-i", n.name, "-p", "tcp", "--dport", "546", "-j", "ACCEPT"}, []string{"ipv6", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "53", "-j", "ACCEPT"}, []string{"ipv6", n.name, "", "INPUT", "-i", n.name, "-p", "tcp", "--dport", "53", "-j", "ACCEPT"}, []string{"ipv6", n.name, "", "OUTPUT", "-o", n.name, "-p", "udp", "--sport", "546", "-j", "ACCEPT"}, []string{"ipv6", n.name, "", "OUTPUT", "-o", n.name, "-p", "tcp", "--sport", "546", "-j", "ACCEPT"}, []string{"ipv6", n.name, "", "OUTPUT", "-o", n.name, "-p", "udp", "--sport", "53", "-j", "ACCEPT"}, []string{"ipv6", n.name, "", "OUTPUT", "-o", n.name, "-p", "tcp", "--sport", "53", "-j", "ACCEPT"}} for _, rule := range rules { err = networkIptablesPrepend(rule[0], rule[1], rule[2], rule[3], rule[4:]...) if err != nil { return err } } // Allow forwarding if n.config["ipv6.routing"] == "" || shared.IsTrue(n.config["ipv6.routing"]) { // Get a list of proc entries entries, err := ioutil.ReadDir("/proc/sys/net/ipv6/conf/") if err != nil { return err } // First set accept_ra to 2 for everything for _, entry := range entries { content, err := ioutil.ReadFile(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/accept_ra", entry.Name())) if err == nil && string(content) != "1\n" { continue } err = networkSysctl(fmt.Sprintf("ipv6/conf/%s/accept_ra", entry.Name()), "2") if err != nil && !os.IsNotExist(err) { return err } } // Then set forwarding for all of them for _, entry := range entries { err = networkSysctl(fmt.Sprintf("ipv6/conf/%s/forwarding", entry.Name()), "1") if err != nil && !os.IsNotExist(err) { return err } } err = networkIptablesPrepend("ipv6", n.name, "", "FORWARD", "-i", n.name, "-j", "ACCEPT") if err != nil { return err } err = networkIptablesPrepend("ipv6", n.name, "", "FORWARD", "-o", n.name, "-j", "ACCEPT") if err != nil { return err } } else { err = networkIptablesPrepend("ipv6", n.name, "", "FORWARD", "-i", n.name, "-j", "REJECT") if err != nil { return err } err = networkIptablesPrepend("ipv6", n.name, "", "FORWARD", "-o", n.name, "-j", "REJECT") if err != nil { return err } } // Add the address err = shared.RunCommand("ip", "-6", "addr", "add", "dev", n.name, n.config["ipv6.address"]) if err != nil { return err } // Configure NAT if shared.IsTrue(n.config["ipv6.nat"]) { err = networkIptablesPrepend("ipv6", n.name, "nat", "POSTROUTING", "-s", subnet.String(), "!", "-d", subnet.String(), "-j", "MASQUERADE") if err != nil { return err } } } // Configure the fan if n.config["bridge.mode"] == "fan" { tunName := fmt.Sprintf("%s-fan", n.name) // Parse the underlay underlay := n.config["fan.underlay_subnet"] _, underlaySubnet, err := net.ParseCIDR(underlay) if err != nil { return nil } // Parse the overlay overlay := n.config["fan.overlay_subnet"] if overlay == "" { overlay = "240.0.0.0/8" } _, overlaySubnet, err := net.ParseCIDR(overlay) if err != nil { return err } // Get the address fanAddress, devName, devAddr, err := networkFanAddress(underlaySubnet, overlaySubnet) if err != nil { return err } addr := strings.Split(fanAddress, "/") if n.config["fan.type"] == "ipip" { fanAddress = fmt.Sprintf("%s/24", addr[0]) } // Parse the host subnet _, hostSubnet, err := net.ParseCIDR(fmt.Sprintf("%s/24", addr[0])) if err != nil { return err } // Add the address err = shared.RunCommand("ip", "-4", "addr", "add", "dev", n.name, fanAddress) if err != nil { return err } // Update the dnsmasq config dnsmasqCmd = append(dnsmasqCmd, []string{ fmt.Sprintf("--listen-address=%s", addr[0]), "--dhcp-no-override", "--dhcp-authoritative", fmt.Sprintf("--dhcp-leasefile=%s", shared.VarPath("networks", n.name, "dnsmasq.leases")), fmt.Sprintf("--dhcp-hostsfile=%s", shared.VarPath("networks", n.name, "dnsmasq.hosts")), "--dhcp-range", fmt.Sprintf("%s,%s", networkGetIP(hostSubnet, 2).String(), networkGetIP(hostSubnet, -2).String())}...) // Setup the tunnel if n.config["fan.type"] == "ipip" { err = shared.RunCommand("ip", "-4", "route", "flush", "dev", "tunl0") if err != nil { return err } err = shared.RunCommand("ip", "link", "set", "tunl0", "up") if err != nil { return err } // Fails if the map is already set shared.RunCommand("ip", "link", "change", "tunl0", "type", "ipip", "fan-map", fmt.Sprintf("%s:%s", overlay, underlay)) err = shared.RunCommand("ip", "route", "add", overlay, "dev", "tunl0", "src", addr[0]) if err != nil { return err } } else { vxlanID := fmt.Sprintf("%d", binary.BigEndian.Uint32(overlaySubnet.IP.To4())>>8) err = shared.RunCommand("ip", "link", "add", tunName, "type", "vxlan", "id", vxlanID, "dev", devName, "dstport", "0", "local", devAddr, "fan-map", fmt.Sprintf("%s:%s", overlay, underlay)) if err != nil { return err } err = networkAttachInterface(n.name, tunName) if err != nil { return err } err = shared.RunCommand("ip", "link", "set", tunName, "mtu", mtu, "up") if err != nil { return err } err = shared.RunCommand("ip", "link", "set", n.name, "up") if err != nil { return err } } // Configure NAT err = networkIptablesPrepend("ipv4", n.name, "nat", "POSTROUTING", "-s", underlaySubnet.String(), "!", "-d", underlaySubnet.String(), "-j", "MASQUERADE") if err != nil { return err } } // Configure tunnels for _, tunnel := range tunnels { getConfig := func(key string) string { return n.config[fmt.Sprintf("tunnel.%s.%s", tunnel, key)] } tunProtocol := getConfig("protocol") tunLocal := getConfig("local") tunRemote := getConfig("remote") tunName := fmt.Sprintf("%s-%s", n.name, tunnel) // Configure the tunnel cmd := []string{"ip", "link", "add", tunName} if tunProtocol == "gre" { // Skip partial configs if tunProtocol == "" || tunLocal == "" || tunRemote == "" { continue } cmd = append(cmd, []string{"type", "gretap", "local", tunLocal, "remote", tunRemote}...) } else if tunProtocol == "vxlan" { tunGroup := getConfig("group") // Skip partial configs if tunProtocol == "" { continue } cmd = append(cmd, []string{"type", "vxlan"}...) if tunLocal != "" && tunRemote != "" { cmd = append(cmd, []string{"local", tunLocal, "remote", tunRemote}...) } else { if tunGroup == "" { tunGroup = "239.0.0.1" } _, devName, err := networkDefaultGatewaySubnetV4() if err != nil { return err } cmd = append(cmd, []string{"group", tunGroup, "dev", devName}...) } tunPort := getConfig("port") if tunPort == "" { tunPort = "0" } cmd = append(cmd, []string{"dstport", tunPort}...) tunId := getConfig("id") if tunId == "" { tunId = "1" } cmd = append(cmd, []string{"id", tunId}...) } // Create the interface err = shared.RunCommand(cmd[0], cmd[1:]...) if err != nil { return err } // Bridge it and bring up err = networkAttachInterface(n.name, tunName) if err != nil { return err } err = shared.RunCommand("ip", "link", "set", tunName, "mtu", mtu, "up") if err != nil { return err } err = shared.RunCommand("ip", "link", "set", n.name, "up") if err != nil { return err } } // Kill any existing dnsmasq daemon for this network err = networkKillDnsmasq(n.name, false) if err != nil { return err } // Configure dnsmasq if n.config["bridge.mode"] == "fan" || !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) || !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) { dnsDomain := n.config["dns.domain"] if dnsDomain == "" { dnsDomain = "lxd" } // Setup the dnsmasq domain if n.config["dns.mode"] != "none" { dnsmasqCmd = append(dnsmasqCmd, []string{"-s", dnsDomain, "-S", fmt.Sprintf("/%s/", dnsDomain)}...) } // Create raw config file if n.config["raw.dnsmasq"] != "" { err = ioutil.WriteFile(shared.VarPath("networks", n.name, "dnsmasq.raw"), []byte(fmt.Sprintf("%s\n", n.config["raw.dnsmasq"])), 0) if err != nil { return err } dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--conf-file=%s", shared.VarPath("networks", n.name, "dnsmasq.raw"))) } // Create DHCP hosts file if !shared.PathExists(shared.VarPath("networks", n.name, "dnsmasq.hosts")) { err = ioutil.WriteFile(shared.VarPath("networks", n.name, "dnsmasq.hosts"), []byte(""), 0) if err != nil { return err } } // Start dnsmasq (occasionaly races, try a few times) output, err := tryExec(dnsmasqCmd[0], dnsmasqCmd[1:]...) if err != nil { return fmt.Errorf("Failed to run: %s: %s", strings.Join(dnsmasqCmd, " "), strings.TrimSpace(string(output))) } // Update the static leases err = networkUpdateStatic(n.daemon) if err != nil { return err } } return nil }