package cmd import ( "errors" "fmt" "github.com/c-robinson/iplib" log "github.com/sirupsen/logrus" "net" "os" "regexp" "strconv" "strings" ) func setJailConfigUpdated(jail *Jail) error { if len(jail.ConfigPath) == 0 { return errors.New(fmt.Sprintf("No config path for jail %s", jail.Name)) } j, err := getJailFromArray(jail.Name, gJails) if err != nil { return err } j.ConfigUpdated = true return nil } func mountProcFs(jail *Jail) error { cmd := fmt.Sprintf("mount -t procfs proc %s/proc", jail.RootPath) _, err := executeCommand(cmd) if err != nil { return errors.New(fmt.Sprintf("Error mounting procfs on %s/proc: %s", jail.RootPath, err.Error())) } return nil } func mountLinProcFs(jail *Jail) error { ldir := fmt.Sprintf("%s/compat/linux/proc", jail.RootPath) _, err := os.Stat(ldir) if os.IsNotExist(err) { errDir := os.MkdirAll(ldir, 0755) if errDir != nil { return errors.New(fmt.Sprintf("Error creating directory %s: %s", ldir, errDir.Error())) } } cmd := fmt.Sprintf("mount -t linprocfs linproc %s", ldir) _, err = executeCommand(cmd) if err != nil { return errors.New(fmt.Sprintf("Error mounting linprocfs on %s: %s", ldir, err.Error())) } return nil } func mountDevFs(jail *Jail) error { cmd := fmt.Sprintf("mount -t devfs dev %s/dev", jail.RootPath) _, err := executeCommand(cmd) if err != nil { return errors.New(fmt.Sprintf("Error mounting devfs on %s/dev: %s", jail.RootPath, err.Error())) } return nil } func mountFdescFs(jail *Jail) error { // FreeBSD <= 9.3 do not support fdescfs if gHostVersion <= 9.3 { fmt.Printf(" FreeBSD <= 9.3 does not support fdescfs, disabling in config\n") jail.Config.Mount_fdescfs = 0 // Tag config so it will be synced on disk jail.ConfigUpdated = true // Should we consider this an error? return nil } cmd := fmt.Sprintf("mount -t fdescfs descfs %s/dev/fd", jail.RootPath) _, err := executeCommand(cmd) if err != nil { return errors.New(fmt.Sprintf("Error mounting fdescfs on %s/dev/fd: %s", jail.RootPath, err.Error())) } return nil } func mountAllJailFsFromHost(jail *Jail) error { procfsFound := false linProcfsFound := false devfsFound := false fdescfsFound := false cmd := "mount -p" out, err := executeCommand(cmd) if err != nil { return errors.New(fmt.Sprintf("Error executing mount: %s", err.Error())) } var outclean []string remSpPtrn := regexp.MustCompile(`\s+`) for _, l := range strings.Split(out, "\n") { outclean = append(outclean, remSpPtrn.ReplaceAllString(l, " ")) } // Check if these FS are already mounted for _, l := range outclean { f := strings.Split(l, " ") if len(f) > 2 { if strings.EqualFold(f[1], fmt.Sprintf("%s/proc", jail.RootPath)) { procfsFound = true } if strings.EqualFold(f[1], fmt.Sprintf("%s/compat/linux/proc", jail.RootPath)) { linProcfsFound = true } if strings.EqualFold(f[1], fmt.Sprintf("%s/dev", jail.RootPath)) { devfsFound = true } if strings.EqualFold(f[1], fmt.Sprintf("%s/dev/fd", jail.RootPath)) { fdescfsFound = true } } } // Mount wanted FS if jail.Config.Mount_procfs > 0 && procfsFound == false { err := mountProcFs(jail) if err != nil { return err } } if jail.Config.Mount_linprocfs > 0 && linProcfsFound == false { err = mountLinProcFs(jail) if err != nil { return err } } if jail.Config.Mount_devfs > 0 && devfsFound == false { err := mountDevFs(jail) if err != nil { return err } } if jail.Config.Mount_fdescfs > 0 && fdescfsFound == false { err := mountFdescFs(jail) if err != nil { return err } } // Ces montages doivent-ils etre effectués une fois le jail démarré? // FreeBSD <= 9.3 do not support fdescfs //if gHostVersion <= 9.3 && jail.Config.Allow_mount_tmpfs > 0 { if gHostVersion <= 9.3 && jail.Config.Allow_mount_tmpfs > 0 { fmt.Printf(" FreeBSD <= 9.3 does not support tmpfs, disabling in config\n") jail.Config.Allow_mount_tmpfs = 0 // Tag config so it will be synced on disk jail.ConfigUpdated = true err = setJailConfigUpdated(jail) if err != nil { fmt.Printf(fmt.Sprintf("Error updating config for jail %s: %s", jail.Name, err.Error())) return err } } if gHostVersion < 12 { if jail.Config.Allow_mlock > 0 { jail.Config.Allow_mlock = 0 jail.ConfigUpdated = true /* WIP err = setJailProperty(jail, "Config.Allow_mlock", "0") if err != nil { return err }*/ } if jail.Config.Allow_mount_fusefs > 0 { } if jail.Config.Allow_vmm > 0 { } } return nil } // TODO func prepareJailedZfsDatasets(jail *Jail) error { if jail.Config.Jail_zfs > 0 { // For jail to mount filesystem, enforce_statfs should be 1 or lower (2 is the default) if jail.Config.Allow_mount != 1 { jail.Config.Allow_mount = 1 setJailConfigUpdated(jail) } if jail.Config.Allow_mount_zfs != 1 { jail.Config.Allow_mount_zfs = 1 setJailConfigUpdated(jail) } // TODO : Overload Json Unmarshalling to fix bad typed values, keeping iocage compatibility if jail.Config.Enforce_statfs > "1" { jail.Config.Enforce_statfs = "1" setJailConfigUpdated(jail) } for _, d := range strings.Split(jail.Config.Jail_zfs_dataset, " ") { // Check if dataset exist, create if necessary cmd := fmt.Sprintf("zfs get -H creation %s/%s", jail.Zpool, d) out, err := executeCommand(cmd) if err != nil { if strings.HasSuffix(out, "dataset does not exist") { cmd = fmt.Sprintf("zfs create -o compression=lz4 -o mountpoint=none %s/%s", jail.Zpool, d) _, err = executeCommand(cmd) if err != nil { return errors.New(fmt.Sprintf("Error creating dataset %s/%s: %s", jail.Zpool, d, err.Error())) } } else { return errors.New(fmt.Sprintf("Error getting zfs dataset %s: %s", d, err.Error())) } } cmd = fmt.Sprintf("zfs set jailed=on %s/%s", jail.Zpool, d) out, err = executeCommand(cmd) if err != nil { return errors.New(fmt.Sprintf("Error executing \"zfs set jailed=on %s/%s\": %s", jail.Zpool, d, err.Error())) } // TODO : Execute "zfs jail $jailname $dataset" when jail will be up } } return nil } // tcp(80:8080),tcp(3300-3310:33060-33070) func getNatForwardsArray(nat_forwards string, decompose_range bool) ([]NatDesc, error) { var res []NatDesc regx := `(tcp|udp)\(([0-9]{1,5}(?:-[0-9]{1,5})?):([0-9]{1,5}(?:-[0-9]{1,5})?)\)` re := regexp.MustCompile(regx) for _, match := range re.FindAllStringSubmatch(nat_forwards, -1) { if strings.Contains(match[2], "-") == true && decompose_range == true { sjrange, err := strconv.Atoi(strings.Split(match[2], "-")[0]) if err != nil { return res, err } ejrange, err := strconv.Atoi(strings.Split(match[2], "-")[1]) if err != nil { return res, err } shrange, err := strconv.Atoi(strings.Split(match[3], "-")[0]) if err != nil { return res, err } ehrange, err := strconv.Atoi(strings.Split(match[3], "-")[1]) if err != nil { return res, err } if (ejrange - sjrange) != (ehrange - shrange) { return res, errors.New(fmt.Sprintf("Invalid port range in nat_forwards: %s", match[0])) } for i := sjrange; i <= ejrange; i++ { nd := NatDesc{Proto: match[1], JailPort: strconv.Itoa(i), HostPort: strconv.Itoa(shrange + (i - sjrange)), } res = append(res, nd) } } else { nd := NatDesc{Proto: match[1], JailPort: match[2], HostPort: match[3], } res = append(res, nd) } } return res, nil } /***************************************************************************** * Enable or disables DHCP or accept_rtadv for interfaces declared with this * option ****************************************************************************/ func configureDhcpOrAcceptRtadv(jail *Jail, ipproto int, enable bool) error { var nics []string // Iocage legacy behavior to enable on all interfaces if Config.Dhcp is set... if ipproto == IPv4 && jail.Config.Dhcp > 0 || enable == false { nic_list := strings.Split(jail.Config.Interfaces, ",") for _, n := range nic_list { nics = append(nics, strings.Split(n, ":")[0]) } // ...else enable for selected interface in Config.IpX_addr } else { if ipproto == IPv4 { for _, i := range strings.Split(jail.Config.Ip4_addr, ",") { if strings.EqualFold(strings.ToLower(strings.Split(i, "|")[1]), "dhcp") { nics = append(nics, i) } } } else { for _, i := range strings.Split(jail.Config.Ip6_addr, ",") { if strings.EqualFold(strings.ToLower(strings.Split(i, "|")[1]), "accept_rtadv") { nics = append(nics, i) } } } } for _, n := range nics { // vnet0 is epair0b inside jail if strings.Contains(n, "vnet") { n = fmt.Sprintf("%sb", strings.Replace(n, "vnet", "epair", 1)) } key := fmt.Sprintf("ifconfig_%s", n) value := "SYNCDHCP" if ipproto == IPv6 { key = fmt.Sprintf("%s_ipv6", key) value = "inet6 auto_linklocal accept_rtadv autoconf" } if enable == true { err := enableRcKeyValue(jail.ConfigPath, key, value) if err != nil { return fmt.Errorf("ERROR setting %s=%s with sysrc for jail %s: %s\n", key, value, jail.Name, err) } } else { err := disableRcKey(jail.ConfigPath, key) if err != nil { return fmt.Errorf("ERROR deleting %s with sysrc for jail %s: %s\n", key, value, jail.Name, err) } } } return nil } func checkRtsold(jail *Jail) error { if strings.Contains(jail.Config.Ip6_addr, "accept_rtadv") == false { return fmt.Errorf("Must set at least one ip6_addr to accept_rtadv!\n") } err := enableRcKeyValue(jail.ConfigPath, "rtsold_enable", "yes") if err != nil { return fmt.Errorf("ERROR setting rtsold_enable=YES with sysrc for jail %s: %s\n", jail.Name, err) } return nil } func checkNat(backend string) error { cmd := "/sbin/sysctl -q net.inet.ip.forwarding=1" _, err := executeCommand(cmd) if err != nil { return fmt.Errorf("ERROR executing \"/sbin/sysctl -q net.inet.ip.forwarding=1\": %s", err) } if strings.EqualFold(backend, "pf") { // Load module and enable pf out, err := executeCommand("/sbin/kldload -n pf") if err != nil { if false == strings.Contains(out, "module already loaded or in kernel") { return fmt.Errorf("ERROR executing \"/sbin/kldload pf\": %s", err) } } out, err = executeCommand("/sbin/pfctl -e") if err != nil { if false == strings.Contains(out, "pf already enabled") { return fmt.Errorf("ERROR executing \"/sbin/pfctl -e\": %s", err) } } } else if strings.EqualFold(backend, "ipwf") { // Check if module loaded out, err := executeCommand("/sbin/sysctl net.inet.ip.fw.enable=1") if err != nil { if false == strings.Contains(out, "unknown oid 'net.inet.ip.fw.enable'") { return fmt.Errorf("ERROR executing \"/sbin/sysctl net.inet.ip.fw.enable=1\": %s", err) } } _, _ = executeCommand("/bin/kenv net.inet.ip.fw.default_to_accept=1") _, _ = executeCommand("/sbin/kldload -n ipfw") _, _ = executeCommand("/sbin/kldload -n ipfw_nat") _, err = executeCommand("/sbin/sysctl -q net.inet.ip.fw.enable=1") if err != nil { return fmt.Errorf("ERROR executing \"/sbin/sysctl -q net.inet.ip.fw.enable=1\": %s", err) } } return nil } func getJailsInUseIPv4() ([]string, error) { var ips []string re := regexp.MustCompile(ifconfigipv4re) for _, j := range gJails { out, err := executeCommandInJail(&j, "/sbin/ifconfig") if err != nil { return ips, fmt.Errorf("ERROR executing \"/sbin/ifconfig\" in jail %s: %s", j.Name, err) } for _, line := range strings.Split(out, "\n") { if re.MatchString(line) { ips = append(ips, re.FindStringSubmatch(line)[1]) } } } return ips, nil } func genNatIpv4(jail *Jail) ([]string, error) { var ippair []string // Get all IP in use, host and jails inuseip4, err := getHostInUseIPv4() if err != nil { return ippair, err } ij, err := getJailsInUseIPv4() if err != nil { return ippair, err } inuseip4 = append(inuseip4, ij...) // TODO : Voir https://github.com/iocage/iocage/blob/e94863d4c54f02523fb09e62e48be7db9ac92eda/iocage_lib/ioc_common.py#L1026 for i := 0; i < 256; i++ { for j := 0; j < 256; j += 4 { n := iplib.NewNet4(net.ParseIP(fmt.Sprintf("172.16.%d.%d", i, j)), 30) for _, ip := range n.Enumerate(0, 0) { ippair = append(ippair, ip.String()) } found := false for _, ip := range inuseip4 { for _, ipn := range ippair { if ip == ipn { found = true } } } if found == false { return ippair, nil } } } return ippair, nil } func buildDevfsRuleSet(jail *Jail) (error, int) { rulesets := []int{} // Get known rulesets out, err := executeCommand("devfs rule showsets") if err != nil { return errors.New(fmt.Sprintf("Error executing command \"devfs rule showsets\": %v; command returned: %s\n", err, out)), 0 } srs := strings.Split(out, "\n") for _, i := range srs { j, err := strconv.Atoi(i) if err != nil { panic(err) } rulesets = append(rulesets, j) } // Build a dynamic ruleset ruleset := MIN_DYN_DEVFS_RULESET for _, r := range rulesets { if ruleset == r { ruleset++ } } log.Debug("buildDevfsRuleSet: Build ruleset %d\n", ruleset) // Get default devfs_ruleset for the datastore // UPDATE: We don't need this as every jail have a default Devfs_ruleset value /*ds, err := getDatastoreFromArray(jail.Datastore, gDatastores) if err != nil { return errors.New(fmt.Sprintf("Error getting datastore %s for jail %s", jail.Datastore, jail.Name)), 0 } defaultrs, err := strconv.ParseInt(ds.DefaultJailConfig.Devfs_ruleset, 10, 64) if err != nil { return errors.New(fmt.Sprintf("Error parsing default devfs_ruleset for datastore %s", jail.Datastore)), 0 }*/ // Clone configured devfs_ruleset to a dynamic ruleset if false == isStringInArray(srs, jail.Config.Devfs_ruleset) { return errors.New(fmt.Sprintf("Unknown ruleset: %s", jail.Config.Devfs_ruleset)), 0 } rs, _ := strconv.Atoi(jail.Config.Devfs_ruleset) err = copyDevfsRuleset(ruleset, rs) if err != nil { return err, 0 } // Add rules for enabled options if jail.Config.Allow_mount_fusefs > 0 { err := addDevfsRuleToRuleset("path fuse unhide", ruleset) if err != nil { return err, 0 } } if jail.Config.Bpf > 0 { err := addDevfsRuleToRuleset("path bpf* unhide", ruleset) if err != nil { return err, 0 } } if jail.Config.Allow_tun > 0 { err := addDevfsRuleToRuleset("path tun* unhide", ruleset) if err != nil { return err, 0 } } return nil, ruleset } /***************************************************************************** * Build the file containing jail start parameters, in * /var/run/jail.${jail_internal_name}.conf ****************************************************************************/ func buildJailParameters(jail *Jail, dynruleset int) error { parameterFilePath := fmt.Sprintf("/var/run/jail.%s.conf", jail.InternalName) pfile, err := os.Create(parameterFilePath) if err != nil { return errors.New(fmt.Sprintf("Unable to create file /var/run/jail.%d.conf", jail.InternalName)) } defer pfile.Close() if _, err = pfile.WriteString(fmt.Sprintf("%s {\n", jail.InternalName)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if jail.Config.Vnet > 0 { if _, err = pfile.WriteString(" vnet;\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } // FreeBSD 9.3 and under do not support these 2 options if gJailHost.version.major > 9 || (gJailHost.version.major == 9 && gJailHost.version.minor > 3) { if _, err = pfile.WriteString(" mount.fdescfs = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if jail.Config.Allow_mount_tmpfs > 0 { if _, err = pfile.WriteString(" allow.mount.tmpfs = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } } if gJailHost.version.major >= 12 { if jail.Config.Allow_mlock > 0 { if _, err = pfile.WriteString(" allow.mlock = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if jail.Config.Allow_mount_fusefs > 0 { if _, err = pfile.WriteString(" allow.mount.fusefs = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if jail.Config.Allow_vmm > 0 { if _, err = pfile.WriteString(" allow.vmm = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } } if jail.Config.Allow_set_hostname > 0 { if _, err = pfile.WriteString(" allow.set_hostname = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if jail.Config.Mount_devfs > 0 { if _, err = pfile.WriteString(" mount.devfs = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if jail.Config.Allow_raw_sockets > 0 { if _, err = pfile.WriteString(" allow.raw_sockets = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if jail.Config.Allow_sysvipc > 0 { if _, err = pfile.WriteString(" allow.sysvipc = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if jail.Config.Allow_quotas > 0 { if _, err = pfile.WriteString(" allow.quotas = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if jail.Config.Allow_socket_af > 0 { if _, err = pfile.WriteString(" allow.socket_af = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if jail.Config.Allow_chflags > 0 { if _, err = pfile.WriteString(" allow.chflags = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if jail.Config.Allow_mount > 0 { if _, err = pfile.WriteString(" allow.mount = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if jail.Config.Allow_mount_devfs > 0 { if _, err = pfile.WriteString(" allow.mount.devfs = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if jail.Config.Allow_mount_nullfs > 0 { if _, err = pfile.WriteString(" allow.mount.nullfs = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if jail.Config.Allow_mount_procfs > 0 { if _, err = pfile.WriteString(" allow.mount.procfs = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if jail.Config.Allow_mount_zfs > 0 { if _, err = pfile.WriteString(" allow.mount.zfs = \"1\";\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if gJailHost.version.major > 10 || (gJailHost.version.major == 10 && gJailHost.version.minor > 3) { if _, err = pfile.WriteString(fmt.Sprintf(" sysvmsg = \"%s\";\n", jail.Config.Sysvmsg)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if _, err = pfile.WriteString(fmt.Sprintf(" sysvsem = \"%s\";\n", jail.Config.Sysvsem)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if _, err = pfile.WriteString(fmt.Sprintf(" sysvshm = \"%s\";\n", jail.Config.Sysvshm)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if gJailHost.version.major >= 12 { if _, err = pfile.WriteString(fmt.Sprintf(" exec.created = \"%s\";\n", jail.Config.Exec_created)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if _, err = pfile.WriteString(fmt.Sprintf(" host.domainname = \"%s\";\n", jail.Config.Host_domainname)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if _, err = pfile.WriteString(fmt.Sprintf(" host.hostname = \"%s\";\n", jail.Config.Host_hostname)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if _, err = pfile.WriteString(fmt.Sprintf(" path = \"%s\";\n", jail.RootPath)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if _, err = pfile.WriteString(fmt.Sprintf(" securelevel = \"%s\";\n", jail.Config.Securelevel)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } // FIXME : Handle the same name jail on different datastores if _, err = pfile.WriteString(fmt.Sprintf(" host.hostuuid = \"%s\";\n", jail.Config.Host_hostuuid)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if _, err = pfile.WriteString(fmt.Sprintf(" devfs_ruleset = \"%d\";\n", dynruleset)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if _, err = pfile.WriteString(fmt.Sprintf(" enforce_statfs = \"%s\";\n", jail.Config.Enforce_statfs)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if _, err = pfile.WriteString(fmt.Sprintf(" children.max = \"%s\";\n", jail.Config.Children_max)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if _, err = pfile.WriteString(fmt.Sprintf(" exec.clean = \"%d\";\n", jail.Config.Exec_clean)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if _, err = pfile.WriteString(fmt.Sprintf(" exec.timeout = \"%s\";\n", jail.Config.Exec_timeout)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if _, err = pfile.WriteString(fmt.Sprintf(" stop.timeout = \"%s\";\n", jail.Config.Stop_timeout)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } splits := strings.Split(jail.RootPath, "/") fstabPath := strings.Join(append(splits[:len(splits)-1], strings.Replace(splits[len(splits)-1], "root", "fstab", 1)), "/") if _, err = pfile.WriteString(fmt.Sprintf(" mount.fstab = \"%s\";\n", fstabPath)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if _, err = pfile.WriteString(" allow.dying;\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } // get current datastore to get mountpoint ds, err := getDatastoreFromArray(jail.Datastore, gDatastores) if err != nil { return errors.New(fmt.Sprintf("Unable to get datastore %s for jail %s", jail.Datastore, jail.Name)) } consolelog := fmt.Sprintf("%s/log/%s-console.log", ds.Mountpoint, jail.InternalName) if _, err = pfile.WriteString(fmt.Sprintf(" exec.consolelog = \"%s\";\n", consolelog)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if jail.Config.Ip_hostname > 0 { if _, err = pfile.WriteString(fmt.Sprintf(" ip_hostname = \"%s\";\n", jail.Config.Ip_hostname)); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } } if _, err = pfile.WriteString(" persist;\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } if _, err = pfile.WriteString("}\n"); err != nil { return errors.New(fmt.Sprintf("Unable to write to file /var/run/jail.%d.conf: %v", jail.InternalName, err)) } return nil } /* Start jail: Check jail fstab? Mount procfs Mount linprocfs Mount devfs? Mount fdescfs? If jail_zfs, then check jail_zfs_dataset exist (and create otherwise) TODO : Check NAT settings and compatibility with other jails Generate devfsruleset from configured Write config file in /var/run/jails.ioc-$NAME.conf Execute PreStart (Exec_prestart) Start jail (With ENV VARS for network conf) Start networking Mount jail_zfs_datasets inside jail Generate resolv.Conf Copy /etc/localtime into jail (?) Configure NAT Execute Exec_start into jail Execute Exec_poststart If DHCP, check with ifconfig inside jail Set RCTL Rules Use setfib for each jail command */ func StartJail(args []string) { // jail we have to start var cj *Jail var err error for _, j := range args { fmt.Printf("> Starting jail %s\n", j) for i, rj := range gJails { if rj.Name == j { // Get jail reference, not a copy of it; So we can modify attributes cj = &gJails[i] break } } if cj == nil { fmt.Printf("Jail not found: %s\n", j) continue } if cj.Running == true { fmt.Printf("Jail %s is already running!\n", cj.Name) continue } if len(cj.Config.Hostid) > 0 && cj.Config.Hostid_strict_check > 0 { if strings.EqualFold(gJailHost.hostid, cj.Config.Hostid) == false { fmt.Printf("hostid is not matching and hostid_strict_check is on. Not starting jail.\n") return } } var props_missing []string // DHCP can also be set with "dhcp" value in ip4_addr (Eg: "vnet0|dhcp") if cj.Config.Dhcp > 0 || strings.Contains(strings.ToLower(cj.Config.Ip4_addr), "dhcp") == true { if cj.Config.Bpf == 0 { props_missing = append(props_missing, fmt.Sprintf("%s: dhcp requires bpf", cj.Name)) } if cj.Config.Vnet == 0 { props_missing = append(props_missing, fmt.Sprintf("%s: dhcp requires vnet", cj.Name)) } } // tcp(80:8080),tcp(3300-3310:33000-33010) if cj.Config.Nat > 0 && strings.EqualFold(cj.Config.Nat_forwards, "none") == false { // If NAT && port forwarding is enabled, check that port does not conflict // with another running jail for _, j := range gJails { if j.Running == false || strings.EqualFold(j.Config.Nat_forwards, "none") == false || j.Config.Nat != 1 { continue } else { jnd, err := getNatForwardsArray(j.Config.Nat_forwards, true) if err != nil { fmt.Printf(err.Error()) return } cjnd, err := getNatForwardsArray(cj.Config.Nat_forwards, true) if err != nil { fmt.Printf(err.Error()) return } for _, jn := range jnd { for _, cjn := range cjnd { if jn == cjn { fmt.Printf("nat_forwards rule \"%s\" is in conflict with jail %s, won't start\n", fmt.Sprintf("%s(%s:%s)", cjn.Proto, cjn.JailPort, cjn.HostPort), j.Name) return } } } } } } if cj.Config.Nat > 0 && strings.EqualFold(cj.Config.Nat_interface, "none") == true { var jhost JailHost cj.Config.Nat_interface = jhost.GetDefaultInterface() cj.ConfigUpdated = true } if cj.Config.Vnet > 0 && strings.EqualFold(cj.Config.Defaultrouter, "auto") == true { var jhost JailHost cj.Config.Defaultrouter = jhost.GetDefaultGateway4() // "auto" default Gateway should not be updated to support jailhost route change } if cj.Config.Vnet > 0 && strings.EqualFold(cj.Config.Defaultrouter6, "auto") == true { var jhost JailHost cj.Config.Defaultrouter6 = jhost.GetDefaultGateway6() // "auto" default Gateway should not be updated to support jailhost route change } if strings.EqualFold(cj.Config.Ip6_addr, "accept_rtadv") && cj.Config.Vnet == 0 { props_missing = append(props_missing, fmt.Sprintf("%s: accept_rtadv requires vnet", cj.Name)) } if cj.Config.Bpf > 0 && cj.Config.Vnet == 0 { props_missing = append(props_missing, fmt.Sprintf("%s: bpf requires vnet", cj.Name)) } if len(props_missing) > 0 { for _, m := range props_missing { fmt.Printf("%s\n", m) } return } if cj.Config.Dhcp > 0 || strings.Contains(strings.ToLower(cj.Config.Ip4_addr), "dhcp") == true { err = configureDhcpOrAcceptRtadv(cj, IPv4, true) } else { err = configureDhcpOrAcceptRtadv(cj, IPv4, false) } if err != nil { fmt.Printf(err.Error()) return } if cj.Config.Rtsold > 0 { err = checkRtsold(cj) } if err != nil { fmt.Printf(err.Error()) return } if strings.Contains(strings.ToLower(cj.Config.Ip6_addr), "accept_rtadv") == true { err = configureDhcpOrAcceptRtadv(cj, IPv6, true) } else { err = configureDhcpOrAcceptRtadv(cj, IPv6, false) } if err != nil { fmt.Printf(err.Error()) return } fmt.Printf(" > Mount special filesystems:\n") err := mountAllJailFsFromHost(cj) if err != nil { fmt.Printf("ERROR: %s\n", err.Error()) } else { fmt.Printf(" > Mount special filesystems: OK\n") } if cj.Config.Jail_zfs > 0 { fmt.Printf(" > Prepare ZFS Datasets:\n") err := prepareJailedZfsDatasets(cj) if err != nil { fmt.Printf("ERROR: %s\n", err.Error()) } else { fmt.Printf(" > Prepare ZFS Datasets: OK\n") } } // TODO : Check capabilites relative to FreeBSD Version when executing jail with all parameters // See l.335 of https://github.com/iocage/iocage/blob/e94863d4c54f02523fb09e62e48be7db9ac92eda/iocage_lib/ioc_start.py //checkCapabilities(cj) // Check NAT backend if cj.Config.Nat > 0 { log.Debug("Check NAT backend %s\n", cj.Config.Nat_backend) err = checkNat(cj.Config.Nat_backend) if err != nil { fmt.Printf(err.Error()) return } if cj.Config.Vnet == 0 { log.Debug("Generate NAT IPv4 without VNet\n") ip4, err := genNatIpv4(cj) if err != nil { fmt.Printf("%s\n", err.Error()) return } log.Debug("Configuring NAT : Set ip4_addr to %s\n", ip4[0]) // This IP should not be saved into json cj.Config.Ip4_addr = fmt.Sprintf("%s|%s", cj.Config.Nat_interface, ip4[0]) } else { log.Debug("Generate NAT IPv4 with VNet\n") ip4, err := genNatIpv4(cj) if err != nil { fmt.Printf("%s\n", err.Error()) return } log.Debug("Configuring NAT : Set ip4_addr to %s, defaultrouter to %s\n", ip4[1], ip4[0]) // This IP should not be saved into json cj.Config.Ip4_addr = fmt.Sprintf("vnet0|%s", ip4[1]) cj.Config.Defaultrouter = ip4[0] } } // See https://github.com/iocage/iocage/blob/e94863d4c54f02523fb09e62e48be7db9ac92eda/iocage_lib/ioc_start.py:401 if cj.Config.Vnet == 0 { // Not supported fmt.Printf("Only VNet jails supported\n") return } var net []string if false == strings.EqualFold(cj.Config.Vnet_interfaces, "none") { net = append(net, strings.Split(cj.Config.Vnet_interfaces, " ")...) } err, dynrs := buildDevfsRuleSet(cj) if err != nil { fmt.Printf("%s\n", err.Error()) return } // CONTINUE HERE, around https://github.com/iocage/iocage/blob/master/iocage_lib/ioc_start.py:516 err = buildJailParameters(cj, dynrs) if err != nil { fmt.Printf("%s\n", err.Error()) return } // Synchronize jail config to disk WriteConfigToDisk(false) /* out, err := executeCommand(fmt.Sprintf("rctl jail:%s", cj.InternalName)) if err == nil && len(out) > 0 { fmt.Printf(" > Remove RCTL rules:\n") err := removeRctlRules(cj.InternalName, []string{""}) if err != nil { fmt.Printf("ERROR: %s\n", err.Error()) } else { fmt.Printf(" > Remove RCTL rules: OK\n") } } if len (cj.Config.Exec_prestop) > 0 { fmt.Printf(" > Execute prestop:\n") _, err := executeCommand(cj.Config.Exec_prestop) if err != nil { fmt.Printf("ERROR: %s\n", err.Error()) } else { fmt.Printf(" > Execute prestop: OK\n") } } if len (cj.Config.Exec_stop) > 0 { fmt.Printf(" > Execute stop:\n") _, err := executeCommandInJail(cj, cj.Config.Exec_stop) if err != nil { fmt.Printf("ERROR: %s\n", err.Error()) } else { fmt.Printf(" > Execute stop: OK\n") } } if cj.Config.Jail_zfs > 0 { fmt.Printf(" > Umount jailed ZFS:\n") err := umountAndUnjailZFS(cj) if err != nil { fmt.Printf("ERROR: %s\n", err.Error()) } else { fmt.Printf(" > Umount jailed ZFS: OK\n") } } if cj.Config.Vnet > 0 && len(cj.Config.Ip4_addr) > 0 { fmt.Printf(" > Destroy VNet interfaces:\n") err := destroyVNetInterfaces(cj) if err != nil { fmt.Printf("ERROR: %s\n", err.Error()) } else { fmt.Printf(" > Destroy VNet interfaces: OK\n") } } fmt.Printf(" > Remove devfsruleset %s:\n", cj.Config.Devfs_ruleset) err = deleteDevfsRuleset(cj) if err != nil { fmt.Printf("ERROR: %s\n", err.Error()) } else { fmt.Printf(" > Remove devfsruleset %s: OK\n", cj.Config.Devfs_ruleset) } fmt.Printf(" > Stop jail %s:\n", cj.Name) err = stopJail(cj) if err != nil { fmt.Printf("ERROR: %s\n", err.Error()) } else { fmt.Printf(" > Stop jail %s: OK\n", cj.Name) } */ } }