Commit 1c354e6c authored by Julien Muchembled's avatar Julien Muchembled

dist: use new entry_points['console_scripts'] way to ship scripts

The old distutils way is not compatible with zc.recipe.egg in develop mode,
because egg_info does not provide any information about such scripts.
parent 274d7bd2
...@@ -7,7 +7,7 @@ Standards-Version: 3.9.1 ...@@ -7,7 +7,7 @@ Standards-Version: 3.9.1
Package: re6stnet Package: re6stnet
Architecture: all Architecture: all
Depends: ${misc:Depends}, python (>= 2.6.6-3), python (<< 2.8), python-argparse, python-openssl (>= 0.13), openvpn (>= 2.3), babeld (= 1.6.0-nxd1), iproute2 | iproute, openssl Depends: ${misc:Depends}, python (>= 2.6.6-3), python-pkg-resources, python-argparse, python-openssl (>= 0.13), openvpn (>= 2.3), babeld (= 1.6.0-nxd1), iproute2 | iproute, openssl
Recommends: ${python:Recommends}, logrotate Recommends: ${python:Recommends}, logrotate
Suggests: ndisc6 Suggests: ndisc6
Description: resilient, scalable, IPv6 network application Description: resilient, scalable, IPv6 network application
#!/usr/bin/python #!/usr/bin/python
import sqlite3, sys import sqlite3, sys
import os; sys.path[0] = os.path.dirname(sys.path[0]) if 're6st' not in sys.modules:
import os; sys.path[0] = os.path.dirname(sys.path[0])
from re6st import utils, x509 from re6st import utils, x509
from OpenSSL import crypto from OpenSSL import crypto
......
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
import argparse, httplib, select, socket, sqlite3, struct, sys, time, traceback import argparse, httplib, select, socket, sqlite3, struct, sys, time, traceback
import xml.etree.cElementTree as ET import xml.etree.cElementTree as ET
from collections import defaultdict from collections import defaultdict
import os; sys.path[0] = os.path.dirname(sys.path[0]) if 're6st' not in sys.modules:
import os; sys.path[0] = os.path.dirname(sys.path[0])
from re6st import ctl, tunnel, utils from re6st import ctl, tunnel, utils
class iterRoutes(object): class iterRoutes(object):
......
#!/usr/bin/python
import argparse, atexit, binascii, errno, hashlib
import os, subprocess, sqlite3, sys, time
from OpenSSL import crypto
from re6st import registry, utils, x509
def create(path, text=None, mode=0666):
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, mode)
try:
os.write(fd, text)
finally:
os.close(fd)
def loadCert(pem):
return crypto.load_certificate(crypto.FILETYPE_PEM, pem)
def main():
parser = argparse.ArgumentParser(
description="Setup script for re6stnet.",
formatter_class=utils.HelpFormatter)
_ = parser.add_argument
_('--fingerprint', metavar='ALG:FINGERPRINT',
help="Check CA fingerprint to protect against MITM.")
_('--registry', required=True, metavar='URL',
help="HTTP URL of the server delivering certificates.")
_('--is-needed', action='store_true',
help="Exit immediately after asking the registry CA. Status code is"
" non-zero if we're already part of the network, which means"
" re6st is already running or we're behind a re6st router.")
_('--ca-only', action='store_true',
help='Only fetch CA from registry and exit.')
_('-d', '--dir',
help="Directory where the key and certificate will be stored.")
_('-r', '--req', nargs=2, action='append', metavar=('KEY', 'VALUE'),
help="The registry only sets the Common Name of your certificate,"
" which actually encodes your allocated prefix in the network."
" You can repeat this option to add any field you want to its"
" subject.")
_('--email',
help="Email address where your token is sent. Use -r option if you"
" want to show an email in your certificate.")
_('--token', help="The token you received.")
_('--anonymous', action='store_true',
help="Request an anonymous certificate. No email is required but the"
" registry may deliver a longer prefix.")
config = parser.parse_args()
if config.dir:
os.chdir(config.dir)
conf_path = 're6stnet.conf'
ca_path = 'ca.crt'
cert_path = 'cert.crt'
key_path = 'cert.key'
# Establish connection with server
s = registry.RegistryClient(config.registry)
# Get CA
ca = loadCert(s.getCa())
if config.fingerprint:
try:
alg, fingerprint = config.fingerprint.split(':', 1)
fingerprint = binascii.a2b_hex(fingerprint)
if hashlib.new(alg).digest_size != len(fingerprint):
raise ValueError("wrong size")
except StandardError, e:
parser.error("invalid fingerprint: %s" % e)
if x509.fingerprint(ca, alg).digest() != fingerprint:
sys.exit("CA fingerprint doesn't match")
else:
print "WARNING: it is strongly recommended to use --fingerprint option."
network = x509.networkFromCa(ca)
if config.is_needed:
route, err = subprocess.Popen(('ip', '-6', '-o', 'route', 'get',
utils.ipFromBin(network)),
stdout=subprocess.PIPE).communicate()
sys.exit(err or route and
utils.binFromIp(route.split()[8]).startswith(network))
create(ca_path, crypto.dump_certificate(crypto.FILETYPE_PEM, ca))
if config.ca_only:
sys.exit()
reserved = 'CN', 'serial'
req = crypto.X509Req()
try:
with open(cert_path) as f:
cert = loadCert(f.read())
components = dict(cert.get_subject().get_components())
for k in reserved:
components.pop(k, None)
except IOError, e:
if e.errno != errno.ENOENT:
raise
components = {}
if config.req:
components.update(config.req)
subj = req.get_subject()
for k, v in components.iteritems():
if k in reserved:
sys.exit(k + " field is reserved.")
if v:
setattr(subj, k, v)
cert_fd = token_advice = None
try:
token = config.token
if config.anonymous:
if not (token is config.email is None):
parser.error("--anonymous conflicts with --email/--token")
token = ''
elif not token:
if not config.email:
config.email = raw_input('Please enter your email address: ')
s.requestToken(config.email)
token_advice = "Use --token to retry without asking a new token\n"
while not token:
token = raw_input('Please enter your token: ')
try:
with open(key_path) as f:
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read())
key = None
print "Reusing existing key."
except IOError, e:
if e.errno != errno.ENOENT:
raise
bits = ca.get_pubkey().bits()
print "Generating %s-bit key ..." % bits
pkey = crypto.PKey()
pkey.generate_key(crypto.TYPE_RSA, bits)
key = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)
create(key_path, key, 0600)
req.set_pubkey(pkey)
req.sign(pkey, 'sha512')
req = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
# First make sure we can open certificate file for writing,
# to avoid using our token for nothing.
cert_fd = os.open(cert_path, os.O_CREAT | os.O_WRONLY, 0666)
print "Requesting certificate ..."
cert = s.requestCertificate(token, req)
if not cert:
token_advice = None
sys.exit("Error: invalid or expired token")
except:
if cert_fd is not None and not os.lseek(cert_fd, 0, os.SEEK_END):
os.remove(cert_path)
if token_advice:
atexit.register(sys.stdout.write, token_advice)
raise
os.write(cert_fd, cert)
os.ftruncate(cert_fd, len(cert))
os.close(cert_fd)
cert = loadCert(cert)
not_after = x509.notAfter(cert)
print("Setup complete. Certificate is valid until %s UTC"
" and will be automatically renewed after %s UTC.\n"
"Do not forget to backup to your private key (%s) or"
" you will lose your assigned subnet." % (
time.asctime(time.gmtime(not_after)),
time.asctime(time.gmtime(not_after - registry.RENEW_PERIOD)),
key_path))
if not os.path.lexists(conf_path):
create(conf_path, """\
registry %s
ca %s
cert %s
key %s
# increase re6stnet verbosity:
#verbose 3
# enable OpenVPN logging:
#ovpnlog
# increase OpenVPN verbosity:
#O--verb
#O3
""" % (config.registry, ca_path, cert_path, key_path))
print "Sample configuration file created."
cn = x509.subnetFromCert(cert)
subnet = network + utils.binFromSubnet(cn)
print "Your subnet: %s/%u (CN=%s)" \
% (utils.ipFromBin(subnet), len(subnet), cn)
if __name__ == "__main__":
main()
re6st/cli/conf.py
\ No newline at end of file
#!/usr/bin/python
import httplib, logging, socket
from BaseHTTPServer import BaseHTTPRequestHandler
from SocketServer import ThreadingTCPServer
from urlparse import parse_qsl
from re6st import ctl, registry, utils, version
# To generate server ca and key with serial for 2001:db8:42::/48
# openssl req -nodes -new -x509 -key ca.key -set_serial 0x120010db80042 -days 3650 -out ca.crt
IPV6_V6ONLY = 26
SOL_IPV6 = 41
class RequestHandler(BaseHTTPRequestHandler):
if __import__("sys").version_info < (2, 7, 4):
def address_string(self):
# Workaround for http://bugs.python.org/issue6085
return self.client_address[0]
def do_GET(self):
try:
try:
path, query = self.path.split('?', 1)
except ValueError:
path = self.path
query = {}
else:
query = dict(parse_qsl(query, keep_blank_values=1,
strict_parsing=1))
_, path = path.split('/')
if not _:
return self.server.handle_request(self, path, query)
except Exception:
logging.info(self.requestline, exc_info=1)
self.send_error(httplib.BAD_REQUEST)
def log_error(*args):
pass
class HTTPServer4(ThreadingTCPServer):
allow_reuse_address = True
daemon_threads = True
class HTTPServer6(HTTPServer4):
address_family = socket.AF_INET6
def server_bind(self):
self.socket.setsockopt(SOL_IPV6, IPV6_V6ONLY, 1)
HTTPServer4.server_bind(self)
def main():
parser = utils.ArgParser(fromfile_prefix_chars='@',
description="re6stnet registry used to bootstrap nodes"
" and deliver certificates.")
_ = parser.add_argument
_('--port', type=int, default=80,
help="Port on which the server will listen.")
_('-4', dest='bind4', default='0.0.0.0',
help="Bind server to this IPv4.")
_('-6', dest='bind6', default='::',
help="Bind server to this IPv6.")
_('--db', default='/var/lib/re6stnet/registry.db',
help="Path to SQLite database file. It is automatically initialized"
" if the file does not exist.")
_('--dh',
help='File containing Diffie-Hellman parameters in .pem format')
_('--ca', required=True, help=parser._ca_help)
_('--key', required=True,
help="CA private key in .pem format.")
_('--mailhost', required=True,
help="SMTP host to send confirmation emails. For debugging"
" purpose, it can also be an absolute or existing path to"
" a mailbox file")
_('--prefix-length', default=16, type=int,
help="Default length of allocated prefixes.")
_('--anonymous-prefix-length', type=int,
help="Length of allocated anonymous prefixes."
" If 0 or unset, registration by email is required")
_('--ipv4', nargs=2, metavar=("IP/N", "PLEN"),
help="Enable ipv4. Each node is assigned a subnet of length PLEN"
" inside network IP/N.")
_('-l', '--logfile', default='/var/log/re6stnet/registry.log',
help="Path to logging file.")
_('-r', '--run', default='/var/run/re6stnet',
help="Path to re6stnet runtime directory:\n"
"- babeld.sock (option -R of babeld)\n")
_('-v', '--verbose', default=1, type=int,
help="Log level. 0 disables logging."
" Use SIGUSR1 to reopen log.")
_('--min-protocol', default=version.min_protocol, type=int,
help="Reject nodes that are too old. Current is %s." % version.protocol)
_ = parser.add_argument_group('routing').add_argument
_('--hello', type=int, default=15,
help="Hello interval in seconds, for both wired and wireless"
" connections. OpenVPN ping-exit option is set to 4 times the"
" hello interval. It takes between 3 and 4 times the"
" hello interval for Babel to re-establish connection with a"
" node for which the direct connection has been cut.")
_ = parser.add_argument_group('tunnelling').add_argument
_('--encrypt', action='store_true',
help='Specify that tunnels should be encrypted.')
_('--client-count', default=10, type=int,
help="Number of client tunnels to set up.")
_('--max-clients', type=int,
help="Maximum number of accepted clients per OpenVPN server. (default:"
" client-count * 2, which actually represents the average number"
" of tunnels to other peers)")
_('--tunnel-refresh', default=300, type=int,
help="Interval in seconds between two tunnel refresh: the worst"
" tunnel is closed if the number of client tunnels has reached"
" its maximum number (client-count).")
config = parser.parse_args()
if not version.min_protocol <= config.min_protocol <= version.protocol:
parser.error("--min-protocol: value must between %s and %s (included)"
% (version.min_protocol, version.protocol))
if config.ipv4:
ipv4, plen = config.ipv4
try:
ip, n = ipv4.split('/')
config.ipv4 = "%s/%s" % (socket.inet_ntoa(socket.inet_aton(ip)),
int(n)), int(plen)
except (socket.error, ValueError):
parser.error("invalid argument --ipv4")
utils.setupLog(config.verbose, config.logfile)
if config.max_clients is None:
config.max_clients = config.client_count * 2
server = registry.RegistryServer(config)
def requestHandler(request, client_address, _):
RequestHandler(request, client_address, server)
server_dict = {}
if config.bind4:
r = HTTPServer4((config.bind4, config.port), requestHandler)
server_dict[r.fileno()] = r._handle_request_noblock
if config.bind6:
r = HTTPServer6((config.bind6, config.port), requestHandler)
server_dict[r.fileno()] = r._handle_request_noblock
if server_dict:
while True:
args = server_dict.copy(), {}, []
server.select(*args)
utils.select(*args)
if __name__ == "__main__":
main()
re6st/cli/registry.py
\ No newline at end of file
import json, logging, os, sqlite3, socket, subprocess, time, zlib import json, logging, os, sqlite3, socket, subprocess, time, zlib
from re6st.registry import RegistryClient from .registry import RegistryClient
from . import utils, version, x509 from . import utils, version, x509
class Cache(object): class Cache(object):
......
#!/usr/bin/python
import argparse, atexit, binascii, errno, hashlib
import os, subprocess, sqlite3, sys, time
from OpenSSL import crypto
if 're6st' not in sys.modules:
sys.path[0] = os.path.dirname(os.path.dirname(sys.path[0]))
from re6st import registry, utils, x509
def create(path, text=None, mode=0666):
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, mode)
try:
os.write(fd, text)
finally:
os.close(fd)
def loadCert(pem):
return crypto.load_certificate(crypto.FILETYPE_PEM, pem)
def main():
parser = argparse.ArgumentParser(
description="Setup script for re6stnet.",
formatter_class=utils.HelpFormatter)
_ = parser.add_argument
_('--fingerprint', metavar='ALG:FINGERPRINT',
help="Check CA fingerprint to protect against MITM.")
_('--registry', required=True, metavar='URL',
help="HTTP URL of the server delivering certificates.")
_('--is-needed', action='store_true',
help="Exit immediately after asking the registry CA. Status code is"
" non-zero if we're already part of the network, which means"
" re6st is already running or we're behind a re6st router.")
_('--ca-only', action='store_true',
help='Only fetch CA from registry and exit.')
_('-d', '--dir',
help="Directory where the key and certificate will be stored.")
_('-r', '--req', nargs=2, action='append', metavar=('KEY', 'VALUE'),
help="The registry only sets the Common Name of your certificate,"
" which actually encodes your allocated prefix in the network."
" You can repeat this option to add any field you want to its"
" subject.")
_('--email',
help="Email address where your token is sent. Use -r option if you"
" want to show an email in your certificate.")
_('--token', help="The token you received.")
_('--anonymous', action='store_true',
help="Request an anonymous certificate. No email is required but the"
" registry may deliver a longer prefix.")
config = parser.parse_args()
if config.dir:
os.chdir(config.dir)
conf_path = 're6stnet.conf'
ca_path = 'ca.crt'
cert_path = 'cert.crt'
key_path = 'cert.key'
# Establish connection with server
s = registry.RegistryClient(config.registry)
# Get CA
ca = loadCert(s.getCa())
if config.fingerprint:
try:
alg, fingerprint = config.fingerprint.split(':', 1)
fingerprint = binascii.a2b_hex(fingerprint)
if hashlib.new(alg).digest_size != len(fingerprint):
raise ValueError("wrong size")
except StandardError, e:
parser.error("invalid fingerprint: %s" % e)
if x509.fingerprint(ca, alg).digest() != fingerprint:
sys.exit("CA fingerprint doesn't match")
else:
print "WARNING: it is strongly recommended to use --fingerprint option."
network = x509.networkFromCa(ca)
if config.is_needed:
route, err = subprocess.Popen(('ip', '-6', '-o', 'route', 'get',
utils.ipFromBin(network)),
stdout=subprocess.PIPE).communicate()
sys.exit(err or route and
utils.binFromIp(route.split()[8]).startswith(network))
create(ca_path, crypto.dump_certificate(crypto.FILETYPE_PEM, ca))
if config.ca_only:
sys.exit()
reserved = 'CN', 'serial'
req = crypto.X509Req()
try:
with open(cert_path) as f:
cert = loadCert(f.read())
components = dict(cert.get_subject().get_components())
for k in reserved:
components.pop(k, None)
except IOError, e:
if e.errno != errno.ENOENT:
raise
components = {}
if config.req:
components.update(config.req)
subj = req.get_subject()
for k, v in components.iteritems():
if k in reserved:
sys.exit(k + " field is reserved.")
if v:
setattr(subj, k, v)
cert_fd = token_advice = None
try:
token = config.token
if config.anonymous:
if not (token is config.email is None):
parser.error("--anonymous conflicts with --email/--token")
token = ''
elif not token:
if not config.email:
config.email = raw_input('Please enter your email address: ')
s.requestToken(config.email)
token_advice = "Use --token to retry without asking a new token\n"
while not token:
token = raw_input('Please enter your token: ')
try:
with open(key_path) as f:
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read())
key = None
print "Reusing existing key."
except IOError, e:
if e.errno != errno.ENOENT:
raise
bits = ca.get_pubkey().bits()
print "Generating %s-bit key ..." % bits
pkey = crypto.PKey()
pkey.generate_key(crypto.TYPE_RSA, bits)
key = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)
create(key_path, key, 0600)
req.set_pubkey(pkey)
req.sign(pkey, 'sha512')
req = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
# First make sure we can open certificate file for writing,
# to avoid using our token for nothing.
cert_fd = os.open(cert_path, os.O_CREAT | os.O_WRONLY, 0666)
print "Requesting certificate ..."
cert = s.requestCertificate(token, req)
if not cert:
token_advice = None
sys.exit("Error: invalid or expired token")
except:
if cert_fd is not None and not os.lseek(cert_fd, 0, os.SEEK_END):
os.remove(cert_path)
if token_advice:
atexit.register(sys.stdout.write, token_advice)
raise
os.write(cert_fd, cert)
os.ftruncate(cert_fd, len(cert))
os.close(cert_fd)
cert = loadCert(cert)
not_after = x509.notAfter(cert)
print("Setup complete. Certificate is valid until %s UTC"
" and will be automatically renewed after %s UTC.\n"
"Do not forget to backup to your private key (%s) or"
" you will lose your assigned subnet." % (
time.asctime(time.gmtime(not_after)),
time.asctime(time.gmtime(not_after - registry.RENEW_PERIOD)),
key_path))
if not os.path.lexists(conf_path):
create(conf_path, """\
registry %s
ca %s
cert %s
key %s
# increase re6stnet verbosity:
#verbose 3
# enable OpenVPN logging:
#ovpnlog
# increase OpenVPN verbosity:
#O--verb
#O3
""" % (config.registry, ca_path, cert_path, key_path))
print "Sample configuration file created."
cn = x509.subnetFromCert(cert)
subnet = network + utils.binFromSubnet(cn)
print "Your subnet: %s/%u (CN=%s)" \
% (utils.ipFromBin(subnet), len(subnet), cn)
if __name__ == "__main__":
main()
#!/usr/bin/python
import atexit, errno, logging, os, shutil, signal
import socket, struct, subprocess, sys, time, threading
from collections import deque
from functools import partial
if 're6st' not in sys.modules:
sys.path[0] = os.path.dirname(os.path.dirname(sys.path[0]))
from re6st import plib, tunnel, utils, version, x509
from re6st.cache import Cache
from re6st.utils import exit, ReexecException
def getConfig():
parser = utils.ArgParser(fromfile_prefix_chars='@',
description="Resilient virtual private network application.")
_ = parser.add_argument
_('-V', '--version', action='version', version=version.version)
_('--ip', action='append', default=[],
help="IP address advertised to other nodes. Special values:\n"
"- upnp: redirect ports when UPnP device is found\n"
"- any: ask peers our IP\n"
" (default: like 'upnp' if miniupnpc is installed,\n"
" otherwise like 'any')")
_('--registry', metavar='URL', required=True,
help="Public HTTP URL of the registry, for bootstrapping.")
_('-l', '--log', default='/var/log/re6stnet',
help="Path to the directory used for log files:\n"
"- re6stnet.log: log file of re6stnet itself\n"
"- babeld.log: log file of router\n"
"- <iface>.log: 1 file per spawned OpenVPN\n")
_('-r', '--run', default='/var/run/re6stnet',
help="Path to re6stnet runtime directory:\n"
"- babeld.pid (option -I of babeld)\n"
"- babeld.sock (option -R of babeld)\n")
_('-s', '--state', default='/var/lib/re6stnet',
help="Path to re6stnet state directory:\n"
"- cache.db: cache of network parameters and peer addresses\n"
"- babeld.state: see option -S of babeld\n")
_('-v', '--verbose', default=1, type=int, metavar='LEVEL',
help="Log level of re6stnet itself. 0 disables logging."
" Use SIGUSR1 to reopen log."
" See also --babel-verb and --verb for logs of spawned processes.")
_('-i', '--interface', action='append', dest='iface_list', default=[],
help="Extra interface for LAN discovery. Highly recommanded if there"
" are other re6st node on the same network segment.")
_('-I', '--main-interface', metavar='IFACE', default='lo',
help="Set re6stnet IP on given interface. Any interface not used for"
" tunnelling can be chosen.")
_('--up', metavar='CMD',
help="Shell command to run after successful initialization.")
_('--daemon', action='append', metavar='CMD',
help="Same as --up, but run in background: the command will be killed"
" at exit (with a TERM signal, followed by KILL 5 seconds later"
" if process is still alive).")
_('--test', metavar='EXPR',
help="Exit after configuration parsing. Status code is the"
" result of the given Python expression. For example:\n"
" main_interface != 'eth0'")
_ = parser.add_argument_group('routing').add_argument
_('-B', dest='babel_args', metavar='ARG', action='append', default=[],
help="Extra arguments to forward to Babel.")
_('-D', '--default', action='store_true',
help="Access internet via this network (in this case, make sure you"
" don't already have a default route), or if your kernel was"
" compiled without support for source address based routing"
" (CONFIG_IPV6_SUBTREES). Meaningless with --gateway.")
_('--table', type=int, choices=(0,),
help="DEPRECATED: Use --default instead of --table=0")
_('--gateway', action='store_true',
help="Act as a gateway for this network (the default route will be"
" exported). Do never use it if you don't know what it means.")
_ = parser.add_argument_group('tunnelling').add_argument
_('-O', dest='openvpn_args', metavar='ARG', action='append', default=[],
help="Extra arguments to forward to both server and client OpenVPN"
" subprocesses. Often used to configure verbosity.")
_('--ovpnlog', action='store_true',
help="Tell each OpenVPN subprocess to log to a dedicated file.")
_('--pp', nargs=2, action='append', metavar=('PORT', 'PROTO'),
help="Port and protocol to be announced to other peers, ordered by"
" preference. For each protocol (udp, tcp, udp6, tcp6), start one"
" openvpn server on the first given port."
" (default: --pp 1194 udp --pp 1194 tcp)")
_('--dh',
help="File containing Diffie-Hellman parameters in .pem format"
" (default: DH from registry)")
_('--ca', required=True, help=parser._ca_help)
_('--cert', required=True,
help="Local peer's signed certificate in .pem format."
" Common name defines the allocated prefix in the network.")
_('--key', required=True,
help="Local peer's private key in .pem format.")
_('--client-count', type=int,
help="Number of client tunnels to set up."
" (default: value from registry)")
_('--max-clients', type=int,
help="Maximum number of accepted clients per OpenVPN server."
" (default: value from registry)")
_('--remote-gateway', action='append', dest='gw_list',
help="Force each tunnel to be created through one the given gateways,"
" in a round-robin fashion.")
_('--disable-proto', action='append',
choices=('none', 'udp', 'tcp', 'udp6', 'tcp6'), default=['udp', 'udp6'],
help="Do never try to create tunnels using given protocols."
" 'none' has precedence over other options.")
_('--client', metavar='HOST,PORT,PROTO[;...]',
help="Do not run any OpenVPN server, but only 1 OpenVPN client,"
" with specified remotes. Any other option not required in this"
" mode is ignored (e.g. client-count, max-clients, etc.)")
_('--neighbour', metavar='CN', action='append', default=[],
help="List of peers that should be reachable directly, by creating"
" tunnels if necesssary.")
return parser.parse_args()
def main():
# Get arguments
config = getConfig()
cert = x509.Cert(config.ca, config.key, config.cert)
config.openvpn_args += cert.openvpn_args
if config.test:
sys.exit(eval(config.test, None, config.__dict__))
# Set logging
utils.setupLog(config.verbose, os.path.join(config.log, 're6stnet.log'))
logging.trace("Environment: %r", os.environ)
logging.trace("Configuration: %r", config)
utils.makedirs(config.state)
db_path = os.path.join(config.state, 'cache.db')
if config.ovpnlog:
plib.ovpn_log = config.log
exit.signal(0, signal.SIGINT, signal.SIGTERM)
exit.signal(-1, signal.SIGHUP, signal.SIGUSR2)
cache = Cache(db_path, config.registry, cert)
network = cert.network
if config.client_count is None:
config.client_count = cache.client_count
if config.max_clients is None:
config.max_clients = cache.max_clients
if config.table is not None:
logging.warning("--table option is deprecated: use --default instead")
config.default = True
if config.default and config.gateway:
sys.exit("error: conflicting options --default and --gateway")
if 'none' in config.disable_proto:
config.disable_proto = ()
if config.default:
# Make sure we won't tunnel over re6st.
config.disable_proto = tuple(set(('tcp6', 'udp6')).union(
config.disable_proto))
address = ()
server_tunnels = {}
forwarder = None
if config.client:
config.babel_args.append('re6stnet')
elif config.max_clients:
if config.pp:
pp = [(int(port), proto) for port, proto in config.pp]
for port, proto in pp:
if proto in config.disable_proto:
sys.exit("error: conflicting options --disable-proto %s"
" and --pp %u %s" % (proto, port, proto))
else:
pp = [x for x in ((1194, 'udp'), (1194, 'tcp'))
if x[1] not in config.disable_proto]
def ip_changed(ip):
for family, proto_list in ((socket.AF_INET, ('tcp', 'udp')),
(socket.AF_INET6, ('tcp6', 'udp6'))):
try:
socket.inet_pton(family, ip)
break
except socket.error:
pass
else:
family = None
return family, [(ip, str(port), proto) for port, proto in pp
if not family or proto in proto_list]
if config.gw_list:
gw_list = deque(config.gw_list)
def remote_gateway(dest):
gw_list.rotate()
return gw_list[0]
else:
remote_gateway = None
if len(config.ip) > 1:
if 'upnp' in config.ip or 'any' in config.ip:
sys.exit("error: argument --ip can be given only once with"
" 'any' or 'upnp' value")
logging.info("Multiple --ip passed: note that re6st does nothing to"
" make sure that incoming paquets are replied via the correct"
" gateway. So without manual network configuration, this can"
" not be used to accept server connections from multiple"
" gateways.")
if 'upnp' in config.ip or not config.ip:
logging.info('Attempting automatic configuration via UPnP...')
try:
from re6st.upnpigd import Forwarder
forwarder = Forwarder('re6stnet openvpn server')
except Exception, e:
if config.ip:
raise
logging.info("%s: assume we are not NATed", e)
else:
atexit.register(forwarder.clear)
for port, proto in pp:
forwarder.addRule(port, proto)
ip_changed = forwarder.checkExternalIp
address = ip_changed(),
elif 'any' not in config.ip:
address = map(ip_changed, config.ip)
ip_changed = None
for x in pp:
server_tunnels.setdefault('re6stnet-' + x[1], x)
else:
ip_changed = remote_gateway = None
def call(cmd):
logging.debug('%r', cmd)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
if p.returncode:
raise EnvironmentError("%r failed with error %u\n%s"
% (' '.join(cmd), p.returncode, stderr))
return stdout
def ip4(object, *args):
args = ['ip', '-4', object, 'add'] + list(args)
call(args)
args[3] = 'del'
cleanup.append(lambda: subprocess.call(args))
def ip(object, *args):
args = ['ip', '-6', object, 'add'] + list(args)
call(args)
args[3] = 'del'
cleanup.append(lambda: subprocess.call(args))
try:
subnet = network + cert.prefix
my_ip = utils.ipFromBin(subnet, '1')
my_subnet = '%s/%u' % (utils.ipFromBin(subnet), len(subnet))
my_network = "%s/%u" % (utils.ipFromBin(network), len(network))
os.environ['re6stnet_ip'] = my_ip
os.environ['re6stnet_iface'] = config.main_interface
os.environ['re6stnet_subnet'] = my_subnet
os.environ['re6stnet_network'] = my_network
# Init db and tunnels
config.babel_args += server_tunnels
timeout = 4 * cache.hello
cleanup = [lambda: cache.cacheMinimize(config.client_count),
lambda: shutil.rmtree(config.run, True)]
utils.makedirs(config.run, 0700)
control_socket = os.path.join(config.run, 'babeld.sock')
if config.client_count and not config.client:
tunnel_manager = tunnel.TunnelManager(control_socket,
cache, cert, config.openvpn_args, timeout,
config.client_count, config.iface_list, address, ip_changed,
remote_gateway, config.disable_proto, config.neighbour)
config.babel_args += tunnel_manager.new_iface_list
else:
tunnel_manager = tunnel.BaseTunnelManager(cache, cert)
cleanup.append(tunnel_manager.sock.close)
try:
exit.acquire()
ipv4 = getattr(cache, 'ipv4', None)
if ipv4:
serial = int(cert.cert.get_subject().serialNumber)
if cache.ipv4_sublen <= 16 and serial < 1 << cache.ipv4_sublen:
dot4 = lambda x: socket.inet_ntoa(struct.pack('!I', x))
ip4('route', 'unreachable', ipv4, 'proto', 'static')
ipv4, n = ipv4.split('/')
ipv4, = struct.unpack('!I', socket.inet_aton(ipv4))
n = int(n) + cache.ipv4_sublen
x = ipv4 | serial << 32 - n
ipv4 = dot4(x | (n < 31))
config.openvpn_args += '--ifconfig', \
ipv4, dot4((1<<32) - (1<<32-n))
ipv4 = ipv4, n
if not isinstance(tunnel_manager, tunnel.TunnelManager):
ip4('addr', "%s/%s" % ipv4,
'dev', config.main_interface)
if config.main_interface == "lo":
ip4('route', 'unreachable', "%s/%s" % (dot4(x), n),
'proto', 'static')
else:
logging.warning(
"IPv4 payload disabled due to wrong network parameters")
ipv4 = None
if os.uname()[2] < '2.6.40': # BBB
logging.warning("Fallback to ip-addrlabel because Linux < 3.0"
" does not support RTA_PREFSRC for ipv6. Note however that"
" this workaround does not work with extra interfaces that"
" already have a public IP")
ip('addrlabel', 'prefix', my_network, 'label', '99')
# No need to tell babeld not to set a preferred source IP in
# installed routes. The kernel will silently discard the option.
R = {}
if config.client:
address_list = [x for x in utils.parse_address(config.client)
if x[2] not in config.disable_proto]
if not address_list:
sys.exit("error: --disable_proto option disables"
" all addresses given by --client")
cleanup.append(plib.client('re6stnet',
address_list, cache.encrypt, '--ping-restart',
str(timeout), *config.openvpn_args).stop)
elif server_tunnels:
dh = config.dh
if not dh:
dh = os.path.join(config.state, "dh.pem")
cache.getDh(dh)
for iface, (port, proto) in server_tunnels.iteritems():
r, x = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM)
cleanup.append(plib.server(iface, config.max_clients,
dh, x.fileno(), port, proto, cache.encrypt,
'--ping-exit', str(timeout), *config.openvpn_args,
preexec_fn=r.close).stop)
R[r] = partial(tunnel_manager.handleServerEvent, r)
x.close()
ip('addr', my_ip + '/%s' % len(subnet),
'dev', config.main_interface)
if_rt = ['ip', '-6', 'route', 'del',
'fe80::/64', 'dev', config.main_interface]
if config.main_interface == 'lo':
# WKRD: Removed this useless route now, since the kernel does
# not even remove it on exit.
subprocess.call(if_rt)
if_rt[4] = my_subnet
cleanup.append(lambda: subprocess.call(if_rt))
if config.default:
def check_no_default_route():
for route in call(('ip', '-6', 'route', 'show',
'default')).splitlines():
if ' proto 42 ' not in route:
sys.exit("Detected default route (%s)"
" whereas you specified --default."
" Fix your configuration." % route)
check_no_default_route()
def check_no_default_route_thread():
try:
while True:
time.sleep(60)
try:
check_no_default_route()
except OSError, e:
if e.errno != errno.ENOMEM:
raise
except:
utils.log_exception()
finally:
exit.kill_main(1)
t = threading.Thread(target=check_no_default_route_thread)
t.daemon = True
t.start()
ip('route', 'unreachable', my_network)
config.babel_args += config.iface_list
cleanup.append(plib.router((my_ip, len(subnet)), ipv4,
None if config.gateway else
'' if config.default else
my_network, cache.hello,
os.path.join(config.log, 'babeld.log'),
os.path.join(config.state, 'babeld.state'),
os.path.join(config.run, 'babeld.pid'),
control_socket, cache.babel_default,
*config.babel_args).stop)
if config.up:
exit.release()
r = os.system(config.up)
if r:
sys.exit(r)
exit.acquire()
for cmd in config.daemon or ():
cleanup.insert(-1, utils.Popen(cmd, shell=True).stop)
try:
cleanup[-1:-1] = (tunnel_manager.delInterfaces,
tunnel_manager.killAll)
except AttributeError:
pass
# main loop
exit.release()
select_list = [forwarder.select] if forwarder else []
select_list += tunnel_manager.select, utils.select
while True:
args = R.copy(), {}, []
for s in select_list:
s(*args)
finally:
# XXX: We have a possible race condition if a signal is handled at
# the beginning of this clause, just before the following line.
exit.acquire(0) # inhibit signals
while cleanup:
try:
cleanup.pop()()
except:
pass
exit.release()
except ReexecException, e:
logging.info(e)
except Exception:
utils.log_exception()
sys.exit(1)
try:
sys.exitfunc()
finally:
os.execvp(sys.argv[0], sys.argv)
if __name__ == "__main__":
main()
#!/usr/bin/python
import httplib, logging, os, socket, sys
from BaseHTTPServer import BaseHTTPRequestHandler
from SocketServer import ThreadingTCPServer
from urlparse import parse_qsl
if 're6st' not in sys.modules:
sys.path[0] = os.path.dirname(os.path.dirname(sys.path[0]))
from re6st import ctl, registry, utils, version
# To generate server ca and key with serial for 2001:db8:42::/48
# openssl req -nodes -new -x509 -key ca.key -set_serial 0x120010db80042 -days 3650 -out ca.crt
IPV6_V6ONLY = 26
SOL_IPV6 = 41
class RequestHandler(BaseHTTPRequestHandler):
if __import__("sys").version_info < (2, 7, 4):
def address_string(self):
# Workaround for http://bugs.python.org/issue6085
return self.client_address[0]
def do_GET(self):
try:
try:
path, query = self.path.split('?', 1)
except ValueError:
path = self.path
query = {}
else:
query = dict(parse_qsl(query, keep_blank_values=1,
strict_parsing=1))
_, path = path.split('/')
if not _:
return self.server.handle_request(self, path, query)
except Exception:
logging.info(self.requestline, exc_info=1)
self.send_error(httplib.BAD_REQUEST)
def log_error(*args):
pass
class HTTPServer4(ThreadingTCPServer):
allow_reuse_address = True
daemon_threads = True
class HTTPServer6(HTTPServer4):
address_family = socket.AF_INET6
def server_bind(self):
self.socket.setsockopt(SOL_IPV6, IPV6_V6ONLY, 1)
HTTPServer4.server_bind(self)
def main():
parser = utils.ArgParser(fromfile_prefix_chars='@',
description="re6stnet registry used to bootstrap nodes"
" and deliver certificates.")
_ = parser.add_argument
_('--port', type=int, default=80,
help="Port on which the server will listen.")
_('-4', dest='bind4', default='0.0.0.0',
help="Bind server to this IPv4.")
_('-6', dest='bind6', default='::',
help="Bind server to this IPv6.")
_('--db', default='/var/lib/re6stnet/registry.db',
help="Path to SQLite database file. It is automatically initialized"
" if the file does not exist.")
_('--dh',
help='File containing Diffie-Hellman parameters in .pem format')
_('--ca', required=True, help=parser._ca_help)
_('--key', required=True,
help="CA private key in .pem format.")
_('--mailhost', required=True,
help="SMTP host to send confirmation emails. For debugging"
" purpose, it can also be an absolute or existing path to"
" a mailbox file")
_('--prefix-length', default=16, type=int,
help="Default length of allocated prefixes.")
_('--anonymous-prefix-length', type=int,
help="Length of allocated anonymous prefixes."
" If 0 or unset, registration by email is required")
_('--ipv4', nargs=2, metavar=("IP/N", "PLEN"),
help="Enable ipv4. Each node is assigned a subnet of length PLEN"
" inside network IP/N.")
_('-l', '--logfile', default='/var/log/re6stnet/registry.log',
help="Path to logging file.")
_('-r', '--run', default='/var/run/re6stnet',
help="Path to re6stnet runtime directory:\n"
"- babeld.sock (option -R of babeld)\n")
_('-v', '--verbose', default=1, type=int,
help="Log level. 0 disables logging."
" Use SIGUSR1 to reopen log.")
_('--min-protocol', default=version.min_protocol, type=int,
help="Reject nodes that are too old. Current is %s." % version.protocol)
_ = parser.add_argument_group('routing').add_argument
_('--hello', type=int, default=15,
help="Hello interval in seconds, for both wired and wireless"
" connections. OpenVPN ping-exit option is set to 4 times the"
" hello interval. It takes between 3 and 4 times the"
" hello interval for Babel to re-establish connection with a"
" node for which the direct connection has been cut.")
_ = parser.add_argument_group('tunnelling').add_argument
_('--encrypt', action='store_true',
help='Specify that tunnels should be encrypted.')
_('--client-count', default=10, type=int,
help="Number of client tunnels to set up.")
_('--max-clients', type=int,
help="Maximum number of accepted clients per OpenVPN server. (default:"
" client-count * 2, which actually represents the average number"
" of tunnels to other peers)")
_('--tunnel-refresh', default=300, type=int,
help="Interval in seconds between two tunnel refresh: the worst"
" tunnel is closed if the number of client tunnels has reached"
" its maximum number (client-count).")
config = parser.parse_args()
if not version.min_protocol <= config.min_protocol <= version.protocol:
parser.error("--min-protocol: value must between %s and %s (included)"
% (version.min_protocol, version.protocol))
if config.ipv4:
ipv4, plen = config.ipv4
try:
ip, n = ipv4.split('/')
config.ipv4 = "%s/%s" % (socket.inet_ntoa(socket.inet_aton(ip)),
int(n)), int(plen)
except (socket.error, ValueError):
parser.error("invalid argument --ipv4")
utils.setupLog(config.verbose, config.logfile)
if config.max_clients is None:
config.max_clients = config.client_count * 2
server = registry.RegistryServer(config)
def requestHandler(request, client_address, _):
RequestHandler(request, client_address, server)
server_dict = {}
if config.bind4:
r = HTTPServer4((config.bind4, config.port), requestHandler)
server_dict[r.fileno()] = r._handle_request_noblock
if config.bind6:
r = HTTPServer6((config.bind6, config.port), requestHandler)
server_dict[r.fileno()] = r._handle_request_noblock
if server_dict:
while True:
args = server_dict.copy(), {}, []
server.select(*args)
utils.select(*args)
if __name__ == "__main__":
main()
#!/usr/bin/python
import atexit, errno, logging, os, shutil, signal
import socket, struct, subprocess, sys, time, threading
from collections import deque
from functools import partial
from re6st import plib, tunnel, utils, version, x509
from re6st.cache import Cache
from re6st.utils import exit, ReexecException
def getConfig():
parser = utils.ArgParser(fromfile_prefix_chars='@',
description="Resilient virtual private network application.")
_ = parser.add_argument
_('-V', '--version', action='version', version=version.version)
_('--ip', action='append', default=[],
help="IP address advertised to other nodes. Special values:\n"
"- upnp: redirect ports when UPnP device is found\n"
"- any: ask peers our IP\n"
" (default: like 'upnp' if miniupnpc is installed,\n"
" otherwise like 'any')")
_('--registry', metavar='URL', required=True,
help="Public HTTP URL of the registry, for bootstrapping.")
_('-l', '--log', default='/var/log/re6stnet',
help="Path to the directory used for log files:\n"
"- re6stnet.log: log file of re6stnet itself\n"
"- babeld.log: log file of router\n"
"- <iface>.log: 1 file per spawned OpenVPN\n")
_('-r', '--run', default='/var/run/re6stnet',
help="Path to re6stnet runtime directory:\n"
"- babeld.pid (option -I of babeld)\n"
"- babeld.sock (option -R of babeld)\n")
_('-s', '--state', default='/var/lib/re6stnet',
help="Path to re6stnet state directory:\n"
"- cache.db: cache of network parameters and peer addresses\n"
"- babeld.state: see option -S of babeld\n")
_('-v', '--verbose', default=1, type=int, metavar='LEVEL',
help="Log level of re6stnet itself. 0 disables logging."
" Use SIGUSR1 to reopen log."
" See also --babel-verb and --verb for logs of spawned processes.")
_('-i', '--interface', action='append', dest='iface_list', default=[],
help="Extra interface for LAN discovery. Highly recommanded if there"
" are other re6st node on the same network segment.")
_('-I', '--main-interface', metavar='IFACE', default='lo',
help="Set re6stnet IP on given interface. Any interface not used for"
" tunnelling can be chosen.")
_('--up', metavar='CMD',
help="Shell command to run after successful initialization.")
_('--daemon', action='append', metavar='CMD',
help="Same as --up, but run in background: the command will be killed"
" at exit (with a TERM signal, followed by KILL 5 seconds later"
" if process is still alive).")
_('--test', metavar='EXPR',
help="Exit after configuration parsing. Status code is the"
" result of the given Python expression. For example:\n"
" main_interface != 'eth0'")
_ = parser.add_argument_group('routing').add_argument
_('-B', dest='babel_args', metavar='ARG', action='append', default=[],
help="Extra arguments to forward to Babel.")
_('-D', '--default', action='store_true',
help="Access internet via this network (in this case, make sure you"
" don't already have a default route), or if your kernel was"
" compiled without support for source address based routing"
" (CONFIG_IPV6_SUBTREES). Meaningless with --gateway.")
_('--table', type=int, choices=(0,),
help="DEPRECATED: Use --default instead of --table=0")
_('--gateway', action='store_true',
help="Act as a gateway for this network (the default route will be"
" exported). Do never use it if you don't know what it means.")
_ = parser.add_argument_group('tunnelling').add_argument
_('-O', dest='openvpn_args', metavar='ARG', action='append', default=[],
help="Extra arguments to forward to both server and client OpenVPN"
" subprocesses. Often used to configure verbosity.")
_('--ovpnlog', action='store_true',
help="Tell each OpenVPN subprocess to log to a dedicated file.")
_('--pp', nargs=2, action='append', metavar=('PORT', 'PROTO'),
help="Port and protocol to be announced to other peers, ordered by"
" preference. For each protocol (udp, tcp, udp6, tcp6), start one"
" openvpn server on the first given port."
" (default: --pp 1194 udp --pp 1194 tcp)")
_('--dh',
help="File containing Diffie-Hellman parameters in .pem format"
" (default: DH from registry)")
_('--ca', required=True, help=parser._ca_help)
_('--cert', required=True,
help="Local peer's signed certificate in .pem format."
" Common name defines the allocated prefix in the network.")
_('--key', required=True,
help="Local peer's private key in .pem format.")
_('--client-count', type=int,
help="Number of client tunnels to set up."
" (default: value from registry)")
_('--max-clients', type=int,
help="Maximum number of accepted clients per OpenVPN server."
" (default: value from registry)")
_('--remote-gateway', action='append', dest='gw_list',
help="Force each tunnel to be created through one the given gateways,"
" in a round-robin fashion.")
_('--disable-proto', action='append',
choices=('none', 'udp', 'tcp', 'udp6', 'tcp6'), default=['udp', 'udp6'],
help="Do never try to create tunnels using given protocols."
" 'none' has precedence over other options.")
_('--client', metavar='HOST,PORT,PROTO[;...]',
help="Do not run any OpenVPN server, but only 1 OpenVPN client,"
" with specified remotes. Any other option not required in this"
" mode is ignored (e.g. client-count, max-clients, etc.)")
_('--neighbour', metavar='CN', action='append', default=[],
help="List of peers that should be reachable directly, by creating"
" tunnels if necesssary.")
return parser.parse_args()
def main():
# Get arguments
config = getConfig()
cert = x509.Cert(config.ca, config.key, config.cert)
config.openvpn_args += cert.openvpn_args
if config.test:
sys.exit(eval(config.test, None, config.__dict__))
# Set logging
utils.setupLog(config.verbose, os.path.join(config.log, 're6stnet.log'))
logging.trace("Environment: %r", os.environ)
logging.trace("Configuration: %r", config)
utils.makedirs(config.state)
db_path = os.path.join(config.state, 'cache.db')
if config.ovpnlog:
plib.ovpn_log = config.log
exit.signal(0, signal.SIGINT, signal.SIGTERM)
exit.signal(-1, signal.SIGHUP, signal.SIGUSR2)
cache = Cache(db_path, config.registry, cert)
network = cert.network
if config.client_count is None:
config.client_count = cache.client_count
if config.max_clients is None:
config.max_clients = cache.max_clients
if config.table is not None:
logging.warning("--table option is deprecated: use --default instead")
config.default = True
if config.default and config.gateway:
sys.exit("error: conflicting options --default and --gateway")
if 'none' in config.disable_proto:
config.disable_proto = ()
if config.default:
# Make sure we won't tunnel over re6st.
config.disable_proto = tuple(set(('tcp6', 'udp6')).union(
config.disable_proto))
address = ()
server_tunnels = {}
forwarder = None
if config.client:
config.babel_args.append('re6stnet')
elif config.max_clients:
if config.pp:
pp = [(int(port), proto) for port, proto in config.pp]
for port, proto in pp:
if proto in config.disable_proto:
sys.exit("error: conflicting options --disable-proto %s"
" and --pp %u %s" % (proto, port, proto))
else:
pp = [x for x in ((1194, 'udp'), (1194, 'tcp'))
if x[1] not in config.disable_proto]
def ip_changed(ip):
for family, proto_list in ((socket.AF_INET, ('tcp', 'udp')),
(socket.AF_INET6, ('tcp6', 'udp6'))):
try:
socket.inet_pton(family, ip)
break
except socket.error:
pass
else:
family = None
return family, [(ip, str(port), proto) for port, proto in pp
if not family or proto in proto_list]
if config.gw_list:
gw_list = deque(config.gw_list)
def remote_gateway(dest):
gw_list.rotate()
return gw_list[0]
else:
remote_gateway = None
if len(config.ip) > 1:
if 'upnp' in config.ip or 'any' in config.ip:
sys.exit("error: argument --ip can be given only once with"
" 'any' or 'upnp' value")
logging.info("Multiple --ip passed: note that re6st does nothing to"
" make sure that incoming paquets are replied via the correct"
" gateway. So without manual network configuration, this can"
" not be used to accept server connections from multiple"
" gateways.")
if 'upnp' in config.ip or not config.ip:
logging.info('Attempting automatic configuration via UPnP...')
try:
from re6st.upnpigd import Forwarder
forwarder = Forwarder('re6stnet openvpn server')
except Exception, e:
if config.ip:
raise
logging.info("%s: assume we are not NATed", e)
else:
atexit.register(forwarder.clear)
for port, proto in pp:
forwarder.addRule(port, proto)
ip_changed = forwarder.checkExternalIp
address = ip_changed(),
elif 'any' not in config.ip:
address = map(ip_changed, config.ip)
ip_changed = None
for x in pp:
server_tunnels.setdefault('re6stnet-' + x[1], x)
else:
ip_changed = remote_gateway = None
def call(cmd):
logging.debug('%r', cmd)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
if p.returncode:
raise EnvironmentError("%r failed with error %u\n%s"
% (' '.join(cmd), p.returncode, stderr))
return stdout
def ip4(object, *args):
args = ['ip', '-4', object, 'add'] + list(args)
call(args)
args[3] = 'del'
cleanup.append(lambda: subprocess.call(args))
def ip(object, *args):
args = ['ip', '-6', object, 'add'] + list(args)
call(args)
args[3] = 'del'
cleanup.append(lambda: subprocess.call(args))
try:
subnet = network + cert.prefix
my_ip = utils.ipFromBin(subnet, '1')
my_subnet = '%s/%u' % (utils.ipFromBin(subnet), len(subnet))
my_network = "%s/%u" % (utils.ipFromBin(network), len(network))
os.environ['re6stnet_ip'] = my_ip
os.environ['re6stnet_iface'] = config.main_interface
os.environ['re6stnet_subnet'] = my_subnet
os.environ['re6stnet_network'] = my_network
# Init db and tunnels
config.babel_args += server_tunnels
timeout = 4 * cache.hello
cleanup = [lambda: cache.cacheMinimize(config.client_count),
lambda: shutil.rmtree(config.run, True)]
utils.makedirs(config.run, 0700)
control_socket = os.path.join(config.run, 'babeld.sock')
if config.client_count and not config.client:
tunnel_manager = tunnel.TunnelManager(control_socket,
cache, cert, config.openvpn_args, timeout,
config.client_count, config.iface_list, address, ip_changed,
remote_gateway, config.disable_proto, config.neighbour)
config.babel_args += tunnel_manager.new_iface_list
else:
tunnel_manager = tunnel.BaseTunnelManager(cache, cert)
cleanup.append(tunnel_manager.sock.close)
try:
exit.acquire()
ipv4 = getattr(cache, 'ipv4', None)
if ipv4:
serial = int(cert.cert.get_subject().serialNumber)
if cache.ipv4_sublen <= 16 and serial < 1 << cache.ipv4_sublen:
dot4 = lambda x: socket.inet_ntoa(struct.pack('!I', x))
ip4('route', 'unreachable', ipv4, 'proto', 'static')
ipv4, n = ipv4.split('/')
ipv4, = struct.unpack('!I', socket.inet_aton(ipv4))
n = int(n) + cache.ipv4_sublen
x = ipv4 | serial << 32 - n
ipv4 = dot4(x | (n < 31))
config.openvpn_args += '--ifconfig', \
ipv4, dot4((1<<32) - (1<<32-n))
ipv4 = ipv4, n
if not isinstance(tunnel_manager, tunnel.TunnelManager):
ip4('addr', "%s/%s" % ipv4,
'dev', config.main_interface)
if config.main_interface == "lo":
ip4('route', 'unreachable', "%s/%s" % (dot4(x), n),
'proto', 'static')
else:
logging.warning(
"IPv4 payload disabled due to wrong network parameters")
ipv4 = None
if os.uname()[2] < '2.6.40': # BBB
logging.warning("Fallback to ip-addrlabel because Linux < 3.0"
" does not support RTA_PREFSRC for ipv6. Note however that"
" this workaround does not work with extra interfaces that"
" already have a public IP")
ip('addrlabel', 'prefix', my_network, 'label', '99')
# No need to tell babeld not to set a preferred source IP in
# installed routes. The kernel will silently discard the option.
R = {}
if config.client:
address_list = [x for x in utils.parse_address(config.client)
if x[2] not in config.disable_proto]
if not address_list:
sys.exit("error: --disable_proto option disables"
" all addresses given by --client")
cleanup.append(plib.client('re6stnet',
address_list, cache.encrypt, '--ping-restart',
str(timeout), *config.openvpn_args).stop)
elif server_tunnels:
dh = config.dh
if not dh:
dh = os.path.join(config.state, "dh.pem")
cache.getDh(dh)
for iface, (port, proto) in server_tunnels.iteritems():
r, x = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM)
cleanup.append(plib.server(iface, config.max_clients,
dh, x.fileno(), port, proto, cache.encrypt,
'--ping-exit', str(timeout), *config.openvpn_args,
preexec_fn=r.close).stop)
R[r] = partial(tunnel_manager.handleServerEvent, r)
x.close()
ip('addr', my_ip + '/%s' % len(subnet),
'dev', config.main_interface)
if_rt = ['ip', '-6', 'route', 'del',
'fe80::/64', 'dev', config.main_interface]
if config.main_interface == 'lo':
# WKRD: Removed this useless route now, since the kernel does
# not even remove it on exit.
subprocess.call(if_rt)
if_rt[4] = my_subnet
cleanup.append(lambda: subprocess.call(if_rt))
if config.default:
def check_no_default_route():
for route in call(('ip', '-6', 'route', 'show',
'default')).splitlines():
if ' proto 42 ' not in route:
sys.exit("Detected default route (%s)"
" whereas you specified --default."
" Fix your configuration." % route)
check_no_default_route()
def check_no_default_route_thread():
try:
while True:
time.sleep(60)
try:
check_no_default_route()
except OSError, e:
if e.errno != errno.ENOMEM:
raise
except:
utils.log_exception()
finally:
exit.kill_main(1)
t = threading.Thread(target=check_no_default_route_thread)
t.daemon = True
t.start()
ip('route', 'unreachable', my_network)
config.babel_args += config.iface_list
cleanup.append(plib.router((my_ip, len(subnet)), ipv4,
None if config.gateway else
'' if config.default else
my_network, cache.hello,
os.path.join(config.log, 'babeld.log'),
os.path.join(config.state, 'babeld.state'),
os.path.join(config.run, 'babeld.pid'),
control_socket, cache.babel_default,
*config.babel_args).stop)
if config.up:
exit.release()
r = os.system(config.up)
if r:
sys.exit(r)
exit.acquire()
for cmd in config.daemon or ():
cleanup.insert(-1, utils.Popen(cmd, shell=True).stop)
try:
cleanup[-1:-1] = (tunnel_manager.delInterfaces,
tunnel_manager.killAll)
except AttributeError:
pass
# main loop
exit.release()
select_list = [forwarder.select] if forwarder else []
select_list += tunnel_manager.select, utils.select
while True:
args = R.copy(), {}, []
for s in select_list:
s(*args)
finally:
# XXX: We have a possible race condition if a signal is handled at
# the beginning of this clause, just before the following line.
exit.acquire(0) # inhibit signals
while cleanup:
try:
cleanup.pop()()
except:
pass
exit.release()
except ReexecException, e:
logging.info(e)
except Exception:
utils.log_exception()
sys.exit(1)
try:
sys.exitfunc()
finally:
os.execvp(sys.argv[0], sys.argv)
if __name__ == "__main__":
main()
re6st/cli/node.py
\ No newline at end of file
...@@ -14,6 +14,7 @@ Requires: openssl ...@@ -14,6 +14,7 @@ Requires: openssl
Requires: openvpn >= 2.3 Requires: openvpn >= 2.3
Requires: python >= 2.7 Requires: python >= 2.7
Requires: pyOpenSSL >= 0.13 Requires: pyOpenSSL >= 0.13
Requires: python-setuptools
%description %description
......
...@@ -57,11 +57,13 @@ setup( ...@@ -57,11 +57,13 @@ setup(
long_description = ".. contents::\n\n" + open('README').read() long_description = ".. contents::\n\n" + open('README').read()
+ "\n" + open('CHANGES').read() + git_rev, + "\n" + open('CHANGES').read() + git_rev,
packages = find_packages(), packages = find_packages(),
scripts = [ entry_points = {
're6stnet', 'console_scripts': [
're6st-conf', 're6st-conf=re6st.cli.conf:main',
're6st-registry', 're6stnet=re6st.cli.node:main',
're6st-registry=re6st.cli.registry:main',
], ],
},
package_data = { package_data = {
're6st': [ 're6st': [
'ovpn-server', 'ovpn-server',
......
Markdown is supported
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