Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
language: python
python:
- "2.7"
#- "3.6"
- "3.7"
- "3.8"
script: make test
25 changes: 22 additions & 3 deletions hooks
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python3

"""Libvirt port-forwarding hook.

Expand All @@ -15,8 +15,7 @@ import re
import subprocess
import sys

# python 2.6 support
if "check_output" not in dir( subprocess ):
if "check_output" not in dir(subprocess):
def f(*popenargs, **kwargs):
if 'stdout' in kwargs:
raise ValueError('stdout argument not allowed, it will be overridden.')
Expand Down Expand Up @@ -61,6 +60,10 @@ def json_minify(string, strip_space=True):
# replace white space as defined in standard
tmp = re.sub('[ \t\n\r]+', '', tmp)
new_str.append(tmp)
elif not strip_space:
# Replace comments with white space so that the JSON parser reports
# the correct column numbers on parsing errors.
new_str.append(' ' * (match.start() - index))

index = match.end()
val = match.group()
Expand All @@ -79,11 +82,19 @@ def json_minify(string, strip_space=True):
in_single = True
elif val == '*/' and in_multi and not (in_string or in_single):
in_multi = False
if not strip_space:
new_str.append(' ' * len(val))
elif val in '\r\n' and not (in_multi or in_string) and in_single:
in_single = False
elif not ((in_multi or in_single) or (val in ' \r\n\t' and strip_space)): # noqa
new_str.append(val)

if not strip_space:
if val in '\r\n':
new_str.append(val)
elif in_multi or in_single:
new_str.append(' ' * len(val))

new_str.append(string[index:])
return ''.join(new_str)

Expand Down Expand Up @@ -156,6 +167,8 @@ def populate_chains(dnat_chain, snat_chain, fwd_chain, public_ip, private_ip, do
subprocess.call([IPTABLES_BINARY, "-t", "nat", "-A", dnat_chain, "-p", protocol,
"-d", public_ip, "--dport", str(public_port), "-j", "DNAT", "--to", dest] +
(["-s", source_ip] if source_ip else []))
subprocess.call([IPTABLES_BINARY, "-t", "nat", "-A", snat_chain, "-p", protocol,
"-s", private_ip, "--dport", str(private_port), "-j", "SNAT", "--to-source", public_ip])
subprocess.call([IPTABLES_BINARY, "-t", "nat", "-A", snat_chain, "-p", protocol,
"-s", private_ip, "-d", private_ip, "--dport", str(public_port), "-j", "MASQUERADE"])
interface = ["-o", domain["interface"]
Expand All @@ -172,6 +185,8 @@ def populate_chains(dnat_chain, snat_chain, fwd_chain, public_ip, private_ip, do
private_ip, ports_range.replace(":", "-", 1))
subprocess.call([IPTABLES_BINARY, "-t", "nat", "-A", dnat_chain, "-p", port_range["protocol"],
"-d", public_ip, "--dport", ports_range, "-j", "DNAT", "--to", dest])
subprocess.call([IPTABLES_BINARY, "-t", "nat", "-A", snat_chain, "-p", port_range["protocol"],
"-s", private_ip, "--dport", str(private_port), "-j", "SNAT", "--to-source", public_ip])
subprocess.call([IPTABLES_BINARY, "-t", "nat", "-A", snat_chain, "-p", port_range["protocol"],
"-s", private_ip, "-d", private_ip, "--dport", ports_range, "-j", "MASQUERADE"])
interface = ["-o", domain["interface"]
Expand All @@ -186,12 +201,16 @@ def insert_chains(action, dnat_chain, snat_chain, fwd_chain, public_ip, private_
"OUTPUT", "-d", public_ip, "-j", dnat_chain])
subprocess.call([IPTABLES_BINARY, "-t", "nat", action,
"PREROUTING", "-d", public_ip, "-j", dnat_chain])
# TODO: Find solution for connections from different private_ip to public_ip
# maybe use private_ip_net as source instead
# WORKAROUND: remove `"-s", private_ip, `
subprocess.call([IPTABLES_BINARY, "-t", "nat", action, "POSTROUTING",
"-s", private_ip, "-d", private_ip, "-j", snat_chain])
subprocess.call([IPTABLES_BINARY, "-t", "filter", action,
"FORWARD", "-d", private_ip, "-j", fwd_chain])

# the snat_chain doesn't work unless we turn off filtering bridged packets
# https://wiki.libvirt.org/page/Net.bridge.bridge-nf-call_and_sysctl.conf


def disable_bridge_filtering():
Expand Down
3 changes: 3 additions & 0 deletions hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"cloud-admin": {
"interface": "virbr1", // you can use comments
"private_ip": "192.168.124.10", /* both styles */
// source_ip is not the hosts public_ip
// it is a remote IP which gets permission to access this port
"source_ip": "8.8.8.8",
"port_map": {
"tcp": [[1100, 3000], 443]
Expand All @@ -10,6 +12,7 @@
"cloud-node1": {
"private_ip": "192.168.126.2",
"port_map": {
// [[private_ip, public_ip]]
"tcp": [[1101, 80],
[1102, 443]]
}
Expand Down
10 changes: 7 additions & 3 deletions test_qemu.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/usr/bin/python
#!/usr/bin/env python3

import imp
import json
import os
import socket
import sys
Expand Down Expand Up @@ -37,13 +36,15 @@ def capture_output(self, func):
qemu.IPTABLES_BINARY = '/bin/echo'
to = open(outfile, "w")
os.dup2(to.fileno(), sys.stdout.fileno())
to.close()
func()
finally:
sys.stdout.flush()
os.dup2(orig_out, sys.stdout.fileno())
qemu.IPTABLES_BINARY = orig_binary

output = open(outfile).read()
with open(outfile, 'r') as f:
output = f.read()
os.remove(outfile)
return output

Expand All @@ -62,12 +63,15 @@ def test_setup(self):
-t nat -N SNAT-test
-t filter -N FWD-test
-t nat -A DNAT-test -p udp -d 192.168.1.1 --dport 53 -j DNAT --to 127.0.0.1:53
-t nat -A SNAT-test -p udp -s 127.0.0.1 --dport 53 -j SNAT --to-source 192.168.1.1
-t nat -A SNAT-test -p udp -s 127.0.0.1 -d 127.0.0.1 --dport 53 -j MASQUERADE
-t filter -A FWD-test -p udp -d 127.0.0.1 --dport 53 -j ACCEPT -o virbr0
-t nat -A DNAT-test -p tcp -d 192.168.1.1 --dport 80 -j DNAT --to 127.0.0.1:8080
-t nat -A SNAT-test -p tcp -s 127.0.0.1 --dport 8080 -j SNAT --to-source 192.168.1.1
-t nat -A SNAT-test -p tcp -s 127.0.0.1 -d 127.0.0.1 --dport 80 -j MASQUERADE
-t filter -A FWD-test -p tcp -d 127.0.0.1 --dport 8080 -j ACCEPT -o virbr0
-t nat -A DNAT-test -p tcp -d 192.168.1.1 --dport 443 -j DNAT --to 127.0.0.1:443
-t nat -A SNAT-test -p tcp -s 127.0.0.1 --dport 443 -j SNAT --to-source 192.168.1.1
-t nat -A SNAT-test -p tcp -s 127.0.0.1 -d 127.0.0.1 --dport 443 -j MASQUERADE
-t filter -A FWD-test -p tcp -d 127.0.0.1 --dport 443 -j ACCEPT -o virbr0
-t nat -I OUTPUT -d 192.168.1.1 -j DNAT-test
Expand Down