├── .gitignore ├── CHANGES.rst ├── COPYING ├── MANIFEST.in ├── Makefile ├── README.rst ├── TODO ├── daemon ├── README.conf ├── logrotate.conf ├── re6st-registry.service └── re6stnet.service ├── debian ├── common.mk ├── control ├── copyright ├── examples ├── maintscript ├── postrm ├── rules └── source │ └── format ├── demo ├── .gitignore ├── README.rst ├── demo ├── dh2048.pem ├── m1 │ ├── cert.key │ └── re6stnet.conf ├── m10 │ ├── cert.key │ └── re6stnet.conf ├── m2 │ ├── cert.key │ └── re6stnet.conf ├── m3 │ ├── cert.key │ └── re6stnet.conf ├── m4 │ ├── cert.key │ └── re6stnet.conf ├── m5 │ ├── cert.key │ └── re6stnet.conf ├── m6 │ ├── cert.key │ └── re6stnet.conf ├── m7 │ ├── cert.key │ └── re6stnet.conf ├── m8 │ ├── cert.key │ └── re6stnet.conf ├── miniupnpd.conf ├── ping.py ├── py ├── registry │ ├── ca.key │ ├── cert.key │ ├── community.conf │ ├── re6st-registry.conf │ └── re6stnet.conf ├── registry2 │ ├── ca.key │ ├── cert.key │ ├── re6st-registry.conf │ └── re6stnet.conf └── test_hmac.py ├── docs ├── re6st-conf.rst ├── re6st-registry.rst └── re6stnet.rst ├── draft ├── re6st-cn └── re6st-geo ├── examples └── iptables-rules.sh ├── hatch_build.py ├── pyproject.toml ├── re6st-conf ├── re6st-registry ├── re6st ├── __init__.py ├── cache.py ├── cli │ ├── __init__.py │ ├── conf.py │ ├── node.py │ └── registry.py ├── debug.py ├── multicast.py ├── ovpn-client ├── ovpn-server ├── plib.py ├── registry.py ├── routing.py ├── tests │ ├── .gitignore │ ├── README.MD │ ├── __init__.py │ ├── registry.json │ ├── test_end2end │ │ ├── __init__.py │ │ └── test_registry_client.py │ ├── test_network │ │ ├── .gitignore │ │ ├── __init__.py │ │ ├── miniupnpd.conf │ │ ├── network_build.py │ │ ├── ping.py │ │ ├── re6st_wrap.py │ │ └── test_net.py │ ├── test_unit │ │ ├── __init__.py │ │ ├── test_conf.py │ │ ├── test_registry.py │ │ ├── test_registry_client.py │ │ └── test_tunnel │ │ │ ├── __init__.py │ │ │ ├── test_base_tunnel_manager.py │ │ │ └── test_multi_gateway_manager.py │ └── tools.py ├── tunnel.py ├── upnpigd.py ├── utils.py ├── version.py └── x509.py ├── re6stnet └── simulation ├── graph.cpp ├── main.cpp ├── main.h ├── realistic_dataset ├── data │ └── refresh1 │ │ ├── arity.png │ │ ├── distance.png │ │ ├── distancelog.png │ │ └── mkGraph.py ├── graph.cpp ├── latency.cpp ├── main.cpp └── main.h ├── results.cpp └── results ├── connectivity.png ├── distance.pdf ├── distance3d.pdf └── distance_final.csv /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | Change History 2 | ============== 3 | 4 | 0.1 (2012-09-06) 5 | ---------------- 6 | 7 | Initial release. 8 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include docs/*.rst 2 | include TODO CHANGES.rst 3 | include re6st/ovpn-* 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DESTDIR = / 2 | # https://github.com/pypa/pip/issues/10978 3 | PREFIX = /usr/local 4 | MANDIR = $(PREFIX)/share/man 5 | UNITDIR = $(PREFIX)/lib/systemd/system 6 | PIP_INSTALL = python3 -m pip install 7 | 8 | MANPAGELIST := $(patsubst %,docs/%,re6st-conf.1 re6st-registry.1 re6stnet.8) 9 | 10 | all: $(MANPAGELIST) 11 | 12 | %.1 %.8: %.rst 13 | rst2man $< $@ 14 | 15 | install: install-noinit 16 | for x in daemon/*.service; \ 17 | do install -Dpm 0644 $$x $(DESTDIR)$(UNITDIR)/$${x##*/}; \ 18 | done 19 | 20 | install-noinit: install-man 21 | set -e $(DESTDIR)$(PREFIX) /bin/re6stnet; [ -x $$1$$2 ] || \ 22 | { [ "$(PREFIX)" = /usr/local ]; $(PIP_INSTALL) --root=$(DESTDIR) .; }; \ 23 | install -d $$1/sbin; mv $$1$$2 $$1/sbin 24 | install -Dpm 0644 daemon/README.conf $(DESTDIR)/etc/re6stnet/README 25 | install -Dpm 0644 daemon/logrotate.conf $(DESTDIR)/etc/logrotate.d/re6stnet 26 | 27 | install-man: $(MANPAGELIST) 28 | set -e; for x in $^; \ 29 | do install -Dpm 0644 $$x $(DESTDIR)$(MANDIR)/man$${x##*.}/$${x##*/}; \ 30 | done 31 | 32 | clean: 33 | find -name __pycache__ -print0 |xargs -0 rm -rf dist $(MANPAGELIST) 34 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | re6stnet 3 | ========== 4 | 5 | --------------------------------------------- 6 | Resilient, Scalable, IPv6 Network application 7 | --------------------------------------------- 8 | 9 | :Contact: Julien Muchembled 10 | 11 | Overview 12 | ======== 13 | 14 | re6stnet creates a resilient, scalable, ipv6 network on top of an existing ipv4 15 | network, by creating tunnels on the fly, and then routing targeted traffic 16 | through these tunnels. 17 | 18 | re6stnet can be used to: 19 | 20 | - guarantee connectedness between computers connected to the 21 | internet, for which there exists a working route (in case the direct route 22 | isn't available). 23 | - create large networks 24 | - give ipv6 addresses to machines with only ipv4 available 25 | 26 | Building an ipv4 network is also supported if one has software that does not 27 | support ipv6. 28 | 29 | How to pronounce `re6st`? Like `resist`. 30 | 31 | HOW IT WORKS 32 | ============ 33 | 34 | A re6stnet network consists of at least one server (re6st-registry) and many 35 | nodes (re6stnet). The server is only used to deliver certificates for secure 36 | authentication of peers, and to bootstrap new nodes. 37 | re6stnet can detect and take into account nodes present on the local network. 38 | 39 | Resilience 40 | ---------- 41 | re6stnet guarantees that if there exists a route between two machines, 42 | traffic will be correctly routed between these two machines. 43 | Even if the registry node is down, the probability that the network is not 44 | connected is very low for big enough networks (more than a hundred nodes). 45 | 46 | Scalability 47 | ----------- 48 | 49 | Since nodes don't need to know the whole graph of the network, re6stnet is 50 | easily scalable to tens of thousand of nodes. 51 | 52 | Requirements 53 | ============ 54 | 55 | - Python 3.11 56 | - OpenSSL binary and development libraries 57 | - OpenVPN 2.4.* 58 | - Babel_ (with Nexedi patches) 59 | - geoip2: `python library`_ and `country lite database`_ (optional) 60 | - python-miniupnpc for UPnP support (optional) 61 | - for the demo: miniupnpd_, Graphviz, Screen_, Nemu_, MultiPing_, psutil_ 62 | - for multicast: pim-dm_ and PyYAML_ 63 | 64 | See also `setup.py` for Python dependencies. 65 | 66 | .. _Babel: https://lab.nexedi.com/nexedi/babeld 67 | .. _Nemu: https://github.com/thetincho/nemu 68 | .. _miniupnpd: http://miniupnp.free.fr/ 69 | .. _MultiPing: https://github.com/romana/multi-ping 70 | .. _psutil: https://pypi.org/project/psutil/ 71 | .. _Screen: http://savannah.gnu.org/projects/screen 72 | .. _python library: https://pypi.org/project/geoip2/ 73 | .. _country lite database: https://dev.maxmind.com/geoip/geoip2/geolite2/ 74 | .. _pim-dm: https://pypi.org/project/pim-dm/ 75 | .. _PyYAML: https://pypi.org/project/PyYAML/ 76 | 77 | Installation 78 | ============ 79 | 80 | Packages (preferred method) 81 | --------------------------- 82 | 83 | We are providing a `re6st-node` package for many distributions. 84 | In order to install it, go to 85 | 86 | https://build.opensuse.org/package/show/home:VIFIBnexedi/Re6stnet 87 | 88 | and find your distribution on the build result at the right of the page. 89 | Once you have your distribution name , the repository to add is 90 | 91 | http://download.opensuse.org/repositories/home:/VIFIBnexedi/ 92 | 93 | For example (as root): 94 | 95 | * Ubuntu 16.04:: 96 | 97 | echo "deb http://download.opensuse.org/repositories/home:/VIFIBnexedi/xUbuntu_16.04 ./" >/etc/apt/sources.list.d/re6stnet.list 98 | wget -qO - https://download.opensuse.org/repositories/home:/VIFIBnexedi/xUbuntu_16.04/Release.key |apt-key add - 99 | 100 | * Debian 9:: 101 | 102 | echo "deb http://download.opensuse.org/repositories/home:/VIFIBnexedi/Debian_9.0 ./" >/etc/apt/sources.list.d/re6stnet.list 103 | wget -qO - https://download.opensuse.org/repositories/home:/VIFIBnexedi/Debian_9.0/Release.key |apt-key add - 104 | 105 | Then:: 106 | 107 | apt update 108 | apt install re6st-node 109 | 110 | | The packaging is maintained at 111 | | https://lab.nexedi.com/nexedi/slapos.package/tree/master/obs/re6st 112 | 113 | Python egg 114 | ---------- 115 | 116 | | re6stnet is also distributed as a Python egg: 117 | | https://pypi.org/project/re6stnet/ 118 | 119 | References 120 | ========== 121 | 122 | | Building a resilient overlay network : Re6stnet 123 | | http://www.j-io.org/P-ViFiB-Resilient.Overlay.Network/Base_download 124 | | GrandeNet - The Internet on Steroids 125 | | https://www.nexedi.com/blog/NXD-Document.Blog.Grandenet.Internet.On.Steroids 126 | | Grandenet success case 127 | | https://www.nexedi.com/success/erp5-GKR.Success.Case 128 | | n-Order Re6st - Achieving Resiliency and Scaliblity 129 | | https://www.nexedi.com/blog/NXD-Document.Blog.N.Order.Res6st.Resiliency.And.Scalability 130 | 131 | Usage 132 | ===== 133 | 134 | See ``re6stnet``\ (8) man page. 135 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - We have forked babeld for several changes. We'd like all our patches to be 2 | merged upstream. See https://lab.nexedi.com/nexedi/babeld 3 | 4 | - Fix babeld not updating routes atomically. There was a discussion about this 5 | at https://lists.alioth.debian.org/pipermail/babel-users/2016-June/002547.html 6 | 7 | - Filter non-routable IPs. Add an option not to do it. 8 | 9 | - More runtime configuration changes (i.e. without complete restart). 10 | 11 | - Several subprocesses like babeld are started at the beginning and never 12 | watched, because they should never exit. Their management must be moved to 13 | the main loop, to restart them in case of failure or configuration change. 14 | 15 | - Put more information in the token mail (registry), such as: 16 | 17 | - the ip address of the network being built 18 | - the creator of the network ( add option in registry ? ) 19 | 20 | - registry: add '--home PATH' command line option so that / display an HTML 21 | page from PATH (use new str.format for templating) 22 | 23 | - Better UI to revoke certificates, for example with a HTML form. 24 | Currently, one have to forge the URL manually. Examples: 25 | wget -O /dev/null http://re6st.example.com/revoke?cn_or_serial=123 26 | wget -O /dev/null http://re6st.example.com/revoke?cn_or_serial=4/16 27 | -------------------------------------------------------------------------------- /daemon/README.conf: -------------------------------------------------------------------------------- 1 | re6stnet configuration files 2 | 3 | re6stnet is started automatically if /etc/re6stnet contains a 're6stnet.conf' 4 | file with all parameters to pass to the daemon. 5 | Same for the registry: all parameters must be specified in 're6st-registry.conf' 6 | 7 | So for a normal node, you should have the following files: 8 | re6stnet.conf ca.crt cert.crt cert.key 9 | And if you also host the registry: 10 | re6st-registry.conf ca.key 11 | 12 | If you still have a dh2048.pem file in this folder, you can remove it, 13 | as well as the corresponding 'dh' line in re6stnet.conf 14 | -------------------------------------------------------------------------------- /daemon/logrotate.conf: -------------------------------------------------------------------------------- 1 | /var/log/re6stnet/re6stnet.log 2 | /var/log/re6stnet/babeld.log { 3 | rotate 52 4 | weekly 5 | compress 6 | delaycompress 7 | missingok 8 | notifempty 9 | postrotate 10 | set -e 11 | pid=`systemctl show --value -p MainPID re6stnet.service` 12 | [ $pid = 0 ] || { 13 | kill -USR1 $pid 14 | # to avoid a dependency to procps 15 | read x < /proc/$pid/task/$pid/children || : 16 | for pid in $x; do 17 | read x < /proc/$pid/comm 18 | [ $x = babeld ] && kill -USR2 $pid 19 | done 20 | } 21 | endscript 22 | } 23 | -------------------------------------------------------------------------------- /daemon/re6st-registry.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Server application for re6snet 3 | ConditionPathExists=/etc/re6stnet/re6st-registry.conf 4 | 5 | [Service] 6 | WorkingDirectory=/etc/re6stnet 7 | ExecStart=/usr/bin/env re6st-registry @re6st-registry.conf 8 | Restart=on-failure 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /daemon/re6stnet.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Resilient, Scalable, IPv6 Network application 3 | ConditionPathExists=/etc/re6stnet/re6stnet.conf 4 | StartLimitIntervalSec=0 5 | 6 | [Service] 7 | WorkingDirectory=/etc/re6stnet 8 | ExecStart=/bin/sh -c 'GEOIP2_MMDB=/etc/re6stnet/GeoLite2-Country.mmdb; [ -r $GEOIP2_MMDB ] && export GEOIP2_MMDB; exec re6stnet @re6stnet.conf' 9 | Restart=on-failure 10 | RestartSec=30 11 | StandardOutput=null 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /debian/common.mk: -------------------------------------------------------------------------------- 1 | PACKAGE = $(shell dh_listpackages) 2 | TMP = $(CURDIR)/debian/$(PACKAGE) 3 | 4 | ifdef VERSION 5 | define CHANGELOG 6 | $(PACKAGE) ($(VERSION)) nexedi; urgency=low 7 | 8 | -- $(shell git log -1 --pretty='%cN <%cE> %cD') 9 | endef 10 | export CHANGELOG 11 | 12 | .PHONY: debian/changelog 13 | 14 | debian/changelog: 15 | echo "$$CHANGELOG" >$@ 16 | endif 17 | 18 | override_dh_auto_test: 19 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: re6stnet 2 | Maintainer: Julien Muchembled 3 | Section: net 4 | Priority: optional 5 | Build-Depends: debhelper-compat (= 13), pybuild-plugin-pyproject, python3, python3-hatchling, python3-docutils 6 | Standards-Version: 4.7.0 7 | X-Python3-Version: >= 3.11 8 | 9 | Package: re6stnet 10 | Architecture: all 11 | Depends: ${misc:Depends}, ${python3:Depends}, python3-openssl (>= 0.13), openvpn (>= 2.4), openpvn (<< 2.5~), babeld (= 1.12.1+nxd3), iproute2, openssl 12 | Recommends: ${python3:Recommends}, logrotate 13 | Suggests: ${python3:Suggests}, ndisc6 14 | Conflicts: re6st-node 15 | Replaces: re6st-node 16 | Description: resilient, scalable, IPv6 network application 17 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://dep.debian.net/deps/dep5 2 | 3 | Files: * 4 | Copyright: 2012 Nexedi SA and Contributors 5 | License: GPL-2+ 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | . 11 | This package is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | . 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | . 19 | On Debian systems, the complete text of the GNU General 20 | Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". 21 | -------------------------------------------------------------------------------- /debian/examples: -------------------------------------------------------------------------------- 1 | examples/* 2 | -------------------------------------------------------------------------------- /debian/maintscript: -------------------------------------------------------------------------------- 1 | rm_conffile /etc/NetworkManager/dispatcher.d/50re6stnet 2 | rm_conffile /etc/network/if-down.d/re6stnet 3 | rm_conffile /etc/network/if-up.d/re6stnet 4 | -------------------------------------------------------------------------------- /debian/postrm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | if [ "$1" = purge ]; then 5 | # https://lists.debian.org/debian-mentors/2015/12/msg00367.html 6 | [ -x /usr/sbin/re6stnet ] && exit 7 | for d in lib log; do 8 | d=/var/$d/re6stnet 9 | [ ! -d "$d" ] || rm -r "$d" || : 10 | done 11 | fi 12 | 13 | #DEBHELPER# 14 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | #export DH_VERBOSE=1 4 | 5 | VERSION = $(shell python3 re6st/version.py) 6 | 7 | # In order to build DEB snapshot package whose version is derived from current 8 | # Git revision, the `debian/changelog` file must be generated automatically, 9 | # that's why you can't use `dpkg-buildpackage` directly: run `debian/rules` 10 | # instead. 11 | build-package: debian/changelog 12 | dpkg-buildpackage -us -uc -b 13 | 14 | include debian/common.mk 15 | 16 | override_dh_python3: 17 | dh_python3 --no-guessing-deps --recommends=miniupnpc --suggests=geoip2 18 | 19 | override_dh_auto_clean: 20 | dh_auto_clean 21 | make clean 22 | 23 | override_dh_auto_install: 24 | dh_auto_install 25 | make DESTDIR=$(TMP) PREFIX=/usr install 26 | find $(TMP)/usr -name 'ovpn-*' -print0 | \ 27 | xargs -0 sed -i "1s,.*,#!/usr/bin/python3 -S," 28 | 29 | %: 30 | dh $@ --with python3 --buildsystem=pybuild 31 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | dnsmasq.leases 2 | *.log 3 | *.pid 4 | *.db 5 | *.state 6 | *.crt 7 | *.pem 8 | /mbox 9 | *.sock 10 | -------------------------------------------------------------------------------- /demo/README.rst: -------------------------------------------------------------------------------- 1 | Demo 2 | ==== 3 | 4 | Usage 5 | ----- 6 | 7 | To run the demo, make sure all the dependencies are installed 8 | and run ``./demo 8000`` (or any port). 9 | 10 | Troubleshooting 11 | --------------- 12 | 13 | If the demo crashes and fails to clean up its resources properly, 14 | run the following commands:: 15 | 16 | for b in $(sudo ip l | grep -Po 'NETNS\w\w[\d\-a-f]+'); do sudo ip l del $b; done 17 | pkill screen 18 | killall python 19 | killall python3 20 | find . -name '*.crt' -delete; find . -name '*.db' -delete; find . -name '*.log' -delete 21 | 22 | .. warning:: 23 | 24 | This will kill all Python processes. These commands assume you're running 25 | the demo on a dedicated machine with nothing else on it. 26 | -------------------------------------------------------------------------------- /demo/dh2048.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIIBCAKCAQEAjrgRXpVvK4odEnfapsCCBde65mjEGiH8kvZ/H8IGgUn//MgSOsRt 3 | qU0OXFQQBSwEM6zlaQSocq8LfidkhsDOdGyCK/+v8vCTRf/83AhyyHrDe73mlu+b 4 | ruWeKSK6PDai/egMoLaj8J49ScJZYJM/ClQm2bmaLhMfVGCbrMSjnSW4MkzKmTBQ 5 | lCIKuLuE+1di6FHknar+Nx7Al6E8N+VhwPTb5TN7WcCPSsUYtwxFDzCyUBFkR9BT 6 | J2oBjUPFlYGvFuaZAiePL6roL+ecSG1Q83/vJZwGYDDYZR4Hdx7QcqxRJPzsQG3+ 7 | 32AeiFaX3IMooqaBYCjHDI0VMFZsv5u+mwIBAg== 8 | -----END DH PARAMETERS----- 9 | -------------------------------------------------------------------------------- /demo/m1/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDAZt/QdZh6jJ29 3 | 9RIoPxYXFiTr2gDH2MK8XLi1nzNKK1/uDNEI9y6KqYOwpLc4+0zjAsYXcO3E8qFU 4 | wEF9EVRZPo5rGDg+0RmRlAe1mt/gxv74RGz3KuUrkDJbP1YTTocNXJYARg4Om6a3 5 | TckyRlZcQM4qGYfZttER4xImRYhY3eRGa1FJ1ejzQF0uEjRfRkN+tD34fv2oBpYg 6 | jPVwUats6g9Pm5suUcGCjBr0HnkLCuSuDtjwIr3pfO2BrRkLan12ig6VV0cQlD8g 7 | Y/4q0hNsb/yC9wYjE0dhRFHoMz7RrNa8gV8fW5bOg+mOr/firSh7OInAA7SkclDf 8 | wCoVDmn3AgMBAAECggEAUdxzZdJi6eFiTCw1aYJN98yoMOE0a9NCJ7qbrErsG6aQ 9 | zkwNtG7XEpBFQ6cRfeEOOZg4iEi4IY/KLLEQLBJvfPksx0wqwi9dXZ9KGG6oyo5g 10 | dWlHo3JQx1Q22LP9/4Dt3TWDbOJhoL2BFYS3EfjzTZUVIrxyHKO4GJAwbLAw7v3c 11 | /etCl5XyLhHRqdszz5BRsvVMTnibYGXIVnFKlbhIKlXgQMnNkXKVV763UuTLNGXN 12 | SiFVXlCFuyGSAVHIWfhMgHsPA1uPLwXP25vxODKJzTedfRmFA9UHsPlVDRrbmheD 13 | cNRE4pj7Uswf4rRPMWF2gDT2x83+jKqT7WwPYVCAIQKBgQDhQnsGWCSHTIuHGwrJ 14 | ZlH6xH+HzDei5cvIR2jNvusKXLRKe6BkLYhQ2hCsFNU6UN+uafeJ5sZPxxkLwe+e 15 | UGK/BcbJnSYXEXEF6wPpdTeDR56eFPtg2B08SoDylhgnqjit6JYD7mnOX9Si7Geg 16 | v8bNv1qc+MlNkj5DYWunLkICrQKBgQDaqH6g7LGMgejNcoCXICMYVRQSNFAT6OOU 17 | MKzeEkBYjAXuVoPEpxtFcyi0FNP3p/s8Sg2LfJ51cgzMwNgD5Tg7qSMyLnuQ7FNz 18 | qMygzvtif6iw53lB8XJuL/udQEJOAqoz13v1OGHYe4sBT4GAZeoQiXNJ+cCt2YdM 19 | sdp4t20XswKBgGcu0LPDRFhFk9Q7auF+YScBQ3ms7f9MS+Bd9X/paDjC5kMIBhxd 20 | TwfaG7D3igTAbvWxzJX6g/SfOrXMDDr0j4b42moX9g6zCpavZIdYDimwcNPwwIVH 21 | +XxYlGCKqF3i9zuSQsViPE0wUHKg34ce0a4FbThiLbO+vY6X3XwjEFGxAoGBAIEn 22 | ARSgegbaPN4NAfNB4jjfF+ngyGPaLYI8df79nNAwdNhgdZhUdAFDkgk8PC+OIxFr 23 | VDDNvPL2GKVgSreSTvK+ZrGfTxE85dXtIGDjoQx/Bu7m6nKO4N5ArUgmyoM1g773 24 | Spp/YRvzbRmrEnz6tZRq+5P4V1zYndgpPTgMb/2NAoGBAIa5GIyoBLugkluaOKjv 25 | ATewNO1JyR7vTLwZlHUyXjNqqCiS9nwnv0rHCZg0IuydJWW68dMBtoiszFAVj2r0 26 | 6Gnlps/47mJ5tqrRcg0URmWzIa5WpsgiVT+K+lOf+m25g68Mt9vVnxpIZsthUsaf 27 | qJy0PkzJxuV7JnF1nesTAlPO 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /demo/m1/re6stnet.conf: -------------------------------------------------------------------------------- 1 | log m1/ 2 | run m1/run 3 | state m1/ 4 | pp 1194 tcp 5 | ca ca.crt 6 | cert m1/cert.crt 7 | key m1/cert.key 8 | neighbour 6/16 9 | -------------------------------------------------------------------------------- /demo/m10/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA3M5AShAyBJPwQzF29mA+2ua+yIeZF5PNa0HKbOD7jZy4Q+5G 3 | rTSnMDGJ0LBnLbcdmf3kZjhEABx8OA/ZO+/gj5nhPLvZ+RerX0626xXz855KwQXE 4 | ENJolzEkFHXsVu7ka9UjiWkOZSjXAwSJTzyO48VjstoUCduKfMlSAlAzdDSrhbJp 5 | 41xbKvjF/7/Rypr7/BdVZ8TTJ21A1eWcCL5Ke8vqZowerq5AdAaZ8Gl9hETWdqud 6 | PWJuVu40/waYbFVmuQ2eWtPJ139CgfcUKN/kTPp965cvUIZJ1QYSVxcqmjwxWd7P 7 | TF6YUyU8VupdeSvIsXi5O+AXYHwIOtOFNENNnQIDAQABAoIBAFKke5l84EuoV0e+ 8 | 7sieE0h5DrUdU2ZosnzPfgUsW9qP6sO6Hgfb6it2jx9ltuh8Xf7x8Rd1PbZQ8wlE 9 | nUj6qjD1JkFGCfpaKpEcdAgfClSHTXFbSBwHtI/KG7fwvwl2llXpy0g14uZn17nQ 10 | a9gVUWHc0kjxqIhb/ERiUWh+PhPipLGnwEuUG7l4Pak/7rNX+dSGNhCx9TtVE5x7 11 | REaJwo9+MToW3ivyzzm+MlS2sYBrmIpcTLbo2bpqLG7VU6n1d0GDjkHCjqyheD2m 12 | M810/jJ4rURY5M+yQamO6zb7HWHkWJwGcXGfBYGxZi9g1Kn9iHaQsGB8KG/movfe 13 | dX3MBD0CgYEA9cmOQpDh31ycoLyMqR/HxMfXeYzXZr652ci55RJ0Tm4zFwa457il 14 | 0TvvpOC5P3tPnrdjoppyZWa7QNCpEYr0HH1CFcUWJRG76Jd5D4biBKCsrYzACbqI 15 | wFTtNWf51s7Zqp7lWPf0VEvfSdcOqOCgYPUOrqIo50V9GvCR6Vzb/K8CgYEA5fr3 16 | DrWMk9tCjbeynIglxH1rklnbgSoWl8OH/Mj+YXE6Qyh33DTB3DP5JkVac3V6FjxS 17 | 9SUGN25A7hizj6zsmIeDxhBU0vaK2UMwQrFIkn/zGf18Y2VYNO8wXmFsaYtZO4J9 18 | qyNuYF3onSFStr9oks6ShpNH8dmxbQdybXKspXMCgYEAyryolYvAZGeV4pfkRpSq 19 | SUy2FdLw0hU8Y8HIZIVmXlNT3Qh0eN68F+yibZPX+d8S+841ZyiSd8XXa00ySAAf 20 | /2fqnN5mt8j7AI4BE6ekw0BtbvIMbXnp1wu2ZjkPwfn5XV2XvF06slWrJtM3imFO 21 | qOs6Yx3rM2kEGraZBK7N3o0CgYB73PPtDyi8hY3NyA9BsS4uKqKiOxU29tUePehi 22 | 5FnlX1dSzlvn1N5IXBGnZBj4MGBl022WrK8xcYc09UbYbfkrmWkGZstYqAJWS5KV 23 | iOgeRx1GdTjmS7H7KIvzeSMroSqZG4nVf4q2Cj37SvRCisqmru1J388S2f1uTGF7 24 | wxRrMwKBgA4GnLLnV9ekqXvHdNgyW6hnGJrnCthgARwNj0e9vWC1C5en064XPwdI 25 | TPx/yILuxTY9F0nkRhM9DpTBk8HkKSO4W4yh/FqT4uSHNkLU/52r1zrpHkLFUYZj 26 | LnCXYu5dzUoylnw3+UNGKOa5671Jr0WgLR3pf1GaEAnB/x6RYe+H 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /demo/m10/re6stnet.conf: -------------------------------------------------------------------------------- 1 | log m10/ 2 | run m10/run 3 | state m10/ 4 | ca ca2.crt 5 | cert m10/cert.crt 6 | key m10/cert.key 7 | client-count 2 8 | max-clients 2 9 | -------------------------------------------------------------------------------- /demo/m2/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD33Mb+GkvL5rwu 3 | YYF0Mf+dZb4irfQWgQ1K/RD8MbyaMlsA4yDUYXMYV4NToVACybuHwhjhq6BPaRL6 4 | FmUafhjkwa018X93BDeTgocoTK9Kp2WWwwHhWTrWjeK407iB+5zjQypAtg7M1rsE 5 | wTyfqfNOPEKvDGgIzNPLObFJr5olgO82W7ZOEWNHKHIuVv7n+KZ44wdAP2X3KQUC 6 | LksRWnAA/Ggzr/EhG/zkLV9aAJabRnFE6TUtdsYLP7DoRd6qql4N/zl+iFStX373 7 | NhkHdzr5rvNnu1oBZLdN9X6FT3tJLo9Fvcz2L44ixS9sQgcT+kxp9YiwyAzjKHGU 8 | 0VRQ2T/vAgMBAAECggEAM7ci5d3CttZ7qQm7Iu1TshmKZF37Mb7XcTAAHT6bZWjg 9 | R/0b2nRCuW94/TgJI6vHrvm2i09t8WOxC/EkQzTQKNqoMzsiJ0j/940ieiwV3vOy 10 | ZJRnjgc6GY4dIgc61hQTle9rn3jc+axL9SAEcCPbmhVErJVgF4X1Is/0x7UV6oDl 11 | SkAeNsPaWju7s9EvB98kPaOM8d/3XNRWgpEqhwLOmNhDnrixqrZ/yGeZvtoMU/qN 12 | Oxj7hX5iNQoadRPT34HYkuCGSnBRB4vGs1s7gceC3AFzVBrWBqk+yXL+osoQw4Rl 13 | SInqmk5Q3SxJONWlmfapqD208u+8EaL8eOjLBOrtIQKBgQD9JWJhYo3dA+Q8A05j 14 | cokY8yxOpsyk7SuBY1zo6g9zcmi1cUPt0yfCjd02GHtojmF7Z1xIvTVBDYDIum5n 15 | Q66E+H1UedbWPxEHk86QgU4sNQfSOgL8WD57mVuhmusb9UQKzOwnv48kJJUgE+fx 16 | glx+xTRgVV+9YPeADTga48a0OQKBgQD6qCTLcvHshNP+wqmZAVIeya1jA8UuW88/ 17 | Hp43ILchVTKZWrxIvEvfmFZWJFsSTeq90cB4sTjNAWjL9k7RZBsclK6K6WLCsVCi 18 | KjE5bME3JCClSXZs/AZqrxBB1FMoJOcrhszeYpr2fWYLusYsynzl0KZmnP7QXOC8 19 | 4uYr866lZwKBgDoCWRsL5IpjG+gMsefkFTqcbujPHFIm7zNFO8M777J64zfA7toO 20 | 6yuQKsL8tTrudbQ7qVAZ25w+peZZoncTcWiTMNUs3mhWJ2bfHhTLLMjAljw8gvak 21 | mhzktbTAtWPAvojjELLDGOAlae7h4OLdBqkeM7Rnm/rfv5eeb97pl10xAoGAOmEv 22 | MLN05UGKnqt4FMrZ9ZHDcGxQYJvBiVH4R563CGxvfIhFkitJAKracPXGwFylb9CK 23 | o5GjSRwq7FZRxTMrnVyDypbRCceCoEJzdIx/8/fWIqkZjAim9fIQN0TcmVz++oRK 24 | 8wDEVuSd/91gis4eiOgv2XZ3YtA/B79RHKuE4cECgYEAg1o2Y/PtU8X1nEL89SQh 25 | W8jt3Yv4HGNSTuOKrprqC5N2fXebzA47IjxBm+PUobFXHJYZFfFaRcHsy1u+P34s 26 | Cqdkuo/zf4p9tJzHKql7dB8MmfnRcDfo9K5lZxLbL+EsGzQWWnwW8NepDlGNHxid 27 | hyAWRlKThYNyiut5JTdKI4Q= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /demo/m2/re6stnet.conf: -------------------------------------------------------------------------------- 1 | log m2/ 2 | run m2/run 3 | state m2/ 4 | pp 1194 tcp 5 | ca ca.crt 6 | cert m2/cert.crt 7 | key m2/cert.key 8 | -------------------------------------------------------------------------------- /demo/m3/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqm64S/8j5X54f 3 | n5aFMl/co5OtX180A/xcpnMqRXLPunDcjsc8dqNSYizofRE2/bV67GfmQqY9tUsv 4 | /mhFrQG60cLS3qJlPgGzigd69y5+U5wl3OGBTlbrpfsjc+qUaztKrVeRcb5hxcp6 5 | aIz51jj5CIRF2IJwb6R6Lg7zP7ty47NGhhjYG9gTcJ2X53EMLZrGqHpAnldGmqN3 6 | MW5IOjpkClW433VG/XYrR4b/E+LLDaPPVFcH5zrHTOtJC4ajDW8iNG+dV7Of08Ec 7 | R8za94teQ3gDClJud+M651Pfc+m2ulp0piwDT0eN4d6LbJYybVO1x1Ld0CegpMp1 8 | Xx3JVIbxAgMBAAECggEAJF9UWVCW9DNibrODT5pRvaRSE6BFl71RzB7gZp6vv4Gs 9 | FhmKeGWCpEmx8U7YoLSuQCqua/TBpF5GTVEbwd8Eg2O8+V/ll1gE3NYMijawftYs 10 | cBYLLZoaBds5E3HfDQ1fcqhKBwQM5qNYa4hmpcz6EH20VX+3L2ld036GSW3I9NEl 11 | hkSdzPEBtQYkNzJCOEZemnoioEnC8+2lvfNK/NGu4lT1JbW/MQGlS23fAk2dwRJM 12 | o97C/+YaMi+YypRa5Ybb4fXTZtCxlS4RhSJtyoyNrYEmjzeL5owt/gwJ/2gG6ObT 13 | 3IeasSTGd0fJi3fkNNEL2RInEc2m5INEq7c4hIZP0QKBgQDSyVg8Y81OxewsJjqt 14 | pPM2ebMcwBM3+nbOd7jeAkz0vQ6QDMnYXmlBwGz5aPNh8RU/CO0PO3WmcQfes/Ra 15 | pYKG237dqG1JmCVSZiZzEuenXBTCIMP3BMhkNw7QQqw5gOtT6mfJI3I16crxcl0y 16 | yhvfTBANN8rgZNhKYLw92G0fPwKBgQDPNBICiUoXJ2jdFOsbTdvxOH9Gl2cT6BbB 17 | gqPcRbHW/CYR2CbRxeXOmULfX87bm5mZW2ltM2Unt33wxAxMRszEygM7Y9/HqNnq 18 | u1Wy9PIOxZfz9AurHQX3wVXD0vokbiLtj2I9MKNls/O7a9nR13mtr75zrNLE4qOo 19 | Cr6AKAn9zwKBgQCp3zoM8SF0am165xRC/LxFGq8ObH0Qj/lPS4zg3lnGotYpoSTK 20 | CswpATrRhZMfS3sj4b2qmPCnwk/lWoGCy//1Ts9fM21f8g2h6lDyTDoE1mCkUmOb 21 | n28f1H9Lt2+0qAD/D9m0b6yLZm73RoN1ewwbWOFWxa/qR1tIFc2eydynYQKBgFz6 22 | IJrCwFxjcsrB+D3oUimMEJC8n6RvIMb0uFK/c23k97BKLiEzjEuIullBxLEGwYXv 23 | Xv+BcILB8+F2F46itoAXphugVWpiema6aVpET9oREH6HpDTEcUkbmECnC1gNk97F 24 | 2G9cw/V/Kn+Phc6DwjYOBoghpPJ0QLvHFnSrgtBBAoGAJcbu/Gd4MILQzlHmBqAN 25 | Nx/zP/n4LTLjSBzGYRavQLZeF7aEvk42bOTF1FZk6oDQXktwL3e/grPSm+04PtE8 26 | 0j+0ScJe1JXeKtnyEJyLbCRZxQB8DNhCOSiOmkxPpQQdIVxtWE9zmPW4nwej5fSU 27 | ZiMjH5lr+IA9fDdD314ni/4= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /demo/m3/re6stnet.conf: -------------------------------------------------------------------------------- 1 | log m3/ 2 | run m3/run 3 | state m3/ 4 | pp 1194 tcp 5 | ca ca.crt 6 | cert m3/cert.crt 7 | key m3/cert.key 8 | -------------------------------------------------------------------------------- /demo/m4/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbQvB+mkq+/W0v 3 | pIPFLTS6EMv5kho3zDLwPJNOvZk882ENftElGnVTdNLHRIzhM/h6MoTOeYqQYxYU 4 | 1AnvQOUUhojMGmsQfb/3bA6gablElxPADz5REJIRZRi45ROjrJhiOZm4mVS/4EWf 5 | 44oMY3GLnpDZAdFXNhy7tTJGulJLKa1ZPwtIzEqMKkYXjELah+QZo4C4gUwsduKJ 6 | /HsrD0cLgGdoCDhLOs9JNd44c6A8qdxhgVdSW0RBNRgYjfe4fW03gv4r1X9ZKqoq 7 | ABukMHZWb4cKOUPbFVphyMP6g4F1mFdQgiLDBbCHZjVXo2komFCN4Zvf6LfbggDE 8 | paUiBc2NAgMBAAECggEBAMfX8kUiMiBz8hGre+K6ukUgzBkTvku/FWp7e7QYAVRZ 9 | a8sWHGAaBnfjkb3H0DPu33nZE8zxYIcLmPOw88Eav/D+Ui7doVntk4ta9NXmptNK 10 | c9joy96tIMU0BN15BXZ3+IgGggKL1n/wVOVPSeLSvIoKI3F3Q6ueiX/YvhNS/XC8 11 | wvWR7uJc/E9hfllRfMPRZ3UQcxqHeOpLqYRjnZCLxZ32yHXP0PcHRTrU0wvgA9dV 12 | fTmYCmBsj0uzsVggdFBQRu9+wGNJwRQJyhYjqkjjFKjRp/h79u4gjvi2nTn4WS0z 13 | kNCi1RobwLJA8BZXQWup+X9s4ADagC/86F5gu+QTQg0CgYEA7+JfLwEE/MKMtir1 14 | KoqJ26DLED6q9rbme9tmARWrIpL3sWTeZymGVoi6i7B+eDHUh0J7hMNy1cJOYkti 15 | N03nh7wZ5ZugbD07l/1tJUEbkEXiuIrpnyKXpL7+xTSp1RW44ITJ4Cm3xiUzQXy3 16 | 8987t4KAOIzxtrpJC+qDUQYjBs8CgYEA6f3jdnv4hubq/DeOmsTfS2rlUlJR/QLb 17 | rB1xL9dNYDyQ9qfn650or9uC+0U8rHqMWkFQ6tW/RuGqhWP5Wo7mfRhnGoR2uweO 18 | RdSWxDCqu/zZ9rOC/TU5scX1p37eD7Gyg+EYIQMDQSpcmZMrtgF/K5FePqTDu+Q+ 19 | nctu/rZN/OMCgYArjNNu+yZmpQNaxy2/TWt9vhFfn6D2LXpmngxEwiRRoJDrWb6u 20 | x/EloKkNVzt+or1M2RsHnVwpxp7oZXstQy13jQS+lKRpQiyhiVbZXpqcdLOWgVGs 21 | nGEffbVPJCJ5Fvp7eGAWHDzK8lH936Cd27dBt6WDnsL7Q5KjYS359b1CLQKBgHr+ 22 | 8t7oIWY/4Ys8ZOBj1gAdq3dPQDufkfrx6vo70Tdh/s04y0blcGqoFzyfU1pSfI5A 23 | wRj/zDClFG5Tc9IvRWbCOggNUx/Sw+1e5vUvo793FNeB2rvgi3gv14X4wR88x9pc 24 | qsNHgyFw9AXh/1sHhGJRYlnJX/3W5zcopSqMh4gDAoGAezP4Plez+mjD0DXwZLhC 25 | yvi1NGuaUSjOWCEzA4tXRR19hT/THcUWddxdG+edaf4U6/DYjGKXcNHKjaexSzg8 26 | Wk9eCRRrfb2+BsOUA3r0y8TzRixkynde5brAjd3/5T/y4pdfpsF2/+THcEOWuSvx 27 | HDAzL3D1JIkYdQjHBMSogx8= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /demo/m4/re6stnet.conf: -------------------------------------------------------------------------------- 1 | log m4/ 2 | run m4/run 3 | state m4/ 4 | pp 1194 tcp 5 | ca ca.crt 6 | cert m4/cert.crt 7 | key m4/cert.key 8 | -------------------------------------------------------------------------------- /demo/m5/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDl6dlbzrCKYi5e 3 | nWvXUb7sY3wx2OckDpY+A6zf861cFTOVpfuJsGn2zkZmOXeryzUi4L7tbI3ZUXtI 4 | Xqwc7O9O2HDf8/V05Ppn8cstb4VlZfS3qgUSzIKExmBzC8riiE+sVRZR3RAn7Atr 5 | 4ywcyj45jEnbk47MZAI5u9nXzsOdfiiezXdJAHnr0ex/PNmesj371DrjwsxEIagL 6 | q1dLo7lKo1Rc8dTpGTUtcpnt3DCVl34NdSaRWCMHPRuuNb86wH4jrUJ43YKtL9nh 7 | yTJqYSwdHF73+cKKeNa+QpcRIv2IGX2pORSzY4eRtLiUht7Ita5eAgk53B8+htWD 8 | Ap7aPBTJAgMBAAECggEAJ/4tmRKzEt9RQZQQljvaAjSr/d/FFTiUWeU5xRsWJNZr 9 | SrS8tI7DX/Mxu8ZzUqbeb1q424PuiMbrswME3WM5nXV47Pv6zoKmLD6bISQtCB5c 10 | Gzav+z0n1wDK9jgu6ojbkxaZ81OI4+jjc7joi3mCqXAFyPQ5+sPKv1A17JxC2roq 11 | 1/z1FNwH+1SeawgZcgdUb6yr+1JbZ6uggVxbERi6zAXTRZgO81NQrObBrdru/qix 12 | 8pVcAyQnh/lWTSe0JzlYv7ApRAQLN7j0c6WAyIIJoORo9C0v4TiNF9CNOGkHTw0E 13 | rXLHJ2jXl0NOhKvBqZjcdh0CnlEIZc0Jpd5EL3XwAQKBgQD5Hz2gOp6S1Arigd1d 14 | s669rirRUOOtkcsb+ayodNlouCw7bd9ekZtDh/m+TXv4lYfuJj8CTl2/kKjpgANl 15 | jb30EG+jGtqF/DZgx7CILUlznnzTKylRiDqXkHVGKIzlzAzJn2lPkcWIc0BjPbZr 16 | WrAJajoWIqSbut6Obsnt7jk3wQKBgQDsQthN8W8WiNpm4E2ZJy3A0dvPy1GVwis/ 17 | bYG6NtE7zkY/qUshHLIhyZgmE8liXJZJDDwaAqjyjQP5+/CRwBve2KeYFmRfxeZW 18 | JYzMaqkCQ74LhHUWrlZbjiWXITcQi/xzfSiXz4SCQq2w9e6rXC4b5ywtZuYpW0s7 19 | VieUDKjfCQKBgQCoyVzkmbLkwlvZmalVGi+zfjSuewK3j8cwXhp4TCsdQ2NcpJWp 20 | qjerKo6wPClZhF6DoVAhjN0BtM5s3fnvLXjcyuIIfWI/U2G4crONoDla56+lF2I4 21 | RneERzDd5w2HiJfFZ4dfjOFsEMI4OxDEbb5Fsr5GTxR+Gr89e5y+4x/LQQKBgQDM 22 | HJfZBqttcYAV/tSrauCpQ+S+aa4rTpukaYv4/6DWJUglebHRgoQWkp6iztdd5hVk 23 | OHC4G/Iv0kpMLy/QC4NIjC6GQ1omL8fmRvyUIdWvgizusFmj5avv3yd0oJp87iOE 24 | 9u0Z8otNKiP6zQ2L99BSBO7yroJL2nZtZeo9io5eWQKBgQCeVnCJM/uF+XDYQr95 25 | oLGR4490scpKzykohZ8PAz31SxPDvq/nSsG/itqKhgRchcawVMQEDkQML+iI/E0F 26 | rfmZyFGE3DESKGdpDP435rrKR6h1enkLfyTyvp0bR8rYOUOxmprzoB4gGGENrFb3 27 | qhKfVVjOqWo/k300JpFcMhhELA== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /demo/m5/re6stnet.conf: -------------------------------------------------------------------------------- 1 | log m5/ 2 | run m5/run 3 | state m5/ 4 | ca ca.crt 5 | cert m5/cert.crt 6 | key m5/cert.key 7 | client-count 0 8 | max-clients 0 9 | -------------------------------------------------------------------------------- /demo/m6/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQC/yfEU4Cphx7K4 3 | ce9FSVB0MfrZcKEU/YJZ0lHharicDdLH0bP7vWWj58A4cATThCgV8NYnLRwqp1Se 4 | A8cYJDnHU37YmxRFyHVHbTCxHoa3282e4yYWzR85AbD9g33SEjT/7R9PThGLjX73 5 | 5C/AXSaotmHOhYvleA6WF/QcoyWUHQBYfEThu9XsoXliJsP1yewabtxS48cgDA5e 6 | /mizYI/FQh5cSIWuT0hKWMkKoclFyjOGxSOiq0/9Wcfq5MydLj+o+ogTyFFqpLUw 7 | rzn8o1GBzb/Bo7Ck/+m1opo96zzqD2hA3JiKoQNBnTy7jlJIdZZvj4zNDeyH9FOx 8 | 8uGZQSy1AgMBAAECggEAKBwJqT4AXxauj9h0G99rTgGt3o+pgIRMIJDVwvjYxvo1 9 | HMkEhR9lBFRRZPw4Ziv7XitR/jt26oy4K5vKpYfd9YvGuXIlWTrFHDz/s+sYbZcw 10 | d4w1yQr4/qkV5M79H1PjdaqXo7RGAxOCfEFioy5YCWyrI8/WJBVpp4Y27U5Ja6IQ 11 | XgwtFLCUgm6Z+Z9xyMbUFQ45+KTvs26PSxklk/vEAVzHVrxpojoeiLgbE4IVEqBP 12 | 1EBkK1VzKrnjEYJrt6BZwHM2YvcbJVtAmFby+J1tmo2lXv2HFEoZZjnIxO6XNeGA 13 | 0/NjXiwtOJuEYomHmyb8uZKVlukgxJoUd81R+wpFeQKBgQDims4NJjFMdHJuj3s4 14 | rKX+UhiWk+UuqL8OdVEhnyzkF2fE/w5/+DJeH0bWTkU7vaxKWkBDgRDPf1bMVNNy 15 | IPSqzhHXrO38aseXLl8VNypQP7OjtOeJMdXgnsbK6GCAprTVnIkZnM+1PhL16WVK 16 | fLEAHTSo7W9n/LHCEKyNdGW8EwKBgQDYqvSAHLXPYXzr9nfHphIE8gU6csWCYb3W 17 | moLw1yMfRkmoFQW3lpbUhgUQcOGHbfEc05ueDcZPoJ7yjq5Mz+agKLQJ+lfrpa3c 18 | 58LzgdBNM/7XVyGS9qwdVILYzyW47ftzkm27fbiWIih/agN8LykwXfLB7Sp/DsY6 19 | exnXhJh9FwKBgBDs3R0QUDhUrkNhxiNaid9wBFQi4chVZEfRLWkkeXbj5KVLI6sd 20 | P63WCjVxjSxEI30+NnSUTEwjR7llUJHCsmOblquUSAZFd3jsRr/P6gIqb+F3Xp/Z 21 | sA7h7Rk0uBiD2xflZDwcgUsZQtNJRn6Ex7epdshzS5HpxuEm26yJGBwvAoGAVqhp 22 | iiPxg7RWJno5hhyh8GBsPuhQnslBCGhcHr+tSIDwTM27r6YHRZEkzWKYkXYjjYgF 23 | Ib4Ypxhy+kie5hHxB/W/rI34REPLuiLoEkTFb1twfehXgVQvmruGafjP+ZsViy/x 24 | u5+ZCFM5J1zbb/vc2X957J5S9OaP2iwKNezOoJkCfw794jtokJIyIoUZ6rdSJfT2 25 | 8aB8enBFvuLazIhU0aaO91h1mRwsdauNBz2EiLIjznZv+2wA1IIdJuRJnY/guc5D 26 | PM2F/4zjsqEv30U9SU8Y1j/XRKw+4S7yPrR1a7lotx73wIcNyZOXj0i2oamGiUTo 27 | WsybAQjlSEqnNm/AFjA= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /demo/m6/re6stnet.conf: -------------------------------------------------------------------------------- 1 | log m6/ 2 | run m6/run 3 | state m6/ 4 | ip fc42:6::1 5 | pp 1194 tcp 6 | pp 1194 udp 7 | pp 1195 udp6 8 | ca ca.crt 9 | cert m6/cert.crt 10 | key m6/cert.key 11 | disable-proto none 12 | # TODO: Run a DHCPv4 client on machine9. Unfortunately, isc-dhcp-client 4.2.4 13 | # fails with "Bind socket to interface: No such device" 14 | daemon "exec dnsmasq -d8 - -i $re6stnet_iface -F 192.168.42.2,192.168.42.254,255.255.255.0,infinite -F ${re6stnet_subnet%/*},ra-only,${re6stnet_subnet#*/},1d -O option:router,192.168.42.1 -l m6/dnsmasq.leases" 15 | -------------------------------------------------------------------------------- /demo/m7/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfm4XUr8MTVzJA 3 | f5VFArRcvZcGguE1BFvu6MauoN/XdJ92EhgxElbV43QZXVtUlp67+HqiUrrebzOp 4 | sBJ+MYWSNuM63FguUxZHMpDFfYnTpv/vyJVZ7e2gSKyHYDHAEHElVkcyfVWifmom 5 | ZAR1X7JTey0IK1vnqS+rc4rSRSiuMvqoRHkdzuultNZge+TPkX5pmvmPwXi3fKHS 6 | t/LOu/Eyxf07eTBw0Tsl6eU4du6BsWQ+ynS8EJKzjUnaLMC6ICPs+wS9PBTyJIIm 7 | Z92Qri2s14Y+fsN/wPD9ZiKnjmi6x5v9nZNxfzFunpCEgPL7J4slh7tpNEENOMob 8 | 47gTb+4bAgMBAAECggEAVXAhQescV9wTcGdbdmeuOP7cO4YQHBtLQyakRJKlgGZT 9 | fhbNkGcqyLBLU9CftTYznpeyvfXfRnbshudT3u2PfOeY18dgBNxBTreIu88p2nzJ 10 | AIi1OkCdNXHxuT4LI68pgj0fO63QC330QLzFjjc99GY5gugC+I2ekP72zvxqfGTB 11 | M/Pnz7yAYdRiYGV/OUtg7Odoj16SJIRXmM1GgidHwo0Y7n/WxN2sBmlryo/YE25G 12 | h0ohaD3ia7fIDXRsKmeMo1DYEFwKNPH2RcZyBpUePmJ0KZ/9YvhcINa0rss3pm5R 13 | mpO6KjrtD7wwrE8eNu0i5CW4g4lPoWc2sXUiO7lkcQKBgQDRF357Uw2OpNvi28kq 14 | kmtEKD8NR3XDiWdcaCuA8SGiTLRFoyB5X+2vjoabUh8KrbRIpKRn5i1abTDR7NyF 15 | NO5LfMBWmsQYuZBtGAFpWCGxLWruPSVZBDGr9dUIFN4x7jCk2e4Ef3ewjg3PG4CB 16 | M/2pQRq9ChgYgqWEAHFtNKoGNQKBgQDDag+fKigQJZrotnsCSRkEnnCIvjmDGUhW 17 | B+YaZB7mFUaYrCy36ET8RiFhBHRFeEKt9S79zPJGwWAxE622Vy5DIa+hJsX1eKHH 18 | CV/flZCjwqBP8AoLWXgSN8BZa4hH1Vi4WQw2cgdVXdWfl2JatDXSabJ4/7jv8Gig 19 | 3WC5HCFtDwKBgQCa/qR2vMEu/Uw2ZaBAm5tCQedDa7aDRWbGXD3rblP1YJC9kkfv 20 | UUn7OlbT6lMyckNTGiD5F+qEvq5S3xc082C1untFd6JnhZ7nD8V0Fq2bDkTW56K4 21 | 0uATTb8mJ3nyX1PVz+qdkPPjf9oCratbm3OstKMigMoN2ULikAWE42YqBQKBgQCw 22 | ivliCmv3aoHxDCtFfVSk3587at/6mLTJRImV/i4MH9yPwb0EyUrJv3IYfDWvLV7Q 23 | WloB4U5grgOBUw31Vf3tmFlbdfQSONGvR8Dd9fmeeQ7sKShp6IKZstSL9KJCg3SL 24 | 16c46PvHG+cLL3EkEPsvBV7AAgfKfZ+I9XeUxN1N8QKBgQCMceN/1DHqZqaM4s4y 25 | S3WEqKSN9QE0UBpNHDCee74J3u4T8eDKVNlBs/NjCozdFu1oU/atyaopS+4wMJxz 26 | BqyF1kLbXd+8NPnXX2LQv5G/4m5iGB/Le/YUR1CHCvHB0QaiDn7osdBPO5DcJLs6 27 | XpphqglUPgBRCBZyE2BmG7IwDw== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /demo/m7/re6stnet.conf: -------------------------------------------------------------------------------- 1 | log m7/ 2 | run m7/run 3 | state m7/ 4 | pp 1194 tcp 5 | pp 1194 udp 6 | pp 1195 udp6 7 | ca ca.crt 8 | cert m7/cert.crt 9 | key m7/cert.key 10 | disable-proto none 11 | -------------------------------------------------------------------------------- /demo/m8/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCypXS4+KQ0yuaG 3 | b+xky5gKgs5lT/DhM3QQv78GUGg/4a8eCaKFGxJyOZwryIvVlwQQM2G9NfPSr9ba 4 | wfR/9t7EIcaqPgVtUvzSRE5eTh52kLnXAmCR4XiKO/HxYvZ3mTDz20ONOcmfohCx 5 | McO+S5rrDDUoxd6qSUWTSh6TLs8FbHVRx9zhW08kRjpQJ2A/bOwpHpsUeVOUv1vV 6 | 0bKiO/gFfoZkRAPJOLBtxqpWy8OPLUWDk86yta9lXAnG8OrG9pl1Qqch66ajspcL 7 | KSVraotKPau/lwudWcEkbhuiwKWt1QjeFZDY2QKXeuucXbuumbDPyOsXwsIdvrsW 8 | IqbJAt+lAgMBAAECggEBAKBkz0SX8Q55Mbp4WN0ysmKViOwLdzEqukWjcsYfgUoB 9 | vfGRIvqxEMG/mcjxuSLfAMXu7A0umKiMObKVO4l8xAuCa9VD9Clwvg+nRlmDd/Rq 10 | AoDZ4Ix5zHkIUzSv7qv4vavkLGVrAeyyXJxLGv0K8p+giUX/SejH+pDAlmz9NOAf 11 | PUwcGOS9GB6cLGWeNvtep+5OZ7q/xgM7JHLJZpGy9nzz3HOrv43FU4BmClYeN9jZ 12 | RhaBRZHdhGQYV7qO9heWrhpcwX4gsNHodzFzGA6fLZfrcAWhqPaYqHTTC64S3sBo 13 | rmzNz0E1FAzccr8NBBmiQZRBxoXwCKjAXwT2QDrGt6ECgYEA4G9GlIWdyGqpEqK1 14 | oTpkFwvzt0ATbvzyFWqAUSpiBDQsBGVntEYSB9Grs2uNBfckUGQAPEyu4FGb5qMw 15 | 8/Fftmr3jRP8KjoExGED+zjfsyxSvJ4Yki6Ab5cQZPCRmCZLmumrG0iqiVDot4eF 16 | htiy3QvIrQM9D4khaUCfsIL1MTkCgYEAy8WUbP31/Gvz1YhfpiKrMxf/V/QpDKDP 17 | vkpL8xKn7D01qkocDCggQwxNqhtC7RccJPgTJtXcdT7T+0q85x8c7d5yknBkJoOh 18 | NbM9wvO3lOOpXoNUbTBiEd1MqByoAfSRawrBduT6z/NjQ+7oTItJFSnJ32a8M13k 19 | JVVi6w/PHc0CgYEAp1xzbnfBNF3NXJc3CFbJoqIICOPgWgiH4c58h4oqc2YQWOrh 20 | jX4fHfONrYsLK6KjUstvnYe1dJOGxVN2QsMBE7/qgCqiBT8kpOiPlnxP3IW14O+n 21 | 9QJ2RkCJOixm9eXAxXFwZjUm7qUGFS4bNXZM0ydhaxsaIoapAprtOiw9+YkCgYAG 22 | y/5ZbFcqJkep1bSrC/j96U0BGAnOfAax6DSEVRj4zknd9j7dQPFiiySECgi/c8fi 23 | i8vHvdZuqrvTY/jNFMKYRJU5wTn19uoHqoTi3dI/yyA5INROGBENW35VFS+dcRTw 24 | pxkw6A5dpVaoS23AL90uMYikRP7+D6GuhRyZjptv+QKBgQC+Njwb4fudZ6E57QdT 25 | T2/B7VRtPPSOsGHi8K+6d7talsJjmk3SUPqXuRyQqyE8v8j1XGMhT6akO8bUqu/B 26 | OD19f39fvgWF43jjvku3KpDrdVyu5giahRCzBsUfiBQ3jR2yiruro8HgNidzw8Jp 27 | mSJW4O10KX4nT0Fg6Alg5Srj2Q== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /demo/m8/re6stnet.conf: -------------------------------------------------------------------------------- 1 | log m8/ 2 | run m8/run 3 | state m8/ 4 | ca ca.crt 5 | cert m8/cert.crt 6 | key m8/cert.key 7 | disable-proto none 8 | client fc42:6::1,1195,udp6;fc42:7::1,1195,udp6 9 | -------------------------------------------------------------------------------- /demo/miniupnpd.conf: -------------------------------------------------------------------------------- 1 | clean_ruleset_interval=600 2 | allow 1024-65535 10.0.0.0/8 1024-65535 3 | deny 0-65535 0.0.0.0/0 0-65535 4 | -------------------------------------------------------------------------------- /demo/ping.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Script launched on machines from the demo with the option -p/--ping 4 | It uses Multiping to ping several IPs passed as arguments. 5 | After Re6st is stable, this script logs when it does not get response from a 6 | machine in a csv file stored in the directory of the machine in this format: 7 | time, sequence number, number of non-responding machines, ip of these machines 8 | ''' 9 | import argparse, errno, logging, os, socket, time 10 | from multiping import MultiPing 11 | from threading import Thread, Lock 12 | 13 | PING_INTERVAL = 0.1 14 | PING_TIMEOUT = 4 15 | csv_lock = Lock() 16 | 17 | class MultiPing(MultiPing): 18 | # Patch of Multiping because it stays blocked to ipv4 19 | # emission when we want to ping only ipv6 addresses. 20 | # So we only keep the ipv6 part for the demo. 21 | # Bug issued: https://github.com/romana/multi-ping/issues/22 22 | def _read_all_from_socket(self, timeout): 23 | pkts = [] 24 | if self._ipv6_address_present: 25 | try: 26 | self._sock6.settimeout(timeout) 27 | while True: 28 | p = self._sock6.recv(128) 29 | pkts.append((bytearray(p), time.time())) 30 | self._sock6.settimeout(0) 31 | except socket.timeout: 32 | pass 33 | except socket.error as e: 34 | if e.errno == errno.EWOULDBLOCK: 35 | pass 36 | else: 37 | raise 38 | return pkts 39 | 40 | class Ping(Thread): 41 | 42 | seq = None 43 | seq_lock = Lock() 44 | 45 | def run(self): 46 | mp = MultiPing(addrs) 47 | assert mp._last_used_id is None 48 | cls = self.__class__ 49 | with cls.seq_lock: 50 | mp._last_used_id = cls.seq 51 | mp.send() 52 | seq = cls.seq = mp._last_used_id 53 | responses, no_responses = mp.receive(PING_TIMEOUT) 54 | x = list(responses) 55 | x += no_responses 56 | assert sorted(x) == sorted(addrs), (addrs, responses, no_responses) 57 | with csv_lock: 58 | if no_responses: 59 | my_csv.write('%r,%d,%d,%s\n' % (time.time(), seq, 60 | len(no_responses), ' '.join(no_responses))) 61 | my_csv.flush() 62 | else : 63 | # Update modification/access time of csv 64 | os.utime(csv_path, (time.time(), time.time())) 65 | 66 | for add in no_responses: 67 | print('No response from %s with seq no %d' % (add, seq)) 68 | 69 | parser = argparse.ArgumentParser() 70 | parser.add_argument('n', help = 'my machine name (m1,m2...)') 71 | parser.add_argument('a', nargs = '+', help = 'the list of addresses to ping') 72 | args = parser.parse_args() 73 | my_name = args.n 74 | addrs = args.a 75 | 76 | print('Waiting for every machine to answer ping..') 77 | while True: 78 | mp = MultiPing(addrs) 79 | mp.send() 80 | _, no_responses = mp.receive(0.5) 81 | if not no_responses: 82 | break 83 | # Currently useless because MultiPing does not return earlier if it 84 | # couldn't send any ping (e.g. no route to host). Let's hope it will 85 | # improve. 86 | time.sleep(PING_INTERVAL) 87 | print('Network is stable, starting to ping..') 88 | 89 | csv_path = '{}/ping_logs.csv'.format(my_name) 90 | my_csv = open(csv_path, 'w+') 91 | my_csv.write('%r,%s,%d\n' % (time.time(), 0, 0)) 92 | my_csv.flush() 93 | 94 | while True: 95 | Ping().start() 96 | time.sleep(PING_INTERVAL) 97 | -------------------------------------------------------------------------------- /demo/py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | def __file__(): 3 | import argparse, os, sys 4 | sys.dont_write_bytecode = True 5 | sys.path[0] = os.path.dirname(sys.path[0]) 6 | from argparse import ArgumentParser 7 | _parse_args = ArgumentParser.parse_args 8 | ArgumentParser.parse_args = lambda self: _parse_args(self, sys.argv[2:]) 9 | # We also import to prevent re6st.cli.node from altering the first sys.path 10 | from re6st import registry, tunnel 11 | registry.NETCONF_TEMP = 600 12 | tunnel.NETCONF_CHECK = 60 13 | 14 | if 1: 15 | # Check renewal of certificate. 16 | from random import randrange 17 | registry.RENEW_PERIOD = 60 18 | _createCertificate = registry.RegistryServer.createCertificate 19 | def createCertificate(self, client_prefix, *args): 20 | self.cert_duration = 200 if int(client_prefix, 2) == 7 else \ 21 | randrange(10, 60) ** 2 22 | try: 23 | return _createCertificate(self, client_prefix, *args) 24 | finally: 25 | del self.cert_duration 26 | registry.RegistryServer.createCertificate = createCertificate 27 | 28 | # Simulate the case of a node that does 29 | # not renew its certificate in time. 30 | if sys.argv[2] == "@m7/re6stnet.conf": 31 | registry.RENEW_PERIOD = -30 32 | 33 | return os.path.join(sys.path[0], sys.argv[1]) 34 | __file__ = __file__() 35 | with open(__file__) as f: 36 | exec(compile(f.read(), __file__, 'exec')) 37 | -------------------------------------------------------------------------------- /demo/registry/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzKdaI1gddt8iN 3 | 5MTNMe+bKuVt0Cfq34OUh0y73NQm1HjUMDHiU/5djkBuw+Y8q8OMIwCuN8Pgyp6C 4 | fFivLH3a42ebgxJn2kfU7078gibCEuIPWD9GXQjSgP+7MJVv3q24g4YZTsa6lKJ8 5 | Y1OfP2i2uNR3v7/3BBIsob8pial2kbLgz96L+czDjPOVsV+VH91Mkq6kO7jfNpNo 6 | BHVSXzkQpVupxi86wEFIrkzlAYZRmY0fRcprhovI1iDLpdsJsY/yyPgfXNJk9xhT 7 | lPUoNxyH0EPhGv0KArJZkwlzdDj5RVYYcLTEge374gr1EIMyzex/kN5y1as2tX9l 8 | wXTROc5pAgMBAAECggEAE6L/R4olzojJK3wqcc8KUvh1ov6QkXakBlB6AZEnk4Xw 9 | JFmP7h2EoJ5pw3onLvkoeqCPf4jPKEEs9GJKmhCHTslj3rCUANv0yYrdHmHpe5X0 10 | PvhOHxktUV0gKlUd2+ANLE8GEJoIwARwdq+wR6D8iiJh7yoOETXaBBhKSnQzJbKN 11 | NoIm7SNnRSZhGSd+cECFFVdhkSrUApL5CodhHaEVZchrO8rbUeHoa5QTAe14lkO/ 12 | 7UezQHw6/Ma8YfXSsCTgngoM/gvZRR9iHK6Kj/oL/JWZ939wKeLrdnWSflEHtOyv 13 | 9XYODYcY0PQfGCOh3gWWllhe0SX5jZFsklxTnq9x7QKBgQDtM0kZOEATB29bDuA9 14 | mhvOluQYcSRtNa6scMJbY4kkGY2fbHZQHqse2iL+6DjnYO2oxnWZSySfk1JCSyA3 15 | F845LLnPVNvvoT0bUAGlsWfg4+k57uthi5jg15JSGqmCuO3zfYRPje2O5ZrZJRKe 16 | hhsAPLOkfastXzlpOBQkaLeAbwKBgQDBXQDbYK/EenGyWK2ULeAMpcUmYNaaaWFI 17 | UM5I8QDQHTK2x3LCQDaNwuK+W/7z/GKJ5G1hMqYDaQY7yC+ngpCp0TDNVgnfoRja 18 | VY8hwWrWVGettPpozXWRKiZRWZfILgIsv49//lW0Xvw+GU/4pSmnFj6o2ZaY8bdi 19 | 2NuumoJapwKBgGqfOgWHHm4vUmPZDP1Dz6oOc5t1CE266riCuyq/VD8Q6XM3Gvuq 20 | vXrRzRdOJX4EOPA7vVLZzy2X2EsKYAHDxqQ2sZM77t0JWmFzljn3w9z4Nbcf6Vhg 21 | mqi+3fvgFkA3hmaEDjyAbL9mADQJkRQG7g4uOwX+ozpy6micl5lCJPIrAoGAAKYr 22 | RpFAhcxTbWHW01SEGAbGbqaMkeAgr3l199C3S/uNHAf3XqeQh1FMKY9tf6wtOIFH 23 | zLe6zvAfUTwOzOUnTyqgm0/aoKGNz83RuS9JCIcoAfFFlex6pI4bqtI+LDHbWAMc 24 | nDViXESlXCABoLgNN75fX3m7g6/sCazor+Fc5qkCgYEAjWm1mwj+qaL31/nLZ2eE 25 | PbV6paORRRvKDA2I7qb/FGEQmselhdI/2V5felyuJ1yNfFEwpY9A0n93Bj77DmjM 26 | SVALmLZgnks0OZ6ZXZ5SpO7P48k5m00+ikec/Eojfy0TrxeCMxk+kC1qv7g0lpMP 27 | n6LSHs8XW5znCNoS86UTVcc= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /demo/registry/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCq0nQcrqY5WMJs 3 | PjhHKh5GfZDX+DRWY8MTqI3yXD6AvqXpSvyEcQz52Q+Zy4MXxUk80HywZ0M8zM3s 4 | c1pj49rsd6dp4Y0+f0qLeNMyWunCcnC4N/4PZzmaRZYQxKcqhfdijSLmNv6+IDGY 5 | g4kWTXsmOdpZ1lU43oRpmIs6QVNCqEklVUk21CebQO5eG26orW9Z7/ZupXyhf8tQ 6 | u03yGxyg0WDpOp54ThHScQ5Lz30oOtahfVBzNxMjzme4Ku94DznLefXHC9L/PrRD 7 | RrYOPV9lvx5cdjjHflyR1tp5sr6FpQ6vWdW2r7rqcJSNmzoTiC99PfgaUyUrR1sl 8 | hOkr+NPPAgMBAAECggEAIhh5lpQBl6awv9w3bXTpsBY2B/WBtNCayly1YG+rBhWH 9 | liYieuj5txDtPTxSwTbepGXUzXz+/a6/V3e6zf9X3iUP5GFhyNyta2NFfGKLEAW5 10 | m/uJGVf4NmO3TUeDOkidFS2ojpzhgd95JsDyZuI2N24KuC3AuuYnNdB90bXiQYZz 11 | /ycW/J6zj6BIC405Y8cEAOsAX+G15UQtoFdUw4+zZSTiQpYT8snbqTEVBm0W/sdA 12 | m3k3IXlkH3ngpAUS76s+4cb6K1vok+SQzHjdyrpTOihON3i5xmUtWU6XWIgeVNsA 13 | J7HVLnpmZYTUAYbZlBO+BmHN0fKc9halCpzSTArokQKBgQDTN/Yt4lLV0Xjj9239 14 | DqhFWFRJzpoISmSVvXGXTGtI4ZCehAjul+gAhEcIWiwE+qt9C49fmOumvBmhTtvV 15 | QSL+/j71SagVFBXl4JJ9rv6fxYEjctaSsXU7RggSzWQwOwG3R0I0eT++jin7fKmz 16 | zhMf0qKW0QBpCv8ruz7Ql+aaZwKBgQDPCfFLcJM+DgvCstIPoeEorUJ8hgO1fY+E 17 | oSUEV/u9z83VkKc8WvO/jxWHX1QGawGOGSv8lNlHAjvY8k6iMcqx4w3fqekifp6M 18 | LaK9GzJXgIwZBCSB1uINlEwsk1Nzewx0JFICu8W53Kcq28YJffhgWkYgpn0+VWdW 19 | tRgReh3qWQKBgQDEI4Ft1m9Jsv1p5Ei057mGu6F58GrFLB//lS44a3Rs91nOeZm5 20 | xkJgyHUV4nZiIUzCD8wR3/VknePHEW2+uIAA/qQEL2PwPviewU4HfEtinotvGaL8 21 | EeJlKoEwxJ4203Sid3+ZLsbs8yM1L+x+1eyYbochOfABm36dY4HPPl+cHQKBgFgy 22 | NhYa+L6AoUXpg06heRco55/APyg7Or4XH3vANz9enKKTooo1fIxn2ZX3aAXr45ey 23 | 5WgNyJlVPEVow1KYv24T+T8EVIobQlMYxbClO0bHPecSOrMM5PM/70s132DcVCvN 24 | pRoA/3PzqUq6oJw+i2YQTrtt8QByxTjMoFohmUjRAoGBAJlDz4JCXGmzLb/e3r3b 25 | NM3C22jtNYmjOklXmBF4zuUGnTsMjJCrgJ4FU7MuOi/itf0XvDA0h3B/866j230F 26 | 1mhgAHB8b2djg3PF1moQcEU1+k7QzOL707nnIPQpYmWjrnD0gt4jtDm1yfYBp93S 27 | Ea43JF1o8zYAdtiEy3FEDARn 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /demo/registry/community.conf: -------------------------------------------------------------------------------- 1 | # Community example config 2 | 000 * 3 | 001 @AS AU 4 | 010 FR DE IT 5 | -------------------------------------------------------------------------------- /demo/registry/re6st-registry.conf: -------------------------------------------------------------------------------- 1 | ca ca.crt 2 | key registry/ca.key 3 | dh dh2048.pem 4 | logfile registry/registry.log 5 | run registry/run 6 | hello 4 7 | client-count 2 8 | tunnel-refresh 100 9 | ipv4 10.42.0.0/16 8 10 | prefix-length 13 11 | community registry/community.conf 12 | -------------------------------------------------------------------------------- /demo/registry/re6stnet.conf: -------------------------------------------------------------------------------- 1 | log registry/ 2 | run registry/run 3 | state registry/ 4 | dh dh2048.pem 5 | ca ca.crt 6 | cert registry/cert.crt 7 | key registry/cert.key 8 | gateway 9 | disable-proto none 10 | -------------------------------------------------------------------------------- /demo/registry2/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDtUCjUUvdTyyYr 3 | jrO2DcXJSHjxzp62Eu7OnhhReZlSXjRMIAJE+GR8hidQJ/44uM+g2EkiF19eRNcS 4 | vFVu6CJi45DavDrYxnk7YRmsY0us4uqFzdlEqGuTCUJEJsCkKvoxSOm3RYjY8kFp 5 | fYaTvcWN04GzuUVxYHKt4GQqFCWyiE/iZYagNADDnc7zJTBNLtwyYFunjZxN4Oe4 6 | JksPFqyVsVyUNIyTH/zBIhCQyjrZ4MjWAesuQFc4jZgJ9WYOPN5E5ElghbTu1bKx 7 | y+pdWg1K54DahtliXUrp+ZJVl32vbqJ1BwSqqh6lLK/sDwVqiQ287pn+Yozoaemf 8 | RsidW6mPAgMBAAECggEAchebyZipt+tvSnmtBNXSRVdGblq8CyVHEqcHYgRoJiWM 9 | Vxz8elRBW2zT8cGIg5S2ncnePzmlbBkEnclV+aA5B/oIZmEgmZ+yIU4pnauPcmsV 10 | /YZd8phWP7av2TwYWdUfvBol4yrXBBZURHdJADa4h9sr/FAShtOrztSW03QkO5RI 11 | VRAN3CLNRk/dunJCc43D8/4njYQiS4owRvV7C5GTVfvEOeOAizvzjtfM1cTiRClp 12 | phwa5W1sBs2tTpQJK0fnRwxDTMM2+p6F4eFogy5ajCXHju69Az/8bA7P+YTCWxC4 13 | ANIJxs1RPL9Q/SyhGgS2BqljQq5CrjDigZ+byKaswQKBgQD+YR4e4vRvSjq4AL0S 14 | jMF1navzbPaHGy4qJdgIjkZn2oqjs81pdUKYP7RKIgqHNugP3EclaSuUoNf1dxIf 15 | /HkcP/OdRC76zvyoe6JZicLyUHkVVSiXEYOllVZ7aQKa3XMKEWksv4GywgLNdbI9 16 | NvobwKpt7xvXY43tvq3oo6BtdwKBgQDu0zUd3a14gV9qtljue35m7JEVbB9KXsOL 17 | WaT8MqWSounuCFjh7rx68K4xLmsB2tdUpyI1+pKvQOOWtZYnbYCoYrSYBH7/CpSw 18 | YRFVgoNEO4aJgywMJ7rfDLizmjiveOBNmEgvh+lGNBVUqYlJwZE4SY4ohENAxsWu 19 | 9nPIBWlKqQKBgQCz98W1mF/S9LNCRtN9cjPUoG5s1CQ+Rc6NZyTGONI623TGiIjF 20 | GX659Cf7YsYMD55yyidTomqAxqLDOTCLjLWqdNxH4VtTemlqUb30lvBjOufPXeZP 21 | qsZ2uYbr3MlJA29GKjc8v2hlLbmJ3sDxahnc1Jw/FrGd2wMotoSXWFxB1QKBgEHL 22 | cBu9QZgsVCQq/k8dOJKUY9f6BJjRiJ+wX7KXJWRDe5z3Mb10rvpTqjmkZxiIuL/6 23 | l4M1eAnOH6Uae7Z7BXHeV5B11KLgwFvjMgpTvWQj3gmuWIk0vNfMQmpAd5NoAqt9 24 | 440srUiI+sNrPYZTTHWsVfy1i22iFT4BaZ5WV06ZAoGBAMF67k6WkjmjG86EqrY4 25 | Fc48TczNmgUW9NPArRSYcqlgUNA0QK/hNQz72lWx5XbjxeWYBky7csrgSprNDy3M 26 | WXl56tq6Oo1XCQCFFxmyZ89s6O084I7TBfNEaCNrBVX4qBTRKH9DNyYemu9fGAAK 27 | 1cBQp/DJVIBpnOu1ga2YuA58 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /demo/registry2/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAvFkywZvNyAZFoUou+e5e5fFmBi1IpfouItb+4d4OjfLfqsz9 3 | oOsEhlv+t08gs68n0UvJtk2XkgUZMYVQsEZvaMUC20R0wi9LLCCurpNldqx7eZeD 4 | 0auo8FjWUS3FRn/ceU15dI8oFy1SYE2gfcui7tTDa2rcFn7pR9AIfv0oGIc8TIZk 5 | BWz9C5oQ8trl9SXj2HShmk11Hxq/rEDod+iYZL4E6lRnrPpmm7CpiEm7QVSyMYtf 6 | 4EXhsSnB10nXJ2IaazhLjyh839UdfGuk3JzKraPp08ZaLNvV38DpNwPf9HXnGzHp 7 | fYAZz2adykiyqddvRChSGrwUmhx8Edu3bZJyMQIDAQABAoIBAAgA/mAt38vNQG4/ 8 | OcnTMGMzI/PFPt4WyJkga8prZlDv7kNS3MBW3GVdSXC+wxP/sIB7aQH/QB6rasuo 9 | 76neBGP+Y0WwHgoWempQpTuz9c7aO5EKOEbTtaUpEUp32HdLfbIF8dBbOQfrHCIN 10 | inFuEul1TyykaN/n2r/7fjDvTyHjoqaHrofUqC/8/+jaPASflRpNP+vKWM8YbD2E 11 | PGiBhtobk4AOfdCM9afRpisAQI4C0RsGCHZL54vF7LbyPtAjgau+trwggj9ErILn 12 | U+DjbhnHmB+GeMd7+56YjZccUTjtamHVBMrVdC0qNy+Na0x7rmfGGiN5S+qurP7p 13 | FdROQvECgYEA47RQQWoRUMUCrcO2ZfsjeDu1r69qdBhzzjU7P9FDfsjxVWLl+cVT 14 | uGxHxfDvh0J3NnoEhXsf7cVFoDZd8vQLIERZy3PbcgyEAj4TkSS+oNhQ/R7pTg5X 15 | 3ccamQmy2vqXGlwm3z5ZW5jh0vlJNWfvXP024UgpOHmg485ti6bTtr0CgYEA08Dm 16 | 5Mnx3sXifwzAOdqb9CApF+CRI6O5S+u3zwF4Bp1Spo31YZVnDVgCxBgv3k0OSFkE 17 | 7JCvY6KWOrwiPSUQCBnH74ePvheHiz39vRvZefgZHYFsyKc5UdmTc8gHLR9/0SK9 18 | lcvcGitEsOp/Ft3Ob4j82NL+R5WgqyUuT1BXqoUCgYA8eeFu3jrnFswVVouVHlRX 19 | VJR2qsI9P1lShTTNSEkC3V+ra+7knZIUwz10xv73D6IV5+ZXhVH/lotdd9MP+d0S 20 | ntSygnzgF47gAZi0zeuLUKiB8bnJL2oKzxyzVK4aFsAXEi6I0EhvWXVw0SCufIJI 21 | UkBHKSE5jKQ8nNDfbFmCiQKBgFuYCUfVgmXWOs6x+UQNJ4BFmQDXBnDgFPqeD3ff 22 | LsfNrT6WERoQwe6nryqbO7lwo0jwGI0rWHJEla76SeUg7vpSDIWzoZ2cF+lG+0ad 23 | kUiM5HA6149DpiudbYabc181TNhtqovtDlvTc3cDN83wS6c7HgO74HmeY00kXA+6 24 | tPDxAoGBANtrY9fLqECgv3XRoQhE0xp4V8fr8iRG2QwKcZ0F1O0jfDMH/pfbl7IP 25 | BjQsmuM6gORKDYdZHZ8bmpa5kzJCM+Oxf4y0bzgF9mE0gUd3LU9W2qLqidCBAveW 26 | n4FwYaNHHUDEGNdJMPB8EB9n5FC3TUZIobz16/jn7DZghdag9e3c 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /demo/registry2/re6st-registry.conf: -------------------------------------------------------------------------------- 1 | ca ca2.crt 2 | key registry2/ca.key 3 | dh dh2048.pem 4 | logfile registry2/registry2.log 5 | run registry2/run 6 | hello 4 7 | client-count 2 8 | tunnel-refresh 100 9 | ipv4 10.42.0.0/16 8 10 | -------------------------------------------------------------------------------- /demo/registry2/re6stnet.conf: -------------------------------------------------------------------------------- 1 | log registry2/ 2 | run registry2/run 3 | state registry2/ 4 | dh dh2048.pem 5 | ca ca2.crt 6 | cert registry2/cert.crt 7 | key registry2/cert.key 8 | gateway 9 | -------------------------------------------------------------------------------- /demo/test_hmac.py: -------------------------------------------------------------------------------- 1 | from binascii import b2a_hex 2 | import psutil 3 | 4 | BABEL_HMAC = 'babel_hmac0', 'babel_hmac1', 'babel_hmac2' 5 | 6 | def getConfig(db, name): 7 | r = db.execute("SELECT value FROM config WHERE name=?", (name,)).fetchone() 8 | if r: 9 | return b2a_hex(*r).decode() 10 | 11 | def killRe6st(node): 12 | for p in psutil.Process(node._screen.pid).children(): 13 | if p.cmdline()[-1].startswith('set ./py re6stnet '): 14 | p.kill() 15 | break 16 | 17 | def checkHMAC(db, machines): 18 | hmac = [getConfig(db, k) for k in BABEL_HMAC] 19 | rc = True 20 | for x in psutil.Process().children(True): 21 | if x.name() == 'babeld': 22 | sign = accept = None 23 | args = x.cmdline() 24 | for x in args: 25 | if x.endswith('/babeld.log'): 26 | if x[:-11] not in machines: 27 | break 28 | elif x.startswith('key '): 29 | x = x.split() 30 | if 'sign' in x: 31 | sign = x[-1] 32 | elif 'accept' in x: 33 | accept = x[-1] 34 | else: 35 | i = 0 if hmac[0] else 1 36 | if hmac[i] != sign or hmac[i+1] != accept: 37 | print('HMAC config wrong for in %s' % args) 38 | rc = False 39 | if rc: 40 | print('All nodes use Babel with the correct HMAC configuration') 41 | else: 42 | print('Expected config: %s' % dict(zip(BABEL_HMAC, hmac))) 43 | return rc 44 | -------------------------------------------------------------------------------- /docs/re6st-conf.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | re6st-conf 3 | ============ 4 | 5 | ------------------------------- 6 | Configuration tool for re6stnet 7 | ------------------------------- 8 | 9 | :Author: Nexedi 10 | :Manual section: 1 11 | 12 | SYNOPSIS 13 | ======== 14 | 15 | ``re6st-conf`` ``--registry`` `url` [`options`...] 16 | 17 | DESCRIPTION 18 | =========== 19 | 20 | re6st-conf is a tool generating the files necessary to add a node to a re6st 21 | network. It connects to the re6st registry to get a signed certificate with 22 | an allocated IPv6 subnet. 23 | 24 | USAGE 25 | ===== 26 | 27 | Use ``re6st-conf --help`` to get the complete list of options. 28 | 29 | SEE ALSO 30 | ======== 31 | 32 | ``re6stnet``\ (8) 33 | -------------------------------------------------------------------------------- /docs/re6st-registry.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | re6st-registry 3 | ================ 4 | 5 | -------------------------------- 6 | Server application for re6snet 7 | -------------------------------- 8 | 9 | :Author: Nexedi 10 | :Manual section: 1 11 | 12 | SYNOPSIS 13 | ======== 14 | 15 | ``re6st-registry`` ``--db`` `db-path` ``--ca`` `ca-path` ``--key`` `key-path` 16 | ``--mailhost`` `mailhost` ``--private`` `re6st-ip` [`options`...] 17 | 18 | DESCRIPTION 19 | =========== 20 | 21 | re6st-registry is a server for the re6st network. Its role is to deliver 22 | certificates to new nodes, and to bootstrap nodes that don't know any address 23 | of other nodes. 24 | 25 | The network can host only one registry, which should run on a re6st node. 26 | 27 | USAGE 28 | ===== 29 | 30 | Use ``re6st-registry --help`` to get the complete list of options. 31 | 32 | The network prefix can be changed by renewing the Certificate authority with 33 | a different serial number, but keeping the existing key. Then all nodes of the 34 | network must fetch the new CA with `re6st-conf` and restart `re6stnet`. 35 | 36 | SEE ALSO 37 | ======== 38 | 39 | ``re6stnet``\ (8) 40 | -------------------------------------------------------------------------------- /docs/re6stnet.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | re6stnet 3 | ========== 4 | 5 | --------------------------------------------- 6 | Resilient, Scalable, IPv6 Network application 7 | --------------------------------------------- 8 | 9 | :Author: Nexedi 10 | :Manual section: 8 11 | 12 | SYNOPSIS 13 | ======== 14 | 15 | ``re6stnet`` ``--registry`` `registry-url` ``--dh`` `dh-path` 16 | ``--ca`` `ca-path` ``--cert`` `cert-path` ``--key`` `key-path` 17 | [`options`...] [``--`` [`openvpn-options`...]] 18 | 19 | DESCRIPTION 20 | =========== 21 | 22 | `re6stnet` runs a node of a re6st network. It establishes connections 23 | with other nodes by creating OpenVPN tunnels and uses Babel for routing. 24 | 25 | USAGE 26 | ===== 27 | 28 | Use ``re6stnet --help`` to get the complete list of options. 29 | 30 | If you already have IPv6 connectivity by autoconfiguration and still want to 31 | use it for communications that are unrelated to this network, then: 32 | 33 | - your kernel must support source address based routing (because the re6st 34 | default route will need to contain a source address in order to forward re6st 35 | packets through the re6st network). 36 | - you must set ``net.ipv6.conf..accept_ra`` sysctl to value 2 and 37 | trigger SLAAC with ``rdisc6 `` to restore the default route if the 38 | kernel removed while enabling forwarding. 39 | 40 | Following environment variables are available for processes started with 41 | ``--up`` or ``--daemon``: 42 | 43 | re6stnet_iface 44 | value of ``--main-interface`` option 45 | re6stnet_ip 46 | IPv6 set on main interface 47 | re6stnet_subnet 48 | your subnet, written in CIDR notation 49 | re6stnet_network 50 | the re6st network you belong to, written in CIDR notation 51 | 52 | Setting up a UPnP server 53 | ------------------------ 54 | 55 | In order to share the connectivity with others, it is necessary for re6stnet 56 | port (as specified by ``--pp`` option and default to `1194`) to be reachable 57 | from outside. If the node has a public IPv4 address, then this is not 58 | necessary, otherwise a UPnP server should be set up on the gateway. 59 | 60 | You can check the connectivity with other re6st nodes of the network with 61 | ``netstat -tn | grep 1194``. 62 | 63 | Sample configuration file for `miniupnpd`:: 64 | 65 | ext_ifname=ppp0 66 | listening_ip=eth0 67 | clean_ruleset_interval=600 68 | allow 1024-65535 192.168.0.0/24 1024-65535 69 | deny 0-65535 0.0.0.0/0 0-65535 70 | 71 | After restarting ``re6stnet`` service on the clients within the LAN, you can 72 | either check ``/var/log/re6stnet.log`` or the ``iptables`` ``NAT`` table to 73 | see that the port ``1194`` is properly redirected, for example:: 74 | 75 | # iptables -t nat -L -nv 76 | [...] 77 | Chain MINIUPNPD (1 references) 78 | target prot opt source destination 79 | DNAT tcp -- anywhere anywhere tcp dpt:37194 to:192.168.0.5:1194 80 | DNAT tcp -- anywhere anywhere tcp dpt:34310 to:192.168.0.233:1194 81 | 82 | Starting re6st automatically 83 | ---------------------------- 84 | 85 | If the `/etc/re6stnet/re6stnet.conf` configuration file exists, `re6stnet` is 86 | automatically started as a system daemon, by ``systemd``\ (1). Debian package 87 | also provides SysV init scripts. 88 | 89 | Correct usage of NetworkManager 90 | ------------------------------- 91 | 92 | It is required to configure properly every connection defined in NetworkManager 93 | because default settings are wrong and conflict with re6st. If your kernel has 94 | no source address based routing, then disable IPv6, else enable the following 95 | options in the [ipv6] section:: 96 | 97 | ignore-auto-routes=true 98 | never-default=true 99 | 100 | In applets, these options are usually named: 101 | 102 | - Ignore automatically obtained routes (KDE & Gnome) 103 | - Use only for resources on this connection (KDE) 104 | - Use this connection only for resources on its network (Gnome) 105 | 106 | 107 | HOW TO 108 | ====== 109 | 110 | Joining an existing network 111 | --------------------------- 112 | 113 | Once you know the registry URL of an existing network, use `re6st-conf` to get 114 | a certificate:: 115 | 116 | re6st-conf --registry http://re6st.example.com/ 117 | 118 | Use `-r` option to add public information to your certificate. 119 | A token will be sent to the email you specify, in order to confirm your 120 | subscription. 121 | Files will be created by default in current directory and they are all 122 | required for `re6stnet`:: 123 | 124 | re6stnet --dh dh2048.pem --ca ca.crt --cert cert.crt --key cert.key \ 125 | --registry http://re6st.example.com/ 126 | 127 | Setting a new network 128 | --------------------- 129 | 130 | First you need to know the prefix of your network: let's suppose it is 131 | `2001:db8:42::/48`. From it, you computes the serial number of the Certificate 132 | authority (CA) that will be used by the registry node to sign delivered 133 | certificates, as follows: translate the significant part to hexadecimal 134 | (ie. 20010db80042) add a **1** as the most significant digit:: 135 | 136 | openssl req -nodes -new -x509 -key ca.key -set_serial 0x120010db80042 \ 137 | -days 365 -out ca.crt 138 | 139 | (see ``re6st-registry --help`` for examples to create key/dh files) 140 | 141 | The CA email will be used as sender for mails containing tokens. 142 | The registry can now be started:: 143 | 144 | re6st-registry --ca ca.crt --key ca.key --mailhost smtp.example.com 145 | 146 | The registry uses the builtin HTTP server of Python. For security, it should be 147 | behind a proxy like Apache. 148 | 149 | The first registered node should be always up because its presence is used by 150 | all other nodes to garantee they are connected to the network. The registry 151 | also emits UDP packets that are forwarded via a localhost re6st node, and it is 152 | recommended that this is the first one:: 153 | 154 | re6st-conf --registry http://localhost/ 155 | 156 | If `re6st-conf` is run in the directory containing CA files, ca.crt will be 157 | overridden without harm. See previous section for more information to create 158 | a node. 159 | 160 | For bootstrapping, you may have to explicitly set an IP in the configuration 161 | of the first node, via the ``--ip`` option. Otherwise, additional nodes won't 162 | be able to connect to it. 163 | 164 | You can use communities to group prefixes in different subprefixes based on 165 | their location. Each line in the community configuration is a mapping 166 | from a subprefix (in binary) to a list of locations. Each location is either 167 | "*" (default assignment), a country (Alpha-2 code), or a continent (Alpha-2 168 | code) preceded by "@". See demo/registry/community.conf for an example. 169 | 170 | TROUBLESHOOTING 171 | =============== 172 | 173 | When many nodes are saturated or behind unconfigurated NAT, it may take 174 | some time to bootstrap. However, if you really think something goes wrong, 175 | you should first enable OpenVPN logs and increase verbosity: 176 | see commented directives in configuration generated by `re6st-conf`. 177 | 178 | Besides of firewall configuration described below, other security components 179 | may also break re6st. For example, default SELinux configuration on Fedora 180 | prevents execution of OpenVPN server processes. 181 | 182 | Misconfigured firewall 183 | ---------------------- 184 | 185 | A common failure is caused by a misconfigured firewall. The following ports 186 | need to be opened: 187 | 188 | - **TCP/UDP ports 1194** (Specified by ``--pp`` option and default on `1194`): 189 | re6st launches several OpenVPN processes. Those in client mode may connect 190 | to any TCP/UDP port in IPv4. Server processes only listen to ports specified 191 | by ``--pp`` option. 192 | 193 | - **UDP port 326**: used by re6st nodes to communicate. It must be open on all 194 | re6st IPv6. 195 | 196 | - **UDP port 6696 on link-local IPv6 (fe80::/10)** on all interfaces managed 197 | by Babel: OpenVPN always aborts due to inactivity timeout when Babel paquets 198 | are filtered. 199 | 200 | - **ICMPv6 neighbor-solicitation/neighbor-advertisement**. Moreover, the 201 | following ICMPv6 packets should also generally be allowed in an IPv6 202 | network: `destination-unreachable`, `packet-too-big`, `time-exceeded`, 203 | `parameter-problem`. 204 | 205 | - **UDP source port 1900**: required for UPnP server (see `Setting up a UPnP 206 | server`_ for further explanations). 207 | 208 | You can refer to `examples/iptables-rules.sh` for an example of iptables and 209 | ip6tables rules. 210 | 211 | SEE ALSO 212 | ======== 213 | 214 | ``re6st-conf``\ (1), ``re6st-registry``\ (1), ``babeld``\ (8), ``openvpn``\ (8), 215 | ``rdisc6``\ (8), ``req``\ (1) 216 | -------------------------------------------------------------------------------- /draft/re6st-cn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | import sqlite3, sys 3 | if 're6st' not in sys.modules: 4 | import os; sys.path[0] = os.path.dirname(sys.path[0]) 5 | from re6st import utils, x509 6 | from OpenSSL import crypto 7 | 8 | with open("/etc/re6stnet/ca.crt", "rb") as f: 9 | ca = crypto.load_certificate(crypto.FILETYPE_PEM, f.read()) 10 | network = x509.networkFromCa(ca) 11 | 12 | db = sqlite3.connect("/var/lib/re6stnet/registry.db") 13 | for x in sys.argv[1:]: 14 | try: 15 | a, b = x.split('/') 16 | except ValueError: 17 | prefix = x 18 | else: 19 | b = int(b) 20 | try: 21 | prefix = bin(int(a))[2:].zfill(b) 22 | except ValueError: 23 | a = utils.binFromIp(a) 24 | assert a.startswith(network) 25 | prefix = a[len(network):b] 26 | a = db.execute("select * from cert where prefix=?", (prefix,)).fetchone() 27 | b = network + prefix 28 | b = '%s/%s' % (utils.ipFromBin(b), len(b)) 29 | if a: 30 | subject = crypto.load_certificate(crypto.FILETYPE_PEM, a[2]).get_subject() 31 | print "%s\t%s\t%s" % (b, a[1], ''.join('/%s=%s' % x for x in subject.get_components())) 32 | else: 33 | print "%s\t-" % b 34 | db.close() 35 | -------------------------------------------------------------------------------- /draft/re6st-geo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | # -*- coding: utf-8 -*- 3 | import argparse, httplib, select, socket, sqlite3, struct, sys, time, traceback 4 | import xml.etree.cElementTree as ET 5 | from collections import defaultdict 6 | if 're6st' not in sys.modules: 7 | import os; sys.path[0] = os.path.dirname(sys.path[0]) 8 | from re6st import routing, tunnel, utils 9 | 10 | from re6st.registry import RegistryServer 11 | 12 | 13 | @apply 14 | class proxy(object): 15 | 16 | def __init__(self): 17 | self.sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 18 | recv = RegistryServer.recv.__func__ 19 | sendto = RegistryServer.sendto.__func__ 20 | 21 | 22 | def cmd_update(db, config): 23 | s = proxy.sock, 24 | q = db.execute 25 | ip, n = config.network.split('/') 26 | network = utils.binFromIp(ip)[:int(n)] 27 | p = dict(q("SELECT prefix, mode FROM ip")) 28 | peers = set() 29 | now = int(time.time()) 30 | for prefix in routing.iterRoutes(config.control_socket, network): 31 | if prefix in p: 32 | q("UPDATE ip SET last=? WHERE prefix=?", (now, prefix)) 33 | if not p[prefix]: 34 | continue 35 | else: 36 | q("INSERT INTO ip (prefix, last) VALUES (?, ?)", (prefix, now)) 37 | peers.add(prefix) 38 | for retry in xrange(3): 39 | if not peers: 40 | break 41 | p = peers.copy() 42 | while True: 43 | r, w, _ = select.select(s, s if p else (), (), 1) 44 | if r: 45 | prefix, address = proxy.recv(1) 46 | peers.discard(prefix) 47 | ip = None 48 | for ip, _, _ in utils.parse_address(address): 49 | try: 50 | if utils.binFromIp(ip): #.startswith(network): 51 | ip = None 52 | except socket.error: 53 | try: 54 | a = socket.inet_aton(ip) 55 | except socket.error: 56 | pass 57 | else: 58 | if bin(*struct.unpack('>I', a))[2:].startswith(( 59 | '10100000', '11111110', 60 | '101011000001', 61 | '1100000010101000')): 62 | ip = None 63 | if ip: 64 | q("UPDATE ip SET ip=? WHERE prefix=?", 65 | (ip, prefix)) 66 | if w: 67 | proxy.sendto(p.pop(), 1) 68 | elif not r: 69 | break 70 | db.commit() 71 | 72 | def cmd_ip(db, config): 73 | q = db.execute 74 | for ip in config.ip: 75 | cn, ip = ip.split('=') 76 | prefix = utils.binFromSubnet(cn) 77 | try: 78 | q("UPDATE ip SET mode=? WHERE prefix=?", 79 | (('manual', 'auto').index(ip), prefix)) 80 | except ValueError: 81 | q("UPDATE ip SET mode=0, ip=? WHERE prefix=?", 82 | (ip or None, prefix)) 83 | db.commit() 84 | 85 | 86 | def geo_geolite2(): 87 | from geoip2 import database, errors 88 | country = database.Reader(os.environ['GEOIP2_MMDB']).country 89 | def geo(ip): 90 | x = country(ip).country 91 | return None, None, '%s, %s' % (x.iso_code, x.name) 92 | return geo 93 | 94 | 95 | def geo_freegeoip(): 96 | import json 97 | host = 'freegeoip.net' 98 | c = httplib.HTTPConnection(host, httplib.HTTP_PORT, timeout=60) 99 | p = sys.stdout.write 100 | def geo(ip): 101 | for ip in {x[-1][0] for x in socket.getaddrinfo(ip, 0, 0, 102 | socket.SOCK_STREAM)}: 103 | p("Querying %s for %s ..." % (host, ip)) 104 | c.putrequest('GET', '/json/' + ip, skip_accept_encoding=1) 105 | c.endheaders() 106 | r = c.getresponse() 107 | status = r.status 108 | r = r.read() 109 | if status == httplib.OK: 110 | r = json.loads(r) 111 | title = None 112 | country_code = r.get("country_code") or "??" 113 | for k in "city", "region_name": 114 | title = r[k] 115 | if title: 116 | title += ", %s" % country_code 117 | break 118 | else: 119 | title = r["country_name"] or country_code 120 | lat = r['latitude'] 121 | long = r['longitude'] 122 | p(" %s,%s,%s\n" % (lat, long, title.encode("utf-8"))) 123 | return lat, long, title 124 | p(" %s %s\n" % (status, httplib.responses.get(status, "???"))) 125 | return geo 126 | 127 | 128 | def cmd_geoip(db, config): 129 | q = db.execute 130 | mode_dict = {} 131 | cache_dict = {} 132 | mute = False 133 | for ip, mode, latitude in q( 134 | "SELECT distinct ip.ip, loc.mode, loc.latitude" 135 | " FROM ip left join loc on (ip.ip=loc.ip)" 136 | " WHERE ip.ip is not null" 137 | " AND (loc.mode is null or loc.mode != 'manual')"): 138 | if latitude is None or config.all: 139 | insert = mode is None 140 | try: 141 | loc = cache_dict[ip] 142 | except KeyError: 143 | if mode in (None, 'auto'): 144 | mode = 'geolite2' 145 | try: 146 | geo = mode_dict[mode] 147 | except KeyError: 148 | geo = mode_dict[mode] = globals()['geo_' + mode]() 149 | try: 150 | loc = geo(ip) 151 | except Exception, e: 152 | if mute: 153 | traceback.print_exception(type(e), e, None) 154 | else: 155 | traceback.print_exc() 156 | mute = True 157 | loc = None 158 | cache_dict[ip] = loc 159 | if loc: 160 | if insert: 161 | q("INSERT INTO loc (ip) VALUES (?)", (ip,)) 162 | q("UPDATE loc SET latitude=?, longitude=?, title=? WHERE ip=?", 163 | (loc[0], loc[1], loc[2], ip)) 164 | db.commit() 165 | 166 | def kml(db): 167 | d = ET.Element("Document") 168 | loc_dict = defaultdict(list) 169 | t = None 170 | try: 171 | for prefix, latitude, longitude, title, last in db.execute( 172 | "SELECT prefix, latitude, longitude, title, last FROM ip, loc" 173 | " WHERE ip.ip=loc.ip and latitude ORDER BY last DESC"): 174 | if t is None: 175 | t = last - 86400 176 | if last < t: 177 | break 178 | loc_dict[(latitude, longitude, title)].append(prefix) 179 | finally: 180 | db.rollback() 181 | for (latitude, longitude, title), prefix_list in loc_dict.iteritems(): 182 | p = ET.SubElement(d, "Placemark") 183 | ET.SubElement(p, "name").text = "%s (%s)" % (title, len(prefix_list)) 184 | ET.SubElement(p, "description").text = '\n'.join( 185 | "%s/%s" % (int(prefix, 2), len(prefix)) 186 | for prefix in prefix_list) 187 | ET.SubElement(ET.SubElement(p, "Point"), "coordinates") \ 188 | .text = "%s,%s" % (longitude, latitude) 189 | return ('\n' 190 | '\n' 191 | '%s\n' % ET.tostring(d)) 192 | 193 | def cmd_gis(db, config): 194 | import SimpleHTTPServer, SocketServer 195 | class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler): 196 | def do_GET(self): 197 | if self.path != '/': 198 | self.send_error(404) 199 | else: 200 | xml = kml(db) 201 | self.send_response(200) 202 | self.send_header('Content-Length', len(xml)) 203 | self.send_header('Content-type', 'text/xml; charset=utf-8') 204 | self.end_headers() 205 | self.wfile.write(xml) 206 | class TCPServer(SocketServer.TCPServer): 207 | address_family = socket.AF_INET6 208 | allow_reuse_address = True 209 | TCPServer((config.bind6, config.port), Handler).serve_forever() 210 | 211 | 212 | def main(): 213 | parser = argparse.ArgumentParser() 214 | _ = parser.add_argument 215 | _('--db', required=True, 216 | help="Path to SQLite database file collecting all IP geolocalization.") 217 | parsers = parser.add_subparsers(dest='command') 218 | 219 | _ = parsers.add_parser('update', 220 | help="Query all running nodes to fetch their tunnel IP." 221 | " CN marked for manual update with 'ip' subcommand are skipped." 222 | ).add_argument 223 | _('--control-socket', metavar='CTL_SOCK', 224 | default='/var/run/re6stnet/babeld.sock', 225 | help="Socket path to use for communication between re6stnet and babeld" 226 | " (option -R of Babel).") 227 | _('network') 228 | 229 | _ = parsers.add_parser('geoip', 230 | help="Get latitude & longitude information." 231 | " CN marked for manual lookup with 'loc' subcommand are skipped." 232 | ).add_argument 233 | _('-a', '--all', action='store_true', 234 | help="Also update information for nodes with a known location.") 235 | 236 | _ = parsers.add_parser('ip', help='Set IP').add_argument 237 | _('ip', nargs='+', metavar="CN={IP|MODE}", 238 | help="MODE can be one of: manual, auto.") 239 | 240 | _ = parsers.add_parser('loc', help='Set latitude & longitude').add_argument 241 | _('loc', nargs='+', metavar="IP={φ,λ,TITLE|MODE}", 242 | help="MODE can be one of: manual, freegeoip, auto." 243 | " 'auto' defaults to 'freegeoip'") 244 | 245 | _ = parsers.add_parser('gis').add_argument 246 | _('--port', type=int, default=httplib.HTTP_PORT, 247 | help="Port on which the server will listen.") 248 | _('-6', dest='bind6', default='::', 249 | help="Bind server to this IPv6.") 250 | 251 | config = parser.parse_args() 252 | utils.setupLog(False) 253 | 254 | db = sqlite3.connect(config.db) 255 | db.execute("""CREATE TABLE IF NOT EXISTS ip ( 256 | prefix text primary key, 257 | mode integer default 1, 258 | ip text, 259 | last integer)""") 260 | db.execute("""CREATE TABLE IF NOT EXISTS loc ( 261 | ip text primary key, 262 | mode text default 'auto', 263 | latitude real, 264 | longitude real, 265 | title text)""") 266 | globals()['cmd_' + config.command](db, config) 267 | db.close() 268 | 269 | 270 | if __name__ == "__main__": 271 | main() 272 | -------------------------------------------------------------------------------- /examples/iptables-rules.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Example iptables/ip6tables rules on a desktop computer when re6st is only 4 | # used to build an IPv6 overlay network. REJECT for INPUT and DROP everything 5 | # by default: 6 | # 7 | # - Incoming traffic (INPUT): only open ports needed for re6st and also allow 8 | # packets associated with an existing connection (ESTABLISHED, RELATED). 9 | # 10 | # - Forwarding traffic (FORWARD): a re6st node is a router and 11 | # it is crucial that it never drops any packet between two other nodes. 12 | # 13 | # - Outgoing traffic (OUTPUT): allow new/existing connections (NEW, 14 | # ESTABLISHED, RELATED). 15 | # 16 | # WARNING: THIS SCRIPT *MUST NOT* JUST BE COPY-PASTED WITHOUT A BASIC 17 | # UNDERSTANDING OF IPTABLES/IP6TABLES (see iptables(8) and 18 | # iptables-extensions(8) manpages). 19 | 20 | GATEWAY_IP=192.168.0.1 21 | 22 | ## IPv4 23 | iptables -P INPUT DROP 24 | iptables -P OUTPUT DROP 25 | 26 | iptables -A INPUT -i lo -j ACCEPT 27 | iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT 28 | iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 900/min -j ACCEPT 29 | iptables -A INPUT -p icmp --icmp-type echo-reply -m limit --limit 900/min -j ACCEPT 30 | # re6st 31 | iptables -A INPUT -p tcp -m tcp --dport 1194 -j ACCEPT 32 | # UPnP 33 | iptables -A INPUT -p udp -m udp --sport 1900 -s $GATEWAY_IP -j ACCEPT 34 | 35 | # Add custom INPUT rules before 36 | iptables -A INPUT -j REJECT 37 | 38 | iptables -A OUTPUT -o lo -j ACCEPT 39 | iptables -A OUTPUT -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT 40 | 41 | # more rules needed if you set up a private IPv4 network 42 | 43 | ## IPv6 44 | ip6tables -P INPUT DROP 45 | ip6tables -P FORWARD DROP 46 | ip6tables -P OUTPUT DROP 47 | 48 | ip6tables -N RE6ST 49 | ip6tables -A RE6ST -i re6stnet+ -j ACCEPT 50 | # For every --interface option: 51 | ip6tables -A RE6ST -i eth0 -j ACCEPT 52 | 53 | ip6tables -A INPUT -i lo -j ACCEPT 54 | ip6tables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT 55 | ip6tables -A INPUT -p udp -m udp --dport babel --src fe80::/10 -j ACCEPT 56 | # Babel 57 | ip6tables -A INPUT -p udp -m udp --dport 326 -j RE6ST 58 | ip6tables -A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type destination-unreachable -j ACCEPT 59 | ip6tables -A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type packet-too-big -j ACCEPT 60 | ip6tables -A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type time-exceeded -j ACCEPT 61 | ip6tables -A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type parameter-problem -j ACCEPT 62 | ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-request -m limit --limit 900/min -j ACCEPT 63 | ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-reply -m limit --limit 900/min -j ACCEPT 64 | ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbor-solicitation -m hl --hl-eq 255 -j ACCEPT 65 | ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbor-advertisement -m hl --hl-eq 255 -j ACCEPT 66 | 67 | # Add custom INPUT rules before 68 | ip6tables -A INPUT -j REJECT 69 | 70 | ip6tables -A FORWARD -o re6stnet+ -j RE6ST 71 | # Same as in RE6ST chain. 72 | ip6tables -A FORWARD -o eth0 -j RE6ST 73 | 74 | ip6tables -A OUTPUT -o lo -j ACCEPT 75 | ip6tables -A OUTPUT -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT 76 | ip6tables -A OUTPUT -p ipv6-icmp -m icmp6 --icmpv6-type destination-unreachable -j ACCEPT 77 | ip6tables -A OUTPUT -p ipv6-icmp -m icmp6 --icmpv6-type packet-too-big -j ACCEPT 78 | ip6tables -A OUTPUT -p ipv6-icmp -m icmp6 --icmpv6-type time-exceeded -j ACCEPT 79 | ip6tables -A OUTPUT -p ipv6-icmp -m icmp6 --icmpv6-type parameter-problem -j ACCEPT 80 | ip6tables -A OUTPUT -p icmpv6 --icmpv6-type neighbor-solicitation -m hl --hl-eq 255 -j ACCEPT 81 | ip6tables -A OUTPUT -p icmpv6 --icmpv6-type neighbor-advertisement -m hl --hl-eq 255 -j ACCEPT 82 | -------------------------------------------------------------------------------- /hatch_build.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | from hatchling.builders.hooks.plugin.interface import BuildHookInterface 3 | from hatchling.metadata.plugin.interface import MetadataHookInterface 4 | 5 | version = {"__file__": "re6st/version.py"} 6 | with open(version["__file__"]) as f: 7 | code = compile(f.read(), version["__file__"], 'exec') 8 | exec(code, version) 9 | 10 | 11 | class CustomMetadataHook(MetadataHookInterface): 12 | 13 | def update(self, metadata): 14 | metadata['version'] = egg_version = "0.%(revision)s" % version 15 | metadata['readme'] = { 16 | 'content-type': 'text/x-rst', 17 | 'text': ".. contents::\n\n" + open('README.rst').read() 18 | + "\n" + open('CHANGES.rst').read() + """ 19 | 20 | Git Revision: %s == %s 21 | """ % (egg_version, version["short"]), 22 | } 23 | 24 | 25 | class CustomBuildHook(BuildHookInterface): 26 | 27 | def initialize(self, _, build_data): 28 | f = self.__version = tempfile.NamedTemporaryFile('w') 29 | for x in sorted(version.items()): 30 | if not x[0].startswith("_"): 31 | f.write("%s = %r\n" % x) 32 | f.flush() 33 | build_data["force_include"][f.name] = version["__file__"] 34 | 35 | def finalize(self, *_): 36 | self.__version.close() 37 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "re6stnet" 7 | description = "Resilient, Scalable, IPv6 Network" 8 | authors = [ 9 | { name = "Nexedi", email = "re6stnet@erp5.org" }, 10 | ] 11 | license = { text = "GPL-2.0-or-later" } 12 | classifiers = [ 13 | "Environment :: Console", 14 | "Operating System :: POSIX :: Linux", 15 | "Topic :: Internet", 16 | "Topic :: System :: Networking", 17 | ] 18 | requires-python = ">= 3.11" 19 | dependencies = [ 20 | "pyOpenSSL >= 0.13", 21 | "miniupnpc", 22 | ] 23 | dynamic = ["readme", "version"] 24 | 25 | [project.optional-dependencies] 26 | geoip = ["geoip2"] 27 | multicast = ["PyYAML"] 28 | test = ["mock", "nemu3", "unshare", "multiping", "psutil"] 29 | 30 | [project.scripts] 31 | re6st-conf = "re6st.cli.conf:main" 32 | re6stnet = "re6st.cli.node:main" 33 | re6st-registry = "re6st.cli.registry:main" 34 | 35 | [project.urls] 36 | Homepage = "http://re6st.net" 37 | 38 | [tool.hatch.metadata.hooks.custom] 39 | 40 | [tool.hatch.build.hooks.custom] 41 | 42 | [tool.hatch.build] 43 | include = [ 44 | "/re6st", 45 | "/docs", 46 | "/*.rst", 47 | ] 48 | exclude = [ 49 | "/re6st/tests", 50 | ] 51 | 52 | [tool.hatch.build.targets.wheel] 53 | only-packages = true 54 | -------------------------------------------------------------------------------- /re6st-conf: -------------------------------------------------------------------------------- 1 | re6st/cli/conf.py -------------------------------------------------------------------------------- /re6st-registry: -------------------------------------------------------------------------------- 1 | re6st/cli/registry.py -------------------------------------------------------------------------------- /re6st/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nexedi/re6stnet/9689045ca24539913b409754a4fdcd22415cfb89/re6st/__init__.py -------------------------------------------------------------------------------- /re6st/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nexedi/re6stnet/9689045ca24539913b409754a4fdcd22415cfb89/re6st/cli/__init__.py -------------------------------------------------------------------------------- /re6st/cli/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse, atexit, binascii, errno, hashlib 3 | import os, subprocess, sqlite3, sys, time 4 | from OpenSSL import crypto 5 | if 're6st' not in sys.modules: 6 | sys.path[0] = os.path.dirname(os.path.dirname(sys.path[0])) 7 | from re6st import registry, utils, x509 8 | 9 | def create(path, text=None, mode=0o666): 10 | fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, mode) 11 | try: 12 | os.write(fd, text) 13 | finally: 14 | os.close(fd) 15 | 16 | def loadCert(pem: bytes): 17 | return crypto.load_certificate(crypto.FILETYPE_PEM, pem) 18 | 19 | def main(): 20 | parser = argparse.ArgumentParser( 21 | description="Setup script for re6stnet.", 22 | formatter_class=utils.HelpFormatter) 23 | _ = parser.add_argument 24 | _('--fingerprint', metavar='ALG:FINGERPRINT', 25 | help="Check CA fingerprint to protect against MITM.") 26 | _('--registry', required=True, metavar='URL', 27 | help="HTTP URL of the server delivering certificates.") 28 | _('--is-needed', action='store_true', 29 | help="Exit immediately after asking the registry CA. Status code is" 30 | " non-zero if we're already part of the network, which means" 31 | " re6st is already running or we're behind a re6st router.") 32 | _('--ca-only', action='store_true', 33 | help='Only fetch CA from registry and exit.') 34 | _('-d', '--dir', 35 | help="Directory where the key and certificate will be stored.") 36 | _('-r', '--req', nargs=2, action='append', metavar=('KEY', 'VALUE'), 37 | help="The registry only sets the Common Name of your certificate," 38 | " which actually encodes your allocated prefix in the network." 39 | " You can repeat this option to add any field you want to its" 40 | " subject.") 41 | _('--email', 42 | help="Email address where your token is sent. Use -r option if you" 43 | " want to show an email in your certificate.") 44 | _('--token', help="The token you received.") 45 | _('--anonymous', action='store_true', 46 | help="Request an anonymous certificate. No email is required but the" 47 | " registry may deliver a longer prefix.") 48 | _('--location', 49 | help="Alpha-2 codes of country and continent separated by a comma." 50 | " Will be used for the community assignment (default: location" 51 | " is automatically detected). Example: FR,EU") 52 | config = parser.parse_args() 53 | if config.dir: 54 | os.chdir(config.dir) 55 | conf_path = 're6stnet.conf' 56 | ca_path = 'ca.crt' 57 | cert_path = 'cert.crt' 58 | key_path = 'cert.key' 59 | 60 | # Establish connection with server 61 | s = registry.RegistryClient(config.registry) 62 | 63 | # Get CA 64 | ca = loadCert(s.getCa()) 65 | if config.fingerprint: 66 | try: 67 | alg, fingerprint = config.fingerprint.split(':', 1) 68 | fingerprint = binascii.a2b_hex(fingerprint) 69 | if hashlib.new(alg).digest_size != len(fingerprint): 70 | raise ValueError("wrong size") 71 | except Exception as e: 72 | parser.error("invalid fingerprint: %s" % e) 73 | if x509.fingerprint(ca, alg).digest() != fingerprint: 74 | sys.exit("CA fingerprint doesn't match") 75 | else: 76 | print("WARNING: it is strongly recommended to use --fingerprint option.") 77 | network = x509.networkFromCa(ca) 78 | if config.is_needed: 79 | with subprocess.Popen(('ip', '-6', '-o', 'route', 'get', 80 | utils.ipFromBin(network)), 81 | stdout=subprocess.PIPE) as proc: 82 | route, err = proc.communicate() 83 | sys.exit(err or route and 84 | utils.binFromIp(route.split()[8]).startswith(network)) 85 | 86 | create(ca_path, crypto.dump_certificate(crypto.FILETYPE_PEM, ca)) 87 | if config.ca_only: 88 | sys.exit() 89 | 90 | reserved = 'CN', 'serial' 91 | req = crypto.X509Req() 92 | try: 93 | with open(cert_path, "rb") as f: 94 | cert = loadCert(f.read()) 95 | components = \ 96 | {k.decode(): v for k, v in cert.get_subject().get_components()} 97 | for k in reserved: 98 | components.pop(k, None) 99 | except IOError as e: 100 | if e.errno != errno.ENOENT: 101 | raise 102 | components = {} 103 | if config.req: 104 | components.update(config.req) 105 | subj = req.get_subject() 106 | for k, v in components.items(): 107 | if k in reserved: 108 | sys.exit(k + " field is reserved.") 109 | if v: 110 | setattr(subj, k, v) 111 | 112 | cert_fd = token_advice = None 113 | try: 114 | token = config.token 115 | if config.anonymous: 116 | if not (token is config.email is None): 117 | parser.error("--anonymous conflicts with --email/--token") 118 | token = '' 119 | elif not token: 120 | if not config.email: 121 | config.email = input('Please enter your email address: ') 122 | s.requestToken(config.email) 123 | token_advice = "Use --token to retry without asking a new token\n" 124 | while not token: 125 | token = input('Please enter your token: ') 126 | 127 | try: 128 | with open(key_path) as f: 129 | pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read()) 130 | key = None 131 | print("Reusing existing key.") 132 | except IOError as e: 133 | if e.errno != errno.ENOENT: 134 | raise 135 | bits = ca.get_pubkey().bits() 136 | print("Generating %s-bit key ..." % bits) 137 | pkey = crypto.PKey() 138 | pkey.generate_key(crypto.TYPE_RSA, bits) 139 | key = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey) 140 | create(key_path, key, 0o600) 141 | 142 | req.set_pubkey(pkey) 143 | req.sign(pkey, 'sha512') 144 | req = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req).decode() 145 | 146 | # First make sure we can open certificate file for writing, 147 | # to avoid using our token for nothing. 148 | cert_fd = os.open(cert_path, os.O_CREAT | os.O_WRONLY, 0o666) 149 | print("Requesting certificate ...") 150 | if config.location: 151 | cert = s.requestCertificate(token, req, location=config.location) 152 | else: 153 | cert = s.requestCertificate(token, req) 154 | if not cert: 155 | token_advice = None 156 | sys.exit("Error: invalid or expired token") 157 | except: 158 | if cert_fd is not None and not os.lseek(cert_fd, 0, os.SEEK_END): 159 | os.remove(cert_path) 160 | if token_advice: 161 | atexit.register(sys.stdout.write, token_advice) 162 | raise 163 | os.write(cert_fd, cert) 164 | os.ftruncate(cert_fd, len(cert)) 165 | os.close(cert_fd) 166 | 167 | cert = loadCert(cert) 168 | not_after = x509.notAfter(cert) 169 | print("Setup complete. Certificate is valid until %s UTC" 170 | " and will be automatically renewed after %s UTC.\n" 171 | "Do not forget to backup to your private key (%s) or" 172 | " you will lose your assigned subnet." % ( 173 | time.asctime(time.gmtime(not_after)), 174 | time.asctime(time.gmtime(not_after - registry.RENEW_PERIOD)), 175 | key_path)) 176 | 177 | if not os.path.lexists(conf_path): 178 | create(conf_path, ("""\ 179 | registry %s 180 | ca %s 181 | cert %s 182 | key %s 183 | %s 184 | # increase re6stnet verbosity: 185 | #verbose 3 186 | # enable OpenVPN logging: 187 | #ovpnlog 188 | # uncomment the following 2 lines to increase OpenVPN verbosity: 189 | #O--verb 190 | #O3 191 | """ % (config.registry, ca_path, cert_path, key_path, 192 | ('country ' + config.location.split(',', 1)[0]) 193 | if config.location else '')).encode()) 194 | print("Sample configuration file created.") 195 | 196 | cn = x509.subnetFromCert(cert) 197 | subnet = network + utils.binFromSubnet(cn) 198 | print("Your subnet: %s/%u (CN=%s)" 199 | % (utils.ipFromBin(subnet), len(subnet), cn)) 200 | 201 | if __name__ == "__main__": 202 | main() 203 | -------------------------------------------------------------------------------- /re6st/cli/registry.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import http.client, logging, os, socket, sys 3 | from http import HTTPStatus 4 | from http.server import BaseHTTPRequestHandler 5 | from socketserver import ThreadingTCPServer 6 | from urllib.parse import parse_qsl 7 | if 're6st' not in sys.modules: 8 | sys.path[0] = os.path.dirname(os.path.dirname(sys.path[0])) 9 | from re6st import registry, utils, version 10 | 11 | # To generate server ca and key with serial for 2001:db8:42::/48 12 | # openssl req -nodes -new -x509 -key ca.key -set_serial 0x120010db80042 -days 3650 -out ca.crt 13 | 14 | IPV6_V6ONLY = 26 15 | SOL_IPV6 = 41 16 | 17 | 18 | class RequestHandler(BaseHTTPRequestHandler): 19 | 20 | def do_GET(self): 21 | try: 22 | try: 23 | path, query = self.path.split('?', 1) 24 | except ValueError: 25 | path = self.path 26 | query = {} 27 | else: 28 | query = dict(parse_qsl(query, keep_blank_values=True, 29 | strict_parsing=True)) 30 | _, path = path.split('/') 31 | if not _: 32 | return self.server.handle_request(self, path, query) 33 | except Exception: 34 | logging.info(self.requestline, exc_info=True) 35 | self.send_error(HTTPStatus.BAD_REQUEST) 36 | 37 | def log_error(*args): 38 | pass 39 | 40 | 41 | class HTTPServer4(ThreadingTCPServer): 42 | 43 | allow_reuse_address = True 44 | daemon_threads = True 45 | 46 | 47 | class HTTPServer6(HTTPServer4): 48 | 49 | address_family = socket.AF_INET6 50 | 51 | def server_bind(self): 52 | self.socket.setsockopt(SOL_IPV6, IPV6_V6ONLY, 1) 53 | HTTPServer4.server_bind(self) 54 | 55 | 56 | def main(): 57 | parser = utils.ArgParser(fromfile_prefix_chars='@', 58 | description="re6stnet registry used to bootstrap nodes" 59 | " and deliver certificates.") 60 | _ = parser.add_argument 61 | _('--port', type=int, default=80, 62 | help="Port on which the server will listen.") 63 | _('-4', dest='bind4', default='0.0.0.0', 64 | help="Bind server to this IPv4.") 65 | _('-6', dest='bind6', default='::', 66 | help="Bind server to this IPv6.") 67 | _('--db', default='/var/lib/re6stnet/registry.db', 68 | help="Path to SQLite database file. It is automatically initialized" 69 | " if the file does not exist.") 70 | _('--dh', required=True, 71 | help="File containing Diffie-Hellman parameters in .pem format." 72 | " To generate them, you can use something like:\n" 73 | "openssl dhparam -out dh2048.pem 2048") 74 | _('--ca', required=True, help=parser._ca_help) 75 | _('--key', required=True, 76 | help="CA private key in .pem format. For example:\nopenssl" 77 | " genpkey -out ca.key -algorithm rsa -pkeyopt rsa_keygen_bits:2048") 78 | _('--mailhost', 79 | help="SMTP host to send confirmation emails. For debugging" 80 | " purpose, it can also be an absolute or existing path to" 81 | " a mailbox file. If unset, registration by mail is disabled.") 82 | _('--smtp-user', 83 | help="SMTP login.") 84 | _('--smtp-pwd', 85 | help="SMTP password.") 86 | _('--smtp-starttls', action='store_true', 87 | help="Use STARTTLS for SMTP connections.") 88 | _('--prefix-length', default=16, type=int, 89 | help="Default length of allocated prefixes." 90 | " If 0, registration by email is disabled.") 91 | _('--anonymous-prefix-length', type=int, 92 | help="Length of allocated anonymous prefixes." 93 | " If 0 or unset, anonymous registration is disabled.") 94 | _('--ipv4', nargs=2, metavar=("IP/N", "PLEN"), 95 | help="Enable ipv4. Each node is assigned a subnet of length PLEN" 96 | " inside network IP/N.") 97 | _('-l', '--logfile', default='/var/log/re6stnet/registry.log', 98 | help="Path to logging file.") 99 | _('-r', '--run', default='/var/run/re6stnet', 100 | help="Path to re6stnet runtime directory:\n" 101 | "- babeld.sock (option -R of babeld)\n") 102 | _('-v', '--verbose', default=1, type=int, 103 | help="Log level. 0 disables logging. 1=WARNING, 2=INFO," 104 | " 3=DEBUG, 4=TRACE. Use SIGUSR1 to reopen log.") 105 | _('--min-protocol', default=version.min_protocol, type=int, 106 | help="Reject nodes that are too old. Current is %s." % version.protocol) 107 | _('--authorized-origin', action='append', default=['127.0.0.1', '::1'], 108 | help="Authorized IPs to access origin-restricted RPC.") 109 | _('--community', 110 | help="File containing community configuration. This file cannot be" 111 | " empty and must contain the default location ('*').") 112 | _('--grace-period', default=8640000, type=int, 113 | help="Period in seconds during which a client can renew its" 114 | " certificate even if expired (default 100 days)") 115 | 116 | _ = parser.add_argument_group('routing').add_argument 117 | _('--hello', type=int, default=15, 118 | help="Hello interval in seconds, for both wired and wireless" 119 | " connections. OpenVPN ping-exit option is set to 4 times the" 120 | " hello interval. It takes between 3 and 4 times the" 121 | " hello interval for Babel to re-establish connection with a" 122 | " node for which the direct connection has been cut.") 123 | 124 | _ = parser.add_argument_group('tunnelling').add_argument 125 | _('--encrypt', action='store_true', 126 | help='Specify that tunnels should be encrypted.') 127 | _('--client-count', default=10, type=int, 128 | help="Number of client tunnels to set up.") 129 | _('--max-clients', type=int, 130 | help="Maximum number of accepted clients per OpenVPN server. (default:" 131 | " client-count * 2, which actually represents the average number" 132 | " of tunnels to other peers)") 133 | _('--tunnel-refresh', default=300, type=int, 134 | help="Interval in seconds between two tunnel refresh: the worst" 135 | " tunnel is closed if the number of client tunnels has reached" 136 | " its maximum number (client-count).") 137 | _('--same-country', action='append', metavar="CODE", 138 | help="prevent tunnelling accross borders of listed countries") 139 | 140 | config = parser.parse_args() 141 | 142 | if not version.min_protocol <= config.min_protocol <= version.protocol: 143 | parser.error("--min-protocol: value must between %s and %s (included)" 144 | % (version.min_protocol, version.protocol)) 145 | 146 | if config.ipv4: 147 | ipv4, plen = config.ipv4 148 | try: 149 | ip, n = ipv4.split('/') 150 | config.ipv4 = "%s/%s" % (socket.inet_ntoa(socket.inet_aton(ip)), 151 | int(n)), int(plen) 152 | except (socket.error, ValueError): 153 | parser.error("invalid argument --ipv4") 154 | 155 | utils.setupLog(config.verbose, config.logfile) 156 | 157 | if config.max_clients is None: 158 | config.max_clients = config.client_count * 2 159 | 160 | server = registry.RegistryServer(config) 161 | def requestHandler(request, client_address, _): 162 | RequestHandler(request, client_address, server) 163 | 164 | server_dict = {} 165 | if config.bind4: 166 | r = HTTPServer4((config.bind4, config.port), requestHandler) 167 | server_dict[r.fileno()] = r._handle_request_noblock 168 | if config.bind6: 169 | r = HTTPServer6((config.bind6, config.port), requestHandler) 170 | server_dict[r.fileno()] = r._handle_request_noblock 171 | if server_dict: 172 | while True: 173 | args = server_dict.copy(), {}, [] 174 | server.select(*args) 175 | utils.select(*args) 176 | 177 | 178 | if __name__ == "__main__": 179 | main() 180 | -------------------------------------------------------------------------------- /re6st/debug.py: -------------------------------------------------------------------------------- 1 | import errno, os, socket, stat, threading 2 | 3 | 4 | class Socket: 5 | 6 | def __init__(self, socket: socket.socket): 7 | # In case that the default timeout is not None. 8 | socket.settimeout(None) 9 | self._socket = socket 10 | self._buf = b'' 11 | 12 | def close(self): 13 | self._socket.close() 14 | 15 | def write(self, data: bytes): 16 | self._socket.send(data) 17 | 18 | def readline(self) -> bytes: 19 | recv = self._socket.recv 20 | data = self._buf 21 | while True: 22 | i = 1 + data.find(b'\n') 23 | if i: 24 | self._buf = data[i:] 25 | return data[:i] 26 | d = recv(4096) 27 | data += d 28 | if not d: 29 | self._buf = b'' 30 | return data 31 | 32 | def flush(self): 33 | pass 34 | 35 | def closed(self): 36 | self._socket.setblocking(0) 37 | try: 38 | self._socket.recv(0) 39 | return True 40 | except socket.error as e: 41 | if e.errno != errno.EAGAIN: 42 | raise 43 | self._socket.setblocking(1) 44 | return False 45 | 46 | 47 | class Console: 48 | 49 | def __init__(self, path, pdb): 50 | self.path = path 51 | s = self._sock = socket.socket(socket.AF_UNIX, 52 | socket.SOCK_STREAM | socket.SOCK_CLOEXEC) 53 | try: 54 | self._removeSocket() 55 | except OSError as e: 56 | if e.errno != errno.ENOENT: 57 | raise 58 | s.bind(path) 59 | s.listen(5) 60 | def accept(): 61 | t = threading.Thread(target=pdb, args=(Socket(s.accept()[0]),)) 62 | t.daemon = True 63 | t.start() 64 | def select(r, w, t): 65 | r[s] = accept 66 | self.select = select 67 | 68 | def close(self): 69 | self._removeSocket() 70 | self._sock.close() 71 | 72 | def _removeSocket(self): 73 | if stat.S_ISSOCK(os.lstat(self.path).st_mode): 74 | os.remove(self.path) 75 | -------------------------------------------------------------------------------- /re6st/multicast.py: -------------------------------------------------------------------------------- 1 | import os, struct, subprocess, time, yaml 2 | from ctypes import ( 3 | Structure, Union, POINTER, 4 | pointer, c_ushort, c_byte, c_void_p, c_char_p, c_uint, c_int, 5 | CDLL, util as ctypes_util, 6 | ) 7 | from socket import socket, AF_INET6, AF_NETLINK, NETLINK_ROUTE, SOCK_RAW 8 | from . import utils 9 | 10 | RTMGRP_IPV6_IFINFO = 0x800 11 | RTM_NEWLINK = 16 12 | IFLA_IFNAME = 3 13 | 14 | class struct_sockaddr(Structure): 15 | _fields_ = [ 16 | ('sa_family', c_ushort), 17 | ('sa_data', c_byte * 14), 18 | ] 19 | 20 | class union_ifa_ifu(Union): 21 | _fields_ = [ 22 | ('ifu_broadaddr', POINTER(struct_sockaddr)), 23 | ('ifu_dstaddr', POINTER(struct_sockaddr)), 24 | ] 25 | 26 | class struct_ifaddrs(Structure): 27 | pass 28 | struct_ifaddrs._fields_ = [ 29 | ('ifa_next', POINTER(struct_ifaddrs)), 30 | ('ifa_name', c_char_p), 31 | ('ifa_flags', c_uint), 32 | ('ifa_addr', POINTER(struct_sockaddr)), 33 | ('ifa_netmask', POINTER(struct_sockaddr)), 34 | ('ifa_ifu', union_ifa_ifu), 35 | ('ifa_data', c_void_p), 36 | ] 37 | 38 | libc = CDLL(ctypes_util.find_library('c'), use_errno=True) 39 | getifaddrs = libc.getifaddrs 40 | getifaddrs.restype = c_int 41 | getifaddrs.argtypes = [POINTER(POINTER(struct_ifaddrs))] 42 | freeifaddrs = libc.freeifaddrs 43 | freeifaddrs.restype = None 44 | freeifaddrs.argtypes = [POINTER(struct_ifaddrs)] 45 | 46 | class unpacker: 47 | 48 | def __init__(self, buf): 49 | self._buf = buf 50 | self._offset = 0 51 | 52 | def __call__(self, fmt): 53 | s = struct.Struct(fmt) 54 | result = s.unpack_from(self._buf, self._offset) 55 | self._offset += s.size 56 | return result 57 | 58 | class PimDm: 59 | 60 | def __init__(self): 61 | s_netlink = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) 62 | s_netlink.setblocking(False) 63 | s_netlink.bind((os.getpid(), RTMGRP_IPV6_IFINFO)) 64 | self.s_netlink = s_netlink 65 | 66 | self.started = False 67 | 68 | def addInterface(self, ifname): 69 | while not self.isStarted(): 70 | time.sleep(0.5) 71 | subprocess.call(['pim-dm', '-6', '-aisr', ifname]) 72 | subprocess.call(['pim-dm', '-aimld', ifname]) 73 | 74 | def addInterfaceWhenReady(self): 75 | data = self.s_netlink.recv(65535) 76 | unpack = unpacker(data) 77 | msg_len, msg_type, flags, seq, pid = unpack("=LHHLL") 78 | if msg_type != RTM_NEWLINK: 79 | return 80 | 81 | family, _, if_type, index, flags, change = unpack("=BBHiII") 82 | 83 | while msg_len - unpack._offset: 84 | rta_len, rta_type = unpack("=HH") 85 | if rta_len < 4: 86 | break 87 | rta_data = unpack("%ds" % rta_len)[0].rstrip('\0\n\1') 88 | 89 | if rta_type == IFLA_IFNAME: 90 | if rta_data in self.not_ready_iface_set \ 91 | and rta_data in interfaceUpSet(): 92 | self.addInterface(rta_data) 93 | self.not_ready_iface_set.remove(rta_data) 94 | break 95 | 96 | unpack._offset += (rta_len - 1) & ~(4 - 1) 97 | 98 | def isStarted(self): 99 | if not self.started: 100 | self.started = os.path.exists('/run/pim-dm/0') 101 | return self.started 102 | 103 | def run(self, iface_list, run_path): 104 | # pim-dm requires interface to be up at startup, 105 | # but can handle interfaces going down then up again 106 | iface_set = set(iface_list) 107 | up_set = interfaceUpSet() 108 | self.not_ready_iface_set = iface_set - up_set 109 | iface_set &= up_set 110 | 111 | enabled = (('enabled', True), ('state_refresh', True)) 112 | conf = { 113 | 'PIM-DM': { 114 | 'Interfaces': dict.fromkeys(iface_set, {'ipv6': dict(enabled)}), 115 | }, 116 | 'MLD': { 117 | 'Interfaces': dict.fromkeys(iface_set, dict(enabled[:1])), 118 | }, 119 | } 120 | 121 | conf_file_path = os.path.join(run_path, 'pim-dm.conf') 122 | with open(conf_file_path, 'w') as conf_file: 123 | yaml.dump(conf, conf_file) 124 | 125 | return utils.Popen(['pim-dm', '-config', conf_file_path]) 126 | 127 | def select(self, r, w, t): 128 | if self.not_ready_iface_set: 129 | r[self.s_netlink] = self.addInterfaceWhenReady 130 | 131 | def ifap_iter(ifa): 132 | '''Iterate over linked list of ifaddrs''' 133 | while ifa: 134 | ifa = ifa.contents 135 | yield ifa 136 | ifa = ifa.ifa_next 137 | 138 | def interfaceUpSet(): 139 | ifap = POINTER(struct_ifaddrs)() 140 | getifaddrs(pointer(ifap)) 141 | try: 142 | return { 143 | ifa.ifa_name 144 | for ifa in ifap_iter(ifap) 145 | if ifa.ifa_addr and ifa.ifa_addr.contents.sa_family == AF_INET6 146 | } 147 | finally: 148 | freeifaddrs(ifap) 149 | -------------------------------------------------------------------------------- /re6st/ovpn-client: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S python3 -S 2 | import os, sys 3 | 4 | script_type = os.environ['script_type'] 5 | if script_type == 'up': 6 | # OpenVPN only brings the interface up when it's called with --ifconfig 7 | # i.e. when IPv4 mode is enabled in re6st. 8 | # OpenVPN unsets PATH before calling hooks 9 | # which is equivalent to set /bin:/usr/bin 10 | os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin' 11 | os.execlp('ip', 'ip', 'link', 'set', os.environ['dev'], 'up', 12 | 'mtu', os.environ['tun_mtu']) 13 | 14 | if script_type == 'route-up': 15 | import time 16 | os.write(int(sys.argv[1]), repr((os.environ['common_name'], time.time(), 17 | int(os.environ['tls_serial_0']), os.environ['OPENVPN_external_ip'])) 18 | .encode()) 19 | -------------------------------------------------------------------------------- /re6st/ovpn-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S python3 -S 2 | import os, sys 3 | 4 | script_type = os.environ['script_type'] 5 | external_ip = os.getenv('trusted_ip') or os.environ['trusted_ip6'] 6 | 7 | # Write into pipe connect/disconnect events 8 | fd = int(sys.argv[1]) 9 | os.write(fd, repr((script_type, (os.environ['common_name'], os.environ['dev'], 10 | int(os.environ['tls_serial_0']), external_ip))) 11 | .encode("utf-8")) 12 | 13 | if script_type == 'client-connect': 14 | if os.read(fd, 1) == b'\0': 15 | sys.exit(1) 16 | # Send client its external ip address 17 | with open(sys.argv[2], 'w') as f: 18 | f.write('push "setenv-safe external_ip %s"\n' % external_ip) 19 | -------------------------------------------------------------------------------- /re6st/plib.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import logging, errno, os 3 | from typing import Optional 4 | from . import utils 5 | 6 | here = os.path.realpath(os.path.dirname(__file__)) 7 | ovpn_server = os.path.join(here, 'ovpn-server') 8 | ovpn_client = os.path.join(here, 'ovpn-client') 9 | ovpn_log: Optional[str] = None 10 | 11 | def openvpn(iface: str, encrypt, *args, **kw) -> utils.Popen: 12 | args = ['openvpn', 13 | '--dev-type', 'tap', 14 | '--dev', iface, 15 | '--persist-tun', 16 | '--persist-key', 17 | '--script-security', '2', 18 | '--up', ovpn_client, 19 | #'--user', 'nobody', '--group', 'nogroup', 20 | ] + list(args) 21 | if ovpn_log: 22 | args += '--log-append', os.path.join(ovpn_log, '%s.log' % iface), 23 | if not encrypt: 24 | # TODO: --ncp-disable was deprecated in OpenVPN 2.5 and removed in 2.6 25 | # and is no longer necessary in those versions. 26 | args += '--cipher', 'none', '--ncp-disable' 27 | logging.debug('%r', args) 28 | return utils.Popen(args, **kw) 29 | 30 | ovpn_link_mtu_dict = {'udp4': 1432, 'udp6': 1450} 31 | 32 | def server(iface: str, max_clients: int, dh_path: str, fd: int, 33 | port: int, proto: str, encrypt: bool, *args, **kw) -> utils.Popen: 34 | if proto == 'udp': 35 | proto = 'udp4' 36 | client_script = '%s %s' % (ovpn_server, fd) 37 | try: 38 | args = ('--link-mtu', str(ovpn_link_mtu_dict[proto] + 93), 39 | '--mtu-disc', 'yes') + args 40 | except KeyError: 41 | proto += '-server' 42 | return openvpn(iface, encrypt, 43 | '--tls-server', 44 | '--mode', 'server', 45 | '--client-connect', client_script, 46 | '--client-disconnect', client_script, 47 | '--dh', dh_path, 48 | '--max-clients', str(max_clients), 49 | '--port', str(port), 50 | '--proto', proto, 51 | *args, pass_fds=[fd], **kw) 52 | 53 | 54 | def client(iface: str, address_list: list[tuple[str, int, str]], 55 | encrypt: bool, *args, **kw) -> utils.Popen: 56 | remote = ['--nobind', '--client'] 57 | # XXX: We'd like to pass sections at command-line. 58 | link_mtu = set() 59 | for ip, port, proto in address_list: 60 | if proto == 'udp': 61 | proto = 'udp4' 62 | remote += '--remote', ip, port, proto 63 | link_mtu.add(ovpn_link_mtu_dict.get(proto)) 64 | link_mtu, = link_mtu 65 | if link_mtu: 66 | remote += '--link-mtu', str(link_mtu), '--mtu-disc', 'yes' 67 | remote += args 68 | return openvpn(iface, encrypt, *remote, **kw) 69 | 70 | 71 | def router(ip: tuple[str, int], ip4, rt6: tuple[str, bool, bool], 72 | hello_interval: int, log_path: str, state_path: str, 73 | control_socket: str, default: str, 74 | hmac: tuple[bytes | None, bytes | None], *args, **kw) -> utils.Popen: 75 | network, gateway, has_ipv6_subtrees = rt6 76 | network_mask = int(network[network.index('/')+1:]) 77 | ip, n = ip 78 | hmac_sign, hmac_accept = hmac 79 | if ip4: 80 | ip4, n4 = ip4 81 | cmd = ['babeld', 82 | '-h', str(hello_interval), 83 | '-H', str(hello_interval), 84 | '-L', log_path, 85 | '-S', state_path, 86 | '-I', '', 87 | '-s', 88 | '-C', 'redistribute local deny', 89 | '-C', 'redistribute ip %s/%s eq %s' % (ip, n, n)] 90 | if hmac_sign: 91 | def key(cmd: list[str], id: str, value: bytes): 92 | cmd += '-C', ('key type blake2s128 id %s value %s' % 93 | (id, binascii.hexlify(value).decode())) 94 | key(cmd, 'sign', hmac_sign) 95 | default += ' key sign' 96 | if hmac_accept is not None: 97 | if hmac_accept: 98 | key(cmd, 'accept', hmac_accept) 99 | else: 100 | default += ' accept-bad-signatures true' 101 | cmd += '-C', 'default ' + default 102 | if ip4: 103 | cmd += '-C', 'redistribute ip %s/%s eq %s' % (ip4, n4, n4) 104 | if gateway: 105 | cmd += '-C', 'redistribute ip ::/0 eq 0 src-prefix ' + network 106 | if not has_ipv6_subtrees: 107 | cmd += ( 108 | '-C', 'in ip %s ge %s' % (network, network_mask), 109 | '-C', 'in ip ::/0 deny', 110 | ) 111 | elif has_ipv6_subtrees: 112 | # For backward compatibility, if the default route comes from old 113 | # version (without source-prefix). 114 | cmd += ( 115 | '-C', 'install ip ::/0 eq 0 src-ip ::/0 src-eq 0 src-prefix ' + network, 116 | ) 117 | else: 118 | # We patch babeld: 119 | # - ipv6-subtrees is always true by default 120 | # - if false, source prefix is cleared when the route is installed 121 | cmd += ( 122 | '-C', 'ipv6-subtrees false', 123 | # Accept default route from our network. 124 | '-C', 'in ip ::/0 eq 0 src-ip %s src-eq %s' % (network, network_mask), 125 | # Ignore default route from other networks. For backward 126 | # compatibility we accept default routes from old version 127 | # (without source-prefix). 128 | '-C', 'in ip ::/0 eq 0 src-ip ::/0 src-ge 1 deny', 129 | # Tell neighbours not to route to the internet via us, 130 | # because we could be a black hole in case of misconfiguration. 131 | '-C', 'out ip ::/0 eq 0 deny', 132 | ) 133 | cmd += ('-C', 'redistribute deny', 134 | '-C', 'install pref-src ' + ip) 135 | if ip4: 136 | cmd += '-C', 'install pref-src ' + ip4 137 | if control_socket: 138 | cmd += '-X', '%s' % control_socket 139 | cmd += args 140 | logging.info('%r', cmd) 141 | return utils.Popen(cmd, **kw) 142 | -------------------------------------------------------------------------------- /re6st/tests/.gitignore: -------------------------------------------------------------------------------- 1 | *.cert 2 | *.key -------------------------------------------------------------------------------- /re6st/tests/README.MD: -------------------------------------------------------------------------------- 1 | If you clone this repo directly, you should add re6stnet to PYTHONPATH, so 2 | python can find module re6st 3 | 4 | To run all the tests, you have two methods: one is by creating a new user 5 | namespace, the other is by using root privileges. 6 | 7 | 8 | * `unshare -Unr bash -c "mount -t sysfs /sys & python -m unittest discover"` 9 | * `python -m unittest discover` 10 | 11 | The mount of /sys is because nemu will read files in /sys. 12 | When creating a new network namespace, the exiting network devices will not 13 | disappear, and a re-mount is needed to update /sys. 14 | 15 | If you want to only run the unit tests `python -m unittest discover` is ok, 16 | the network tests will be skipped. 17 | -------------------------------------------------------------------------------- /re6st/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | DEMO_PATH = Path(__file__).resolve().parent.parent.parent / "demo" 3 | -------------------------------------------------------------------------------- /re6st/tests/registry.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": 1, 3 | "port": 9090, 4 | "anonymous_prefix_length": null, 5 | "smtp_pwd": null, 6 | "client_count": 10, 7 | "authorized_origin": [ 8 | "127.0.0.1", 9 | "::1" 10 | ], 11 | "bind6": "::", 12 | "ipv4": null, 13 | "prefix_length": 16, 14 | "min_protocol": 1, 15 | "smtp_starttls": false, 16 | "run": "run", 17 | "bind4": "0.0.0.0", 18 | "db": "registry.db", 19 | "mailhost": "miku@miku.com", 20 | "encrypt": false, 21 | "logfile": "registry.log", 22 | "max_clients": null, 23 | "smtp_user": null, 24 | "same_country": null, 25 | "tunnel_refresh": 300, 26 | "hello": 15, 27 | "community": null, 28 | "grace_period": 8640000 29 | } 30 | -------------------------------------------------------------------------------- /re6st/tests/test_end2end/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nexedi/re6stnet/9689045ca24539913b409754a4fdcd22415cfb89/re6st/tests/test_end2end/__init__.py -------------------------------------------------------------------------------- /re6st/tests/test_end2end/test_registry_client.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | import os 4 | import sqlite3 5 | import subprocess 6 | import tempfile 7 | import time 8 | import unittest 9 | import zlib 10 | 11 | from re6st import registry, x509 12 | from re6st.tests.test_network import re6st_wrap 13 | from re6st.tests import tools 14 | from re6st.tests import DEMO_PATH 15 | 16 | DH_FILE = DEMO_PATH / "dh2048.pem" 17 | 18 | class DummyNode: 19 | """fake node to reuse Re6stRegistry 20 | 21 | error: node.Popen has destory method which not in subprocess.Popen 22 | """ 23 | def __init__(self): 24 | self.ip = "localhost" 25 | self.Popen = subprocess.Popen 26 | self.pid = os.getpid() 27 | 28 | class TestRegistryClientInteract(unittest.TestCase): 29 | 30 | @classmethod 31 | def setUpClass(cls): 32 | # if running in net ns, set lo up 33 | subprocess.check_call(("ip", "link", "set", "lo", "up")) 34 | 35 | def setUp(self): 36 | re6st_wrap.initial() 37 | 38 | self.port = 18080 39 | self.url = "http://localhost:{}/".format(self.port) 40 | # not important, used in network_config check 41 | self.max_clients = 10 42 | 43 | def tearDown(self): 44 | with self.server.proc as p: 45 | p.terminate() 46 | 47 | def test_1_main(self): 48 | """ a client interact a server, no re6stnet node test basic function""" 49 | self.server = re6st_wrap.Re6stRegistry( 50 | DummyNode(), 51 | "2001:db8:42::", 52 | self.max_clients, port=self.port, 53 | recreate=True) 54 | 55 | client = registry.RegistryClient(self.url) 56 | email = "m1@miku.com" 57 | 58 | # simulate the process in conf 59 | # request a token 60 | client.requestToken(email) 61 | # read token from db 62 | db = sqlite3.connect(str(self.server.db), isolation_level=None) 63 | token = None 64 | for _ in range(100): 65 | time.sleep(.1) 66 | token = db.execute("SELECT token FROM token WHERE email=?", 67 | (email,)).fetchone() 68 | if token: 69 | break 70 | else: 71 | self.fail("Request token failed, no token in database") 72 | # token: tuple[unicode,] 73 | token = str(token[0]) 74 | self.assertEqual(client.isToken(token), b"1") 75 | 76 | # request ca 77 | ca = client.getCa() 78 | 79 | # request a cert and get cn 80 | key, csr = tools.generate_csr() 81 | cert = client.requestCertificate(token, csr) 82 | self.assertEqual(client.isToken(token), b'', "token should be deleted") 83 | 84 | # creat x509.cert object 85 | def write_to_temp(text): 86 | """text: bytes""" 87 | file = tempfile.NamedTemporaryFile() 88 | file.write(text) 89 | file.flush() 90 | return file 91 | 92 | ca, key, cert = map(write_to_temp, (ca, key, cert)) 93 | client.cert = x509.Cert(ca.name, key.name, cert.name) 94 | ca.close() 95 | cert.close() 96 | # cert.decrypt use key file, close after entire test 97 | self.addCleanup(key.close) 98 | 99 | # verfiy cn and prefix 100 | prefix = client.cert.prefix 101 | cn = client.getNodePrefix(email).decode() 102 | self.assertEqual(tools.prefix2cn(prefix), cn) 103 | 104 | # simulate the process in cache 105 | # just prove works 106 | net_config = client.getNetworkConfig(prefix) 107 | net_config = json.loads(zlib.decompress(net_config)) 108 | self.assertEqual(net_config[u'max_clients'], self.max_clients) 109 | 110 | # no re6stnet, empty result 111 | bootpeer = client.getBootstrapPeer(prefix) 112 | self.assertEqual(bootpeer, b"") 113 | 114 | # server should not die 115 | self.assertIsNone(self.server.proc.poll()) 116 | 117 | #TODO with a registry and some node, test babel_dump related function 118 | 119 | if __name__ == "__main__": 120 | unittest.main() 121 | -------------------------------------------------------------------------------- /re6st/tests/test_network/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | temp_net_test/ -------------------------------------------------------------------------------- /re6st/tests/test_network/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nexedi/re6stnet/9689045ca24539913b409754a4fdcd22415cfb89/re6st/tests/test_network/__init__.py -------------------------------------------------------------------------------- /re6st/tests/test_network/miniupnpd.conf: -------------------------------------------------------------------------------- 1 | clean_ruleset_interval=600 2 | allow 1024-65535 10.0.0.0/8 1024-65535 3 | deny 0-65535 0.0.0.0/0 0-65535 4 | -------------------------------------------------------------------------------- /re6st/tests/test_network/network_build.py: -------------------------------------------------------------------------------- 1 | import ipaddress 2 | import logging 3 | import nemu 4 | import time 5 | import weakref 6 | from subprocess import DEVNULL, PIPE 7 | from pathlib import Path 8 | 9 | IPTABLES = 'iptables-nft' 10 | 11 | class ConnectableError(Exception): 12 | pass 13 | 14 | class Node(nemu.Node): 15 | """simple nemu.Node used for registry and nodes""" 16 | def __init__(self): 17 | super().__init__() 18 | self.Popen(('sysctl', '-q', 19 | 'net.ipv4.icmp_echo_ignore_broadcasts=0')).wait() 20 | 21 | def _add_interface(self, iface): 22 | self.iface = iface 23 | iface.__dict__['node'] = weakref.proxy(self) 24 | return super()._add_interface(iface) 25 | 26 | @property 27 | def ip(self): 28 | try: 29 | return str(self._ip) 30 | except AttributeError: 31 | # return 1 ipv4 address of the one interface, reverse mode 32 | for iface in self.get_interfaces()[::-1]: 33 | for addr in iface.get_addresses(): 34 | addr = addr['address'] 35 | if '.' in addr: 36 | #TODO different type problem? 37 | self._ip = addr 38 | return addr 39 | 40 | 41 | def connect_switch(self, switch, ip, prefix_len=24): 42 | self.if_s = if_s = nemu.NodeInterface(self) 43 | switch.connect(if_s) 44 | if_s.up = True 45 | if_s.add_v4_address(ip, prefix_len=prefix_len) 46 | return if_s 47 | 48 | class NetManager: 49 | """contain all the nemu object created, so they can live more time""" 50 | def __init__(self): 51 | self.object = [] 52 | self.registries = {} 53 | def connectable_test(self): 54 | """test each node can ping to their registry 55 | Raise: 56 | AssertionError 57 | """ 58 | for reg, nodes in self.registries.items(): 59 | for node in nodes: 60 | with node.Popen(["ping", "-c", "1", reg.ip], 61 | stdout=DEVNULL) as app0: 62 | ret = app0.wait() 63 | if ret: 64 | raise ConnectableError( 65 | "network construct failed {} to {}".format(node.ip, reg.ip)) 66 | 67 | logging.debug("each node can ping to their registry") 68 | 69 | 70 | def net_route(): 71 | """build a network connect by a route(bridge) 72 | 73 | Returns: 74 | a network manager contain 3 nodes 75 | """ 76 | nm = NetManager() 77 | 78 | switch1 = nemu.Switch() 79 | switch1.up = True 80 | 81 | registry = Node() 82 | machine1 = Node() 83 | machine2 = Node() 84 | 85 | registry.connect_switch(switch1, "192.168.1.1") 86 | machine1.connect_switch(switch1, "192.168.1.2") 87 | machine2.connect_switch(switch1, "192.168.1.3") 88 | 89 | nm.object.append(switch1) 90 | nm.registries[registry] = [machine1, machine2] 91 | 92 | nm.connectable_test() 93 | return nm 94 | 95 | def net_demo(): 96 | 97 | internet = Node() 98 | gateway1 = Node() 99 | gateway2 = Node() 100 | 101 | registry = Node() 102 | m1 = Node() 103 | m2 = Node() 104 | m3 = Node() 105 | m4 = Node() 106 | m5 = Node() 107 | m6 = Node() 108 | m7 = Node() 109 | m8 = Node() 110 | 111 | switch1 = nemu.Switch() 112 | switch2 = nemu.Switch() 113 | switch3 = nemu.Switch() 114 | 115 | nm = NetManager() 116 | nm.object = [internet, switch3, switch1, switch2, gateway1, gateway2] 117 | nm.registries = {registry: [m1, m2, m3, m4, m5, m6, m7, m8]} 118 | 119 | # for node in [g1, m3, m4, m5]: 120 | # print "pid: {}".format(node.pid) 121 | 122 | re_if_0, in_if_0 = nemu.P2PInterface.create_pair(registry, internet) 123 | g1_if_0, in_if_1 = nemu.P2PInterface.create_pair(gateway1, internet) 124 | g2_if_0, in_if_2 = nemu.P2PInterface.create_pair(gateway2, internet) 125 | 126 | re_if_0.add_v4_address(address="10.0.0.2", prefix_len=24) 127 | in_if_0.add_v4_address(address='10.0.0.1', prefix_len=24) 128 | in_if_1.add_v4_address(address='10.1.0.1', prefix_len=24) 129 | in_if_2.add_v4_address(address='10.2.0.1', prefix_len=24) 130 | g1_if_0.add_v4_address(address='10.1.0.2', prefix_len=24) 131 | g2_if_0.add_v4_address(address='10.2.0.2', prefix_len=24) 132 | 133 | for iface in [re_if_0, in_if_0, g1_if_0, in_if_1, g2_if_0, in_if_2]: 134 | nm.object.append(iface) 135 | iface.up = True 136 | 137 | ip = ipaddress.ip_address(u"10.1.1.1") 138 | for i, node in enumerate([gateway1, m1, m2]): 139 | iface = node.connect_switch(switch1, str(ip + i)) 140 | nm.object.append(iface) 141 | if i: # except the first 142 | node.add_route(nexthop=ip) 143 | 144 | gateway1.Popen((IPTABLES, '-t', 'nat', '-A', 'POSTROUTING', '-o', g1_if_0.name, '-j', 'MASQUERADE')).wait() 145 | gateway1.Popen((IPTABLES, '-t', 'nat', '-N', 'MINIUPNPD')).wait() 146 | gateway1.Popen((IPTABLES, '-t', 'nat', '-A', 'PREROUTING', '-i', g1_if_0.name, '-j', 'MINIUPNPD')).wait() 147 | gateway1.Popen((IPTABLES, '-N', 'MINIUPNPD')).wait() 148 | 149 | 150 | ip = ipaddress.ip_address(u"10.2.1.1") 151 | for i, node in enumerate([gateway2, m3, m4, m5]): 152 | iface = node.connect_switch(switch1, str(ip + i)) 153 | nm.object.append(iface) 154 | if i: # except the first 155 | node.add_route(prefix='10.0.0.0', prefix_len=8, nexthop=ip) 156 | 157 | ip = ipaddress.ip_address(u"10.0.1.1") 158 | for i, node in enumerate([internet, m6, m7, m8]): 159 | iface = node.connect_switch(switch2, str(ip + i)) 160 | nm.object.append(iface) 161 | if i: # except the first 162 | node.add_route(prefix='10.0.0.0', prefix_len=8, nexthop=ip) 163 | 164 | registry.add_route(prefix='10.0.0.0', prefix_len=8, nexthop='10.0.0.1') 165 | gateway1.add_route(prefix='10.0.0.0', prefix_len=8, nexthop='10.1.0.1') 166 | gateway2.add_route(prefix='10.0.0.0', prefix_len=8, nexthop='10.2.0.1') 167 | internet.add_route(prefix='10.2.0.0', prefix_len=16, nexthop='10.2.0.2') 168 | 169 | MINIUPnP_CONF = Path(__file__).parent / 'miniupnpd.conf' 170 | gateway1.proc = gateway1.Popen(['miniupnpd', '-d', '-f', MINIUPnP_CONF, 171 | '-P', 'miniupnpd.pid', '-a', gateway1.if_s.name, 172 | '-i', g1_if_0.name], 173 | stdout=PIPE, stderr=PIPE) 174 | 175 | switch1.up = switch2.up = switch3.up = True 176 | nm.connectable_test() 177 | return nm 178 | 179 | def network_direct(): 180 | """one server and one client connect direct""" 181 | registry = Node() 182 | m0 = Node() 183 | nm = NetManager() 184 | nm.registries = {registry: [m0]} 185 | 186 | re_if_0, m_if_0 = nemu.P2PInterface.create_pair(registry, m0) 187 | 188 | registry._ip = u"10.1.2.1" 189 | re_if_0.add_v4_address(u"10.1.2.1", prefix_len=24) 190 | 191 | m_if_0.add_v4_address(u"10.1.2.2", prefix_len=24) 192 | re_if_0.up = m_if_0.up = True 193 | 194 | nm.connectable_test() 195 | return nm 196 | 197 | if __name__ == "__main__": 198 | nm = net_demo() 199 | time.sleep(1000000) 200 | -------------------------------------------------------------------------------- /re6st/tests/test_network/ping.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Script launched on machines from the demo with the option -p/--ping 4 | It uses Multiping to ping several IPs passed as arguments. 5 | After Re6st is stable, this script logs when it does not get response from a 6 | machine in a csv file stored in the directory of the machine in this format: 7 | time, sequence number, number of non-responding machines, ip of these machines 8 | ''' 9 | import argparse, errno, socket, time, sys 10 | from multiping import MultiPing 11 | 12 | PING_INTERVAL = 10 13 | PING_TIMEOUT = 4 14 | 15 | class MultiPing(MultiPing): 16 | # Patch of Multiping because it stays blocked to ipv4 17 | # emission when we want to ping only ipv6 addresses. 18 | # So we only keep the ipv6 part for the demo. 19 | # Bug issued: https://github.com/romana/multi-ping/issues/22 20 | def _read_all_from_socket(self, timeout): 21 | pkts = [] 22 | if self._ipv6_address_present: 23 | try: 24 | self._sock6.settimeout(timeout) 25 | while True: 26 | p = self._sock6.recv(128) 27 | pkts.append((bytearray(p), time.time())) 28 | self._sock6.settimeout(0) 29 | except socket.timeout: 30 | pass 31 | except socket.error as e: 32 | if e.errno != errno.EWOULDBLOCK: 33 | raise 34 | return pkts 35 | 36 | def main(): 37 | parser = argparse.ArgumentParser() 38 | parser.add_argument('-a', nargs = '+', help = 'the list of addresses to ping') 39 | parser.add_argument('--retry', action='store_true', help='retry ping unitl success') 40 | 41 | args = parser.parse_args() 42 | addrs = args.a 43 | retry = args.retry 44 | no_responses = "1" 45 | 46 | while retry and no_responses: 47 | mp = MultiPing(addrs) 48 | mp.send() 49 | _, no_responses = mp.receive(PING_TIMEOUT) 50 | 51 | sys.stdout.write(" ".join(no_responses)) 52 | 53 | 54 | if __name__ == '__main__': 55 | main() 56 | -------------------------------------------------------------------------------- /re6st/tests/test_network/re6st_wrap.py: -------------------------------------------------------------------------------- 1 | """wrap the deploy of re6st node, ease the creation of cert 2 | file and run of the node 3 | """ 4 | import errno 5 | import ipaddress 6 | import json 7 | import logging 8 | import re 9 | import shlex 10 | import shutil 11 | import sqlite3 12 | import sys 13 | import tempfile 14 | import time 15 | import weakref 16 | from subprocess import PIPE 17 | from pathlib import Path 18 | 19 | from re6st.tests import tools 20 | from re6st.tests import DEMO_PATH 21 | 22 | WORK_DIR = Path(__file__).parent / "temp_net_test" 23 | DH_FILE = DEMO_PATH / "dh2048.pem" 24 | 25 | PYTHON = shlex.quote(sys.executable) 26 | RE6STNET = PYTHON + " -m re6st.cli.node" 27 | RE6ST_REGISTRY = PYTHON + " -m re6st.cli.registry" 28 | RE6ST_CONF = PYTHON + " -m re6st.cli.conf" 29 | 30 | def initial(): 31 | """create the workplace""" 32 | if WORK_DIR.exists(): 33 | shutil.rmtree(str(WORK_DIR)) 34 | WORK_DIR.mkdir() 35 | 36 | def ip_to_serial(ip6): 37 | """convert ipv6 address to serial""" 38 | ip6 = ipaddress.IPv6Address(u"{}".format(ip6)) 39 | ip6 = "1{:x}".format(int(ip6)).rstrip('0') 40 | return int(ip6, 16) 41 | 42 | 43 | class Re6stRegistry: 44 | """class run a re6st-registry service on a namespace""" 45 | registry_seq = 0 46 | 47 | def __init__(self, node, ip6, client_number, port=80, recreate=False): 48 | self.node = node 49 | # TODO need set once 50 | self.ip = node.ip 51 | self.ip6 = ip6 52 | self.client_number = client_number 53 | self.port = port 54 | self.name = self.generate_name() 55 | 56 | self.path = WORK_DIR / self.name 57 | self.ca_key = self.path / "ca.key" 58 | # because re6st-conf will create ca.crt so use another name 59 | self.ca_crt = self.path / "ca.cert" 60 | self.log = self.path / "registry.log" 61 | self.db = self.path / "registry.db" 62 | self.run_path = tempfile.mkdtemp() 63 | 64 | if recreate and self.path.exists(): 65 | shutil.rmtree(str(self.path)) 66 | 67 | if not self.path.exists(): 68 | self.create_registry() 69 | 70 | # use hash to identify the registry 71 | with self.ca_key.open() as f: 72 | text = f.read() 73 | self.ident = hash(text) 74 | 75 | self.clean() 76 | 77 | self.run() 78 | # wait the servcice started 79 | p = self.node.Popen([sys.executable, '-c', """if 1: 80 | import socket, time 81 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 82 | while True: 83 | try: 84 | s.connect(('localhost', {})) 85 | break 86 | except socket.error: 87 | time.sleep(.1) 88 | """.format(self.port)]) 89 | 90 | now = time.time() 91 | while time.time() - now < 10: 92 | if p.poll() != None: 93 | break 94 | time.sleep(0.1) 95 | else: 96 | logging.error("registry failed to start, %s", self.name) 97 | p.destroy() 98 | raise Exception("registry failed to start") 99 | logging.info("re6st service started") 100 | 101 | @classmethod 102 | def generate_name(cls): 103 | cls.registry_seq += 1 104 | return "registry_{}".format(cls.registry_seq) 105 | 106 | @property 107 | def url(self): 108 | return "http://{ip}/".format(ip=self.ip) 109 | 110 | def create_registry(self): 111 | self.path.mkdir() 112 | tools.create_ca_file(str(self.ca_key), str(self.ca_crt), 113 | serial=ip_to_serial(self.ip6)) 114 | 115 | def run(self): 116 | cmd = ['--ca', self.ca_crt, '--key', self.ca_key, '--dh', DH_FILE, 117 | '--ipv4', '10.42.0.0/16', '8', '--logfile', self.log, '--db', self.db, 118 | '--run', self.run_path, '--hello', '4', '--mailhost', 's', '-v4', 119 | '--client-count', (self.client_number+1)//2, '--port', self.port] 120 | 121 | #PY3: convert PosixPath to str, can be remove in Python 3 122 | cmd = list(map(str, cmd)) 123 | 124 | cmd[:0] = RE6ST_REGISTRY.split() 125 | 126 | logging.debug("run registry %s at ns: %s with cmd: %s", 127 | self.name, self.node.pid, " ".join(cmd)) 128 | self.proc = self.node.Popen(cmd, stdout=PIPE, stderr=PIPE) 129 | 130 | def clean(self): 131 | """remove the file created last time""" 132 | try: 133 | self.log.unlink() 134 | except OSError as e: 135 | if e.errno != errno.ENOENT: 136 | raise 137 | 138 | def terminate(self): 139 | try: 140 | logging.debug("teminate process %s", self.proc.pid) 141 | with self.proc as p: 142 | p.destroy() 143 | except: 144 | pass 145 | 146 | def __del__(self): 147 | self.terminate() 148 | 149 | 150 | class Re6stNode: 151 | """class run a re6stnet service on a namespace""" 152 | node_seq = 0 153 | 154 | def __init__(self, node, registry, name=None, recreate=False): 155 | """ 156 | node: nemu node 157 | name: name for res6st node 158 | """ 159 | self.name = name or self.generate_name() 160 | self.node = node 161 | self.registry = weakref.proxy(registry) 162 | 163 | self.path = WORK_DIR / self.name 164 | self.email = self.name + "@example.com" 165 | 166 | if self.name == self.registry.name: 167 | self.run_path = self.registry.run_path 168 | else: 169 | self.run_path = tempfile.mkdtemp() 170 | 171 | self.log = self.path / "re6stnet.log" 172 | self.crt = self.path / "cert.crt" 173 | self.key = self.path / 'cert.key' 174 | self.console = self.run_path + "/console.sock" 175 | self.data_file = self.path / "data.json" # contain data for restart node 176 | 177 | # condition, node of the registry 178 | if self.name == self.registry.name: 179 | self.ip6 = self.registry.ip6 180 | if not self.crt.exists(): 181 | self.create_node() 182 | else: 183 | # if ca file changed, we need recreate node file 184 | if self.data_file.exists(): 185 | with self.data_file.open() as f: 186 | data = json.load(f) 187 | self.ip6 = data.get("ip6") 188 | recreate = data.get('hash') != self.registry.ident 189 | else: 190 | recreate = True 191 | 192 | if recreate and self.path.exists(): 193 | shutil.rmtree(str(self.path)) 194 | 195 | if not self.path.exists(): 196 | self.path.mkdir() 197 | self.create_node() 198 | 199 | logging.debug("%s's subnet is %s", self.name, self.ip6) 200 | 201 | self.clean() 202 | 203 | def __repr__(self): 204 | return self.name 205 | 206 | @classmethod 207 | def generate_name(cls): 208 | cls.node_seq += 1 209 | return "node_{}".format(cls.node_seq) 210 | 211 | def create_node(self): 212 | """create necessary file for node""" 213 | logging.info("create dir of node %s", self.name) 214 | cmd = ["--registry", self.registry.url, '--email', self.email] 215 | cmd[:0] = RE6ST_CONF.split() 216 | p = self.node.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, 217 | cwd=str(self.path)) 218 | # read token 219 | db = sqlite3.connect(str(self.registry.db), isolation_level=None) 220 | token = None 221 | for _ in range(100): 222 | time.sleep(.1) 223 | token = db.execute("SELECT token FROM token WHERE email=?", 224 | (self.email,)).fetchone() 225 | if token: 226 | break 227 | else: 228 | p.destroy() 229 | raise Exception("can't connect to the Register") 230 | 231 | out, _ = p.communicate(str(token[0])) 232 | # logging.debug("re6st-conf output: {}".format(out)) 233 | # find the ipv6 subnet of node 234 | self.ip6 = re.search('(?<=subnet: )[0-9:a-z]+', 235 | out.decode("utf-8")).group(0) 236 | data = {'ip6': self.ip6, 'hash': self.registry.ident} 237 | with open(str(self.data_file), 'w') as f: 238 | json.dump(data, f) 239 | logging.info("create dir of node %s finish", self.name) 240 | 241 | def run(self, *args): 242 | """execute re6stnet""" 243 | cmd = ['--log', self.path, '--run', self.run_path, '--state', self.path, 244 | '--dh', DH_FILE, '--ca', self.registry.ca_crt, '--cert', self.crt, 245 | '--key', self.key, '-v4', '--registry', self.registry.url, 246 | '--console', self.console] 247 | #PY3: same as for Re6stRegistry.run 248 | cmd = list(map(str, cmd)) 249 | cmd[:0] = RE6STNET.split() 250 | 251 | cmd += args 252 | logging.debug("run node %s at ns: %s with cmd: %s", 253 | self.name, self.node.pid, " ".join(cmd)) 254 | # if len(args) > 4 : 255 | # self.proc = self.node.Popen(cmd) 256 | # else: 257 | self.proc = self.node.Popen(cmd, stdout=PIPE, stderr=PIPE) 258 | 259 | def clean(self): 260 | """remove the file created last time""" 261 | for name in ["re6stnet.log", "babeld.state", "cache.db", "babeld.log"]: 262 | f = self.path / name 263 | try: 264 | f.unlink() 265 | except OSError as e: 266 | if e.errno != errno.ENOENT: 267 | raise 268 | 269 | def stop(self): 270 | """stop running re6stnet process""" 271 | logging.debug("%s teminate process %s", self.name, self.proc.pid) 272 | with self.proc as p: 273 | p.destroy() 274 | 275 | def __del__(self): 276 | """teminate process and rm temp dir""" 277 | try: 278 | self.stop() 279 | except Exception as e: 280 | logging.warning("%s: %s", self.name, e) 281 | 282 | # re6stnet seems auto clean the tempdir 283 | # try: 284 | # shutil.rmtree(self.run_path) 285 | # except Exception as e: 286 | # logging.error("{}: {}".format(self.name, e)) 287 | -------------------------------------------------------------------------------- /re6st/tests/test_network/test_net.py: -------------------------------------------------------------------------------- 1 | """contain ping-test for re6set net""" 2 | import os 3 | import sys 4 | import unittest 5 | import time 6 | import psutil 7 | import logging 8 | import random 9 | from pathlib import Path 10 | 11 | from . import network_build, re6st_wrap 12 | 13 | PING_PATH = str(Path(__file__).parent.resolve() / "ping.py") 14 | 15 | def wait_stable(nodes, timeout=240): 16 | """try use ping6 from each node to the other until ping success to all the 17 | other nodes 18 | Args: 19 | timeout: int, the time for wait 20 | 21 | return: 22 | True if success 23 | """ 24 | logging.info("wait all node stable, timeout: %s", timeout) 25 | now = time.time() 26 | ips = {node.ip6: node.name for node in nodes} 27 | # start the ping processs 28 | for node in nodes: 29 | sub_ips = set(ips) - {node.ip6} 30 | node.ping_proc = node.node.Popen( 31 | [sys.executable, PING_PATH, '--retry', '-a'] + list(sub_ips), 32 | env=os.environ) 33 | 34 | # check all the node network can ping each other, in order reverse 35 | unfinished = list(nodes) 36 | while unfinished: 37 | for i in range(len(unfinished)-1, -1, -1): 38 | node = unfinished[i] 39 | if node.ping_proc.poll() is not None: 40 | logging.debug("%s 's network is stable", node.name) 41 | unfinished.pop(i) 42 | time.sleep(0.5) 43 | 44 | if time.time() - now > timeout: 45 | for node in unfinished: 46 | node.ping_proc.destroy() 47 | logging.warning("%s can't ping to all the nodes", unfinished) 48 | return False 49 | logging.info("wait time cost: %s", time.time() - now) 50 | return True 51 | 52 | @unittest.skipIf(os.geteuid(), "Using root or creating a user namespace") 53 | class TestNet(unittest.TestCase): 54 | """ network test case""" 55 | 56 | @classmethod 57 | def setUpClass(cls): 58 | """create work dir""" 59 | logging.basicConfig(level=logging.INFO) 60 | 61 | def setUp(self): 62 | re6st_wrap.initial() 63 | 64 | def deploy_re6st(self, nm, recreate=False): 65 | net = nm.registries 66 | nodes = [] 67 | registries = [] 68 | re6st_wrap.Re6stRegistry.registry_seq = 0 69 | re6st_wrap.Re6stNode.node_seq = 0 70 | for registry in net: 71 | reg = re6st_wrap.Re6stRegistry(registry, "2001:db8:42::", 72 | len(net[registry]), 73 | recreate=recreate) 74 | reg_node = re6st_wrap.Re6stNode(registry, reg, name=reg.name) 75 | registries.append(reg) 76 | reg_node.run("--gateway", "--disable-proto", "none", 77 | "--ip", registry.ip) 78 | nodes.append(reg_node) 79 | for m in net[registry]: 80 | node = re6st_wrap.Re6stNode(m, reg) 81 | node.run("-i" + m.iface.name) 82 | nodes.append(node) 83 | 84 | def clean_re6st(): 85 | for node in nodes: 86 | node.node.destroy() 87 | node.stop() 88 | 89 | for reg in registries: 90 | reg.terminate() 91 | 92 | self.addCleanup(clean_re6st) 93 | 94 | return nodes, registries 95 | 96 | @classmethod 97 | def tearDownClass(cls): 98 | """watch any process leaked after tests""" 99 | logging.basicConfig(level=logging.WARNING) 100 | for p in psutil.Process().children(): 101 | logging.debug("unterminate ps, name: %s, pid: %s, status: %s, cmd: %s", 102 | p.name(), p.pid, p.status(), p.cmdline()) 103 | p.terminate() 104 | # try: 105 | # p.kill() 106 | # except: 107 | # pass 108 | 109 | def test_ping_router(self): 110 | """create a network in a net segment, test the connectivity by ping 111 | """ 112 | nm = network_build.net_route() 113 | nodes, _ = self.deploy_re6st(nm) 114 | 115 | wait_stable(nodes, 40) 116 | time.sleep(10) 117 | 118 | self.assertTrue(wait_stable(nodes, 30), " ping test failed") 119 | 120 | @unittest.skip("usually failed due to UPnP problem") 121 | def test_reboot_one_machine(self): 122 | """create a network demo, wait the net stable, reboot on machine, 123 | then test if network recover, this test seems always failed 124 | """ 125 | nm = network_build.net_demo() 126 | nodes, _ = self.deploy_re6st(nm) 127 | 128 | wait_stable(nodes, 100) 129 | 130 | # stop on machine randomly 131 | index = int(random.random() * 7) + 1 132 | machine = nodes[index] 133 | machine.stop() 134 | time.sleep(5) 135 | machine.run("-i" + machine.node.iface.name) 136 | logging.info("restart %s", machine.name) 137 | 138 | self.assertTrue(wait_stable(nodes, 400), "network can't recover") 139 | 140 | def test_reboot_one_machine_router(self): 141 | """create a network router, wait the net stable, reboot on machine, 142 | then test if network recover, 143 | """ 144 | nm = network_build.net_route() 145 | nodes, _ = self.deploy_re6st(nm) 146 | 147 | wait_stable(nodes, 40) 148 | 149 | # stop on machine randomly 150 | index = int(random.random() * 2) + 1 151 | machine = nodes[index] 152 | machine.stop() 153 | time.sleep(5) 154 | machine.run("-i" + machine.node.iface.name) 155 | logging.info("restart %s", machine.name) 156 | 157 | self.assertTrue(wait_stable(nodes, 100), "network can't recover") 158 | 159 | 160 | 161 | if __name__ == "__main__": 162 | logging.basicConfig(level=logging.DEBUG, filename='test.log', filemode='w', 163 | format='%(asctime)s %(levelname)s %(message)s', 164 | datefmt='%I:%M:%S') 165 | unittest.main(verbosity=3) 166 | -------------------------------------------------------------------------------- /re6st/tests/test_unit/__init__.py: -------------------------------------------------------------------------------- 1 | """Re6st unittest module 2 | """ -------------------------------------------------------------------------------- /re6st/tests/test_unit/test_conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ unit test for re6st-conf 3 | """ 4 | 5 | import os 6 | import sys 7 | import unittest 8 | from shutil import rmtree 9 | from io import StringIO 10 | from mock import patch 11 | from OpenSSL import crypto 12 | 13 | from re6st.cli import conf 14 | from re6st.tests.tools import generate_cert, serial2prefix, create_ca_file 15 | 16 | 17 | # gloable value from conf.py 18 | conf_path = 're6stnet.conf' 19 | ca_path = 'ca.crt' 20 | cert_path = 'cert.crt' 21 | key_path = 'cert.key' 22 | 23 | # TODO test for is needed 24 | 25 | class TestConf(unittest.TestCase): 26 | """ Unit test case for re6st-conf""" 27 | 28 | @classmethod 29 | def setUpClass(cls): 30 | # because conf will change directory 31 | cls.origin_dir = os.getcwd() 32 | cls.work_dir = "temp" 33 | 34 | if not os.path.exists(cls.work_dir): 35 | os.makedirs(cls.work_dir) 36 | 37 | # mocked server cert and pkey 38 | cls.pkey, cls.cert = create_ca_file(os.devnull, os.devnull) 39 | cls.fingerprint = "".join(cls.cert.digest("sha1").decode().split(":")) 40 | # client.getCa should return a string form cert 41 | cls.cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cls.cert) 42 | 43 | cls.command = "re6st-conf --registry http://localhost/" \ 44 | " --dir %s" % cls.work_dir 45 | 46 | cls.serial = 0 47 | 48 | cls.stdout = sys.stdout 49 | cls.null = open(os.devnull, 'w') 50 | sys.stdout = cls.null 51 | 52 | 53 | @classmethod 54 | def tearDownClass(cls): 55 | # remove work directory 56 | rmtree(cls.work_dir) 57 | cls.null.close() 58 | sys.stdout = cls.stdout 59 | 60 | def setUp(self): 61 | patcher = patch("re6st.registry.RegistryClient") 62 | self.addCleanup(patcher.stop) 63 | self.client = patcher.start()() 64 | 65 | self.client.getCa.return_value = self.cert 66 | prefix = serial2prefix(self.serial) 67 | self.client.requestCertificate.side_effect = \ 68 | lambda _, req: generate_cert(self.cert, self.pkey, req, prefix, self.serial) 69 | self.serial += 1 70 | 71 | def tearDown(self): 72 | # go back to original dir 73 | os.chdir(self.origin_dir) 74 | 75 | @patch("builtins.input") 76 | def test_basic(self, mock_raw_input): 77 | """ go through all the step 78 | getCa, requestToken, requestCertificate 79 | """ 80 | mail = "example@email.com" 81 | token = "a_token" 82 | mock_raw_input.side_effect = [mail, token] 83 | command = self.command \ 84 | + " --fingerprint sha1:%s" % self.fingerprint \ 85 | + " --req L lille" 86 | sys.argv = command.split() 87 | 88 | conf.main() 89 | 90 | self.client.requestToken.assert_called_once_with(mail) 91 | self.assertEqual(self.client.requestCertificate.call_args[0][0], 92 | token) 93 | # created file part 94 | self.assertTrue(os.path.exists(ca_path)) 95 | self.assertTrue(os.path.exists(key_path)) 96 | self.assertTrue(os.path.exists(cert_path)) 97 | self.assertTrue(os.path.exists(conf_path)) 98 | 99 | def test_fingerprint_mismatch(self): 100 | """ wrong fingerprint with same size, 101 | """ 102 | command = self.command \ 103 | + " --fingerprint sha1:a1861330f1299b98b529fa52c3d8e5d1a94dc000" 104 | sys.argv = command.split() 105 | 106 | with self.assertRaises(SystemExit) as e: 107 | conf.main() 108 | 109 | self.assertIn("fingerprint doesn't match", str(e.exception)) 110 | 111 | def test_ca_only(self): 112 | """ only create ca file and exit 113 | """ 114 | command = self.command + " --ca-only" 115 | sys.argv = command.split() 116 | 117 | with self.assertRaises(SystemExit): 118 | conf.main() 119 | 120 | self.assertTrue(os.path.exists(ca_path)) 121 | 122 | def test_anonymous(self): 123 | """ with args anonymous, so script will use '' as token 124 | """ 125 | command = self.command + " --anonymous" 126 | sys.argv = command.split() 127 | 128 | conf.main() 129 | 130 | self.assertEqual(self.client.requestCertificate.call_args[0][0], 131 | '') 132 | 133 | def test_anonymous_failed(self): 134 | """ with args anonymous and token, so script will failed 135 | """ 136 | command = self.command + " --anonymous" \ 137 | + " --token a" 138 | sys.argv = command.split() 139 | text = StringIO() 140 | old_err = sys.stderr 141 | sys.stderr = text 142 | 143 | with self.assertRaises(SystemExit): 144 | conf.main() 145 | 146 | # check the error message 147 | self.assertIn("anonymous conflicts", text.getvalue()) 148 | 149 | sys.stderr = old_err 150 | 151 | def test_req_reserved(self): 152 | """ with args req, but contain reserved value 153 | """ 154 | command = self.command + " --req CN 1111" 155 | sys.argv = command.split() 156 | 157 | with self.assertRaises(SystemExit) as e: 158 | conf.main() 159 | 160 | self.assertIn("CN field", str(e.exception)) 161 | 162 | def test_get_null_cert(self): 163 | """ simulate fake token, and get null cert 164 | """ 165 | command = self.command + " --token a" 166 | sys.argv = command.split() 167 | self.client.requestCertificate.side_effect = "", 168 | 169 | with self.assertRaises(SystemExit) as e: 170 | conf.main() 171 | 172 | self.assertIn("invalid or expired token", str(e.exception)) 173 | 174 | 175 | if __name__ == "__main__": 176 | unittest.main() 177 | -------------------------------------------------------------------------------- /re6st/tests/test_unit/test_registry_client.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import unittest 4 | import hmac 5 | import http.client 6 | import base64 7 | import hashlib 8 | from http import HTTPStatus 9 | from mock import Mock, patch 10 | 11 | from re6st import registry 12 | 13 | class TestRegistryClient(unittest.TestCase): 14 | 15 | @classmethod 16 | def setUpClass(cls): 17 | server_url = "http://10.0.0.2/" 18 | cls.client = registry.RegistryClient(server_url) 19 | cls.client._conn = Mock() 20 | 21 | def test_init(self): 22 | url1 = "https://localhost/example/" 23 | url2 = "http://10.0.0.2/" 24 | 25 | client1 = registry.RegistryClient(url1) 26 | client2 = registry.RegistryClient(url2) 27 | 28 | self.assertEqual(client1._path, "/example") 29 | self.assertEqual(client1._conn.host, "localhost") 30 | self.assertIsInstance(client1._conn, http.client.HTTPSConnection) 31 | self.assertIsInstance(client2._conn, http.client.HTTPConnection) 32 | 33 | def test_rpc_hello(self): 34 | prefix = "0000000011111111" 35 | protocol = "7" 36 | body = "a_hmac_key" 37 | query = "/hello?client_prefix=0000000011111111&protocol=7" 38 | response = fakeResponse(body, HTTPStatus.OK) 39 | self.client._conn.getresponse.return_value = response 40 | 41 | res = self.client.hello(prefix, protocol) 42 | 43 | self.assertEqual(res, body) 44 | conn = self.client._conn 45 | conn.putrequest.assert_called_once_with('GET', query, skip_accept_encoding=1) 46 | conn.close.assert_not_called() 47 | conn.endheaders.assert_called_once() 48 | 49 | def test_rpc_with_cn(self): 50 | query = "/getNetworkConfig?cn=0000000011111111" 51 | cn = "0000000011111111" 52 | # hmac part 53 | self.client._hmac = None 54 | self.client.hello = Mock(return_value = "aaabbb") 55 | self.client.cert = Mock() 56 | key = b"this_is_a_key" 57 | self.client.cert.decrypt.return_value = key 58 | h = hmac.HMAC(key, query.encode(), hashlib.sha1).digest() 59 | key = hashlib.sha1(key).digest() 60 | # response part 61 | body = b'this is a body' 62 | response = fakeResponse(body, HTTPStatus.NO_CONTENT) 63 | response.msg = dict(Re6stHMAC=base64.b64encode( 64 | hmac.HMAC(key, body, hashlib.sha1).digest())) 65 | self.client._conn.getresponse.return_value = response 66 | 67 | res = self.client.getNetworkConfig(cn) 68 | 69 | self.client.cert.verify.assert_called_once_with("bbb", "aaa") 70 | self.assertEqual(self.client._hmac, hashlib.sha1(key).digest()) 71 | conn = self.client._conn 72 | conn.putheader.assert_called_with("Re6stHMAC", base64.b64encode(h)) 73 | conn.close.assert_called_once() 74 | self.assertEqual(res, body) 75 | 76 | 77 | class fakeResponse: 78 | 79 | def __init__(self, body, status, reason = None): 80 | self.body = body 81 | self.status = status 82 | self.reason = reason 83 | 84 | def read(self): 85 | return self.body 86 | 87 | 88 | if __name__ == "__main__": 89 | unittest.main() 90 | -------------------------------------------------------------------------------- /re6st/tests/test_unit/test_tunnel/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nexedi/re6stnet/9689045ca24539913b409754a4fdcd22415cfb89/re6st/tests/test_unit/test_tunnel/__init__.py -------------------------------------------------------------------------------- /re6st/tests/test_unit/test_tunnel/test_base_tunnel_manager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | import unittest 5 | import time 6 | from mock import patch, Mock 7 | 8 | 9 | from re6st import tunnel 10 | from re6st import x509 11 | from re6st import cache 12 | 13 | from re6st.tests import tools 14 | 15 | class testBaseTunnelManager(unittest.TestCase): 16 | 17 | @classmethod 18 | def setUpClass(cls): 19 | ca_key, ca = tools.create_ca_file("ca.key", "ca.cert") 20 | tools.create_cert_file("node.key", "node.cert", ca, ca_key, "00000001", 1) 21 | cls.cert = x509.Cert("ca.cert", "node.key", "node.cert") 22 | cls.control_socket = "babeld.sock" 23 | 24 | 25 | def setUp(self): 26 | patcher = patch("re6st.cache.Cache") 27 | pacher_sock = patch("socket.socket") 28 | self.addCleanup(patcher.stop) 29 | self.addCleanup(pacher_sock.stop) 30 | self.cache = patcher.start()() 31 | self.sock = pacher_sock.start() 32 | self.cache.same_country = False 33 | self.cache.valid_until = None 34 | 35 | address = [(2, [('10.0.0.2', '1194', 'udp'), ('10.0.0.2', '1194', 'tcp')])] 36 | self.tunnel = tunnel.BaseTunnelManager(self.control_socket, 37 | self.cache, self.cert, None, address) 38 | 39 | def tearDown(self): 40 | self.tunnel.close() 41 | del self.tunnel 42 | 43 | #TODO selectTimeout in contain callback, removing, update 44 | 45 | @patch("re6st.tunnel.BaseTunnelManager.selectTimeout") 46 | def test_invalidatePeers(self, selectTimeout): 47 | """normal case, stop_date: p2 < now < p1 < p3 48 | expect: 49 | _peers -> [p1, p3] 50 | next = p1.stoptime 51 | """ 52 | p1 = x509.Peer("00") 53 | p2 = x509.Peer("01") 54 | p3 = x509.Peer("10") 55 | p1.stop_date = time.time() + 1000 56 | p2.stop_date = 1 57 | p3.stop_date = p1.stop_date + 500 58 | self.tunnel._peers = [p1, p2, p3] 59 | 60 | self.tunnel.invalidatePeers() 61 | 62 | self.assertEqual(self.tunnel._peers, [p1, p3]) 63 | selectTimeout.assert_called_once_with(p1.stop_date, self.tunnel.invalidatePeers) 64 | 65 | 66 | # Because _makeTunnel is defined in sub class of BaseTunnelManager, so i comment 67 | # the follow test 68 | # @patch("re6st.tunnel.BaseTunnelManager._makeTunnel", create=True) 69 | # def test_processPacket_address_with_msg_peer(self, makeTunnel): 70 | # """code is 1, peer and msg not none """ 71 | # c = b"\x01" 72 | # msg = "address" 73 | # peer = x509.Peer("000001") 74 | # self.tunnel._connecting = {peer} 75 | 76 | # self.tunnel._processPacket(c + msg, peer) 77 | 78 | # self.cache.addPeer.assert_called_once_with(peer, msg) 79 | # self.assertFalse(self.tunnel._connecting) 80 | # makeTunnel.assert_called_once_with(peer, msg) 81 | 82 | 83 | def test_processPacket_address(self): 84 | """code is 1, for address. And peer or msg are none""" 85 | c = b"\x01" 86 | self.tunnel._address = {1: "1,1", 2: "2,2"} 87 | 88 | res = self.tunnel._processPacket(c) 89 | 90 | self.assertEqual(res, b"1,1;2,2") 91 | 92 | 93 | def test_processPacket_address_with_peer(self): 94 | """code is 1, peer is not none, msg is none 95 | in my opion, this function return address in form address,port,portocl 96 | and each address join by ; 97 | it will truncate address which has more than 3 element 98 | """ 99 | c = b"\x01" 100 | peer = x509.Peer("000001") 101 | peer.protocol = 1 102 | self.tunnel._peers.append(peer) 103 | self.tunnel._address = {1: "1,1,1;0,0,0", 2: "2,2,2,2"} 104 | 105 | res = self.tunnel._processPacket(c, peer) 106 | 107 | self.assertEqual(res, b"1,1,1;0,0,0;2,2,2") 108 | 109 | @patch("re6st.x509.Cert.verifyVersion", Mock(return_value=True)) 110 | @patch("re6st.tunnel.BaseTunnelManager.selectTimeout") 111 | def test_processPacket_version(self, selectTimeout): 112 | """code is 0, for network version, peer is not none 113 | 2 case, one modify the version, one not 114 | """ 115 | c = b"\x00" 116 | peer = x509.Peer("000001") 117 | version1 = b"00003" 118 | version2 = b"00007" 119 | self.tunnel._version = version3 = b"00005" 120 | self.tunnel._peers.append(peer) 121 | 122 | res = self.tunnel._processPacket(c + version1, peer) 123 | self.tunnel._processPacket(c + version2, peer) 124 | 125 | self.assertEqual(res, version3) 126 | self.assertEqual(self.tunnel._version, version2) 127 | self.assertEqual(peer.version, version2) 128 | self.assertEqual(selectTimeout.call_args[0][1], self.tunnel.newVersion) 129 | 130 | 131 | 132 | 133 | if __name__ == "__main__": 134 | unittest.main() 135 | 136 | 137 | -------------------------------------------------------------------------------- /re6st/tests/test_unit/test_tunnel/test_multi_gateway_manager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | import unittest 5 | from mock import patch 6 | 7 | from re6st import tunnel 8 | 9 | 10 | class testMultGatewayManager(unittest.TestCase): 11 | 12 | def setUp(self): 13 | self.manager = tunnel.MultiGatewayManager(lambda x:x+x) 14 | patcher = patch("subprocess.check_call") 15 | self.addCleanup(patcher.stop) 16 | self.sub = patcher.start() 17 | 18 | @patch("logging.trace", create=True) 19 | def test_add(self, log_trace): 20 | """add new dest twice""" 21 | dest = "dest" 22 | 23 | self.manager.add(dest, True) 24 | self.manager.add(dest, True) 25 | 26 | self.assertEqual(self.manager[dest][1], 1) 27 | self.sub.assert_called_once() 28 | cmd = log_trace.call_args[0][1] 29 | self.assertIn(dest+dest, cmd) 30 | self.assertIn("add", cmd) 31 | 32 | 33 | def test_add_null_route(self): 34 | """ add two dest which don't call ip route""" 35 | dest1 = "dest1" 36 | dest2 = "" 37 | 38 | self.manager.add(dest1, False) 39 | self.manager.add(dest2, True) 40 | 41 | self.sub.assert_not_called() 42 | 43 | 44 | @patch("logging.trace", create=True) 45 | def test_remove(self, log_trace): 46 | "remove a dest twice" 47 | dest = "dest" 48 | gw = "gw" 49 | self.manager[dest] = [gw,1] 50 | 51 | self.manager.remove(dest) 52 | self.assertEqual(self.manager[dest][1], 0) 53 | 54 | self.manager.remove(dest) 55 | 56 | self.sub.assert_called_once() 57 | self.assertIsNone(self.manager.get(dest)) 58 | cmd = log_trace.call_args[0][1] 59 | self.assertIn(gw, cmd) 60 | self.assertIn("del", cmd) 61 | 62 | 63 | def test_remove_null_gw(self): 64 | """ remove a dest which don't have gw""" 65 | dest = "dest" 66 | gw = "" 67 | self.manager[dest] = [gw, 0] 68 | 69 | self.manager.remove(dest) 70 | 71 | self.assertIsNone(self.manager.get(dest)) 72 | self.sub.assert_not_called() 73 | 74 | 75 | if __name__ == "__main__": 76 | unittest.main() -------------------------------------------------------------------------------- /re6st/tests/tools.py: -------------------------------------------------------------------------------- 1 | import time 2 | from OpenSSL import crypto 3 | 4 | from re6st import registry, x509 5 | 6 | 7 | def generate_csr(): 8 | """generate a certificate request 9 | 10 | return: 11 | crypto.Pekey and crypto.X509Req both in pem format 12 | """ 13 | key = crypto.PKey() 14 | key.generate_key(crypto.TYPE_RSA, 2048) 15 | req = crypto.X509Req() 16 | req.set_pubkey(key) 17 | req.get_subject().CN = "test ca" 18 | req.sign(key, 'sha256') 19 | csr = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req) 20 | pkey = crypto.dump_privatekey(crypto.FILETYPE_PEM, key) 21 | return pkey, csr 22 | 23 | 24 | def generate_cert(ca, ca_key, csr, prefix, serial, not_after=None): 25 | """generate a certificate 26 | 27 | return 28 | crypto.X509Cert in pem format 29 | """ 30 | if type(ca) is bytes: 31 | ca = crypto.load_certificate(crypto.FILETYPE_PEM, ca) 32 | if type(ca_key) is bytes: 33 | ca_key = crypto.load_privatekey(crypto.FILETYPE_PEM, ca_key) 34 | req = crypto.load_certificate_request(crypto.FILETYPE_PEM, csr) 35 | 36 | cert = crypto.X509() 37 | cert.gmtime_adj_notBefore(0) 38 | if not_after: 39 | cert.set_notAfter( 40 | time.strftime("%Y%m%d%H%M%SZ", time.gmtime(not_after)).encode()) 41 | else: 42 | cert.gmtime_adj_notAfter(registry.RegistryServer.cert_duration) 43 | subject = req.get_subject() 44 | if prefix: 45 | subject.CN = prefix2cn(prefix) 46 | cert.set_subject(req.get_subject()) 47 | cert.set_issuer(ca.get_subject()) 48 | cert.set_pubkey(req.get_pubkey()) 49 | cert.set_serial_number(serial) 50 | cert.sign(ca_key, 'sha512') 51 | return crypto.dump_certificate(crypto.FILETYPE_PEM, cert) 52 | 53 | def create_cert_file(pkey_file, cert_file, ca, ca_key, prefix, serial): 54 | pkey, csr = generate_csr() 55 | cert = generate_cert(ca, ca_key, csr, prefix, serial) 56 | with open(pkey_file, 'wb') as f: 57 | f.write(pkey) 58 | with open(cert_file, 'wb') as f: 59 | f.write(cert) 60 | 61 | return pkey, cert 62 | 63 | 64 | 65 | def create_ca_file(pkey_file, cert_file, serial=0x120010db80042): 66 | """create key and ca file with specify name 67 | return key, cert in pem format """ 68 | key = crypto.PKey() 69 | key.generate_key(crypto.TYPE_RSA, 2048) 70 | cert = crypto.X509() 71 | cert.gmtime_adj_notBefore(0) 72 | cert.gmtime_adj_notAfter(registry.RegistryServer.cert_duration) 73 | subject= cert.get_subject() 74 | subject.C = "FR" 75 | subject.ST = "Lille" 76 | subject.L = "Lille" 77 | subject.O = "nexedi" 78 | subject.CN = "TEST-CA" 79 | cert.set_issuer(cert.get_subject()) 80 | cert.set_serial_number(serial) 81 | cert.set_pubkey(key) 82 | cert.sign(key, "sha512") 83 | 84 | with open(pkey_file, 'wb') as pkey_file: 85 | pkey_file.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key)) 86 | with open(cert_file, 'wb') as cert_file: 87 | cert_file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) 88 | 89 | return key, cert 90 | 91 | 92 | def prefix2cn(prefix: str) -> str: 93 | return "%u/%u" % (int(prefix, 2), len(prefix)) 94 | 95 | def serial2prefix(serial: int) -> str: 96 | return bin(serial)[2:].rjust(16, '0') 97 | 98 | def decrypt(pkey: bytes, incontent: bytes) -> bytes: 99 | pkey = x509.load_pem_private_key(pkey, password=None) 100 | return pkey.decrypt(incontent, x509.PADDING) 101 | -------------------------------------------------------------------------------- /re6st/upnpigd.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | import logging, random, socket, time 3 | import miniupnpc 4 | 5 | 6 | class UPnPException(Exception): 7 | pass 8 | 9 | 10 | class Forwarder: 11 | """ 12 | External port is chosen randomly between 32768 & 49151 included. 13 | """ 14 | 15 | next_refresh = 0 16 | _next_retry = -1 17 | _lcg_n = 0 18 | 19 | @classmethod 20 | def _getExternalPort(cls) -> int: 21 | # Since _refresh() does not test all ports in a row, we prefer to 22 | # return random ports to maximize the chance to find a free port. 23 | # A linear congruential generator should be random enough, without 24 | # wasting memory/cpu by keeping a full 'shuffle'd list of integers. 25 | n = cls._lcg_n 26 | if not n: 27 | cls._lcg_a = 1 + 4 * random.randrange(0, 2048) 28 | cls._lcg_c = 1 + 2 * random.randrange(0, 4096) 29 | n = cls._lcg_n = (n * cls._lcg_a + cls._lcg_c) % 8192 30 | return 32768 + n 31 | 32 | def __init__(self, description): 33 | self._description = description 34 | self._u = miniupnpc.UPnP() 35 | self._u.discoverdelay = 200 36 | self._rules = [] 37 | 38 | def __getattr__(self, name: str): 39 | wrapped = getattr(self._u, name) 40 | def wrapper(*args, **kw): 41 | try: 42 | return wrapped(*args, **kw) 43 | except Exception as e: 44 | raise UPnPException(str(e)) 45 | return wraps(wrapped)(wrapper) 46 | 47 | def select(self, r, w, t): 48 | t.append((self.next_refresh, self.refresh)) 49 | 50 | def checkExternalIp(self, ip=None): 51 | if not ip: 52 | ip = self.refresh() 53 | try: 54 | socket.inet_aton(ip) 55 | except (socket.error, TypeError): 56 | ip = () 57 | return socket.AF_INET, ip and [(ip, str(port or local), proto) 58 | for local, proto, port in self._rules] 59 | 60 | def addRule(self, local_port, proto): 61 | self._rules.append([local_port, proto, None]) 62 | 63 | def refresh(self): 64 | if self._next_retry: 65 | if time.time() < self._next_retry: 66 | return 67 | self._next_retry = 0 68 | else: 69 | try: 70 | return self._refresh() 71 | except UPnPException as e: 72 | logging.debug("UPnP failure", exc_info=True) 73 | self.clear() 74 | try: 75 | try: 76 | self.discover() 77 | except UPnPException as e: 78 | if str(e) != 'Success': 79 | raise 80 | # WKRD: it likely found no device but let selectigd raise 81 | # with a good message 82 | self.selectigd() 83 | return self._refresh() 84 | except UPnPException as e: 85 | self.next_refresh = self._next_retry = time.time() + 60 86 | logging.info(e) 87 | self.clear() 88 | 89 | def _refresh(self): 90 | t = time.time() 91 | force = self.next_refresh < t 92 | if force: 93 | self.next_refresh = t + 500 94 | logging.debug('Refreshing port forwarding') 95 | ip = self.externalipaddress() 96 | lanaddr = self._u.lanaddr 97 | # It's too expensive (CPU/network) to try a full range every minute 98 | # when the router really has no free port. Or with slow routers, it 99 | # can take more than 15 minutes. So let's use some saner limits: 100 | t += 1 101 | retry = 15 102 | for r in self._rules: 103 | local, proto, port = r 104 | if port and not force: 105 | continue 106 | desc = '%s (%u/%s)' % (self._description, local, proto) 107 | args = proto.upper(), lanaddr, local, desc, '' 108 | while True: 109 | if port is None: 110 | if not retry or t < time.time(): 111 | raise UPnPException('No free port to redirect %s' 112 | % desc) 113 | retry -= 1 114 | port = self._getExternalPort() 115 | try: 116 | self.addportmapping(port, *args) 117 | break 118 | except UPnPException as e: 119 | if str(e) != 'ConflictInMappingEntry': 120 | raise 121 | port = None 122 | if r[2] != port: 123 | logging.debug('%s forwarded from %s:%u', desc, ip, port) 124 | r[2] = port 125 | return ip 126 | 127 | def clear(self): 128 | for r in self._rules: 129 | port = r[2] 130 | if port: 131 | r[2] = None 132 | try: 133 | self.deleteportmapping(port, r[1].upper()) 134 | except UPnPException: 135 | pass 136 | -------------------------------------------------------------------------------- /re6st/utils.py: -------------------------------------------------------------------------------- 1 | import argparse, errno, fcntl, hashlib, logging, os, select as _select 2 | import shlex, signal, socket, sqlite3, struct, subprocess 3 | import sys, textwrap, threading, time, traceback 4 | from collections.abc import Iterator, Mapping 5 | 6 | HMAC_LEN = len(hashlib.sha1(b'').digest()) 7 | 8 | class ReexecException(Exception): 9 | pass 10 | 11 | logging_levels = logging.WARNING, logging.INFO, logging.DEBUG, 5 12 | 13 | class FileHandler(logging.FileHandler): 14 | 15 | _reopen = False 16 | 17 | def release(self): 18 | try: 19 | if self._reopen: 20 | self._reopen = False 21 | self.close() 22 | self._open() 23 | finally: 24 | self.lock.release() 25 | # In the rare case _reopen is set just before the lock was released 26 | if self._reopen and self.lock.acquire(False): 27 | self.release() 28 | 29 | def async_reopen(self, *_): 30 | self._reopen = True 31 | if self.lock.acquire(False): 32 | self.release() 33 | 34 | def setupLog(log_level: int, filename: str | None=None, **kw): 35 | if log_level and filename: 36 | makedirs(os.path.dirname(filename)) 37 | handler = FileHandler(filename) 38 | sig = handler.async_reopen 39 | else: 40 | handler = logging.StreamHandler() 41 | sig = signal.SIG_IGN 42 | handler.setFormatter(logging.Formatter( 43 | '%(asctime)s %(levelname)-9s %(message)s', '%Y-%m-%d %H:%M:%S')) 44 | root = logging.getLogger() 45 | root.addHandler(handler) 46 | signal.signal(signal.SIGUSR1, sig) 47 | if log_level: 48 | root.setLevel(logging_levels[log_level-1]) 49 | else: 50 | logging.disable(logging.CRITICAL) 51 | logging.addLevelName(5, 'TRACE') 52 | logging.trace = lambda *args, **kw: logging.log(5, *args, **kw) 53 | 54 | def log_exception(): 55 | f = traceback.format_exception(*sys.exc_info()) 56 | logging.error('%s%s', f.pop(), ''.join(f)) 57 | 58 | 59 | class HelpFormatter(argparse.ArgumentDefaultsHelpFormatter): 60 | 61 | def _get_help_string(self, action): 62 | return super()._get_help_string(action) \ 63 | if action.default else action.help 64 | 65 | def _split_lines(self, text, width): 66 | """Preserves new lines in option descriptions""" 67 | lines = [] 68 | for text in text.splitlines(): 69 | lines += textwrap.wrap(text, width) 70 | return lines 71 | 72 | def _fill_text(self, text, width, indent): 73 | """Preserves new lines in other descriptions""" 74 | kw = dict(width=width, initial_indent=indent, subsequent_indent=indent) 75 | return '\n'.join(textwrap.fill(t, **kw) for t in text.splitlines()) 76 | 77 | class ArgParser(argparse.ArgumentParser): 78 | 79 | class _HelpFormatter(HelpFormatter): 80 | 81 | def _format_actions_usage(self, actions, groups): 82 | r = HelpFormatter._format_actions_usage(self, actions, groups) 83 | if actions and actions[0].option_strings: 84 | r = '[@OPTIONS_FILE] ' + r 85 | return r 86 | 87 | _ca_help = "Certificate authority (CA) file in .pem format." \ 88 | " Serial number defines the prefix of the network." 89 | 90 | def convert_arg_line_to_args(self, arg_line): 91 | if arg_line.split('#', 1)[0].rstrip(): 92 | if arg_line.startswith('@'): 93 | yield arg_line 94 | return 95 | arg_line = shlex.split(arg_line) 96 | arg = '--' + arg_line.pop(0) 97 | yield arg[arg not in self._option_string_actions:] 98 | for arg in arg_line: 99 | yield arg 100 | 101 | def __init__(self, **kw): 102 | super().__init__(formatter_class=self._HelpFormatter, 103 | epilog="""Options can be read from a file. For example: 104 | $ cat OPTIONS_FILE 105 | ca /etc/re6stnet/ca.crt""", **kw) 106 | 107 | 108 | class exit: 109 | 110 | status = None 111 | 112 | def __init__(self): 113 | l = threading.Lock() 114 | self.acquire = l.acquire 115 | r = l.release 116 | def release(): 117 | try: 118 | if self.status is not None: 119 | self.release = r 120 | sys.exit(self.status) 121 | finally: 122 | r() 123 | self.release = release 124 | 125 | def __enter__(self): 126 | self.acquire() 127 | 128 | def __exit__(self, t, v, tb): 129 | self.release() 130 | 131 | def kill_main(self, status): 132 | self.status = status 133 | os.kill(os.getpid(), signal.SIGTERM) 134 | 135 | def signal(self, status, *sigs): 136 | def handler(*args): 137 | if self.status is None: 138 | self.status = status 139 | if self.acquire(False): 140 | self.release() 141 | for sig in sigs: 142 | signal.signal(sig, handler) 143 | 144 | exit = exit() 145 | 146 | 147 | class Popen(subprocess.Popen): 148 | 149 | def __init__(self, *args, **kw): 150 | self._args = tuple(args[0] if args else kw['args']) 151 | try: 152 | super().__init__(*args, **kw) 153 | except OSError as e: 154 | if e.errno != errno.ENOMEM: 155 | raise 156 | self.returncode = -1 157 | 158 | def send_signal(self, sig): 159 | logging.info('Sending signal %s to pid %s %r', 160 | sig, self.pid, self._args) 161 | # We don't need the change from https://bugs.python.org/issue38630 162 | # and it would complicate stop() 163 | assert self.returncode is None 164 | os.kill(self.pid, sig) 165 | 166 | def stop(self): 167 | if self.pid and self.returncode is None: 168 | self.terminate() 169 | t = threading.Timer(5, self.kill) 170 | t.start() 171 | os.waitid(os.P_PID, self.pid, os.WEXITED | os.WNOWAIT) 172 | t.cancel() 173 | self.poll() 174 | 175 | 176 | def setCloexec(fd): 177 | flags = fcntl.fcntl(fd, fcntl.F_GETFD) 178 | fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) 179 | 180 | def select(R: Mapping, W: Mapping, T): 181 | try: 182 | r, w, _ = _select.select(R, W, (), 183 | max(0, min(T)[0] - time.time()) if T else None) 184 | except _select.error as e: 185 | if e.args[0] != errno.EINTR: 186 | raise 187 | return 188 | for r in r: 189 | R[r]() 190 | for w in w: 191 | W[w]() 192 | t = time.time() 193 | for next_refresh, refresh in T: 194 | if next_refresh <= t: 195 | refresh() 196 | 197 | def makedirs(*args): 198 | try: 199 | os.makedirs(*args) 200 | except OSError as e: 201 | if e.errno != errno.EEXIST: 202 | raise 203 | 204 | def binFromIp(ip: str) -> str: 205 | return binFromRawIp(socket.inet_pton(socket.AF_INET6, ip)) 206 | 207 | def binFromRawIp(ip: bytes) -> str: 208 | ip1, ip2 = struct.unpack('>QQ', ip) 209 | return bin(ip1)[2:].rjust(64, '0') + bin(ip2)[2:].rjust(64, '0') 210 | 211 | 212 | def ipFromBin(ip: str, suffix='') -> str: 213 | suffix_len = 128 - len(ip) 214 | if suffix_len > 0: 215 | ip += suffix.rjust(suffix_len, '0') 216 | elif suffix_len: 217 | sys.exit("Prefix exceeds 128 bits") 218 | return socket.inet_ntop(socket.AF_INET6, 219 | struct.pack('>QQ', int(ip[:64], 2), int(ip[64:], 2))) 220 | 221 | def dump_address(address: str) -> str: 222 | return ';'.join(map(','.join, address)) 223 | 224 | # Yield ip, port, protocol, and country if it is in the address 225 | def parse_address(address_list: str) -> Iterator[tuple[str, str, str, str]]: 226 | for address in address_list.split(';'): 227 | try: 228 | a = address.split(',') 229 | int(a[1]) # Check if port is an int 230 | yield tuple(a[:4]) 231 | except ValueError as e: 232 | logging.warning("Failed to parse node address %r (%s)", 233 | address, e) 234 | 235 | def binFromSubnet(subnet: str) -> str: 236 | p, l = subnet.split('/') 237 | return bin(int(p))[2:].rjust(int(l), '0') 238 | 239 | def _newHmacSecret(): 240 | from random import getrandbits as g 241 | pack = struct.Struct(">QQI").pack 242 | assert len(pack(0,0,0)) == HMAC_LEN 243 | # A closure is built to avoid rebuilding the `pack` function at each call. 244 | return lambda x=None: pack(g(64) if x is None else x, g(64), g(32)) 245 | 246 | newHmacSecret = _newHmacSecret() # https://github.com/python/mypy/issues/1174 247 | 248 | ### Integer serialization 249 | # - supports values from 0 to 0x202020202020201f 250 | # - preserves ordering 251 | # - there's always a unique way to encode a value 252 | # - the 3 first bits code the number of bytes 253 | 254 | def packInteger(i: int) -> bytes: 255 | for n in range(8): 256 | x = 32 << 8 * n 257 | if i < x: 258 | return struct.pack("!Q", i + n * x)[7-n:] 259 | i -= x 260 | raise OverflowError 261 | 262 | def unpackInteger(x: bytes) -> tuple[int, int] | None: 263 | n = x[0] >> 5 264 | try: 265 | i, = struct.unpack("!Q", b'\0' * (7 - n) + x[:n+1]) 266 | except struct.error: 267 | return 268 | return sum((32 << 8 * i for i in range(n)), 269 | i - (n * 32 << 8 * n)), n + 1 270 | 271 | ### 272 | 273 | def sqliteCreateTable(db, name, *columns): 274 | sql = "CREATE TABLE %s (%s)" % (name, ','.join('\n ' + x for x in columns)) 275 | for x, in db.execute( 276 | "SELECT sql FROM sqlite_master WHERE type='table' and name=?""", 277 | (name,)): 278 | if x == sql: 279 | return 280 | raise sqlite3.OperationalError( 281 | "table %r already exists with unexpected schema" % name) 282 | db.execute(sql) 283 | return True 284 | -------------------------------------------------------------------------------- /re6st/version.py: -------------------------------------------------------------------------------- 1 | import subprocess as _S 2 | import os.path as _P 3 | _d = _P.realpath(_P.dirname(_P.dirname(__file__))) 4 | 5 | def _git_call(*args): 6 | return _S.call(("git", "-c", "safe.directory=" + _d) + args, cwd=_d) 7 | 8 | def _git_output(*args): 9 | return _S.check_output( 10 | ("git", "-c", "safe.directory=" + _d) + args, 11 | cwd=_d, text=True).strip() 12 | 13 | _git_call("update-index", "-q", "--refresh") 14 | dirty = _git_call("diff-index", "--quiet", "HEAD", "--") 15 | if dirty not in (0, 1): 16 | raise _S.CalledProcessError(dirty, "git") 17 | 18 | try: 19 | revision = int(_git_output("rev-list", "--count", "HEAD")) 20 | except _S.CalledProcessError: # BBB: Git too old 21 | revision = len(_git_output("rev-list", "HEAD").split()) 22 | short = _git_output("rev-parse", "--short", "HEAD") 23 | version = "0-%s.g%s" % (revision, short) 24 | 25 | if dirty: 26 | version += ".dirty" 27 | 28 | # Because the software could be forked or have local changes/commits, above 29 | # properties can't be used to decide whether a peer runs an appropriate version: 30 | # they are intended to the network admin. 31 | # Only 'protocol' is important and it must be increased whenever they would be 32 | # a wish to force an update of nodes. 33 | protocol = 10 34 | min_protocol = 1 35 | 36 | if __name__ == "__main__": 37 | print(version) 38 | -------------------------------------------------------------------------------- /re6stnet: -------------------------------------------------------------------------------- 1 | re6st/cli/node.py -------------------------------------------------------------------------------- /simulation/graph.cpp: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void erase(vector& v, int value) 8 | { 9 | for(int i=0;;i++) 10 | if(v[i] == value) 11 | { 12 | v[i] = v.back(); 13 | v.pop_back(); 14 | break; 15 | } 16 | } 17 | 18 | Graph::Graph(int size, int k, int maxPeers, mt19937& rng) : 19 | distrib(uniform_int_distribution(0, size-1)), 20 | size(size), generator(rng), k(k), maxPeers(maxPeers) 21 | { 22 | adjacency = new vector[size]; 23 | generated = new vector[size]; 24 | 25 | for(int i=0; i alreadyConnected; 29 | alreadyConnected.insert(i); 30 | 31 | for(int j=0; j= 1 34 | || otherNode > i && adjacency[otherNode].size() > maxPeers-k 35 | || adjacency[otherNode].size() > maxPeers) 36 | { } 37 | adjacency[i].push_back(otherNode); 38 | generated[i].push_back(otherNode); 39 | adjacency[otherNode].push_back(i); 40 | } 41 | } 42 | } 43 | 44 | int Graph::AddEdge(int from, unordered_set& alreadyConnected) 45 | { 46 | int otherNode; 47 | while(alreadyConnected.count(otherNode = distrib(generator)) >= 1 48 | || (adjacency[otherNode].size() > maxPeers )) 49 | { } 50 | adjacency[from].push_back(otherNode); 51 | generated[from].push_back(otherNode); 52 | adjacency[otherNode].push_back(from); 53 | return otherNode; 54 | } 55 | 56 | int Graph::RemoveEdge(int from, int to) 57 | { 58 | erase(generated[from], to); 59 | erase(adjacency[from], to); 60 | erase(adjacency[to], from); 61 | } 62 | 63 | void Graph::GetDistancesFrom(int node, int* distance) 64 | { 65 | for(int j=0; j remainingNodes; 70 | remainingNodes.push(node); 71 | 72 | while(!remainingNodes.empty()) 73 | { 74 | int node = remainingNodes.front(); 75 | remainingNodes.pop(); 76 | 77 | for(int neighbor : adjacency[node]) 78 | if(distance[neighbor] == -1) 79 | { 80 | distance[neighbor] = distance[node]+1; 81 | remainingNodes.push(neighbor); 82 | } 83 | } 84 | } 85 | 86 | void Graph::GetRoutesFrom(int node, int* distance, float* routesCount) 87 | { 88 | 89 | unordered_set prevs[size]; 90 | 91 | for(int i=0; i remainingNodes; 99 | remainingNodes.push(node); 100 | 101 | stack order; 102 | 103 | // Get the order 104 | while(!remainingNodes.empty()) 105 | { 106 | int node = remainingNodes.front(); 107 | int d = distance[node]; 108 | remainingNodes.pop(); 109 | order.push(node); 110 | 111 | for(int neighbor : adjacency[node]) 112 | if(distance[neighbor] == -1) 113 | { 114 | distance[neighbor] = d+1; 115 | prevs[neighbor].insert(node); 116 | remainingNodes.push(neighbor); 117 | } 118 | else if(distance[neighbor] == d+1) 119 | prevs[neighbor].insert(node); 120 | } 121 | 122 | // get the BC 123 | while(!order.empty()) 124 | { 125 | int node = order.top(); 126 | order.pop(); 127 | float w = routesCount[node]; 128 | for(int i : prevs[node]) 129 | routesCount[i] += w; 130 | } 131 | 132 | } 133 | 134 | int Graph::CountUnreachableFrom(int node) 135 | { 136 | bool accessibility[size]; 137 | for(int i=0; i toVisit; 143 | toVisit.push(node); 144 | while(!toVisit.empty()) 145 | { 146 | int n = toVisit.front(); 147 | for(int i : adjacency[n]) 148 | { 149 | if(!accessibility[i]) 150 | { 151 | toVisit.push(i); 152 | accessibility[i] = true; 153 | } 154 | } 155 | 156 | unAccessible--; 157 | toVisit.pop(); 158 | } 159 | 160 | return unAccessible; 161 | } 162 | 163 | // kill the last proportion*size machines of the graph 164 | void Graph::KillMachines(float proportion) 165 | { 166 | size = proportion*size; 167 | for(int i=0; i= size) 173 | it = adjacency[i].erase(it); 174 | else 175 | it++; 176 | } 177 | } 178 | } 179 | 180 | int Graph::GetMinCut() 181 | { 182 | int nIter = log(size); 183 | int minCut = -1; 184 | for(int i=0; i minCutCandidate) 189 | minCut = minCutCandidate; 190 | } 191 | 192 | return minCut; 193 | } 194 | 195 | int Graph::GetMinCut(MinCutGraph& copy1) 196 | { 197 | int n=copy1.nodes.size(); 198 | 199 | if(n==2) 200 | return copy1.edges.size(); 201 | 202 | MinCutGraph copy2(copy1); 203 | 204 | int nMerge = min(n-2.0, n/1.414); 205 | copy1.Merge(nMerge, generator); 206 | copy2.Merge(nMerge, generator); 207 | 208 | return min(GetMinCut(copy1), GetMinCut(copy2)); 209 | } 210 | 211 | MinCutGraph::MinCutGraph(vector* adjacency, int n) 212 | { 213 | nodes.resize(n); 214 | int nextEdgeId = 0; 215 | 216 | for(int i=0; i i) 219 | { 220 | nodes[i].v.insert(nextEdgeId); 221 | nodes[j].v.insert(nextEdgeId); 222 | edges.push_back(nullable>(pair(i, j))); 223 | nextEdgeId++; 224 | } 225 | } 226 | 227 | void MinCutGraph::Merge(int nMerge, mt19937& rng) 228 | { 229 | uniform_int_distribution distrib(0, edges.size()-1); 230 | 231 | while(nMerge > 0) 232 | { 233 | // Choose an edge 234 | int eId = distrib(rng); 235 | if(edges[eId].null) 236 | continue; 237 | 238 | int n1 = edges[eId].v.first; 239 | int n2 = edges[eId].v.second; 240 | 241 | // anilate n2 242 | nodes[n2].null = true; 243 | 244 | // redirect all edges from n2 245 | for(int i : nodes[n2].v) 246 | { 247 | if(edges[i].v.first == n1 || edges[i].v.second == n1) 248 | { 249 | nodes[n1].v.erase(i); 250 | edges[i].null = true; 251 | } 252 | 253 | else 254 | { 255 | nodes[n1].v.insert(i); 256 | 257 | if(edges[i].v.first == n2) 258 | edges[i].v.first = n1; 259 | else 260 | edges[i].v.second = n1; 261 | } 262 | } 263 | 264 | nMerge--; 265 | } 266 | 267 | RenumNodes(); 268 | RenumEdges(); 269 | } 270 | 271 | void MinCutGraph::Check() 272 | { 273 | cout << "CHECKING ... "; cout.flush(); 274 | for(int i=0; i= nodes.size()) 281 | cout << "Bad first" << endl; cout.flush(); 282 | if(e.second >= nodes.size()) 283 | cout << "Bad second" << endl; cout.flush(); 284 | if(nodes[e.first].v.count(i) == 0) 285 | cout << "Bad first node" << endl; cout.flush(); 286 | if(nodes[e.second].v.count(i) == 0) 287 | cout << "Bad second node" << endl; cout.flush(); 288 | } 289 | } 290 | 291 | for(int i=0; i 4 | #include 5 | #include 6 | 7 | const char* outName = "out.csv"; 8 | 9 | Results Simulate(int seed, int n, int k, int maxPeer, float alivePercent, int runs) 10 | { 11 | Results results(maxPeer, 20); 12 | mt19937 rng(seed); 13 | 14 | for(int r=0; r minCut) 21 | //results.minKConnexity = minCut; 22 | //results.UpdateArity(graph); 23 | 24 | // Compute the shortest path 25 | for(int i=0; i alreadyConnected; 49 | 50 | // erase some edge 51 | for(int k=0; k> outputStrings; 96 | for(int n=10; n<=100000; n*=2) 97 | for(int k=5; k<=50; k+=5) 98 | for(float a=1; a<=1; a+=0.05) 99 | { 100 | int seed = rng(); 101 | outputStrings.push_back(async(launch::async, [seed, n, k, a]() 102 | { 103 | Results results = Simulate(seed, n, k, 3*k, a, 1); 104 | ostringstream out; 105 | out << n << "," << k << "," << a << "," 106 | << results.avgDistance 107 | << endl; 108 | return out.str(); 109 | })); 110 | } 111 | 112 | cout << "Launching finished" << endl; 113 | 114 | int nTask = outputStrings.size(); 115 | for(int i=0; i 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | template 11 | struct nullable 12 | { 13 | T v; 14 | bool null; 15 | 16 | nullable() : null(false) { }; 17 | nullable(T t) : v(t), null(false) { }; 18 | }; 19 | 20 | class MinCutGraph 21 | { 22 | public: 23 | vector>> edges; 24 | vector>> nodes; 25 | MinCutGraph(vector* adjacency, int n); 26 | void Merge(int nMerge, mt19937& rng); 27 | private: 28 | void Check(); 29 | void RenumEdges(); 30 | void RenumNodes(); 31 | }; 32 | 33 | 34 | class Graph 35 | { 36 | public: 37 | Graph(int size, int k, int maxPeers, mt19937& rng); 38 | ~Graph() { delete[] adjacency; delete[] generated; }; 39 | 40 | void GetDistancesFrom(int node, int* distance); 41 | int AddEdge(int from, unordered_set& alreadyConnected); 42 | int RemoveEdge(int from, int to); 43 | int GetMinCut(); 44 | int CountUnreachableFrom(int node); 45 | void GetRoutesFrom(int node, int* distance, float* routesCount); 46 | 47 | void KillMachines(float proportion); 48 | //void SplitAS(float proportionAS1, float proportionAS2); 49 | 50 | vector* adjacency; 51 | vector* generated; 52 | int size; 53 | private: 54 | int GetMinCut(MinCutGraph& graph); 55 | 56 | uniform_int_distribution distrib; 57 | mt19937& generator; 58 | int maxPeers; 59 | int k; 60 | }; 61 | 62 | class Results 63 | { 64 | public: 65 | Results(int maxArity, int maxDistance); 66 | ~Results(); 67 | 68 | void UpdateArity(const Graph& graph); 69 | void AddAccessibilitySample(double accessibility); 70 | void UpdateDistance(int* distance, int nSamples); 71 | void Finalise(); 72 | 73 | double* arityDistrib; 74 | double* distanceDistrib; 75 | double avgDistance; 76 | double avgAccessibility; 77 | int maxDistanceReached; 78 | int minKConnexity; 79 | 80 | double disconnectionProba; 81 | double arityTooBig; 82 | double distanceTooBig; 83 | int64_t disconnected; 84 | 85 | int maxArity; 86 | int maxDistance; 87 | 88 | private: 89 | void AddAritySample(int arity); 90 | void AddDistanceSample(int distance); 91 | 92 | int64_t nAritySample; 93 | int64_t nDistanceSample; 94 | int64_t nAccessibilitySample; 95 | }; 96 | -------------------------------------------------------------------------------- /simulation/realistic_dataset/data/refresh1/arity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nexedi/re6stnet/9689045ca24539913b409754a4fdcd22415cfb89/simulation/realistic_dataset/data/refresh1/arity.png -------------------------------------------------------------------------------- /simulation/realistic_dataset/data/refresh1/distance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nexedi/re6stnet/9689045ca24539913b409754a4fdcd22415cfb89/simulation/realistic_dataset/data/refresh1/distance.png -------------------------------------------------------------------------------- /simulation/realistic_dataset/data/refresh1/distancelog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nexedi/re6stnet/9689045ca24539913b409754a4fdcd22415cfb89/simulation/realistic_dataset/data/refresh1/distancelog.png -------------------------------------------------------------------------------- /simulation/realistic_dataset/data/refresh1/mkGraph.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | 3 | max_peers = 30 4 | nFiles = 4 5 | nRounds = 900 6 | file_names = ['out_%s.csv' % i for i in range(nFiles)] 7 | 8 | distance = [0] * nRounds 9 | arityLat = [[0] * (max_peers - 9) for i in range(10)] 10 | 11 | arity = [[0] * (max_peers + 1) for i in range(nRounds)] 12 | 13 | for file_name in file_names: 14 | # open the file 15 | f = open(file_name, 'r') 16 | lines = f.read().split('\n') 17 | 18 | for line in lines: 19 | vals = line.split(',') 20 | i = int(vals[0]) 21 | 22 | distance[i] += float(vals[1]) 23 | for j in range(10, 31): 24 | arity[i][j] += float(vals[j - 6]) 25 | 26 | for j in range(0, 10): 27 | for k in range(0, max_peers - 9): 28 | arityLat[j][k] += int(vals[48 + 22 * j + k]) 29 | 30 | for i in range(0, nRounds): 31 | distance[i] = distance[i] / len(file_names) 32 | for j in range(0, 31): 33 | arity[i][j] = arity[i][j] / len(file_names) 34 | 35 | for i in range(0, 10): 36 | s = sum(arityLat[i]) 37 | for j in range(0, max_peers - 9): 38 | arityLat[i][j] = float(arityLat[i][j]) / float(s) 39 | 40 | #plt.plot(range(31), arity[1], range(31), arity[nRounds - 1]) 41 | #plt.legend(('Random network', 'After %s iterations' % nRounds)) 42 | #plt.xlabel('Arity') 43 | #plt.axis([10, 30, 0, 0.3]) 44 | 45 | latRange = range(10, 31) 46 | plt.plot(latRange, arityLat[0], latRange, arityLat[9]) 47 | plt.legend(('average latency < 50ms', 'average latency > 90ms'), loc=2) 48 | plt.xlabel('Arity') 49 | 50 | #plt.plot(range(0, nRounds), distance) 51 | 52 | #plt.yscale('log') 53 | plt.show() 54 | 55 | -------------------------------------------------------------------------------- /simulation/realistic_dataset/latency.cpp: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | Latency::Latency(const char* filePath, int size) : size(size) 4 | { 5 | values = new int*[size]; 6 | avgLatencyToOthers = new double[size]; 7 | for(int i=0; i= 10) 52 | nReachable++; 53 | } 54 | 55 | if(nReachable <= n) 56 | nat[i] = -1; 57 | else 58 | { 59 | nat[i] = nextId; 60 | nextId++; 61 | } 62 | } 63 | 64 | FILE* file = NULL; 65 | file = fopen("latency/pw-1715/rewrite", "w"); 66 | for(int i=0; i=10?values[i][j]:-1); 71 | 72 | fclose(file); 73 | } 74 | 75 | Latency::~Latency() 76 | { 77 | for(int i=0; i 0) 95 | distances[i][j] = values[i][j]; 96 | else 97 | distances[i][j] = numeric_limits::infinity(); 98 | 99 | for(int i=0; i 0) 119 | { 120 | nPing++; 121 | out += values[i][j]; 122 | } 123 | 124 | return out/nPing; 125 | } -------------------------------------------------------------------------------- /simulation/realistic_dataset/main.cpp: -------------------------------------------------------------------------------- 1 | // To compile : g++ -std=c++0x latency.cpp graph.cpp main.cpp -lpthread 2 | // other dataset : http://pdos.csail.mit.edu/p2psim/kingdata/ 3 | // for latency_2 : 4 | // Optimal distance : 16085.3 5 | // Average ping : 75809.4 6 | 7 | #include "main.h" 8 | 9 | void simulate(int size, int k, int maxPeer, int seed, Latency* latency, const char* outName) 10 | { 11 | mt19937 rng(seed); 12 | 13 | FILE* output = fopen(outName, "wt"); 14 | int fno = fileno(output); 15 | double nRoutesKilled = 0; 16 | //int arityKillDistrib[maxPeer+1][10]; 17 | double avgDistance, unreachable; 18 | double arityDistrib[31], bcArity[31]; 19 | 20 | Graph graph(size, k, maxPeer, rng, latency, 0, 0.05); 21 | 22 | cout << "\r" << 0 << "/" << 3000; 23 | cout.flush(); 24 | 25 | for(int i=0; i<3000; i++) 26 | { 27 | /*if(i>=100) 28 | { 29 | for(float a=0.05; a<1; a+=0.05) 30 | { 31 | Graph copy(graph); 32 | copy.KillMachines(a); 33 | fprintf(output, "%d,%f,%f\n",i , a , copy.GetUnAvalaibility()); 34 | fflush(output); 35 | fsync(fno); 36 | } 37 | }*/ 38 | 39 | 40 | 41 | //graph.Reboot(); 42 | graph.UpdateLowRoutes(avgDistance, unreachable, nRoutesKilled, arityDistrib, bcArity, 1, i); 43 | //graph.GetArityKill(arityKillDistrib); 44 | 45 | fprintf(output, "%d,%f,%f", i, avgDistance, nRoutesKilled); 46 | for(int j=k; j<=30; j++) 47 | fprintf(output, ",%f", arityDistrib[j]); 48 | /*fprintf(output, ",B"); 49 | for(int j=k; j<=30; j++) 50 | fprintf(output, ",%f", bcArity[j]); 51 | for(int j=0; j<10; j++) 52 | { 53 | fprintf(output, ",K%d", j); 54 | for(int a=k; a<=maxPeer; a++) 55 | fprintf(output, ",%d", arityKillDistrib[a][j]); 56 | }*/ 57 | fprintf(output, "\n"); 58 | fflush(output); 59 | fsync(fno); 60 | 61 | cout << "\r" << i+1 << "/" << 3000; 62 | cout.flush(); 63 | } 64 | 65 | cout << endl; 66 | fclose(output); 67 | } 68 | 69 | void testOptimized(int size, int k, int maxPeer, int seed, Latency* latency, const char* outName) 70 | { 71 | cout << "\r" << 0 << "/" << 3000; 72 | cout.flush(); 73 | 74 | FILE* output = fopen(outName, "wt"); 75 | int fno = fileno(output); 76 | 77 | FILE* input = fopen("update_order", "r"); 78 | 79 | mt19937 rng(seed); 80 | Graph graph(size, k, maxPeer, rng, latency, 0, 0.1); 81 | 82 | double nRoutesKilled = 0; 83 | int arityDistrib[maxPeer+1]; 84 | 85 | for(int i=0; i<3000; i++) 86 | { 87 | int toUpdate; 88 | fscanf(input, "%d", &toUpdate); 89 | 90 | pair result = graph.UpdateLowRoutesArity(toUpdate); 91 | 92 | nRoutesKilled += result.second; 93 | 94 | graph.GetArity(arityDistrib); 95 | 96 | fprintf(output, "%d,%f,%d,%f,A", i, result.first, toUpdate, nRoutesKilled); 97 | for(int a=k; a<=maxPeer; a++) 98 | fprintf(output, ",%d", arityDistrib[a]); 99 | 100 | fprintf(output, "\n"); 101 | fflush(output); 102 | fsync(fno); 103 | 104 | graph.Reboot(); 105 | 106 | cout << "\r" << i+1 << "/" << 3000; 107 | cout.flush(); 108 | } 109 | 110 | cout << endl; 111 | fclose(output); 112 | fclose(input); 113 | } 114 | 115 | void Optimize(int size, int k, int maxPeer, int seed, Latency* latency, const char* outName) 116 | { 117 | cout << "\r" << 0 << "/" << 3000; 118 | cout.flush(); 119 | 120 | FILE* output = fopen(outName, "wt"); 121 | int fno = fileno(output); 122 | 123 | mt19937 rng(seed); 124 | Graph* graph = new Graph(size, k, maxPeer, rng, latency, 0, 0.1); 125 | int range = maxPeer - k + 1; 126 | 127 | int updates[range]; 128 | for(int i = 0; i results[range]; 132 | Graph* copies[range]; 133 | 134 | double oldDistance = numeric_limits::infinity(); 135 | double nRoutesKilled = 0; 136 | int arityDistrib[maxPeer+1]; 137 | 138 | for(int i=0; i<1000; i++) 139 | { 140 | vector>> threads; 141 | for(int a=0; aUpdateLowRoutesArity(a + k); }; 145 | threads.push_back(async(launch::async, lambda, copies[a], a, k)); 146 | } 147 | 148 | 149 | int minIndice = 0; 150 | double minValue = numeric_limits::infinity(); 151 | 152 | for(int a=0; a 0) 156 | { 157 | double val = (results[a].first - oldDistance)/results[a].second; 158 | if(val < minValue) 159 | { 160 | minIndice = a; 161 | minValue = val; 162 | } 163 | } 164 | } 165 | 166 | swap(graph,copies[minIndice]); 167 | for(int a=0; aGetArity(arityDistrib); 175 | 176 | fprintf(output, "%d,%f,%f,U", i, oldDistance, nRoutesKilled); 177 | for(int a=0; aReboot(); 188 | 189 | cout << "\r" << i+1 << "/" << 3000;cout.flush(); 190 | } 191 | 192 | delete graph; 193 | cout << endl; 194 | } 195 | 196 | string computeDist(int size, int k, int maxPeer, int seed, Latency* latency) 197 | { 198 | mt19937 rng(seed); 199 | Graph graph(size, k, maxPeer, rng, latency, 0, 0.1); 200 | 201 | double avgDistLatency = 0; 202 | int nRoutes[size], prevs[size], distances[size]; 203 | for(int i=0; iGetAverageDistance() << endl; 222 | cout << "Average ping : " << latency->GetAveragePing() << endl; 223 | 224 | vector> threads; 225 | 226 | for(int i=0; i<4; i++) 227 | { 228 | int seed = rng(); 229 | char* out = new char[100]; 230 | sprintf(out, "out_%d.csv", i); 231 | threads.push_back(async(launch::async, [seed, out, latency]() 232 | { simulate(2500, 10, 30, seed, latency, out); delete[] out; })); 233 | } 234 | 235 | for(int i=0; i<4; i++) 236 | threads[i].get(); 237 | 238 | //Optimize(2500, 10, 30, rng(), latency, "out.csv"); 239 | 240 | delete latency; 241 | return 0; 242 | } 243 | -------------------------------------------------------------------------------- /simulation/realistic_dataset/main.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | struct routesResult 15 | { 16 | double avgDistance; 17 | int arity; 18 | int unreachable; 19 | int routesToDelete; 20 | stack toDelete; 21 | }; 22 | 23 | class Latency 24 | { 25 | public: 26 | Latency(const char* filePath, int size); 27 | void Rewrite(int n); 28 | ~Latency(); 29 | double GetAverageDistance(); 30 | double GetAveragePing(); 31 | int** values; 32 | double* avgLatencyToOthers; 33 | 34 | private: 35 | int size; 36 | }; 37 | 38 | class Graph 39 | { 40 | public: 41 | Graph(int size, int k, int maxPeers, mt19937& generator, Latency* latency, double minKillProba, double maxKillProba); 42 | Graph(Graph& g); 43 | ~Graph() { delete[] adjacency; delete[] generated; delete[] killProba;}; 44 | int UpdateLowRoutes(double& avgDistance, double& unreachable, double& nRoutesKilled, double* arityDistrib, double* bcArity, int nRefresh, int round); 45 | double GetUnAvalaibility(); 46 | void Reboot(); 47 | void KillMachines(float proportion); 48 | pair UpdateLowRoutesArity(int arityToUpdate); 49 | void GetArity(int* arity); 50 | void GetRoutesFrom(int from, int* nRoutes, int* prevs, int* distances); 51 | double GetAvgDistanceHop(); 52 | void GetArityLat(int arity[][10]); 53 | void GetArityKill(int arity[][10]); 54 | 55 | private: 56 | void SaturateNode(int node); 57 | bool AddEdge(int from); 58 | void RemoveEdge(int from, int to); 59 | int CountUnreachableFrom(int node); 60 | routesResult GetRouteResult(int node, int nRefresh, double* bc); 61 | 62 | mt19937 generator; 63 | uniform_int_distribution distrib; 64 | int maxPeers; 65 | int k; 66 | int size; 67 | 68 | unordered_set* adjacency; 69 | unordered_set* generated; 70 | Latency* latency; 71 | double* killProba; 72 | }; 73 | -------------------------------------------------------------------------------- /simulation/results.cpp: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | Results::Results(int maxArity, int maxDistance) : 4 | maxArity(maxArity), maxDistance(maxDistance) 5 | { 6 | arityDistrib = new double[maxArity+1]; 7 | for(int i=0; i<=maxArity; i++) 8 | arityDistrib[i] = 0; 9 | 10 | distanceDistrib = new double[maxDistance+1]; 11 | for(int i=0; i<=maxDistance; i++) 12 | distanceDistrib[i] = 0; 13 | 14 | nAccessibilitySample = 0; 15 | nAritySample = 0; 16 | nDistanceSample = 0; 17 | avgAccessibility = 0; 18 | arityTooBig = 0; 19 | distanceTooBig = 0; 20 | disconnected = 0; 21 | avgDistance = 0; 22 | minKConnexity = -1; 23 | maxDistanceReached = -1; 24 | } 25 | 26 | Results::~Results() 27 | { 28 | delete[] arityDistrib; 29 | delete[] distanceDistrib; 30 | } 31 | 32 | void Results::UpdateArity(const Graph& graph) 33 | { 34 | for(int i=0; i