Skip to content

Commit

Permalink
Merge pull request #12 from pchristos/routing
Browse files Browse the repository at this point in the history
Routing
  • Loading branch information
dimrozakis authored Oct 17, 2017
2 parents 6e49a2e + 51127d0 commit 3eadea1
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 35 deletions.
5 changes: 3 additions & 2 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ set -ex

apt-get update -q
apt-get install -yq --no-install-recommends \
python python-pip openvpn uwsgi uwsgi-plugin-python
python python-pip openvpn uwsgi uwsgi-plugin-python \
python-dev build-essential

pip install -U pip
pip install -r $DIR/vpn-proxy/requirements.txt
pip install -r $DIR/requirements.txt

echo "VPN_SERVER_REMOTE_ADDRESS = \"$VPN_IP\"" > $DIR/vpn-proxy/conf.d/0000-vpn-ip.py
SOURCE_CIDRS=`echo "$SOURCE_CIDRS" | sed 's/ /", "/g'`
Expand Down
Empty file.
41 changes: 41 additions & 0 deletions vpn-proxy/app/middleware/cidr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import netaddr
import logging

from django.http import Http404
from django.conf import settings


log = logging.getLogger(__name__)


class CidrMiddleware(object):
"""A middleware that filters requests based on their origin.
This middleware plays the role of settings.ALLOWED_HOSTS, while it also
allows filtering to take place based on ranges of IP addresses given in
CIDR notation.
The host's IP address is verified against the list of networks provided
in settings.SOURCE_CIDRS. In case of no match, an HTTP 404 status code
is returned.
See https://docs.djangoproject.com/en/1.11/ref/settings/#allowed-hosts
"""

def __init__(self, get_response):
# One-time configuration & initialization, when the web server starts.
self.get_response = get_response

self.cidrs = [
netaddr.IPNetwork(cidr) for cidr in settings.SOURCE_CIDRS
]

def __call__(self, request):
# Get the source IP address from the request headers.
host = request.META['REMOTE_ADDR']
for cidr in self.cidrs:
if netaddr.IPAddress(host) in cidr:
return self.get_response(request)
log.critical('Connection attempt from unauthorized source %s', host)
raise Http404()
47 changes: 23 additions & 24 deletions vpn-proxy/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,37 @@
from .tunnels import get_conf, get_client_conf, get_client_script
from .tunnels import add_iptables, del_iptables, add_fwmark, del_fwmark

IFACE_PREFIX = settings.IFACE_PREFIX
SERVER_PORT_START = settings.SERVER_PORT_START

PORT_ALLOC_START, PORT_ALLOC_STOP = settings.PORT_ALLOC_RANGE
ALLOWED_VPN_ADDRESSES = settings.ALLOWED_HOSTS
EXCLUDED_VPN_ADDRESSES = settings.EXCLUDED_HOSTS


log = logging.getLogger(__name__)


