Verified Commit bc286a97 authored by Karel Koci's avatar Karel Koci 🤘
Browse files

nsfarm/lxd: add ability to proxy TCP and UDP ports

This allows access of TCP and UDP services trough container. This can be
used not only to access services running locally in container but also
any service accessible from container network.

The simple use case of this is access of web interfaces of router.
parent a77aaddd
......@@ -60,6 +60,13 @@ def parser(parser):
device specification from image and RESOURCE is resource mapped to it.
'-p', '--proxy',
help="""Adds socket to be proxied to host. The argument has to be proto:address:port, for example:
tcp: The port this is forwarded to is printed on to the terminal when container is started.
return {
None: parser,
......@@ -129,6 +136,17 @@ def op_inspect(args, parser):
connection = LXDConnection()
with Container(connection, args.IMAGE, device_map=device_map, strict=False, **kwargs) as cont:
if args.proxy:
for proxy in args.proxy:
el = proxy.split(':', maxsplit=2)
if len(el) == 1:
args = {"port": el[0]}
elif len(el) == 2:
args = {"address": el[0], "port": el[1]}
args = {"proto": el[0], "address": el[1], "port": el[2]}
localport =**args)
print(f"Proxy '{proxy}' to: {localport}")
sys.exit(['lxc', 'exec',, '/bin/sh']))
......@@ -3,11 +3,8 @@
import os
import typing
import logging
import pexpect
import functools
import typing
import warnings
import ipaddress
import pexpect
from .. import cli
from .connection import LXDConnection
from .image import Image
......@@ -26,7 +23,6 @@ class Container:
def __init__(self, lxd_connection: LXDConnection, image: typing.Union[str, Image], device_map: dict = None,
internet: typing.Optional[bool] = None, strict: bool = True):
self._lxd = lxd_connection
self._internet = False
self._device_map = device_map
self._override_wants_internet = internet
self._strict = strict
......@@ -135,9 +131,7 @@ class Container:
def name(self) -> typing.Union[str, None]:
"""Name of container if prepared, otherwise None.
if self.lxd_container is None:
return None
return if self.lxd_container is not None else None
def image(self) -> Image:
import ipaddress
import contextlib
import socket
def _find_free_port(proto):
"""Simple function that gives us random free port.
The possible issue is that it might be taken in the meantime but because it is random port that is less likely.
socktp = {'tcp': socket.SOCK_STREAM, 'udp': socket.SOCK_DGRAM}[proto]
with contextlib.closing(socket.socket(socket.AF_INET, socktp)) as sock:
sock.bind(('', 0))
return sock.getsockname()[1]
class NetworkInterface():
......@@ -42,3 +54,35 @@ class NetworkInterface():
"""returns list of available interfaces
return self._network.keys()
def proxy_open(self, proto="tcp", address="", port="80"):
"""Proxy socket connection through container.
Warning: This supports only TCP and UDP sockets right now!
Returns local port number service is proxied to.
assert self._container.lxd_container is not None
freeport = _find_free_port(proto)
self._container.lxd_container.devices[f"proxy-{proto}-{freeport}"] = {
"connect": f"{proto}:{address}:{port}",
"listen": f"{proto}:{freeport}",
"type": "proxy"
return freeport
def proxy_close(self, localport, proto="tcp"):
"""Close existing proxy.
del self._container.lxd_container.devices[f"proxy-{proto}-{localport}"]
def proxy(self, **args):
"""Open proxy for limited context.
This is using proxy_open and proxy_close.
port = self.proxy_open(**args)
yield port
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment