diff --git a/alpine/Dockerfile b/alpine/Dockerfile index b420df3de..1e085af99 100644 --- a/alpine/Dockerfile +++ b/alpine/Dockerfile @@ -75,6 +75,7 @@ COPY packages/9pmount-vsock/9pmount-vsock /sbin COPY packages/test/etc /etc COPY packages/test/mobytest /usr/bin COPY packages/sysctl/etc /etc +COPY packages/iptables/iptables /usr/local/sbin/iptables RUN \ rc-update add swap boot && \ diff --git a/alpine/packages/Makefile b/alpine/packages/Makefile index 727332528..0048273d0 100644 --- a/alpine/packages/Makefile +++ b/alpine/packages/Makefile @@ -10,6 +10,7 @@ all: $(MAKE) -C llmnrd OS=linux $(MAKE) -C gummiboot OS=linux $(MAKE) -C 9pmount-vsock OS=linux + $(MAKE) -C iptables OS=linux arm: $(MAKE) -C transfused OS=linux ARCH=arm @@ -33,3 +34,4 @@ clean: $(MAKE) -C llmnrd clean $(MAKE) -C gummiboot clean $(MAKE) -C 9pmount-vsock clean + $(MAKE) -C iptables clean diff --git a/alpine/packages/docker/etc/init.d/docker b/alpine/packages/docker/etc/init.d/docker index e8d8984f7..a1f43538f 100755 --- a/alpine/packages/docker/etc/init.d/docker +++ b/alpine/packages/docker/etc/init.d/docker @@ -60,6 +60,9 @@ start() DOCKER_LOGFILE="/var/log/docker.log" + # Allow iptables to be overriden + export PATH=/usr/local/sbin:$PATH + start-stop-daemon --start --quiet \ --background \ --exec ${command} \ diff --git a/alpine/packages/iptables/.gitignore b/alpine/packages/iptables/.gitignore new file mode 100644 index 000000000..3457ccc49 --- /dev/null +++ b/alpine/packages/iptables/.gitignore @@ -0,0 +1 @@ +iptables diff --git a/alpine/packages/iptables/Dockerfile b/alpine/packages/iptables/Dockerfile new file mode 100644 index 000000000..c39b33bfe --- /dev/null +++ b/alpine/packages/iptables/Dockerfile @@ -0,0 +1,7 @@ +FROM ocaml/opam:alpine +RUN sudo apk add m4 +RUN opam install ocamlfind astring -y +WORKDIR /app +ADD . /app +RUN sudo chown -R opam /app +RUN opam config exec -- ocamlfind ocamlopt -package unix,astring -linkpkg -o iptables main.ml diff --git a/alpine/packages/iptables/Makefile b/alpine/packages/iptables/Makefile new file mode 100644 index 000000000..df248cc0a --- /dev/null +++ b/alpine/packages/iptables/Makefile @@ -0,0 +1,9 @@ +all: iptables + +iptables: Dockerfile main.ml + docker build -t iptables:build . + docker run --rm iptables:build cat /app/iptables > iptables + chmod 755 iptables + +clean: + docker images -q iptables:build | xargs docker rmi -f || true diff --git a/alpine/packages/iptables/main.ml b/alpine/packages/iptables/main.ml new file mode 100644 index 000000000..aa9de21dc --- /dev/null +++ b/alpine/packages/iptables/main.ml @@ -0,0 +1,100 @@ +(* ocamlfind ocamlopt -package unix,astring -linkpkg -o iptables iptables.ml *) + +(* +--wait -t nat -I DOCKER-INGRESS -p tcp --dport 80 -j DNAT --to-destination 172.18.0.2:80 +--wait -t nat -D DOCKER-INGRESS -p tcp --dport 80 -j DNAT --to-destination 172.18.0.2:80 +*) + +let _iptables = "/sbin/iptables" +let _proxy = "/usr/bin/docker-proxy" +let _pid_dir = "/var/run/service-port-opener" + +type port = { + proto: string; + dport: string; (* host port *) + ip: string; (* container ip *) + port: string; (* container port *) +} + +let log_fd = Unix.openfile "/var/log/service-port-opener.log" [ Unix.O_WRONLY; Unix.O_APPEND; Unix.O_CREAT ] 0o0644 + +let logf fmt = + Printf.ksprintf (fun s -> + let s = s ^ "\n" in + let rec loop ofs remaining = + if remaining > 0 then begin + let n = Unix.write log_fd s ofs remaining in + loop (ofs + n) (remaining - n) + end in + loop 0 (String.length s) + ) fmt + +let pid_filename { proto; dport; ip; port } = + Printf.sprintf "%s/%s.%s.%s.%s.pid" _pid_dir proto dport ip port + +let insert ({ proto; dport; ip; port } as p) = + let filename = pid_filename p in + logf "insert: creating a proxy for %s" filename; + let args = [ _proxy; "-proto"; proto; "-container-ip"; ip; "-container-port"; port; "-host-ip"; "0.0.0.0"; "-host-port"; dport; "-i"; "-no-local-ip" ] in + let pid = Unix.fork () in + if pid == 0 then begin + logf "binary = %s args = %s" _proxy (String.concat "; " args); + (* Close the vast number of fds I've inherited from docker *) + (* TODO(djs55): revisit, possibly by filing a docker/docker issue *) + for i = 0 to 1023 do + let fd : Unix.file_descr = Obj.magic i in + try Unix.close fd with Unix.Unix_error(Unix.EBADF, _, _) -> () + done; + let null = Unix.openfile "/dev/null" [ Unix.O_RDWR ] 0 in + Unix.dup2 null Unix.stdin; + Unix.dup2 null Unix.stdout; + Unix.dup2 null Unix.stderr; + (try Unix.execv _proxy (Array.of_list args) with e -> logf "Failed with %s" (Printexc.to_string e)); + exit 1 + end else begin + (* write pid to a file (not atomically) *) + let oc = open_out filename in + output_string oc (string_of_int pid); + close_out oc + end + +let delete ({ proto; dport; ip; port } as p) = + let filename = pid_filename p in + logf "delete: removing a proxy for %s" filename; + (* read the pid from a file *) + try + let ic = open_in filename in + let pid = int_of_string (input_line ic) in + logf "Sending SIGTERM to %d" pid; + Unix.kill pid Sys.sigterm; + Unix.unlink filename + with e -> + logf "delete: failed to remove proxy for %s: %s" filename (Printexc.to_string e); + () + +let parse_ip_port ip_port = match Astring.String.cut ~sep:":" ip_port with + | None -> + failwith ("Failed to parse :" ^ ip_port) + | Some (ip, port) -> + ip, port + +let _ = + ( try Unix.mkdir _pid_dir 0o0755 with Unix.Unix_error(Unix.EEXIST, _, _) -> () ); + let port_forwarding = + try + let ic = open_in "/Database/branch/master/ro/com.docker.driver.amd64-linux/native/port-forwarding" in + bool_of_string (String.trim (input_line ic)) + with _ -> false in + logf "port_forwarding=%b intercepted arguments [%s]" port_forwarding (String.concat "; " (Array.to_list Sys.argv)); + if port_forwarding then begin + match Array.to_list Sys.argv with + | [ _; "--wait"; "-t"; "nat"; "-I"; "DOCKER-INGRESS"; "-p"; proto; "--dport"; dport; "-j"; "DNAT"; "--to-destination"; ip_port ] -> + let ip, port = parse_ip_port ip_port in + insert { proto; dport; ip; port } + | [ _; "--wait"; "-t"; "nat"; "-D"; "DOCKER-INGRESS"; "-p"; proto; "--dport"; dport; "-j"; "DNAT"; "--to-destination"; ip_port ] -> + let ip, port = parse_ip_port ip_port in + delete { proto; dport; ip; port } + | _ -> + () + end; + Unix.execv _iptables Sys.argv diff --git a/alpine/packages/proxy/one.go b/alpine/packages/proxy/one.go index b8f4e1730..e91aa30af 100644 --- a/alpine/packages/proxy/one.go +++ b/alpine/packages/proxy/one.go @@ -11,11 +11,16 @@ import ( ) func onePort() { - host, _, container := parseHostContainerAddrs() + host, _, container, localIP := parseHostContainerAddrs() - ipP, err := libproxy.NewIPProxy(host, container) - if err != nil { - sendError(err) + var ipP libproxy.Proxy + var err error + + if localIP { + ipP, err = libproxy.NewIPProxy(host, container) + if err != nil { + sendError(err) + } } ctl, err := exposePort(host, container) @@ -23,10 +28,14 @@ func onePort() { sendError(err) } - go handleStopSignals(ipP) + go handleStopSignals() // TODO: avoid this line if we are running in a TTY sendOK() - ipP.Run() + if ipP != nil { + ipP.Run() + } else { + select{} // sleep forever + } ctl.Close() // ensure ctl remains alive and un-GCed until here os.Exit(0) } diff --git a/alpine/packages/proxy/proxy.go b/alpine/packages/proxy/proxy.go index c89ff8db2..adc3b8191 100644 --- a/alpine/packages/proxy/proxy.go +++ b/alpine/packages/proxy/proxy.go @@ -8,8 +8,6 @@ import ( "os" "os/signal" "syscall" - - "proxy/libproxy" ) var interactiveMode bool @@ -48,7 +46,7 @@ var vSockUDPPortOffset = 0x20000 // parseHostContainerAddrs parses the flags passed on reexec to create the TCP or UDP // net.Addrs to map the host and container ports -func parseHostContainerAddrs() (host net.Addr, port int, container net.Addr) { +func parseHostContainerAddrs() (host net.Addr, port int, container net.Addr, localIP bool) { var ( proto = flag.String("proto", "tcp", "proxy protocol") hostIP = flag.String("host-ip", "", "host ip") @@ -56,6 +54,7 @@ func parseHostContainerAddrs() (host net.Addr, port int, container net.Addr) { containerIP = flag.String("container-ip", "", "container ip") containerPort = flag.Int("container-port", -1, "container port") interactive = flag.Bool("i", false, "print success/failure to stdout/stderr") + noLocalIP = flag.Bool("no-local-ip", false, "bind only on the Host, not in the VM") ) flag.Parse() @@ -73,17 +72,15 @@ func parseHostContainerAddrs() (host net.Addr, port int, container net.Addr) { default: log.Fatalf("unsupported protocol %s", *proto) } - - return host, port, container + localIP = ! *noLocalIP + return host, port, container, localIP } -func handleStopSignals(p libproxy.Proxy) { +func handleStopSignals() { s := make(chan os.Signal, 10) signal.Notify(s, os.Interrupt, syscall.SIGTERM, syscall.SIGSTOP) for range s { os.Exit(0) - // The vsock proxy cannot be shutdown the same way as the TCP one: - //p.Close() } }