gocage/cmd/start.go

1631 lines
52 KiB
Go

package cmd
import (
"os"
"fmt"
"net"
"sync"
"time"
"errors"
"regexp"
"reflect"
"strconv"
"strings"
"io/ioutil"
"crypto/rand"
"gocage/jail"
"encoding/hex"
"github.com/c-robinson/iplib"
log "github.com/sirupsen/logrus"
)
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 gJailHost.version.major < 9 || (gJailHost.version.major == 9 && gJailHost.version.minor <= 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
if err := setJailConfigUpdated(jail); err != nil {
//fmt.Printf(fmt.Sprintf("Error updating config for jail %s: %s", jail.Name, err.Error()))
return err
}
// 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
}
// devfs & fdescfs are automatically mounted by jail binary (seen on FreeBSD 13.1)
func mountAllJailFsFromHost(jail *Jail) error {
procfsFound := false
linProcfsFound := 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
}
}
}
// 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
}
}
// Ces montages doivent-ils etre effectués une fois le jail démarré?
// FreeBSD <= 9.3 do not support fdescfs
if gJailHost.version.major < 9 || (gJailHost.version.major == 9 && gJailHost.version.minor <= 3) {
if 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 gJailHost.version.major < 12 {
if jail.Config.Allow_mlock > 0 {
jail.Config.Allow_mlock = 0
if err = setJailConfigUpdated(jail); err != nil {
//fmt.Printf(fmt.Sprintf("Error updating config for jail %s: %s", jail.Name, err.Error()))
return err
}
}
if jail.Config.Allow_mount_fusefs > 0 {
jail.Config.Allow_mount_fusefs = 0
if err = setJailConfigUpdated(jail); err != nil {
//fmt.Printf(fmt.Sprintf("Error updating config for jail %s: %s", jail.Name, err.Error()))
return err
}
}
if jail.Config.Allow_vmm > 0 {
jail.Config.Allow_vmm = 0
if err = setJailConfigUpdated(jail); err != nil {
//fmt.Printf(fmt.Sprintf("Error updating config for jail %s: %s", jail.Name, err.Error()))
return err
}
}
}
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
// Support jailing datasets on differents pools : dataset should be specified with pool name
cmd := fmt.Sprintf("zfs get -H creation %s", d)
out, err := executeCommand(cmd)
if err != nil {
if strings.HasSuffix(out, "dataset does not exist") {
// Support jailing datasets on differents pools : dataset should be specified with pool name
cmd = fmt.Sprintf("zfs create -o compression=lz4 -o mountpoint=none %s", d)
_, err = executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error creating dataset %s: %s", 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", d)
out, err = executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error executing \"zfs set jailed=on %s\": %s", d, err.Error()))
}
}
}
return nil
}
func jailZfsDatasets(jail *Jail) error {
if jail.Config.Jail_zfs > 0 {
for _, d := range strings.Split(jail.Config.Jail_zfs_dataset, " ") {
// Jail dataset
// Support jailing datasets on differents pools : dataset should be specified with pool name
cmd := fmt.Sprintf("zfs jail %d %s", jail.JID, d)
out, err := executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error jailling zfs dataset %s: %v: out", d, err, out))
}
// Mount from inside jail if mountpoint is set
cmd = fmt.Sprintf("zfs get -H -o value mountpoint %s", d)
out, err = executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error getting zfs dataset %s mountpoint: %v: %s", d, err, out))
}
if len(out) > 0 && out != "-" && (false == strings.EqualFold(out, "none")) {
// Should we "mount -a" ? cmd = fmt.Sprintf("zfs mount -a")
cmd = fmt.Sprintf("zfs mount %s", d)
out, err = executeCommandInJail(jail, cmd)
if err != nil {
// If already mounted, continue processing
if ! strings.HasSuffix(out, "filesystem already mounted\n") {
return errors.New(fmt.Sprintf("Error mounting zfs dataset %s from inside jail: %v: %s", d, err, out))
}
}
}
}
}
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") {
if strings.HasPrefix(n, "vnet") {
splitd := strings.Split(n, "|")
n = fmt.Sprintf("%sb", strings.Replace(splitd[0], "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(fmt.Sprintf("%s/etc/rc.conf", jail.RootPath), 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(fmt.Sprintf("%s/etc/rc.conf", jail.RootPath), key)
if err != nil {
return fmt.Errorf("ERROR deleting %s with sysrc for jail %s: %v\n", key, 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(fmt.Sprintf("%s/etc/rc.conf", jail.RootPath), "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
}
/*****************************************************************************
* Get IPv4 in use in all running jails
****************************************************************************/
func getJailsInUseIPv4() ([]string, error) {
var ips []string
re := regexp.MustCompile(ifconfigipv4re)
for _, j := range gJails {
if j.Running == true {
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
}
// FIXME : Must lock this function so parallel start do not
func buildDevfsRuleSet(jail *Jail, m *sync.Mutex) (error, int) {
rulesets := []int{}
m.Lock()
//defer m.Unlock()
// Get known rulesets
out, err := executeCommand("devfs rule showsets")
if err != nil {
m.Unlock()
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 {
if len(i) > 0 {
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.Debugf("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 {
m.Unlock()
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 {
m.Unlock()
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) {
m.Unlock()
return errors.New(fmt.Sprintf("Unknown ruleset: %s", jail.Config.Devfs_ruleset)), 0
}
rs, _ := strconv.Atoi(jail.Config.Devfs_ruleset)
err = copyDevfsRuleset(ruleset, rs)
m.Unlock()
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
}
func setJailVnetIp(jail *Jail, nic string, ip string) error {
if strings.Contains(ip, ":") {
if jail.Config.Dhcp == 0 && false == strings.EqualFold(ip, "dhcp") {
cmd := fmt.Sprintf("/sbin/ifconfig %s inet6 %s up", nic, ip)
_, err := executeCommandInJail(jail, cmd)
if err != nil {
return fmt.Errorf("Error setting network inside jail: %v", err)
}
}
} else {
if jail.Config.Dhcp == 0 && false == strings.EqualFold(ip, "dhcp") {
// vnetX is epairXb inside the jail
cmd := fmt.Sprintf("/sbin/ifconfig %s %s up", nic, ip)
_, err := executeCommandInJail(jail, cmd)
if err != nil {
return fmt.Errorf("Error setting network inside jail: %v", err)
}
}
}
return nil
}
// Generate a MAC address for the jail and save in config
// Returns host-side MAC, jail-side MAC, error
func generateMAC(jail *Jail, nic string) ([]byte, []byte, error) {
var prefix []byte
var err error
if len(jail.Config.Mac_prefix) > 0 {
prefix, err = hex.DecodeString(jail.Config.Mac_prefix)
if err != nil {
return []byte{0,0,0,0,0,0}, []byte{0,0,0,0,0,0}, err
}
} else {
ds, err := getDatastoreFromArray(jail.Datastore, gDatastores)
if err != nil {
return []byte{0,0,0,0,0,0}, []byte{0,0,0,0,0,0}, err
}
prefix, err = hex.DecodeString(ds.DefaultJailConfig.Mac_prefix)
if err != nil {
return []byte{0,0,0,0,0,0}, []byte{0,0,0,0,0,0}, err
}
}
// Set the local bit, ensure unicast address
//prefix[0] = (prefix[0] | 2) & 0xfe
suffix := make([]byte, (6-len(prefix)))
_, err = rand.Read(suffix)
if err != nil {
return []byte{0,0,0,0,0,0}, []byte{0,0,0,0,0,0}, err
}
hsmac := append(prefix, suffix...)
jsmac := make([]byte, 6)
copy(jsmac, hsmac)
jsmac[5] = jsmac[5] + 1
// Save MACs to config
pname := fmt.Sprintf("Config.%s_mac", strings.Title(nic))
err = setStructFieldValue(jail, pname, fmt.Sprintf("%s %s", hex.EncodeToString(hsmac), hex.EncodeToString(jsmac)))
if err != nil {
return []byte{0,0,0,0,0,0}, []byte{0,0,0,0,0,0}, fmt.Errorf("Error setting %s for jail %s: %s\n", pname, jail.Name, err.Error())
}
setJailConfigUpdated(jail)
return hsmac, jsmac, nil
}
// Setup interface host side
// Returns array of epair names, error
func setupVnetInterfaceHostSide(jail *Jail) ([]string, error) {
var epairs []string
var jsnic string
var hsmac []byte
var err error
for _, nicCnf := range strings.Split(jail.Config.Interfaces, ",") {
v := strings.Split(nicCnf, ":")
if len(v) != 2 {
return []string{}, fmt.Errorf("Invalid value for Interfaces: %s\n", nicCnf)
}
nic := v[0]
bridge := v[1]
// Get host side MAC
pname := fmt.Sprintf("Config.%s_mac", strings.Title(nic))
var val *reflect.Value
val, pname, err = getStructFieldValue(jail, pname)
if err != nil {
if strings.HasPrefix(err.Error(), "Field not found") {
hsmac, _, err = generateMAC(jail, nic)
if err != nil {
return []string{}, err
}
} else {
return []string{}, err
}
} else {
if strings.EqualFold(val.String(), "none") {
hsmac, _, err = generateMAC(jail, nic)
if err != nil {
return []string{}, err
}
}
hsmac, err = hex.DecodeString(strings.Split(val.String(), " ")[0])
if err != nil {
return []string{}, fmt.Errorf("Error converting %s to hex\n", strings.Split(val.String(), " ")[0])
}
}
// Get bridge MTU
mtu, err := gJailHost.GetBridgeMTU(bridge)
if err != nil {
return []string{}, fmt.Errorf("Error getting bridge \"%s\" mtu: %v\n", bridge, err)
}
// Create epair interface
out, err := executeCommand("/sbin/ifconfig epair create")
if err != nil {
return []string{}, fmt.Errorf("Error creating epair interface: %v\n", err)
}
hsepair := strings.TrimRight(out, "\n")
if true == strings.Contains(nic, "vnet") {
jsnic = strings.Replace(nic, "vnet", "epair", 1) + "b"
} else {
jsnic = nic
}
// Name epair and set MTU
cmd := fmt.Sprintf("/sbin/ifconfig %s name %s.%d mtu %d", hsepair, nic, jail.JID, mtu)
out, err = executeCommand(cmd)
if err != nil {
return []string{}, fmt.Errorf("Error creating epair interface host side: %v\n", err)
}
cmd = fmt.Sprintf("/sbin/ifconfig %s.%d link %s", nic, jail.JID, hex.EncodeToString(hsmac))
out, err = executeCommand(cmd)
if err != nil {
return []string{}, fmt.Errorf("Error setting mac host side: %v\n", err)
}
cmd = fmt.Sprintf("/sbin/ifconfig %s.%d description \"associated with jail: %s as nic: %s\"", nic, jail.JID, jail.Name, jsnic)
out, err = executeCommand(cmd)
if err != nil {
return []string{}, fmt.Errorf("Error setting nic description: %v\n", err)
}
epairs = append(epairs, hsepair)
}
log.Debugf("setupVnetInterfaceHostSide: returning %v\n", epairs)
return epairs, nil
}
func setupVnetInterfaceJailSide(jail *Jail, hostepairs []string) error {
var jsmac []byte
var err error
// Maps of IP by netcard (Ex: jailIp4s["vnet0"] = "192.168.1.1")
var ip4s = make(map[string]string)
var ip6s = make(map[string]string)
for _, i := range strings.Split(jail.Config.Ip4_addr, ",") {
v := strings.Split(i, "|")
if len(v) > 1 {
ip4s[v[0]] = v[1]
}
}
for _, i := range strings.Split(jail.Config.Ip6_addr, ",") {
v := strings.Split(i, "|")
if len(v) > 1 {
ip6s[v[0]] = v[1]
}
}
// Loop through configured interfaces
for i, nicCnf := range strings.Split(jail.Config.Interfaces, ",") {
v := strings.Split(nicCnf, ":")
if len(v) != 2 {
return fmt.Errorf("Invalid value for Interfaces: %s\n", nicCnf)
}
// vnet nic name
nic := v[0]
bridge := v[1]
// inside jail final nic name
jnic := strings.Replace(v[0], "vnet", "epair", 1)
jnic = jnic + "b"
// host side associated jail nic name
jsepair := fmt.Sprintf("%sb", strings.TrimSuffix(hostepairs[i], "a"))
// Get jail side MAC
pname := fmt.Sprintf("Config.%s_mac", strings.Title(nic))
var val *reflect.Value
val, pname, err = getStructFieldValue(jail, pname)
if err != nil {
if strings.HasPrefix(err.Error(), "Field not found") {
_, jsmac, err = generateMAC(jail, nic)
if err != nil {
return err
}
} else {
return err
}
} else {
jsmac, err = hex.DecodeString(strings.Split(val.String(), " ")[1])
if err != nil {
return fmt.Errorf("Error converting %s to hex\n", strings.Split(val.String(), " ")[1])
}
}
cmd := fmt.Sprintf("/sbin/ifconfig %s vnet %s", jsepair, jail.InternalName)
_, err := executeCommand(cmd)
if err != nil {
return fmt.Errorf("Error linking interface to jail: %v\n", err)
}
// Get bridge MTU
mtu, err := gJailHost.GetBridgeMTU(bridge)
if err != nil {
return fmt.Errorf("Error getting bridge \"%s\" mtu: %v\n", bridge, err)
}
cmd = fmt.Sprintf("/usr/sbin/jexec %d ifconfig %s mtu %d", jail.JID, jsepair, mtu)
_, err = executeCommand(cmd)
if err != nil {
return fmt.Errorf("Error setting mtu: %v\n", err)
}
// rename epairXXb to epair0b (or opair1b, ...)
cmd = fmt.Sprintf("/usr/sbin/setfib %s jexec %d ifconfig %s name %s", jail.Config.Exec_fib, jail.JID, jsepair, jnic)
_, err = executeCommand(cmd)
if err != nil {
return fmt.Errorf("Error linking interface to jail: %v\n", err)
}
cmd = fmt.Sprintf("/usr/sbin/setfib %s jexec %d ifconfig %s link %s", jail.Config.Exec_fib,
jail.JID, jnic, hex.EncodeToString(jsmac))
_, err = executeCommand(cmd)
if err != nil {
return fmt.Errorf("Error setting mac: %v\n", err)
}
// TODO: Move outside of this function
// add interface to bridge
if jail.Config.Nat == 0 {
cmd := fmt.Sprintf("/sbin/ifconfig %s addm %s.%d up", bridge, nic, jail.JID)
out, err := executeCommand(cmd)
if err != nil {
return fmt.Errorf("Error adding member %s to %s: %v: %s\n", nic, bridge, err, out)
}
}
// Check we have an IP for the nic, and set it into jail
if len(ip4s[nic]) > 0 {
err = setJailVnetIp(jail, jnic, ip4s[nic])
}
if len(ip6s[nic]) > 0 {
err = setJailVnetIp(jail, jnic, ip6s[nic])
}
// finally up interface
if jail.Config.Nat == 0 {
cmd := fmt.Sprintf("/sbin/ifconfig %s.%d up", nic, jail.JID)
out, err := executeCommand(cmd)
if err != nil {
return fmt.Errorf("Error upping interface %s.%d: %v: %s\n", nic, jail.JID, err, out)
}
}
}
log.Debugf("setupVnetInterfaceJailSide: return with success\n")
return nil
}
func generateResolvConf(jail *Jail) error {
if jail.Config.Resolver != "/etc/resolv.conf" && jail.Config.Resolver != "none" && jail.Config.Resolver != "/dev/null" {
f, err := os.Create(fmt.Sprintf("%s/etc/resolv.conf", jail.RootPath))
if err != nil {
return err
}
defer f.Close()
for _, l := range strings.Split(jail.Config.Resolver, ";") {
f.WriteString(fmt.Sprintf("%s\n", l))
}
} else if jail.Config.Resolver == "none" || jail.Config.Resolver == "/etc/resolv.conf" {
read, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
return fmt.Errorf("Error opening /etc/resolv.conf: %v", err)
}
err = ioutil.WriteFile(fmt.Sprintf("%s/etc/resolv.conf", jail.RootPath), read, 0644)
if err != nil {
return fmt.Errorf("Error writing to %s/etc/resolv.conf: %v", jail.RootPath, err)
}
} else if jail.Config.Resolver == "/dev/null" {
// Just do nothing
}
return nil
}
func copyLocalTime(jail *Jail) error {
if jail.Config.Host_time > 0 {
read, err := ioutil.ReadFile("/etc/localtime")
if err != nil {
return fmt.Errorf("Error opening /etc/localtime: %v", err)
}
err = ioutil.WriteFile(fmt.Sprintf("%s/etc/localtime", jail.RootPath), read, 0644)
if err != nil {
return fmt.Errorf("Error writing to %s/etc/localtime: %v", jail.RootPath, err)
}
}
return nil
}
// TODO: Umount all FS
// TODO: Delete devfs ruleset
// TODO: Remove /var/run/jail-${internalname}.conf
func cleanAfterStartCrash() {
}
// Start all jails with boot=true, in priority order
func StartJailsAtBoot() {
var startList []Jail
var wg *sync.WaitGroup
var curThNb int
var curPri int
// Get boot enabled non-template jails
for _, j := range gJails {
if j.Config.Boot > 0 && !strings.EqualFold(j.Config.Jailtype, "template") {
startList = append(startList, j)
}
}
// Order by priority
js := initJailSortStruct()
fct, _, err := getStructFieldValue(js, "Config.PriorityInc")
if err != nil {
log.Errorf("ERROR getting JailSort struct field \"Config.PriorityInc\"\n")
return
}
JailsOrderedBy(fct.Interface().(jailLessFunc)).Sort(startList)
wg = new(sync.WaitGroup)
curThNb = 0
for i, j := range startList {
jFullName := fmt.Sprintf("%s/%s", j.Datastore, j.Name)
log.Debugf("Starting %s with priority %s\n", jFullName, j.Config.Priority)
jailPri, err := strconv.Atoi(j.Config.Priority)
if err != nil {
panic(fmt.Sprintf("Invalid format for Priority (Jail %s)\n", jFullName))
}
if (curThNb >= gMaxThreads || i == 0) {
// FIXME : Use a pool instead of waiting for all threads to run a new one
wg.Wait()
curThNb = 0
wg.Add(1)
curThNb++
curPri = jailPri
go func(jailFullName string) {
defer wg.Done()
StartJail([]string{jailFullName})
}(jFullName)
} else {
if (curPri == jailPri) {
wg.Add(1)
curThNb++
go func(jailFullName string) {
defer wg.Done()
StartJail([]string{jailFullName})
}(jFullName)
} else {
wg.Wait()
curThNb = 0
wg.Add(1)
curThNb++
curPri = jailPri
go func(jailFullName string) {
defer wg.Done()
StartJail([]string{jailFullName})
}(jFullName)
}
}
}
wg.Wait()
}
/*
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 _, a := range args {
// Check if jail exist and is distinctly named
cj, err = getJailFromArray(a, []string{"basejail", "jail"}, gJails)
if err != nil {
fmt.Printf("Error getting jail: %s\n", err)
continue
}
if cj.Running == true {
fmt.Printf("Jail %s/%s is already running!\n", cj.Datastore, cj.Name)
continue
}
fmt.Printf("> Starting jail %s\n", cj.Name)
// Set InternalName as it is used by some of these
cj.InternalName = fmt.Sprintf("ioc-%s", cj.Name)
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 {
cj.Config.Nat_interface = gJailHost.GetDefaultInterface()
cj.ConfigUpdated = true
}
if cj.Config.Vnet > 0 && strings.EqualFold(cj.Config.Defaultrouter, "auto") == true {
cj.Config.Defaultrouter = gJailHost.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 {
cj.Config.Defaultrouter6 = gJailHost.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")
}
}
// 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, &gMdevfs)
if err != nil {
fmt.Printf("%s\n", err.Error())
return
}
err = buildJailParameters(cj, dynrs)
if err != nil {
fmt.Printf("%s\n", err.Error())
return
}
// Synchronize jail config to disk
cj.WriteConfigToDisk(false)
start_cmd := fmt.Sprintf("/usr/sbin/jail -f /var/run/jail.%s.conf -c", cj.InternalName)
//TODO: handle start_env & prestart_env, could be used by iocage plugins
_, err = executeScript(cj.Config.Exec_prestart)
if err != nil {
fmt.Printf("Error executing exec_prestart script: %v\n", err)
fmt.Printf("Aborting jail start\n")
return
}
fmt.Printf(" > Start jail:\n")
_, err = executeCommand(start_cmd)
if err != nil {
fmt.Printf("Error starting jail %s: %v\n", cj.Name, err)
return
}
fmt.Printf(" > Start jail: OK\n")
fmt.Printf(" > With devfs ruleset %d\n", dynrs)
// Update running state, JID and Devfs_ruleset
cj.Running = true
cj.Devfs_ruleset = dynrs
rjails, err := jail.GetJails()
if err != nil {
fmt.Printf("Error: Unable to list running jails\n")
}
for _, j := range rjails {
if j.Name == cj.InternalName {
cj.JID = j.Jid
break
}
}
// Get a reference to current jail, to update its properties in RAM so API will get fresh data
for i, j := range gJails {
if strings.EqualFold(j.Name, cj.Name) && strings.EqualFold(j.Datastore, cj.Datastore) {
if err = setStructFieldValue(&gJails[i], "Running", "true"); err != nil {
fmt.Printf("ERROR: setting Running property to true: %s\n", err.Error())
}
if err = setStructFieldValue(&gJails[i], "JID", fmt.Sprintf("%d", cj.JID)); err != nil {
fmt.Printf("ERROR: setting JID property to %d: %s\n", cj.JID, err.Error())
}
if err = setStructFieldValue(&gJails[i], "InternalName", cj.InternalName); err != nil {
fmt.Printf("ERROR: Setting InternalName property: %s\n", err.Error())
}
// FIXME: this value of devfs_ruleset should not go in Config, it should reside in Jail root properties as it is volatile (only valid while jail is running)
/*if err = setStructFieldValue(&gJails[i], "Devfs_ruleset", "0"); err != nil {
fmt.Printf("ERROR: setting Devfs_ruleset property to 0: %s\n", err.Error())
}*/
}
}
hostInt, err := gJailHost.GetInterfaces()
if err != nil {
fmt.Printf("Error listing jail host interfaces: %v\n", err)
return
}
if false == strings.EqualFold(cj.Config.Vnet_default_interface, "auto") &&
false == strings.EqualFold(cj.Config.Vnet_default_interface, "none") &&
false == isStringInArray(hostInt, cj.Config.Vnet_default_interface) {
fmt.Printf("vnet_default_interface can be one of \"none\", \"auto\" of a valid interface (e.g. \"lagg0\")\n")
return
}
fmt.Printf(" > Setup VNet network:\n")
hsepairs, err := setupVnetInterfaceHostSide(cj);
if err != nil {
fmt.Printf("Error setting VNet interface host side: %v\n", err)
return
}
if err = setupVnetInterfaceJailSide(cj, hsepairs); err != nil {
fmt.Printf("Error setting VNet interface jail side: %v\n", err)
return
}
fmt.Printf(" > Setup VNet network: OK\n")
// Set default route, unless main network is dhcp
if ! cj.isFirstNetDhcp() && !strings.EqualFold(cj.Config.Ip4_addr, "none") {
fmt.Printf(" > Setup default ipv4 gateway:\n")
cmd := fmt.Sprintf("/usr/sbin/setfib %s /usr/sbin/jexec %d route add default %s", cj.Config.Exec_fib, cj.JID, cj.Config.Defaultrouter)
out, err := executeCommand(cmd)
if err != nil && len(out) > 0 {
fmt.Printf("Error: %v: %s\n", err, out)
} else {
fmt.Printf(" > Setup default ipv4 gateway: OK\n")
}
}
if cj.Config.Ip6_addr != "none" {
fmt.Printf(" > Setup default ipv6 gateway:\n")
cmd := fmt.Sprintf("/usr/sbin/setfib %s /usr/sbin/jexec %d route add -6 default %s", cj.Config.Exec_fib, cj.JID, cj.Config.Defaultrouter6)
out, err := executeCommand(cmd)
if err != nil && len(out) > 0 {
fmt.Printf("Error: %v: %s\n", err, out)
} else {
fmt.Printf(" > Setup default ipv6 gateway: OK\n")
}
}
if cj.Config.Jail_zfs > 0 {
fmt.Printf(" > Jail ZFS datasets:\n")
err = jailZfsDatasets(cj)
if err != nil {
fmt.Printf("%v\n", err)
return
}
fmt.Printf(" > Jail ZFS datasets: OK\n")
}
err = generateResolvConf(cj)
if err != nil {
fmt.Printf("%s\n", err)
}
if cj.Config.Host_time > 0 {
err = copyLocalTime(cj)
if err != nil {
fmt.Printf("%s\n", err)
}
}
// Start services
if len(cj.Config.Exec_start) > 0 {
fmt.Printf(" > Start services:\n")
cmd := fmt.Sprintf("/usr/sbin/setfib %s /usr/sbin/jexec %d %s", cj.Config.Exec_fib, cj.JID, cj.Config.Exec_start)
err := executeCommandNonBlocking(cmd)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf(" > Start services: OK\n")
}
}
if cj.Config.Rtsold > 0 || strings.EqualFold(cj.Config.Ip6_addr, "accept_rtadv") {
fmt.Printf(" > Start rtsold:\n")
cmd := fmt.Sprintf("/usr/sbin/setfib %s /usr/sbin/jexec %d service rtsold start", cj.Config.Exec_fib, cj.JID)
out, err := executeCommand(cmd)
if err != nil && len(out) > 0 {
fmt.Printf("Error: %v: %s\n", err, out)
} else {
fmt.Printf(" > Start rtsold: OK\n")
}
}
// TODO: Execute Exec_poststart
if len(cj.Config.Exec_poststart) > 0 {
fmt.Printf(" > Execute post-start:\n")
cmd := fmt.Sprintf("%s", cj.Config.Exec_poststart)
out, err := executeCommand(cmd)
if err != nil && len(out) > 0 {
fmt.Printf("Error: %v: %s\n", err, out)
} else {
fmt.Printf(" > Execute post-start: OK\n")
}
}
// WIP 10/07/2022 : https://github.com/iocage/iocage/blob/master/iocage_lib/ioc_start.py#L891
// TODO: Handle dhcp
// TODO: Apply rctl
// Update last_started
// 23/07/2023 : This is not working, when writing to disk the old value is used
dt := time.Now()
curDate := fmt.Sprintf("%s", dt.Format("2006-01-02 15:04:05"))
cj.Config.Last_started = curDate
writeConfigToDisk(cj, 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)
}
*/
}
}