Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
R
re6stnet
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Yohann D'Anello
re6stnet
Commits
abf8211e
Commit
abf8211e
authored
Apr 09, 2015
by
Alain Takoudjou
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'proto' into re6st-slapos
parents
47d1bb85
40d4e496
Changes
21
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
232 additions
and
104 deletions
+232
-104
debian/control
debian/control
+1
-1
demo/demo
demo/demo
+50
-26
demo/fixnemu.py
demo/fixnemu.py
+55
-0
demo/m1/re6stnet.conf
demo/m1/re6stnet.conf
+1
-1
demo/m2/re6stnet.conf
demo/m2/re6stnet.conf
+1
-1
demo/m3/re6stnet.conf
demo/m3/re6stnet.conf
+0
-1
demo/m4/re6stnet.conf
demo/m4/re6stnet.conf
+1
-1
demo/m5/re6stnet.conf
demo/m5/re6stnet.conf
+1
-1
demo/m6/re6stnet.conf
demo/m6/re6stnet.conf
+1
-1
demo/m7/re6stnet.conf
demo/m7/re6stnet.conf
+1
-1
demo/m8/re6stnet.conf
demo/m8/re6stnet.conf
+1
-1
demo/registry/re6st-registry.conf
demo/registry/re6st-registry.conf
+1
-0
docs/re6stnet.rst
docs/re6stnet.rst
+2
-2
re6st-registry
re6st-registry
+12
-0
re6st/cache.py
re6st/cache.py
+11
-3
re6st/ctl.py
re6st/ctl.py
+2
-0
re6st/plib.py
re6st/plib.py
+16
-12
re6st/registry.py
re6st/registry.py
+6
-2
re6st/tunnel.py
re6st/tunnel.py
+2
-1
re6stnet
re6stnet
+66
-48
re6stnet.spec
re6stnet.spec
+1
-1
No files found.
debian/control
View file @
abf8211e
...
@@ -7,7 +7,7 @@ Standards-Version: 3.9.1
...
@@ -7,7 +7,7 @@ Standards-Version: 3.9.1
Package: re6stnet
Package: re6stnet
Architecture: all
Architecture: all
Depends: ${misc:Depends}, python (>= 2.6.6-3), python (<< 2.8), python-argparse, python-openssl (>= 0.13), openvpn (>= 2.1.3), babeld (= 1.
5.1-nxd2
), iproute2 | iproute, openssl
Depends: ${misc:Depends}, python (>= 2.6.6-3), python (<< 2.8), python-argparse, python-openssl (>= 0.13), openvpn (>= 2.1.3), babeld (= 1.
6.0~1.g8950d3b-nxd1
), iproute2 | iproute, openssl
Recommends: ${python:Recommends}, logrotate
Recommends: ${python:Recommends}, logrotate
Suggests: ndisc6
Suggests: ndisc6
Description: resilient, scalable, IPv6 network application
Description: resilient, scalable, IPv6 network application
demo/demo
View file @
abf8211e
#!/usr/bin/python
#!/usr/bin/python
import math, nemu, os, signal, socket, subprocess, sys, time, weakref
import math, nemu, os,
re,
signal, socket, subprocess, sys, time, weakref
from collections import defaultdict
from collections import defaultdict
IPTABLES = 'iptables'
IPTABLES = 'iptables'
SCREEN = 'screen'
SCREEN = 'screen'
...
@@ -38,6 +38,8 @@ def _add_interface(node, iface):
...
@@ -38,6 +38,8 @@ def _add_interface(node, iface):
return Node__add_interface(node, iface)
return Node__add_interface(node, iface)
nemu.Node._add_interface = _add_interface
nemu.Node._add_interface = _add_interface
execfile("fixnemu.py")
# create nodes
# create nodes
for name in """internet=I registry=R
for name in """internet=I registry=R
gateway1=g1 machine1=1 machine2=2
gateway1=g1 machine1=1 machine2=2
...
@@ -48,6 +50,8 @@ for name in """internet=I registry=R
...
@@ -48,6 +50,8 @@ for name in """internet=I registry=R
globals()[name] = node = nemu.Node()
globals()[name] = node = nemu.Node()
node.name = name
node.name = name
node.short = short
node.short = short
node.Popen(('sysctl', '-q',
'net.ipv4.icmp_echo_ignore_broadcasts=0')).wait()
node._screen = node.Popen((SCREEN, '-DmS', name))
node._screen = node.Popen((SCREEN, '-DmS', name))
node.screen = (lambda name: lambda *cmd:
node.screen = (lambda name: lambda *cmd:
subprocess.call([SCREEN, '-r', name, '-X', 'eval'] + map(
subprocess.call([SCREEN, '-r', name, '-X', 'eval'] + map(
...
@@ -235,7 +239,12 @@ def node_by_ll(addr):
...
@@ -235,7 +239,12 @@ def node_by_ll(addr):
for a in a:
for a in a:
p = a['prefix_len']
p = a['prefix_len']
a = a['address']
a = a['address']
if a.startswith('2001:db8:'):
if a.startswith('10.'):
if a.startswith('10.42.'):
assert not p % 8
_ll[socket.inet_ntoa(socket.inet_aton(
a)[:p/8].ljust(4, '\0'))] = n, t
elif a.startswith('2001:db8:'):
assert not p % 8
assert not p % 8
a = socket.inet_ntop(socket.AF_INET6,
a = socket.inet_ntop(socket.AF_INET6,
socket.inet_pton(socket.AF_INET6,
socket.inet_pton(socket.AF_INET6,
...
@@ -245,17 +254,19 @@ def node_by_ll(addr):
...
@@ -245,17 +254,19 @@ def node_by_ll(addr):
_ll[a] = n, t
_ll[a] = n, t
return _ll[addr]
return _ll[addr]
def route_svg(
z=4
):
def route_svg(
ipv4, z = 4, default = type('', (), {'short': None})
):
graph = {}
graph = {}
for n in nodes:
for n in nodes:
g = graph[n] = defaultdict(list)
g = graph[n] = defaultdict(list)
for r in n.get_routes():
for r in n.get_routes():
if r.prefix and r.prefix.startswith('2001:db8:'):
if (r.prefix and r.prefix.startswith('10.42.') if ipv4 else
r.prefix is None or r.prefix.startswith('2001:db8:')):
try:
try:
g[node_by_ll(r.nexthop)].append(node_by_ll(r.prefix)[0])
g[node_by_ll(r.nexthop)].append(
node_by_ll(r.prefix)[0] if r.prefix else default)
except KeyError:
except KeyError:
pass
pass
gv = ["digraph { splines = true; edge[color=grey, labelangle=0
, arrowhead=dot
];"]
gv = ["digraph { splines = true; edge[color=grey, labelangle=0];"]
N = len(nodes)
N = len(nodes)
a = 2 * math.pi / N
a = 2 * math.pi / N
edges = set()
edges = set()
...
@@ -268,17 +279,23 @@ def route_svg(z=4):
...
@@ -268,17 +279,23 @@ def route_svg(z=4):
l.append((min(j, N - j), p, r))
l.append((min(j, N - j), p, r))
for j, (l, (p, t), r) in enumerate(sorted(l)):
for j, (l, (p, t), r) in enumerate(sorted(l)):
l = []
l = []
arrowhead = 'odot'
for r in sorted(r.short for r in r):
for r in sorted(r.short for r in r):
if r == p.short:
if r:
r = '
<font
color=
"grey"
>
%s
</font>
' % r
if r == p.short:
l.append(r)
r = '
<font
color=
"grey"
>
%s
</font>
' % r
l.append(r)
else:
arrowhead = 'dot'
if (n.name, p.name) in edges:
if (n.name, p.name) in edges:
r = 'penwidth=0'
r = 'penwidth=0'
else:
else:
edges.add((p.name, n.name))
edges.add((p.name, n.name))
r = 'style=solid' if t else 'style=dashed'
r = 'style=solid' if t else 'style=dashed'
gv.append('%s -> %s [labeldistance=%u, headlabel=
<
%
s
>
, %s];'
gv.append(
% (p.name, n.name, 1.5 * math.sqrt(j) + 2, ','.join(l), r))
'%s -> %s [labeldistance=%u, headlabel=
<
%
s
>
, arrowhead=%s, %s];'
% (p.name, n.name, 1.5 * math.sqrt(j) + 2, ','.join(l),
arrowhead, r))
gv.append('}\n')
gv.append('}\n')
return subprocess.Popen(('neato', '-Tsvg'),
return subprocess.Popen(('neato', '-Tsvg'),
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
...
@@ -288,13 +305,25 @@ if len(sys.argv) > 1:
...
@@ -288,13 +305,25 @@ if len(sys.argv) > 1:
import SimpleHTTPServer, SocketServer
import SimpleHTTPServer, SocketServer
class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler):
class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler):
_path_match = re.compile('/(.+)\.html$').match
pages = 'ipv6', 'ipv4', 'tunnels'
def do_GET(self):
def do_GET(self):
svg = None
svg = None
if self.path == '/route.html':
try:
other = 'tunnel'
page = self.pages.index(self._path_match(self.path).group(1))
svg = route_svg()
except AttributeError, ValueError:
elif self.path == '/tunnel.html':
if self.path == '/':
other = 'route'
self.send_response(302)
self.send_header('Location', self.pages[0] + '.html')
self.end_headers()
else:
self.send_error(404)
return
if page
<
2:
svg =
route_svg(page)
else:
gv =
registry.Popen(('python',
'
-c
',
r
"""
if
1:
gv =
registry.Popen(('python',
'
-c
',
r
"""
if
1:
import
math
,
json
import
math
,
json
from
re6st
.
registry
import
RegistryClient
from
re6st
.
registry
import
RegistryClient
...
@@ -326,21 +355,16 @@ if len(sys.argv) > 1:
...
@@ -326,21 +355,16 @@ if len(sys.argv) > 1:
if not svg:
if not svg:
self.send_error(500)
self.send_error(500)
return
return
else:
if self.path == '/':
self.send_response(302)
self.send_header('Location', 'route.html')
self.end_headers()
else:
self.send_error(404)
return
mt = 'text/html'
mt = 'text/html'
body = """
<html>
body = """
<html>
<head><meta
http-equiv=
"refresh"
content=
"10"
/></head>
<head><meta
http-equiv=
"refresh"
content=
"10"
/></head>
<body><
a
style=
"position: absolute"
href=
"%s.html"
>
%ss
</a
>
<body><
span
style=
"position: absolute"
>
%s
</span
>
%s
%s
</body>
</body>
</html>
""" % (other, other, svg[svg.find('
<svg
')
:
])
</html>
""" % (' '.join(x if i == page else
'
<a
href=
"%s.html"
>
%s
</a>
' % (x, x)
for i, x in enumerate(self.pages)),
svg[svg.find('
<svg
')
:
])
self
.
send_response
(
200
)
self
.
send_response
(
200
)
self
.
send_header
('
Content-Length
',
len
(
body
))
self
.
send_header
('
Content-Length
',
len
(
body
))
self
.
send_header
('
Content-type
',
mt
+
';
charset=
utf-8')
self
.
send_header
('
Content-type
',
mt
+
';
charset=
utf-8')
...
...
demo/fixnemu.py
0 → 100644
View file @
abf8211e
# -*- coding: utf-8 -*-
# Copyright 2010, 2011 INRIA
# Copyright 2011 Martín Ferrari <martin.ferrari@gmail.com>
#
# This file is contains patches to Nemu.
#
# Nemu is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License version 2, as published by the Free
# Software Foundation.
#
# Nemu is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# Nemu. If not, see <http://www.gnu.org/licenses/>.
import
re
from
nemu.iproute
import
backticks
,
get_if_data
,
get_all_route_data
,
route
def
_get_all_route_data
():
ipdata
=
backticks
([
IP_PATH
,
"-o"
,
"route"
,
"list"
])
# "table", "all"
ipdata
+=
backticks
([
IP_PATH
,
"-o"
,
"-f"
,
"inet6"
,
"route"
,
"list"
])
ifdata
=
get_if_data
()[
1
]
ret
=
[]
for
line
in
ipdata
.
split
(
"
\
n
"
):
if
line
==
""
:
continue
# PATCH: parse 'from'
# PATCH: 'dev' is missing on 'unreachable' ipv4 routes
match
=
re
.
match
(
'(?:(unicast|local|broadcast|multicast|throw|'
r'unreachable|prohibit|blackhole|nat) )?(\
S+)(?:
from (\
S+))?
'
r'
(
?
:
via
(
\
S
+
))
?
(
?
:
dev
(
\
S
+
))
?
.
*
(
?
:
metric
(
\
d
+
))
?
', line)
if not match:
raise RuntimeError("Invalid output from `ip route'
:
`
%
s
'" % line)
tipe = match.group(1) or "unicast"
prefix = match.group(2)
#src = match.group(3)
nexthop = match.group(4)
interface = ifdata[match.group(5) or "lo"]
metric = match.group(6)
if prefix == "default" or re.search(r'
/
0
$
', prefix):
prefix = None
prefix_len = 0
else:
match = re.match(r'
([
0
-
9
a
-
f
:.]
+
)(
?
:
/
(
\
d
+
))
?$
', prefix)
prefix = match.group(1)
prefix_len = int(match.group(2) or 32)
ret.append(route(tipe, prefix, prefix_len, nexthop, interface.index,
metric))
return ret
get_all_route_data.func_code = _get_all_route_data.func_code
demo/m1/re6stnet.conf
View file @
abf8211e
...
@@ -5,5 +5,5 @@ pp 1194 tcp
...
@@ -5,5 +5,5 @@ pp 1194 tcp
ca
ca
.
crt
ca
ca
.
crt
cert
m1
/
cert
.
crt
cert
m1
/
cert
.
crt
key
m1
/
cert
.
key
key
m1
/
cert
.
key
table
0
default
neighbour
6
/
16
neighbour
6
/
16
demo/m2/re6stnet.conf
View file @
abf8211e
...
@@ -5,4 +5,4 @@ pp 1194 tcp
...
@@ -5,4 +5,4 @@ pp 1194 tcp
ca
ca
.
crt
ca
ca
.
crt
cert
m2
/
cert
.
crt
cert
m2
/
cert
.
crt
key
m2
/
cert
.
key
key
m2
/
cert
.
key
table
0
default
demo/m3/re6stnet.conf
View file @
abf8211e
...
@@ -5,4 +5,3 @@ pp 1194 tcp
...
@@ -5,4 +5,3 @@ pp 1194 tcp
ca
ca
.
crt
ca
ca
.
crt
cert
m3
/
cert
.
crt
cert
m3
/
cert
.
crt
key
m3
/
cert
.
key
key
m3
/
cert
.
key
table
0
demo/m4/re6stnet.conf
View file @
abf8211e
...
@@ -5,4 +5,4 @@ pp 1194 tcp
...
@@ -5,4 +5,4 @@ pp 1194 tcp
ca
ca
.
crt
ca
ca
.
crt
cert
m4
/
cert
.
crt
cert
m4
/
cert
.
crt
key
m4
/
cert
.
key
key
m4
/
cert
.
key
table
0
default
demo/m5/re6stnet.conf
View file @
abf8211e
...
@@ -4,6 +4,6 @@ state m5/
...
@@ -4,6 +4,6 @@ state m5/
ca
ca
.
crt
ca
ca
.
crt
cert
m5
/
cert
.
crt
cert
m5
/
cert
.
crt
key
m5
/
cert
.
key
key
m5
/
cert
.
key
table
0
default
client
-
count
0
client
-
count
0
max
-
clients
0
max
-
clients
0
demo/m6/re6stnet.conf
View file @
abf8211e
...
@@ -5,7 +5,7 @@ pp 1194 tcp
...
@@ -5,7 +5,7 @@ pp 1194 tcp
ca
ca
.
crt
ca
ca
.
crt
cert
m6
/
cert
.
crt
cert
m6
/
cert
.
crt
key
m6
/
cert
.
key
key
m6
/
cert
.
key
table
0
default
# TODO: Run a DHCPv4 client on machine9. Unfortunately, isc-dhcp-client 4.2.4
# TODO: Run a DHCPv4 client on machine9. Unfortunately, isc-dhcp-client 4.2.4
# fails with "Bind socket to interface: No such device"
# fails with "Bind socket to interface: No such device"
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"
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"
demo/m7/re6stnet.conf
View file @
abf8211e
...
@@ -5,4 +5,4 @@ pp 1194 tcp
...
@@ -5,4 +5,4 @@ pp 1194 tcp
ca
ca
.
crt
ca
ca
.
crt
cert
m7
/
cert
.
crt
cert
m7
/
cert
.
crt
key
m7
/
cert
.
key
key
m7
/
cert
.
key
table
0
default
demo/m8/re6stnet.conf
View file @
abf8211e
...
@@ -4,5 +4,5 @@ state m8/
...
@@ -4,5 +4,5 @@ state m8/
ca
ca
.
crt
ca
ca
.
crt
cert
m8
/
cert
.
crt
cert
m8
/
cert
.
crt
key
m8
/
cert
.
key
key
m8
/
cert
.
key
table
0
default
client
10
.
0
.
1
.
2
,
1194
,
tcp
;
10
.
0
.
1
.
3
,
1194
,
tcp
client
10
.
0
.
1
.
2
,
1194
,
tcp
;
10
.
0
.
1
.
3
,
1194
,
tcp
demo/registry/re6st-registry.conf
View file @
abf8211e
...
@@ -6,3 +6,4 @@ run registry/run
...
@@ -6,3 +6,4 @@ run registry/run
hello
4
hello
4
client
-
count
2
client
-
count
2
tunnel
-
refresh
100
tunnel
-
refresh
100
ipv4
10
.
42
.
0
.
0
/
16
8
docs/re6stnet.rst
View file @
abf8211e
...
@@ -31,7 +31,7 @@ If you already have IPv6 connectivity by autoconfiguration and still want to
...
@@ -31,7 +31,7 @@ If you already have IPv6 connectivity by autoconfiguration and still want to
use it for communications that are unrelated to this network, then:
use it for communications that are unrelated to this network, then:
- your kernel must support source address based routing (because you can't
- your kernel must support source address based routing (because you can't
use ``--
table 0
`` option).
use ``--
default
`` option).
- you must set ``net.ipv6.conf.<iface>.accept_ra`` sysctl to value 2 and
- you must set ``net.ipv6.conf.<iface>.accept_ra`` sysctl to value 2 and
trigger SLAAC with ``rdisc6 <iface>`` to restore the default route if the
trigger SLAAC with ``rdisc6 <iface>`` to restore the default route if the
kernel removed while enabling forwarding.
kernel removed while enabling forwarding.
...
@@ -70,7 +70,7 @@ Important note about NetworkManager
...
@@ -70,7 +70,7 @@ Important note about NetworkManager
It is required to configure properly every connection defined in NetworkManager
It is required to configure properly every connection defined in NetworkManager
because default settings are wrong and conflict with re6st:
because default settings are wrong and conflict with re6st:
- If re6st routes all your IPv6 traffic, using ``--
table 0
`` option, then make
- If re6st routes all your IPv6 traffic, using ``--
default
`` option, then make
sure to disable IPv6 in NetworkManager.
sure to disable IPv6 in NetworkManager.
- Otherwise, the following options must be set in [ipv6] section::
- Otherwise, the following options must be set in [ipv6] section::
...
...
re6st-registry
View file @
abf8211e
...
@@ -83,6 +83,9 @@ def main():
...
@@ -83,6 +83,9 @@ def main():
_
(
'--anonymous-prefix-length'
,
type
=
int
,
_
(
'--anonymous-prefix-length'
,
type
=
int
,
help
=
"Length of allocated anonymous prefixes."
help
=
"Length of allocated anonymous prefixes."
" If 0 or unset, registration by email is required"
)
" If 0 or unset, registration by email is required"
)
_
(
'--ipv4'
,
nargs
=
2
,
metavar
=
(
"IP/N"
,
"PLEN"
),
help
=
"Enable ipv4. Each node is assigned a subnet of length PLEN"
" inside network IP/N."
)
_
(
'-l'
,
'--logfile'
,
default
=
'/var/log/re6stnet/registry.log'
,
_
(
'-l'
,
'--logfile'
,
default
=
'/var/log/re6stnet/registry.log'
,
help
=
"Path to logging file."
)
help
=
"Path to logging file."
)
_
(
'-r'
,
'--run'
,
default
=
'/var/run/re6stnet'
,
_
(
'-r'
,
'--run'
,
default
=
'/var/run/re6stnet'
,
...
@@ -122,6 +125,15 @@ def main():
...
@@ -122,6 +125,15 @@ def main():
parser
.
error
(
"--min-protocol: value must between %s and %s (included)"
parser
.
error
(
"--min-protocol: value must between %s and %s (included)"
%
(
version
.
min_protocol
,
version
.
protocol
))
%
(
version
.
min_protocol
,
version
.
protocol
))
if
config
.
ipv4
:
ipv4
,
plen
=
config
.
ipv4
try
:
ip
,
n
=
ipv4
.
split
(
'/'
)
config
.
ipv4
=
"%s/%s"
%
(
socket
.
inet_ntoa
(
socket
.
inet_aton
(
ip
)),
int
(
n
)),
int
(
plen
)
except
(
socket
.
error
,
ValueError
):
parser
.
error
(
"invalid argument --ipv4"
)
utils
.
setupLog
(
config
.
verbose
,
config
.
logfile
)
utils
.
setupLog
(
config
.
verbose
,
config
.
logfile
)
if
config
.
max_clients
is
None
:
if
config
.
max_clients
is
None
:
...
...
re6st/cache.py
View file @
abf8211e
...
@@ -27,7 +27,7 @@ class Cache(object):
...
@@ -27,7 +27,7 @@ class Cache(object):
q
(
"CREATE INDEX volatile.stat_try ON stat(try)"
)
q
(
"CREATE INDEX volatile.stat_try ON stat(try)"
)
q
(
"INSERT INTO volatile.stat (peer) SELECT prefix FROM peer"
)
q
(
"INSERT INTO volatile.stat (peer) SELECT prefix FROM peer"
)
self
.
_db
.
commit
()
self
.
_db
.
commit
()
self
.
_loadConfig
(
q
(
"SELECT * FROM config"
))
self
.
_loadConfig
(
self
.
_selectConfig
(
q
))
try
:
try
:
cert
.
verifyVersion
(
self
.
version
)
cert
.
verifyVersion
(
self
.
version
)
except
(
AttributeError
,
x509
.
VerifyError
):
except
(
AttributeError
,
x509
.
VerifyError
):
...
@@ -63,6 +63,11 @@ class Cache(object):
...
@@ -63,6 +63,11 @@ class Cache(object):
"value"
)
"value"
)
return
db
return
db
@
staticmethod
def
_selectConfig
(
execute
):
# BBB: blob
return
((
k
,
str
(
v
)
if
type
(
v
)
is
buffer
else
v
)
for
k
,
v
in
execute
(
"SELECT * FROM config"
))
def
_loadConfig
(
self
,
config
):
def
_loadConfig
(
self
,
config
):
cls
=
self
.
__class__
cls
=
self
.
__class__
logging
.
debug
(
"Loading network parameters:"
)
logging
.
debug
(
"Loading network parameters:"
)
...
@@ -98,7 +103,7 @@ class Cache(object):
...
@@ -98,7 +103,7 @@ class Cache(object):
old
=
{}
old
=
{}
with
self
.
_db
as
db
:
with
self
.
_db
as
db
:
remove
=
[]
remove
=
[]
for
k
,
v
in
db
.
execute
(
"SELECT * FROM config"
):
for
k
,
v
in
self
.
_selectConfig
(
db
.
execute
):
if
k
in
config
:
if
k
in
config
:
old
[
k
]
=
v
old
[
k
]
=
v
continue
continue
...
@@ -109,8 +114,11 @@ class Cache(object):
...
@@ -109,8 +114,11 @@ class Cache(object):
remove
.
append
(
k
)
remove
.
append
(
k
)
db
.
execute
(
"DELETE FROM config WHERE name in ('%s')"
db
.
execute
(
"DELETE FROM config WHERE name in ('%s')"
%
"','"
.
join
(
remove
))
%
"','"
.
join
(
remove
))
# BBB: Use buffer because of http://bugs.python.org/issue13676
# on Python 2.6
db
.
executemany
(
"INSERT OR REPLACE INTO config VALUES(?,?)"
,
db
.
executemany
(
"INSERT OR REPLACE INTO config VALUES(?,?)"
,
config
.
iteritems
())
((
k
,
buffer
(
v
)
if
k
in
base64
else
v
)
for
k
,
v
in
config
.
iteritems
()))
self
.
_loadConfig
(
config
.
iteritems
())
self
.
_loadConfig
(
config
.
iteritems
())
return
[
k
for
k
,
v
in
config
.
iteritems
()
return
[
k
for
k
,
v
in
config
.
iteritems
()
if
k
not
in
old
or
old
[
k
]
!=
v
]
if
k
not
in
old
or
old
[
k
]
!=
v
]
...
...
re6st/ctl.py
View file @
abf8211e
...
@@ -266,6 +266,8 @@ class Babel(object):
...
@@ -266,6 +266,8 @@ class Babel(object):
a
=
len
(
self
.
network
)
a
=
len
(
self
.
network
)
for
route
in
routes
:
for
route
in
routes
:
assert
route
.
flags
&
1
,
route
# installed
assert
route
.
flags
&
1
,
route
# installed
if
route
.
prefix
.
startswith
(
'
\
0
\
0
\
0
\
0
\
0
\
0
\
0
\
0
\
0
\
0
\
xff
\
xff
'
):
continue
assert
route
.
neigh_address
==
route
.
nexthop
,
route
assert
route
.
neigh_address
==
route
.
nexthop
,
route
address
=
route
.
neigh_address
,
route
.
ifindex
address
=
route
.
neigh_address
,
route
.
ifindex
neigh_routes
=
n
[
address
]
neigh_routes
=
n
[
address
]
...
...
re6st/plib.py
View file @
abf8211e
...
@@ -59,10 +59,11 @@ def client(iface, address_list, encrypt, *args, **kw):
...
@@ -59,10 +59,11 @@ def client(iface, address_list, encrypt, *args, **kw):
return
openvpn
(
iface
,
encrypt
,
*
remote
,
**
kw
)
return
openvpn
(
iface
,
encrypt
,
*
remote
,
**
kw
)
def
router
(
subnet
,
hello_interval
,
table
,
log_path
,
state_path
,
pidfile
,
def
router
(
ip
,
ip4
,
src
,
hello_interval
,
log_path
,
state_path
,
tunnel_interfaces
,
control_socket
,
default
,
*
args
,
**
kw
):
pidfile
,
control_socket
,
default
,
*
args
,
**
kw
):
s
=
utils
.
ipFromBin
(
subnet
)
ip
,
n
=
ip
n
=
len
(
subnet
)
if
ip4
:
ip4
,
n4
=
ip4
cmd
=
[
'babeld'
,
cmd
=
[
'babeld'
,
'-h'
,
str
(
hello_interval
),
'-h'
,
str
(
hello_interval
),
'-H'
,
str
(
hello_interval
),
'-H'
,
str
(
hello_interval
),
...
@@ -72,16 +73,19 @@ def router(subnet, hello_interval, table, log_path, state_path, pidfile,
...
@@ -72,16 +73,19 @@ def router(subnet, hello_interval, table, log_path, state_path, pidfile,
'-s'
,
'-s'
,
'-C'
,
'default '
+
default
,
'-C'
,
'default '
+
default
,
'-C'
,
'redistribute local deny'
,
'-C'
,
'redistribute local deny'
,
'-C'
,
'redistribute ip %s/%u eq %u'
%
(
s
,
n
,
n
),
'-C'
,
'redistribute ip %s/%s eq %s'
%
(
ip
,
n
,
n
)]
'-C'
,
'redistribute deny'
]
if
ip4
:
if
table
:
cmd
+=
'-C'
,
'redistribute ip %s/%s eq %s'
%
(
ip4
,
n4
,
n4
)
cmd
+=
'-t%u'
%
table
,
'-T%u'
%
table
if
src
:
else
:
cmd
+=
'-C'
,
'install ip ::/0 eq 0 src-prefix '
+
src
cmd
[
-
2
:
-
2
]
=
'-C'
,
'redistribute ip ::/0 eq 0'
elif
src
is
None
:
cmd
+=
'-C'
,
'redistribute ip ::/0 eq 0'
cmd
+=
(
'-C'
,
'redistribute deny'
,
'-C'
,
'install pref-src '
+
ip
)
if
ip4
:
cmd
+=
'-C'
,
'install pref-src '
+
ip4
if
control_socket
:
if
control_socket
:
cmd
+=
'-R'
,
'%s'
%
control_socket
cmd
+=
'-R'
,
'%s'
%
control_socket
for
iface
in
tunnel_interfaces
:
cmd
+=
'-C'
,
'interface %s legacy-rxcost 5120'
%
iface
cmd
+=
args
cmd
+=
args
# WKRD: babeld fails to start if pidfile already exists
# WKRD: babeld fails to start if pidfile already exists
try
:
try
:
...
...
re6st/registry.py
View file @
abf8211e
...
@@ -60,7 +60,7 @@ class RegistryServer(object):
...
@@ -60,7 +60,7 @@ class RegistryServer(object):
"name TEXT PRIMARY KEY NOT NULL"
,
"name TEXT PRIMARY KEY NOT NULL"
,
"value"
)
"value"
)
self
.
prefix
=
self
.
getConfig
(
"prefix"
,
None
)
self
.
prefix
=
self
.
getConfig
(
"prefix"
,
None
)
self
.
version
=
s
elf
.
getConfig
(
"version"
,
"
\
0
"
)
self
.
version
=
s
tr
(
self
.
getConfig
(
"version"
,
"
\
0
"
))
# BBB: blob
utils
.
sqliteCreateTable
(
self
.
db
,
"token"
,
utils
.
sqliteCreateTable
(
self
.
db
,
"token"
,
"token TEXT PRIMARY KEY NOT NULL"
,
"token TEXT PRIMARY KEY NOT NULL"
,
"email TEXT NOT NULL"
,
"email TEXT NOT NULL"
,
...
@@ -119,6 +119,8 @@ class RegistryServer(object):
...
@@ -119,6 +119,8 @@ class RegistryServer(object):
'protocol'
:
version
.
protocol
,
'protocol'
:
version
.
protocol
,
'registry_prefix'
:
self
.
prefix
,
'registry_prefix'
:
self
.
prefix
,
}
}
if
self
.
config
.
ipv4
:
kw
[
'ipv4'
],
kw
[
'ipv4_sublen'
]
=
self
.
config
.
ipv4
for
x
in
(
'client_count'
,
'encrypt'
,
'hello'
,
for
x
in
(
'client_count'
,
'encrypt'
,
'hello'
,
'max_clients'
,
'min_protocol'
,
'tunnel_refresh'
):
'max_clients'
,
'min_protocol'
,
'tunnel_refresh'
):
kw
[
x
]
=
getattr
(
self
.
config
,
x
)
kw
[
x
]
=
getattr
(
self
.
config
,
x
)
...
@@ -126,7 +128,9 @@ class RegistryServer(object):
...
@@ -126,7 +128,9 @@ class RegistryServer(object):
if
config
!=
self
.
getConfig
(
'last_config'
,
None
):
if
config
!=
self
.
getConfig
(
'last_config'
,
None
):
self
.
version
=
self
.
encodeVersion
(
self
.
version
=
self
.
encodeVersion
(
1
+
self
.
decodeVersion
(
self
.
version
))
1
+
self
.
decodeVersion
(
self
.
version
))
self
.
setConfig
(
'version'
,
self
.
version
)
# BBB: Use buffer because of http://bugs.python.org/issue13676
# on Python 2.6
self
.
setConfig
(
'version'
,
buffer
(
self
.
version
))
self
.
setConfig
(
'last_config'
,
config
)
self
.
setConfig
(
'last_config'
,
config
)
self
.
sendto
(
self
.
prefix
,
0
)
self
.
sendto
(
self
.
prefix
,
0
)
kw
[
''
]
=
'version'
,
kw
[
''
]
=
'version'
,
...
...
re6st/tunnel.py
View file @
abf8211e
...
@@ -168,7 +168,8 @@ class BaseTunnelManager(object):
...
@@ -168,7 +168,8 @@ class BaseTunnelManager(object):
# TODO: To minimize downtime when network parameters change, we should do
# TODO: To minimize downtime when network parameters change, we should do
# our best to not restart any process. Ideally, this list should be
# our best to not restart any process. Ideally, this list should be
# empty and the affected subprocesses reloaded.
# empty and the affected subprocesses reloaded.
NEED_RESTART
=
frozenset
((
'babel_default'
,
'encrypt'
,
'hello'
))
NEED_RESTART
=
frozenset
((
'babel_default'
,
'encrypt'
,
'hello'
,
'ipv4'
,
'ipv4_sublen'
))
_forward
=
None
_forward
=
None
...
...
re6stnet
View file @
abf8211e
#!/usr/bin/python
#!/usr/bin/python
import
atexit
,
errno
,
logging
,
os
,
shutil
,
signal
import
atexit
,
errno
,
logging
,
os
,
shutil
,
signal
import
socket
,
subprocess
,
sys
,
time
,
threading
import
socket
,
s
truct
,
s
ubprocess
,
sys
,
time
,
threading
from
collections
import
deque
from
collections
import
deque
from
functools
import
partial
from
functools
import
partial
from
re6st
import
plib
,
tunnel
,
utils
,
version
,
x509
from
re6st
import
plib
,
tunnel
,
utils
,
version
,
x509
...
@@ -58,11 +58,13 @@ def getConfig():
...
@@ -58,11 +58,13 @@ def getConfig():
_
=
parser
.
add_argument_group
(
'routing'
).
add_argument
_
=
parser
.
add_argument_group
(
'routing'
).
add_argument
_
(
'-B'
,
dest
=
'babel_args'
,
metavar
=
'ARG'
,
action
=
'append'
,
default
=
[],
_
(
'-B'
,
dest
=
'babel_args'
,
metavar
=
'ARG'
,
action
=
'append'
,
default
=
[],
help
=
"Extra arguments to forward to Babel."
)
help
=
"Extra arguments to forward to Babel."
)
_
(
'--table'
,
type
=
int
,
default
=
42
,
_
(
'-D'
,
'--default'
,
action
=
'store_true'
,
help
=
"Use given table id. Set 0 to use the main table, if you want to"
help
=
"Access internet via this network (in this case, make sure you"
" access internet via this network (in this case, make sure you"
" don't already have a default route), or if your kernel was"
" don't already have a default route). Don't use this option with"
" compiled without support for source address based routing"
" --gateway (main table is automatically used)."
)
" (CONFIG_IPV6_SUBTREES). Meaningless with --gateway."
)
_
(
'--table'
,
type
=
int
,
choices
=
(
0
,),
help
=
"DEPRECATED: Use --default instead of --table=0"
)
_
(
'--gateway'
,
action
=
'store_true'
,
_
(
'--gateway'
,
action
=
'store_true'
,
help
=
"Act as a gateway for this network (the default route will be"
help
=
"Act as a gateway for this network (the default route will be"
" exported). Do never use it if you don't know what it means."
)
" exported). Do never use it if you don't know what it means."
)
...
@@ -140,9 +142,15 @@ def main():
...
@@ -140,9 +142,15 @@ def main():
if
config
.
max_clients
is
None
:
if
config
.
max_clients
is
None
:
config
.
max_clients
=
cache
.
max_clients
config
.
max_clients
=
cache
.
max_clients
if
config
.
table
is
not
None
:
logging
.
warning
(
"--table option is deprecated: use --default instead"
)
config
.
default
=
True
if
config
.
default
and
config
.
gateway
:
sys
.
exit
(
"error: conflicting options --default and --gateway"
)
if
'none'
in
config
.
disable_proto
:
if
'none'
in
config
.
disable_proto
:
config
.
disable_proto
=
()
config
.
disable_proto
=
()
if
not
config
.
table
:
if
config
.
default
:
# Make sure we won't tunnel over re6st.
# Make sure we won't tunnel over re6st.
config
.
disable_proto
=
tuple
(
set
((
'tcp6'
,
'udp6'
)).
union
(
config
.
disable_proto
=
tuple
(
set
((
'tcp6'
,
'udp6'
)).
union
(
config
.
disable_proto
))
config
.
disable_proto
))
...
@@ -221,6 +229,11 @@ def main():
...
@@ -221,6 +229,11 @@ def main():
raise
EnvironmentError
(
"%r failed with error %u
\
n
%s"
raise
EnvironmentError
(
"%r failed with error %u
\
n
%s"
%
(
' '
.
join
(
cmd
),
p
.
returncode
,
stderr
))
%
(
' '
.
join
(
cmd
),
p
.
returncode
,
stderr
))
return
stdout
return
stdout
def
ip4
(
object
,
*
args
):
args
=
[
'ip'
,
'-4'
,
object
,
'add'
]
+
list
(
args
)
call
(
args
)
args
[
3
]
=
'del'
cleanup
.
append
(
lambda
:
subprocess
.
call
(
args
))
def
ip
(
object
,
*
args
):
def
ip
(
object
,
*
args
):
args
=
[
'ip'
,
'-6'
,
object
,
'add'
]
+
list
(
args
)
args
=
[
'ip'
,
'-6'
,
object
,
'add'
]
+
list
(
args
)
call
(
args
)
call
(
args
)
...
@@ -236,10 +249,9 @@ def main():
...
@@ -236,10 +249,9 @@ def main():
os
.
environ
[
're6stnet_iface'
]
=
config
.
main_interface
os
.
environ
[
're6stnet_iface'
]
=
config
.
main_interface
os
.
environ
[
're6stnet_subnet'
]
=
my_subnet
os
.
environ
[
're6stnet_subnet'
]
=
my_subnet
os
.
environ
[
're6stnet_network'
]
=
my_network
os
.
environ
[
're6stnet_network'
]
=
my_network
my_ip
+=
'/%s'
%
len
(
subnet
)
# Init db and tunnels
# Init db and tunnels
tunnel_interfaces
=
server_tunnels
.
keys
()
config
.
babel_args
+=
server_tunnels
timeout
=
4
*
cache
.
hello
timeout
=
4
*
cache
.
hello
cleanup
=
[
lambda
:
cache
.
cacheMinimize
(
config
.
client_count
),
cleanup
=
[
lambda
:
cache
.
cacheMinimize
(
config
.
client_count
),
lambda
:
shutil
.
rmtree
(
config
.
run
,
True
)]
lambda
:
shutil
.
rmtree
(
config
.
run
,
True
)]
...
@@ -250,29 +262,48 @@ def main():
...
@@ -250,29 +262,48 @@ def main():
cache
,
cert
,
config
.
openvpn_args
,
timeout
,
cache
,
cert
,
config
.
openvpn_args
,
timeout
,
config
.
client_count
,
config
.
iface_list
,
address
,
ip_changed
,
config
.
client_count
,
config
.
iface_list
,
address
,
ip_changed
,
remote_gateway
,
config
.
disable_proto
,
config
.
neighbour
)
remote_gateway
,
config
.
disable_proto
,
config
.
neighbour
)
tunnel_interface
s
+=
tunnel_manager
.
new_iface_list
config
.
babel_arg
s
+=
tunnel_manager
.
new_iface_list
else
:
else
:
tunnel_manager
=
tunnel
.
BaseTunnelManager
(
cache
,
cert
)
tunnel_manager
=
tunnel
.
BaseTunnelManager
(
cache
,
cert
)
cleanup
.
append
(
tunnel_manager
.
sock
.
close
)
cleanup
.
append
(
tunnel_manager
.
sock
.
close
)
try
:
try
:
exit
.
acquire
()
exit
.
acquire
()
# Source address selection is defined by RFC 6724, and in most
# applications, it usually works thanks to rule 5 (prefer outgoing
ipv4
=
getattr
(
cache
,
'ipv4'
,
None
)
# interface). But here, it rarely applies because we use several
if
ipv4
:
# interfaces to connect to a re6st network.
serial
=
int
(
cert
.
cert
.
get_subject
().
serialNumber
)
# Rule 7 is little strange because it prefers temporary addresses
if
cache
.
ipv4_sublen
<=
16
and
serial
<
1
<<
cache
.
ipv4_sublen
:
# over IP with a longer matching prefix (rule 8, which is not even
dot4
=
lambda
x
:
socket
.
inet_ntoa
(
struct
.
pack
(
'!I'
,
x
))
# mandatory).
ip4
(
'route'
,
'unreachable'
,
ipv4
,
'proto'
,
'static'
)
# So only rule 6 can make the difference, i.e. prefer same label.
ipv4
,
n
=
ipv4
.
split
(
'/'
)
# The value of the label does not matter, except that it must be
ipv4
,
=
struct
.
unpack
(
'!I'
,
socket
.
inet_aton
(
ipv4
))
# different from ::/0's (normally equal to 1).
n
=
int
(
n
)
+
cache
.
ipv4_sublen
# XXX: This does not work with extra interfaces that already have
x
=
ipv4
|
serial
<<
32
-
n
# an public IP so Babel must be changed to set a source
ipv4
=
dot4
(
x
|
(
n
<
31
))
# address on routes it installs.
config
.
openvpn_args
+=
'--ifconfig'
,
\
ip
(
'addrlabel'
,
'prefix'
,
my_network
,
'label'
,
'99'
)
ipv4
,
dot4
((
1
<<
32
)
-
(
1
<<
32
-
n
))
ipv4
=
ipv4
,
n
if
not
isinstance
(
tunnel_manager
,
tunnel
.
TunnelManager
):
ip4
(
'addr'
,
"%s/%s"
%
ipv4
,
'dev'
,
config
.
main_interface
)
if
config
.
main_interface
==
"lo"
:
ip4
(
'route'
,
'unreachable'
,
"%s/%s"
%
(
dot4
(
x
),
n
),
'proto'
,
'static'
)
else
:
logging
.
warning
(
"IPv4 payload disabled due to wrong network parameters"
)
ipv4
=
None
if
os
.
uname
()[
2
]
<
'2.6.40'
:
# BBB
logging
.
warning
(
"Fallback to ip-addrlabel because Linux < 3.0"
" does not support RTA_PREFSRC for ipv6. Note however that"
" this workaround does not work with extra interfaces that"
" already have a public IP"
)
ip
(
'addrlabel'
,
'prefix'
,
my_network
,
'label'
,
'99'
)
# No need to tell babeld not to set a preferred source IP in
# installed routes. The kernel will silently discard the option.
R
=
{}
R
=
{}
# prepare persistent interfaces
if
config
.
client
:
if
config
.
client
:
address_list
=
[
x
for
x
in
utils
.
parse_address
(
config
.
client
)
address_list
=
[
x
for
x
in
utils
.
parse_address
(
config
.
client
)
if
x
[
2
]
not
in
config
.
disable_proto
]
if
x
[
2
]
not
in
config
.
disable_proto
]
...
@@ -296,7 +327,8 @@ def main():
...
@@ -296,7 +327,8 @@ def main():
R
[
r
]
=
partial
(
tunnel_manager
.
handleServerEvent
,
r
)
R
[
r
]
=
partial
(
tunnel_manager
.
handleServerEvent
,
r
)
x
.
close
()
x
.
close
()
ip
(
'addr'
,
my_ip
,
'dev'
,
config
.
main_interface
)
ip
(
'addr'
,
my_ip
+
'/%s'
%
len
(
subnet
),
'dev'
,
config
.
main_interface
)
if_rt
=
[
'ip'
,
'-6'
,
'route'
,
'del'
,
if_rt
=
[
'ip'
,
'-6'
,
'route'
,
'del'
,
'fe80::/64'
,
'dev'
,
config
.
main_interface
]
'fe80::/64'
,
'dev'
,
config
.
main_interface
]
if
config
.
main_interface
==
'lo'
:
if
config
.
main_interface
==
'lo'
:
...
@@ -305,30 +337,13 @@ def main():
...
@@ -305,30 +337,13 @@ def main():
subprocess
.
call
(
if_rt
)
subprocess
.
call
(
if_rt
)
if_rt
[
4
]
=
my_subnet
if_rt
[
4
]
=
my_subnet
cleanup
.
append
(
lambda
:
subprocess
.
call
(
if_rt
))
cleanup
.
append
(
lambda
:
subprocess
.
call
(
if_rt
))
x
=
[
my_network
]
if
config
.
default
:
if
config
.
gateway
:
config
.
table
=
0
elif
config
.
table
:
x
+=
'table'
,
str
(
config
.
table
)
try
:
ip
(
'rule'
,
'from'
,
*
x
)
except
EnvironmentError
:
logging
.
error
(
"It seems that your kernel was compiled"
" without support for source address based routing"
" (CONFIG_IPV6_SUBTREES). Consider using --table=0"
" option if you can't change your kernel."
)
raise
ip
(
'rule'
,
'to'
,
*
x
)
call
(
if_rt
)
if_rt
+=
x
[
1
:]
call
(
if_rt
[:
3
]
+
[
'add'
,
'proto'
,
'static'
]
+
if_rt
[
4
:])
else
:
def
check_no_default_route
():
def
check_no_default_route
():
for
route
in
call
((
'ip'
,
'-6'
,
'route'
,
'show'
,
for
route
in
call
((
'ip'
,
'-6'
,
'route'
,
'show'
,
'default'
)).
splitlines
():
'default'
)).
splitlines
():
if
' proto 42 '
not
in
route
:
if
' proto 42 '
not
in
route
:
sys
.
exit
(
"Detected default route (%s)"
sys
.
exit
(
"Detected default route (%s)"
" whereas you specified --
table=0
."
" whereas you specified --
default
."
" Fix your configuration."
%
route
)
" Fix your configuration."
%
route
)
check_no_default_route
()
check_no_default_route
()
def
check_no_default_route_thread
():
def
check_no_default_route_thread
():
...
@@ -347,14 +362,17 @@ def main():
...
@@ -347,14 +362,17 @@ def main():
t
=
threading
.
Thread
(
target
=
check_no_default_route_thread
)
t
=
threading
.
Thread
(
target
=
check_no_default_route_thread
)
t
.
daemon
=
True
t
.
daemon
=
True
t
.
start
()
t
.
start
()
ip
(
'route'
,
'unreachable'
,
*
x
)
ip
(
'route'
,
'unreachable'
,
my_network
)
config
.
babel_args
+=
config
.
iface_list
config
.
babel_args
+=
config
.
iface_list
cleanup
.
append
(
plib
.
router
(
subnet
,
cache
.
hello
,
config
.
table
,
cleanup
.
append
(
plib
.
router
((
my_ip
,
len
(
subnet
)),
ipv4
,
None
if
config
.
gateway
else
''
if
config
.
default
else
my_network
,
cache
.
hello
,
os
.
path
.
join
(
config
.
log
,
'babeld.log'
),
os
.
path
.
join
(
config
.
log
,
'babeld.log'
),
os
.
path
.
join
(
config
.
state
,
'babeld.state'
),
os
.
path
.
join
(
config
.
state
,
'babeld.state'
),
os
.
path
.
join
(
config
.
run
,
'babeld.pid'
),
os
.
path
.
join
(
config
.
run
,
'babeld.pid'
),
tunnel_interfaces
,
control_socket
,
cache
.
babel_default
,
control_socket
,
cache
.
babel_default
,
*
config
.
babel_args
).
stop
)
*
config
.
babel_args
).
stop
)
if
config
.
up
:
if
config
.
up
:
exit
.
release
()
exit
.
release
()
...
...
re6stnet.spec
View file @
abf8211e
...
@@ -8,7 +8,7 @@ Release: %(set %ver; echo ${1#*-})
...
@@ -8,7 +8,7 @@ Release: %(set %ver; echo ${1#*-})
License: GPLv2+
License: GPLv2+
Group: Applications/Internet
Group: Applications/Internet
BuildArch: noarch
BuildArch: noarch
Requires: babeld = 1.
5.1-nxd2
Requires: babeld = 1.
6-git0.8950d3b.nxd1
Requires: iproute
Requires: iproute
Requires: openssl
Requires: openssl
Requires: openvpn
Requires: openvpn
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment