bunpen: get pasta working
full of race conditions and weird edge cases (some of which may have existed before)
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
// vim: set shiftwidth=2 :
|
||||
|
||||
use errors;
|
||||
use errors::ext;
|
||||
use fmt;
|
||||
use log;
|
||||
use os;
|
||||
use os::exec;
|
||||
use restrict;
|
||||
use rt;
|
||||
use rt::ext;
|
||||
use time;
|
||||
|
||||
fn setup_pasta(net: restrict::net_subset) void = {
|
||||
// `pasta PID [options]`: creates a device in the netns of PID.
|
||||
@@ -23,24 +26,102 @@ fn setup_pasta(net: restrict::net_subset) void = {
|
||||
|
||||
match (os::exec::fork()) {
|
||||
case let child_pid: os::exec::process =>
|
||||
errors::ext::check("setup_pasta: attach", attach_pasta(child_pid));
|
||||
time::sleep(100*time::MILLISECOND); //< TODO: BAD
|
||||
errors::ext::check("setup_pasta: attach", attach_pasta(net, child_pid));
|
||||
errors::ext::check("setup_pasta: wait", wait_and_propagate(child_pid));
|
||||
case void =>
|
||||
errors::ext::check("namespace: unshare net", rt::ext::unshare(rt::ext::clone_flag::NEWNET));
|
||||
// drop enough caps so that pasta has permissions to enter our new netns.
|
||||
// but retain caps so that we can manipulate the net devices after pasta setup.
|
||||
// TODO: this interferes badly with no_new_privs!! figure out why pasta
|
||||
// can't enter our ns.
|
||||
let res = restrict::resources {
|
||||
caps = rt::ext::CAPS_NONE, net = restrict::net_all, ...
|
||||
};
|
||||
restrict::caps_add(&res.caps, rt::ext::cap::NET_ADMIN);
|
||||
restrict::capability_restrict(&res);
|
||||
time::sleep(1000*time::MILLISECOND); //< TODO: BAD
|
||||
errors::ext::check("setup_pasta: config routing", config_routing_in_ns(net));
|
||||
case let e: os::exec::error =>
|
||||
errors::ext::check("setup_pasta: fork", e);
|
||||
};
|
||||
};
|
||||
|
||||
// spawn pasta as a separate process, and have it attach to the netns of the given pid.
|
||||
fn attach_pasta(child: os::exec::process) (void | os::exec::error | rt::errno) = {
|
||||
fn attach_pasta(net: restrict::net_subset, child: os::exec::process) (void | os::exec::error | rt::errno) = {
|
||||
return match (os::exec::fork()?) {
|
||||
case let pasta_pid: os::exec::process => yield void;
|
||||
case void =>
|
||||
// pasta needs permissions to create a device in the netns (it apparently
|
||||
// won't raise those caps itself). TODO: reduce these resources!
|
||||
let res = restrict::resources {
|
||||
caps = rt::ext::CAPS_ALL, net = restrict::net_all, ...
|
||||
};
|
||||
restrict::capability_restrict(&res);
|
||||
|
||||
let pidstr = fmt::asprint(child: int);
|
||||
let pidpath = fmt::asprintf("/proc/{}/ns/net", child: int);
|
||||
defer free(pidstr);
|
||||
let pasta_args = [ "pasta", pidstr ];
|
||||
|
||||
// TODO: append dns, gateway arguments to `pasta`.
|
||||
yield rt::ext::execvpe("pasta", pasta_args, os::getenvs());
|
||||
yield rt::ext::execvpe(
|
||||
"pasta",
|
||||
[
|
||||
"pasta",
|
||||
"--ipv4-only",
|
||||
// pasta `up`s `lo` regardless of this flag; `--config-net` just tells
|
||||
// it to assign an IP and routes to the new device it creates
|
||||
"--config-net",
|
||||
// port forwards:
|
||||
"-u", "none",
|
||||
"-t", "none",
|
||||
"-U", "none",
|
||||
"-T", "none",
|
||||
// "-U", "53", #< if using the host's DNS
|
||||
// "-T", "53", #< if using the host's DNS
|
||||
"--outbound-if4", net.dev,
|
||||
"--gateway", net.gateway,
|
||||
"--netns-only",
|
||||
// pidstr,
|
||||
"--netns", pidpath,
|
||||
],
|
||||
os::getenvs(),
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
fn config_routing_in_ns(net: restrict::net_subset) (void | os::exec::error) = {
|
||||
// forward dns to the desired endpoint
|
||||
let dnsdest = fmt::asprintf("{}:53", net.dns);
|
||||
defer free(dnsdest);
|
||||
let rc = rt::ext::shellvpe(
|
||||
"iptables",
|
||||
[
|
||||
"iptables",
|
||||
"-A", "OUTPUT",
|
||||
"-t", "nat",
|
||||
"-p", "udp",
|
||||
"--dport", "53",
|
||||
"-m", "iprange",
|
||||
"--dst-range", "127.0.0.1-127.0.0.255",
|
||||
"-j", "DNAT",
|
||||
"--to-destination", dnsdest,
|
||||
],
|
||||
os::getenvs(),
|
||||
)?;
|
||||
log::printfln("[namespace/pasta] iptables exited {}", rc.status);
|
||||
|
||||
// remove the loopback routing, else iptables-based DNS forwarding (probably)
|
||||
// doesn't work
|
||||
let rc = rt::ext::shellvpe(
|
||||
"ip",
|
||||
[
|
||||
"ip",
|
||||
"addr",
|
||||
"del", "127.0.0.1/8",
|
||||
"dev", "lo",
|
||||
],
|
||||
os::getenvs(),
|
||||
)?;
|
||||
log::printfln("[namespace/pasta] ip addr exited {}", rc.status);
|
||||
};
|
||||
|
@@ -1,7 +1,10 @@
|
||||
// vim: set shiftwidth=2 :
|
||||
|
||||
use errors;
|
||||
use errors::ext;
|
||||
use log;
|
||||
use os;
|
||||
use os::exec;
|
||||
use path;
|
||||
use rt;
|
||||
use strings;
|
||||
@@ -34,6 +37,38 @@ export fn execvpe(name: str, argv: []str, envp: []str = []) rt::errno = {
|
||||
};
|
||||
};
|
||||
|
||||
// run the provided command in a new process, and wait for its return.
|
||||
// under the hood:
|
||||
// 1. fork
|
||||
// 2. in child: execvpe
|
||||
// 3. in parent: wait for child
|
||||
export fn shellvpe(name: str, argv: []str, envp: []str = []) (os::exec::status | os::exec::error) = {
|
||||
return match (os::exec::fork()) {
|
||||
case void =>
|
||||
errors::ext::check("shellvpe", execvpe(name, argv, envp));
|
||||
assert(false, "unreachable");
|
||||
return os::exec::status { ... };
|
||||
case let child_pid: os::exec::process =>
|
||||
yield wait_child(child_pid);
|
||||
};
|
||||
};
|
||||
|
||||
fn wait_child(child_pid: os::exec::process) (os::exec::status | os::exec::error) = {
|
||||
for (true) {
|
||||
match (os::exec::wait(&child_pid)) {
|
||||
case let e: os::exec::error => match (e) {
|
||||
case errors::interrupted =>
|
||||
// i guess before the days of `poll`, `wait` had to wait on either the
|
||||
// child OR a signal sent to this pid; so we need to retry if the
|
||||
// reason we woke isn't because the child died...
|
||||
void;
|
||||
case => return e;
|
||||
};
|
||||
case let status: os::exec::status => return status;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// allocate and return a NULL-terminated array of pointers to c strings.
|
||||
// caller is responsible for free'ing the resulting array AND its strings.
|
||||
fn to_cstr_array(strs: []str) []nullable *const c::char = {
|
||||
|
Reference in New Issue
Block a user