def choose_ip(routable_cidrs, excluded_cidrs=[],
reserved_cidrs=EXCLUDED_VPN_ADDRESSES, client_addr=''):
"""
Find available IP addresses for both sides of a VPN Tunnel (client &
server) based on a list of available_cidrs. This method iterates over the
CIDRs contained in ALLOWED_VPN_ADDRESSES, while excluding the lists of
routable_cidrs, excluded_cidrs, and reserved_cidrs.
:param routable_cidrs: the CIDRs that are to be routed over the
particular VPN Tunnel
:param excluded_cidrs: an optional CIDRs list provided by the end user
in order to be excluded from the address allocation process
:param reserved_cidrs: CIDRs reserved for local usage
:param client_addr: since the client IP is allocated first, the client_addr
is used to attempt to pick an adjacent IP address for the server side.
Alternatively, this is an empty string
def choose_ip(routable_cidrs, excluded_cidrs=[], client_addr=''):
"""Find available IP addresses for both sides of a VPN Tunnel.
This method iterates over the settings.ALLOWED_CIDRS list in order to
allocate available IP address to both the client and server side of a
VPN tunnel. CIDRs that belong to the lists of settings.RESERVED_CIDRS,
`routable_cidrs`, and `excluded_cidrs` are excluded from the allocation
process.
:param routable_cidrs: the CIDRs that are to be routed over a VPN tunnel
:param excluded_cidrs: an optional list of CIDRs to be excluded from the
address allocation process
:param client_addr: the `client_addr` is used to attempt to pick an
adjacent IP address for the server side
:return: a private IP address
"""
exc_nets = routable_cidrs + excluded_cidrs + reserved_cidrs
exc_nets = routable_cidrs + excluded_cidrs + settings.RESERVED_CIDRS
# make sure the exc_nets list does not contain any empty strings
exc_nets = [exc_net for exc_net in exc_nets if exc_net]
# a list of unique, non-overlapping supernets (to be excluded)
exc_nets = IPSet(exc_nets).iter_cidrs()
for network in ALLOWED_VPN_ADDRESSES:
for network in settings.ALLOWED_CIDRS:
available_cidrs = IPSet(IPNetwork(network))
for exc_net in exc_nets:
available_cidrs.remove(exc_net)
Expand Down Expand Up @@ -145,16 +144,16 @@ class Tunnel(BaseModel):
client = models.GenericIPAddressField(protocol='IPv4',
validators=[check_ip])
key = models.TextField(default=gen_key, blank=False, unique=True)
protocol = models.CharField(max_length=3, default='udp',
protocol = models.CharField(max_length=3, default='udp',
choices=[('udp', 'UDP'), ('tcp', 'TCP')])

@property
def name(self):
return '%s%s' % (IFACE_PREFIX, self.id)
return '%s%s' % (settings.IFACE_PREFIX, self.id)

@property
def port(self):
return (SERVER_PORT_START + self.id - 1) if self.id else None
return (settings.SERVER_PORT_START + self.id - 1) if self.id else None

@property
def rtable(self):
Expand Down
9 changes: 4 additions & 5 deletions vpn-proxy/app/tunnels.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@


SOURCE_CIDRS = ','.join(settings.SOURCE_CIDRS)
REMOTE_IP = settings.VPN_SERVER_REMOTE_ADDRESS
IN_IFACE = settings.IN_IFACE


log = logging.getLogger(__name__)

Expand Down Expand Up @@ -233,7 +232,7 @@ def get_conf(tunnel):


def get_client_conf(tunnel):
return '\n'.join(['remote %s' % REMOTE_IP,
return '\n'.join(['remote %s' % settings.VPN_SERVER_REMOTE_ADDRESS,
'dev %s' % tunnel.name,
'dev-type tun',
'port %s' % tunnel.port,
Expand Down Expand Up @@ -299,14 +298,14 @@ def check_iptables(forwarding, job='-C', rule=''):
MASQUERADE packets routed via the virtual interface"""
mangle_rule = ['iptables', '-w', '-t', 'mangle', job, 'PREROUTING',
'-p', 'tcp',
'-i', str(IN_IFACE),
'-i', str(settings.IN_IFACE),
'-s', str(SOURCE_CIDRS),
'--destination-port', str(forwarding.loc_port),
'-j', 'MARK', '--set-mark', str(forwarding.tunnel.id)]

nat_rule = ['iptables', '-w', '-t', 'nat', job, 'PREROUTING',
'-p', 'tcp',
'-i', str(IN_IFACE),
'-i', str(settings.IN_IFACE),
'-s', str(SOURCE_CIDRS),
'--destination-port', str(forwarding.loc_port),
'-j', 'DNAT', '--to-destination', str(forwarding.destination)]
Expand Down
22 changes: 18 additions & 4 deletions vpn-proxy/project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,25 @@
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

# IP addressing settings
ALLOWED_HOSTS = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16']
EXCLUDED_HOSTS = []
# A list of client IPs and hostnames the application may serve
# See https://docs.djangoproject.com/en/1.11/ref/settings/#allowed-hosts
ALLOWED_HOSTS = ['*']

# IP address allocation range
ALLOWED_CIDRS = ['10.0.0.0/8', '172.17.0.0/12', '192.168.0.0/16']
RESERVED_CIDRS = []

# The minimum port number OpenVPN may listen on for incoming connections
SERVER_PORT_START = 1195

# The range of available ports to be allocated on the server-side in order
# to proxy requests over VPN
PORT_ALLOC_RANGE = (5000, 10000)

# VPN interface name prefix
IFACE_PREFIX = 'vpn-tun'

# The interface that may accept proxying requests
IN_IFACE = 'eth0'

# Application definition
Expand All @@ -44,7 +57,8 @@
'app',
]

MIDDLEWARE_CLASSES = [
MIDDLEWARE = [
'app.middleware.cidr.CidrMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
Expand Down

0 comments on commit 3eadea1

Please sign in to comment.