Created
February 13, 2015 19:09
-
-
Save kived/9ee4ad36a16881d4b25e to your computer and use it in GitHub Desktop.
LXC NAT tool
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| ''' | |
| lxc-nat.py: Simple Python script to create port-forwarding rules based on a static table. | |
| Configuration | |
| ============= | |
| Create /etc/lxc/lxc-nat.json with rules such as the following: | |
| :: | |
| { | |
| "config": { | |
| "default_interface": "eth0" | |
| }, | |
| "forwards": [ | |
| { "source": { "ip": "10.0.0.1", "port": 80 }, | |
| "destination": { "name": "www", "port": 80 } }, | |
| { "source": { "port": 3306 }, | |
| "destination": { "name": "mysql_server" } }, | |
| { "proto": "udp", | |
| "source": { "interface": "eth1", "port": 53 }, | |
| "destination": { "name": "dns_server" } } | |
| ] | |
| } | |
| `config` section | |
| ---------------- | |
| This section only contains a single setting: `default_interface`. Useful for | |
| machines which do not have an `eth0` device. | |
| `forwards` section | |
| ------------------ | |
| The bulk of the configuration. This section is an array of port forward | |
| definitions. Each definition may contain the following settings: | |
| - proto: protocol to forward (defaults to 'tcp'). | |
| - source: the source to forward from. `port` is the only required value. | |
| - ip: source IP address (if empty, uses interface instead) | |
| - interface: source interface (defaults to `default_interface`) | |
| - port: source port | |
| - destination: the destination to forward to. `name` is the only required | |
| value. | |
| - name: destination container name | |
| - port: destination port (defaults to source port) | |
| ''' | |
| import os | |
| import argparse | |
| import subprocess | |
| from pprint import pformat | |
| try: | |
| from cjson import decode as loads | |
| except ImportError: | |
| from json import loads | |
| parser = argparse.ArgumentParser(description='Simple Python script to create port-forwarding rules based on a static table') | |
| parser.add_argument('-f', '--flush', action='store_true', help='flush tables without adding new rules') | |
| parser.add_argument('-n', '--noop', action='store_true', help='simulate commands without running them') | |
| parser.add_argument('-c', '--config', nargs=1, action='store', default='/etc/lxc/lxc-nat.json', help='config file (default: /etc/lxc/lxc-nat.json)') | |
| parser.add_argument('-v', '--verbose', action='store_true', help='show detailed output') | |
| parser.add_argument('-R', '--ignoreroot', action='store_true', help='do not check for root access') | |
| args = parser.parse_args() | |
| forwards = [] | |
| containers = {} | |
| if not args.ignoreroot and not os.geteuid() == 0: | |
| print('This script requires root privileges to modify iptables.') | |
| raise SystemExit | |
| lxc_cmd = 'lxc-ls -f -F name,ipv4' | |
| lxc_output = subprocess.check_output(lxc_cmd.split()).decode('utf8') | |
| if args.verbose: | |
| print('LXC container list:') | |
| print('\n'.join('\t' + ln for ln in lxc_output.splitlines())) | |
| for line in lxc_output.splitlines()[2:]: | |
| cols = line.split() | |
| containers[cols[0]] = cols[1] | |
| with open(args.config, 'r') as f: | |
| config = loads(f.read()) | |
| script_config = config.get('config', {}) | |
| default_interface = script_config.get('default_interface', 'eth0') | |
| def print_forward(forward): | |
| print('Port forward:') | |
| print('\n'.join('\t' + ln for ln in pformat(forward).splitlines())) | |
| def name_forward(forward): | |
| proto = forward.get('proto', 'tcp') | |
| src = forward.get('source', {}) | |
| dest = forward.get('destination', {}) | |
| sname = src.get('ip', None) | |
| if not sname: | |
| sname = src.get('interface', default_interface) | |
| sport = src.get('port') | |
| dname = dest.get('name') | |
| dport = dest.get('port', sport) | |
| return '{}/{}:{} -> {}:{}'.format(proto, sname, sport, dname, dport) | |
| for forward in config['forwards']: | |
| fw_src = forward.get('source', {}) | |
| fw_dest = forward.get('destination', {}) | |
| fw_proto = forward.get('proto', 'tcp') | |
| src_ip = fw_src.get('ip', None) | |
| src_iface = fw_src.get('interface', None) | |
| src_port = fw_src.get('port', None) | |
| dest_name = fw_dest.get('name', None) | |
| dest_port = fw_dest.get('port', None) | |
| if not src_iface and not src_ip: | |
| src_iface = default_interface | |
| if not dest_port: | |
| dest_port = src_port | |
| if not src_port: | |
| if args.verbose: | |
| print_forward(forward) | |
| raise RuntimeError('Port forward definition is missing a source port!') | |
| if not dest_name: | |
| if args.verbose: | |
| print_forward(forward) | |
| raise RuntimeError('Port forward definition is missing a destination container name!') | |
| if dest_name not in containers: | |
| if args.verbose: | |
| print_forward(forward) | |
| print('WARNING: Port forward definition refers to non-existant container "{}"'.format(dest_name)) | |
| continue | |
| cmd = ['iptables -t nat -A lxc-nat'] | |
| if src_iface: | |
| cmd.append('-i ' + src_iface) | |
| if src_ip: | |
| cmd.append('-d ' + src_ip) | |
| cmd.append('-p ' + fw_proto) | |
| cmd.append('--dport {}'.format(src_port)) | |
| cmd.append('-j DNAT --to {}:{}'.format(containers[dest_name], dest_port)) | |
| forwards.append((name_forward(forward), ' '.join(cmd))) | |
| def runcmd(cmd): | |
| print('\t' + cmd) | |
| if not args.noop: | |
| subprocess.check_call(cmd.split()) | |
| try: | |
| subprocess.check_call('iptables -t nat -L lxc-nat'.split(), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) | |
| except Exception: | |
| print('No existing chain to flush') | |
| else: | |
| print('Flush existing chain...') | |
| runcmd('iptables -t nat -F lxc-nat') | |
| runcmd('iptables -t nat -D PREROUTING -j lxc-nat') | |
| runcmd('iptables -t nat -X lxc-nat') | |
| if not args.flush: | |
| print('Create chain...') | |
| runcmd('iptables -t nat -N lxc-nat') | |
| runcmd('iptables -t nat -A PREROUTING -j lxc-nat') | |
| for forward in forwards: | |
| print('Forward: ' + forward[0]) | |
| runcmd(forward[1]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment