diff --git a/network/proxy.go b/network/proxy.go new file mode 100644 index 0000000..eeb32c6 --- /dev/null +++ b/network/proxy.go @@ -0,0 +1,202 @@ +package network + +import( + //Builtin + "fmt" + "io" + "net" + "os" + "strconv" + "sync" + + "github.com/subgraph/oz/ns" + + "github.com/op/go-logging" +) + +// Socket list, used to hold ports that should be forwarded +type ProxyConfig struct { + // One of client, server + Nettype string `json:"type"` + + // One of tcp, udp, socket + Proto string + + // TCP or UDP port number + Port int + + // Unix socket to attach to + // applies to proto: socket only + Socket string + + // Optional: Destination address + // In client mode: the host side address to connect to + // In server mode: the container side address to bind to + // If left empty, localhost is used + Destination string +} + +var wgProxy sync.WaitGroup + +func ProxySetup(childPid int, ozSockets []ProxyConfig, log *logging.Logger, ready sync.WaitGroup) error { + for _, socket := range ozSockets { + if socket.Nettype == "" || socket.Nettype == "client" { + err := newProxyClient(childPid, socket.Proto, socket.Destination, socket.Port, log, ready) + if err != nil { + return fmt.Errorf("Unable to setup client socket forwarding %+v, %s", socket, err) + } + } else if socket.Nettype == "server" { + err := newProxyServer(childPid, socket.Proto, socket.Destination, socket.Port, log, ready) + if err != nil { + return fmt.Errorf("Unable to setup server socket forwarding %+s, %s", socket, err) + } + } + } + + return nil +} + +/** + * Listener/Client +**/ +func proxyClientConn(conn *net.Conn, proto, rAddr string, ready sync.WaitGroup) error { + rConn, err := net.Dial(proto, rAddr) + if err != nil { + return fmt.Errorf("Socket: %+v.\n", err) + } + + go io.Copy(rConn, *conn) + go io.Copy(*conn, rConn) + + return nil +} + +func newProxyClient(pid int, proto, dest string, port int, log *logging.Logger, ready sync.WaitGroup) error { + if dest == "" { + dest = "127.0.0.1" + } + + lAddr := net.JoinHostPort("127.0.0.1", strconv.Itoa(port)) + rAddr := net.JoinHostPort(dest, strconv.Itoa(port)) + + log.Info("Starting socket client forwarding: %s://%s.", proto, rAddr) + + listen, err := proxySocketListener(pid, proto, lAddr) + if err != nil { + return err + } + + wgProxy.Add(1) + go func() { + defer wgProxy.Done() + for { + conn, err := listen.Accept() + if err != nil { + log.Error("Socket: %+v.", err) + //panic(err) + continue + } + + go proxyClientConn(&conn, proto, rAddr, ready) + } + }() + + return nil +} + +func proxySocketListener(pid int, proto, lAddr string) (net.Listener, error) { + fd, err := ns.OpenProcess(pid, ns.CLONE_NEWNET) + defer ns.Close(fd) + if err != nil { + return nil, err + } + + return nsSocketListener(fd, proto, lAddr) +} + +func nsSocketListener(fd uintptr, proto, lAddr string) (net.Listener, error) { + origNs, _ := ns.OpenProcess(os.Getpid(), ns.CLONE_NEWNET) + defer ns.Close(origNs) + defer ns.Set(origNs, ns.CLONE_NEWNET) + + err := ns.Set(uintptr(fd), ns.CLONE_NEWNET) + if err != nil { + return nil, err + } + + return net.Listen(proto, lAddr) + +} + +/** + * Connect/Server +**/ +func proxyServerConn(pid int, conn *net.Conn, proto, rAddr string, log *logging.Logger, ready sync.WaitGroup) (error) { + rConn, err := socketConnect(pid, proto, rAddr) + if err != nil { + log.Error("Socket: %+v.", err) + return err + } + + go io.Copy(*conn, rConn) + go io.Copy(rConn, *conn) + + return nil +} + +func newProxyServer(pid int, proto, dest string, port int, log *logging.Logger, ready sync.WaitGroup) (error) { + if dest == "" { + dest = "127.0.0.1" + } + + lAddr := net.JoinHostPort(dest, strconv.Itoa(port)) + rAddr := net.JoinHostPort("127.0.0.1", strconv.Itoa(port)) + + log.Info("Starting socket server forwarding: %s://%s.", proto, lAddr) + + listen, err := net.Listen(proto, lAddr) + if err != nil { + return err + } + + wgProxy.Add(1) + go func() { + defer wgProxy.Done() + for { + conn, err := listen.Accept() + if err != nil { + log.Error("Socket: %+v.", err) + //panic(err) + continue + } + + go proxyServerConn(pid, &conn, proto, rAddr, log, ready) + } + }() + + return nil +} + +func socketConnect(pid int, proto, rAddr string) (net.Conn, error) { + fd, err := ns.OpenProcess(pid, ns.CLONE_NEWNET) + defer ns.Close(fd) + if err != nil { + return nil, err + } + + return nsProxySocketConnect(fd, proto, rAddr) +} + +func nsProxySocketConnect(fd uintptr, proto, rAddr string) (net.Conn, error) { + origNs, _ := ns.OpenProcess(os.Getpid(), ns.CLONE_NEWNET) + defer ns.Close(origNs) + defer ns.Set(origNs, ns.CLONE_NEWNET) + + err := ns.Set(uintptr(fd), ns.CLONE_NEWNET) + if err != nil { + return nil, err + } + + return net.Dial(proto, rAddr) + +} diff --git a/ns/linux_x86_64.go b/ns/linux_x86_64.go new file mode 100644 index 0000000..2b73689 --- /dev/null +++ b/ns/linux_x86_64.go @@ -0,0 +1,9 @@ +package ns + +import ( + +) + +const ( + SYS_SETNS = 308 +) diff --git a/ns/ns.go b/ns/ns.go new file mode 100644 index 0000000..e515330 --- /dev/null +++ b/ns/ns.go @@ -0,0 +1,86 @@ +package ns + +import( + "syscall" + "errors" + "os" + "path" + "strconv" +) + +type Namespace struct { + Path string + Type uintptr +} + +const ( + CLONE_NEWNS = syscall.CLONE_NEWNS + CLONE_NEWUTS = syscall.CLONE_NEWUTS + CLONE_NEWIPC = syscall.CLONE_NEWIPC + CLONE_NEWNET = syscall.CLONE_NEWNET + CLONE_NEWUSER = syscall.CLONE_NEWUSER + CLONE_NEWPID = syscall.CLONE_NEWPID +) + +var ( + Types []Namespace +) + +func init() { + Types = []Namespace{ + Namespace{Path: "ns/user", Type: syscall.CLONE_NEWUSER}, + Namespace{Path: "ns/ipc", Type: syscall.CLONE_NEWIPC}, + Namespace{Path: "ns/uts", Type: syscall.CLONE_NEWUTS}, + Namespace{Path: "ns/net", Type: syscall.CLONE_NEWNET}, + Namespace{Path: "ns/pid", Type: syscall.CLONE_NEWPID}, + Namespace{Path: "ns/mnt", Type: syscall.CLONE_NEWNS}, + } +} + +func Set(fd, nsType uintptr) (error) { + _, _, err := syscall.Syscall(SYS_SETNS, uintptr(fd), uintptr(nsType), 0) + if err != 0 { + return errors.New("Unable to set namespace") + } + + return nil +} + +func GetPath(pid int, nsType uintptr) (string, error) { + var nsPath string + + for _, n := range Types { + if n.Type == nsType { + nsPath = path.Join("/", "proc", strconv.Itoa(pid), n.Path) + break + } + } + + if nsPath == "" { + return "", errors.New("Unable to find namespace type") + } + + return nsPath, nil +} + +func OpenProcess(pid int, nsType uintptr) (uintptr, error) { + nsPath, err := GetPath(pid, nsType) + if err != nil { + return 0, err + } + + return Open(nsPath) +} + +func Open(nsPath string) (uintptr, error) { + fd, err := os.Open(nsPath) + if err != nil { + return 0, err + } + + return fd.Fd(), nil +} + +func Close(fd uintptr) (error) { + return syscall.Close(int(fd)) +} diff --git a/oz-daemon/launch.go b/oz-daemon/launch.go index bc9ca92..8cd6e88 100644 --- a/oz-daemon/launch.go +++ b/oz-daemon/launch.go @@ -137,6 +137,14 @@ func (d *daemonState) launch(p *oz.Profile, pwd string, args, env []string, uid, go func () { sbox.ready.Wait() + + if p.Networking.Nettype != "host" && len(p.Networking.Sockets) > 0 { + err := network.ProxySetup(sbox.init.Process.Pid, p.Networking.Sockets, d.log, sbox.ready) + if err != nil { + log.Warning("Unable to create connection proxy: %+s", err) + } + } + go sbox.launchProgram(pwd, args, log) }() diff --git a/profile.go b/profile.go index 35b6fac..5b3d164 100644 --- a/profile.go +++ b/profile.go @@ -5,6 +5,8 @@ import ( "fmt" "io/ioutil" "path" + + "github.com/subgraph/oz/network" ) type Profile struct { @@ -64,14 +66,14 @@ type EnvVar struct { // Container network definition type NetworkProfile struct { // One of empty, host, bridge - Nettype string + Nettype string `json:"type"` // Name of the bridge to attach to //Bridge string // List of Sockets we want to attach to the jail // Applies to Nettype: bridge and empty only - //Sockets []nsnat.ConfigSocket + Sockets []network.ProxyConfig } const defaultProfileDirectory = "/var/lib/oz/cells.d" diff --git a/profiles/evince.json b/profiles/evince.json index 41e854e..6bdf0a0 100644 --- a/profiles/evince.json +++ b/profiles/evince.json @@ -8,7 +8,7 @@ , "use_pulse_audio": false } , "networking":{ - "nettype":"empty" + "type":"empty" } , "whitelist": [ ] diff --git a/profiles/gajim.json b/profiles/gajim.json index 808f32c..57914da 100644 --- a/profiles/gajim.json +++ b/profiles/gajim.json @@ -7,9 +7,9 @@ , "disable_audio":true } , "networking":{ - "nettype":"empty" + "type":"empty" , "sockets": [ - {"nettype":"client", "proto":"tcp", "port":9050} + {"type":"client", "proto":"tcp", "port":9050} ] } , "whitelist": [ diff --git a/profiles/icedove.json b/profiles/icedove.json index 86f9943..8699973 100644 --- a/profiles/icedove.json +++ b/profiles/icedove.json @@ -7,9 +7,9 @@ , "disable_audio": true } , "networking":{ - "nettype":"bridge" + "type":"bridge" , "sockets": [ - {"nettype":"client", "proto":"tcp", "port":9050} + {"type":"client", "proto":"tcp", "port":9050} ] } , "whitelist": [ diff --git a/profiles/iceweasel.json b/profiles/iceweasel.json index 0da5041..d7181c1 100644 --- a/profiles/iceweasel.json +++ b/profiles/iceweasel.json @@ -6,9 +6,9 @@ , "tray_icon":"/usr/share/icons/hicolor/scalable/apps/iceweasel.svg" } , "networking":{ - "nettype":"bridge" + "type":"bridge" , "sockets": [ - {"nettype":"client", "proto":"tcp", "port":9050} + {"type":"client", "proto":"tcp", "port":9050} ] } , "whitelist": [ diff --git a/profiles/liferea.json b/profiles/liferea.json index 2d64f11..2e4d2d4 100644 --- a/profiles/liferea.json +++ b/profiles/liferea.json @@ -8,9 +8,9 @@ , "_tray_icon":"/usr/share/icons/gnome-colors-common/scalable/apps/liferea.svg" } , "networking":{ - "nettype":"bridge" + "type":"bridge" , "sockets": [ - {"nettype":"client", "proto":"tcp", "port":9050} + {"type":"client", "proto":"tcp", "port":9050} ] } , "whitelist": [ diff --git a/profiles/pidgin.json b/profiles/pidgin.json index 04beb07..d2c5417 100644 --- a/profiles/pidgin.json +++ b/profiles/pidgin.json @@ -6,7 +6,7 @@ , "tray_icon":"/usr/share/icons/gnome-colors-common/scalable/apps/pidgin-menu.svg" } , "networking":{ - "nettype":"bridge" + "type":"bridge" } , "whitelist": [ {"path":"${HOME}/.purple"} diff --git a/profiles/pond.json b/profiles/pond.json index 8478299..9d3d4a7 100644 --- a/profiles/pond.json +++ b/profiles/pond.json @@ -9,10 +9,10 @@ , "use_pulse_audio": false } , "networking":{ - "nettype":"empty" + "type":"empty" , "sockets": [ - {"nettype":"client", "proto":"tcp", "port":9050} - , {"nettype":"client", "proto":"tcp", "port":30003} + {"type":"client", "proto":"tcp", "port":9050} + , {"type":"client", "proto":"tcp", "port":30003} ] } , "whitelist": [ diff --git a/profiles/torbrowser-launcher.json b/profiles/torbrowser-launcher.json index 2f3aae1..bc6dc7a 100644 --- a/profiles/torbrowser-launcher.json +++ b/profiles/torbrowser-launcher.json @@ -9,9 +9,9 @@ , "tray_icon":"/usr/share/pixmaps/torbrowser80.xpm" } , "networking":{ - "nettype":"empty" + "type":"empty" , "sockets": [ - {"nettype":"client", "proto":"tcp", "port":9050} + {"type":"client", "proto":"tcp", "port":9050} ] } , "whitelist": [ diff --git a/profiles/xchat.json b/profiles/xchat.json index a79c57b..a32ec2a 100644 --- a/profiles/xchat.json +++ b/profiles/xchat.json @@ -7,9 +7,9 @@ , "disable_audio": true } , "networking":{ - "nettype":"empty" + "type":"empty" , "sockets": [ - {"nettype":"client", "destination": "10.10.0.90", "proto":"tcp", "port":57000} + {"type":"client", "destination": "10.10.0.90", "proto":"tcp", "port":57000} ] } , "whitelist": [