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
Joanne Hugé
re6stnet
Commits
f3ccd7a2
Commit
f3ccd7a2
authored
May 28, 2013
by
Jondy Zhao
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into cygwin
Conflicts: re6stnet
parents
b04b2b95
6e443638
Changes
19
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
681 additions
and
386 deletions
+681
-386
TODO
TODO
+5
-0
demo/ca.crt
demo/ca.crt
+0
-20
demo/demo
demo/demo
+10
-4
demo/m1/re6stnet.conf
demo/m1/re6stnet.conf
+1
-0
demo/m2/re6stnet.conf
demo/m2/re6stnet.conf
+1
-0
demo/m3/re6stnet.conf
demo/m3/re6stnet.conf
+1
-0
demo/m4/re6stnet.conf
demo/m4/re6stnet.conf
+1
-0
demo/m5/re6stnet.conf
demo/m5/re6stnet.conf
+1
-0
demo/m6/re6stnet.conf
demo/m6/re6stnet.conf
+1
-0
demo/m7/re6stnet.conf
demo/m7/re6stnet.conf
+1
-0
demo/m8/re6stnet.conf
demo/m8/re6stnet.conf
+1
-0
demo/registry/re6stnet.conf
demo/registry/re6stnet.conf
+1
-0
re6st-conf
re6st-conf
+14
-6
re6st-registry
re6st-registry
+91
-286
re6st/db.py
re6st/db.py
+53
-35
re6st/registry.py
re6st/registry.py
+355
-0
re6st/tunnel.py
re6st/tunnel.py
+15
-8
re6st/utils.py
re6st/utils.py
+33
-5
re6stnet
re6stnet
+96
-22
No files found.
TODO
View file @
f3ccd7a2
...
...
@@ -23,3 +23,8 @@
use --main-interface option on it.
- Is it useful that each node regenerates its own DH parameter ?
- Filter non-routable IPs. Add an option not to do it.
- Abort in case of import child process failure (babel, openvpn server,
openvpn client if run with --client).
\ No newline at end of file
demo/ca.crt
deleted
100644 → 0
View file @
b04b2b95
-----BEGIN CERTIFICATE-----
MIIDTTCCAjWgAwIBAgIHASABDbgAQjANBgkqhkiG9w0BAQUFADA+MRowGAYDVQQD
DBFyZTZzdC5leGFtcGxlLmNvbTEgMB4GCSqGSIb3DQEJARYRcmU2c3RAZXhhbXBs
ZS5jb20wHhcNMTIwOTA2MTI0MTM0WhcNMjAwMTAxMTI0MTM0WjA+MRowGAYDVQQD
DBFyZTZzdC5leGFtcGxlLmNvbTEgMB4GCSqGSIb3DQEJARYRcmU2c3RAZXhhbXBs
ZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzKdaI1gddt8iN
5MTNMe+bKuVt0Cfq34OUh0y73NQm1HjUMDHiU/5djkBuw+Y8q8OMIwCuN8Pgyp6C
fFivLH3a42ebgxJn2kfU7078gibCEuIPWD9GXQjSgP+7MJVv3q24g4YZTsa6lKJ8
Y1OfP2i2uNR3v7/3BBIsob8pial2kbLgz96L+czDjPOVsV+VH91Mkq6kO7jfNpNo
BHVSXzkQpVupxi86wEFIrkzlAYZRmY0fRcprhovI1iDLpdsJsY/yyPgfXNJk9xhT
lPUoNxyH0EPhGv0KArJZkwlzdDj5RVYYcLTEge374gr1EIMyzex/kN5y1as2tX9l
wXTROc5pAgMBAAGjUDBOMB0GA1UdDgQWBBSgDNnHOCF5xSGbmA9SCujCLRs0nTAf
BgNVHSMEGDAWgBSgDNnHOCF5xSGbmA9SCujCLRs0nTAMBgNVHRMEBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4IBAQBZQvMkCSCrrJoS432kJUg//iB0+c1mftbYTez+wqHq
NzEPnv5EWJtYsYvZUx6huNvrv5UR9S9MkGyH1u8kw3mW5lRKTPBC9NdAgywhsDES
VTDx02EZhsKEA2VaxhirGyJEDSgXADQNZNtB0Mw+M8/tociZKOiih6gwJw3sYcDz
9mTQFG44YG2nSmxEqP2m+32km0gvxLNIyoCnZN1x25dcRcJ5H9AbbIfSZxC02rqc
Wy0HLmfa7ZPLYD5Qz/TuCXXRXxyy5AYasVsz2GdXDNXRwiEmYqfM69EDtwZqTPZj
cfJdgSNqrysIXYE6SgBi6RUtOlmBubdxke4EZZ4ImdGo
-----END CERTIFICATE-----
demo/demo
View file @
f3ccd7a2
...
...
@@ -5,6 +5,7 @@ IPTABLES = 'iptables'
SCREEN = 'screen'
VERBOSE = 4
REGISTRY='10.0.0.2'
CA_DAYS = 1000
# registry
# |.2
...
...
@@ -154,6 +155,10 @@ gateway1.screen('miniupnpd -d -f miniupnpd.conf -P miniupnpd.pid -a 10.1.1.1'
' -i %s' % g1_if_0_name)
if 1:
import sqlite3
os.path.exists('ca.crt') or subprocess.check_call(
"openssl req -nodes -new -x509 -key registry/ca.key -out ca.crt"
" -subj /CN=re6st.example.com/emailAddress=re6st@example.com"
" -set_serial 0x120010db80042 -days %u" % CA_DAYS, shell=True)
db_path = 'registry/registry.db'
registry.screen('../re6st-registry @registry/re6st-registry.conf --db %s'
' --mailhost %s -v%u' % (db_path, os.path.abspath('mbox'), VERBOSE))
...
...
@@ -189,7 +194,7 @@ if 1:
p.communicate(str(token[0]))
os.remove(dh_path)
os.remove(folder + '/ca.crt')
node.screen('../re6stnet @%s/re6stnet.conf -
-table 0 -
v%u --registry %s %s'
node.screen('../re6stnet @%s/re6stnet.conf -v%u --registry %s %s'
% (folder, VERBOSE, registry, args))
re6stnet(registry, 'registry', '--ip ' + REGISTRY, registry='http://localhost/')
re6stnet(machine1, 'm1', '-I%s' % m1_if_0.name)
...
...
@@ -278,8 +283,9 @@ if len(sys.argv) > 1:
elif self.path == '/tunnel.html':
other = 'route'
gv = registry.Popen(('python', '-c', r"""if 1:
import math, xmlrpclib
g = xmlrpclib.ServerProxy('http://localhost/').topology()
import math
from re6st.registry import RegistryClient
g = eval(RegistryClient('http://localhost/').topology())
print 'digraph {'
a = 2 * math.pi / len(g)
z = 4
...
...
@@ -293,7 +299,7 @@ if len(sys.argv) > 1:
for
p
in
p
or
()
:
print
'"%
s
"
-
>
"%s";' % (n, title(p))
print '}'
"""), stdout=subprocess.PIPE).communicate()[0]
"""), stdout=subprocess.PIPE
, cwd=".."
).communicate()[0]
if gv:
svg = subprocess.Popen(('neato', '-Tsvg'),
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
...
...
demo/m1/re6stnet.conf
View file @
f3ccd7a2
...
...
@@ -7,5 +7,6 @@ dh dh2048.pem
ca
ca
.
crt
cert
m1
/
cert
.
crt
key
m1
/
cert
.
key
table
0
client
-
count
2
tunnel
-
refresh
100
demo/m2/re6stnet.conf
View file @
f3ccd7a2
...
...
@@ -7,5 +7,6 @@ dh dh2048.pem
ca
ca
.
crt
cert
m2
/
cert
.
crt
key
m2
/
cert
.
key
table
0
client
-
count
2
tunnel
-
refresh
100
demo/m3/re6stnet.conf
View file @
f3ccd7a2
...
...
@@ -7,5 +7,6 @@ dh dh2048.pem
ca
ca
.
crt
cert
m3
/
cert
.
crt
key
m3
/
cert
.
key
table
0
client
-
count
2
tunnel
-
refresh
100
demo/m4/re6stnet.conf
View file @
f3ccd7a2
...
...
@@ -7,5 +7,6 @@ dh dh2048.pem
ca
ca
.
crt
cert
m4
/
cert
.
crt
key
m4
/
cert
.
key
table
0
client
-
count
2
tunnel
-
refresh
100
demo/m5/re6stnet.conf
View file @
f3ccd7a2
...
...
@@ -5,5 +5,6 @@ hello 4
ca
ca
.
crt
cert
m5
/
cert
.
crt
key
m5
/
cert
.
key
table
0
client
-
count
0
max
-
clients
0
demo/m6/re6stnet.conf
View file @
f3ccd7a2
...
...
@@ -7,6 +7,7 @@ dh dh2048.pem
ca
ca
.
crt
cert
m6
/
cert
.
crt
key
m6
/
cert
.
key
table
0
client
-
count
2
tunnel
-
refresh
100
# TODO: Run a DHCPv4 client on machine9. Unfortunately, isc-dhcp-client 4.2.4
...
...
demo/m7/re6stnet.conf
View file @
f3ccd7a2
...
...
@@ -7,5 +7,6 @@ dh dh2048.pem
ca
ca
.
crt
cert
m7
/
cert
.
crt
key
m7
/
cert
.
key
table
0
client
-
count
2
tunnel
-
refresh
100
demo/m8/re6stnet.conf
View file @
f3ccd7a2
...
...
@@ -5,4 +5,5 @@ hello 4
ca
ca
.
crt
cert
m8
/
cert
.
crt
key
m8
/
cert
.
key
table
0
client
10
.
0
.
1
.
2
,
1194
,
udp
;
10
.
0
.
1
.
3
,
1194
,
udp
demo/registry/re6stnet.conf
View file @
f3ccd7a2
...
...
@@ -6,5 +6,6 @@ dh dh2048.pem
ca
ca
.
crt
cert
registry
/
cert
.
crt
key
registry
/
cert
.
key
gateway
client
-
count
2
tunnel
-
refresh
100
re6st-conf
View file @
f3ccd7a2
#!/usr/bin/python
import
argparse
,
atexit
,
errno
,
os
,
subprocess
,
sqlite3
,
sys
,
xmlrpclib
import
argparse
,
atexit
,
errno
,
os
,
subprocess
,
sqlite3
,
sys
,
time
from
OpenSSL
import
crypto
from
re6st
import
utils
from
re6st
import
registry
,
utils
def
create
(
path
,
text
=
None
,
mode
=
0666
):
fd
=
os
.
open
(
path
,
os
.
O_CREAT
|
os
.
O_WRONLY
|
os
.
O_TRUNC
,
mode
)
...
...
@@ -10,6 +10,9 @@ def create(path, text=None, mode=0666):
finally
:
os
.
close
(
fd
)
def
loadCert
(
pem
):
return
crypto
.
load_certificate
(
crypto
.
FILETYPE_PEM
,
pem
)
def
main
():
parser
=
argparse
.
ArgumentParser
(
description
=
"Setup script for re6stnet."
,
...
...
@@ -47,11 +50,11 @@ def main():
dh_path
=
'dh2048.pem'
# Establish connection with server
s
=
xmlrpclib
.
ServerProxy
(
config
.
registry
,
allow_none
=
True
)
s
=
registry
.
RegistryClient
(
config
.
registry
)
# Get CA
ca
=
s
.
getCa
()
network
=
utils
.
networkFromCa
(
ca
)
network
=
utils
.
networkFromCa
(
loadCert
(
ca
)
)
if
config
.
is_needed
:
route
,
err
=
subprocess
.
Popen
((
'ip'
,
'-6'
,
'-o'
,
'route'
,
'get'
,
utils
.
ipFromBin
(
network
)),
...
...
@@ -72,7 +75,7 @@ def main():
req
=
crypto
.
X509Req
()
try
:
with
open
(
cert_path
)
as
f
:
cert
=
crypto
.
load_certificate
(
crypto
.
FILETYPE_PEM
,
f
.
read
())
cert
=
loadCert
(
f
.
read
())
components
=
dict
(
cert
.
get_subject
().
get_components
())
components
.
pop
(
'CN'
,
None
)
except
IOError
,
e
:
...
...
@@ -136,7 +139,12 @@ def main():
os
.
ftruncate
(
cert_fd
,
len
(
cert
))
os
.
close
(
cert_fd
)
print
"Certificate setup complete."
cert
=
loadCert
(
cert
)
not_after
=
utils
.
notAfter
(
cert
)
print
(
"Setup complete. Certificate is valid until %s UTC"
" and will be automatically renewed after %s UTC"
%
(
time
.
asctime
(
time
.
gmtime
(
not_after
)),
time
.
asctime
(
time
.
gmtime
(
not_after
-
registry
.
RENEW_PERIOD
))))
if
not
os
.
path
.
lexists
(
conf_path
):
create
(
conf_path
,
"""
\
...
...
re6st-registry
View file @
f3ccd7a2
#!/usr/bin/python
import
errno
,
logging
,
mailbox
,
os
,
random
,
select
import
smtplib
,
socket
,
sqlite3
,
string
,
subprocess
,
sys
import
threading
,
time
,
traceback
,
xmlrpclib
from
collections
import
deque
from
SimpleXMLRPCServer
import
SimpleXMLRPCServer
,
SimpleXMLRPCRequestHandler
from
email.mime.text
import
MIMEText
from
OpenSSL
import
crypto
from
re6st
import
tunnel
,
utils
import
errno
,
httplib
,
logging
,
select
,
socket
from
BaseHTTPServer
import
BaseHTTPRequestHandler
from
SocketServer
import
ThreadingTCPServer
from
urlparse
import
parse_qsl
from
re6st
import
registry
,
utils
# To generate server ca and key with serial for 2001:db8:42::/48
#
openssl req -nodes -new -x509 -key ca.key -set_serial 0x120010db80042 -days 365
-out ca.crt
#
openssl req -nodes -new -x509 -key ca.key -set_serial 0x120010db80042 -days 3650
-out ca.crt
IPV6_V6ONLY
=
26
SOL_IPV6
=
41
class
RequestHandler
(
SimpleXMLRPC
RequestHandler
):
class
RequestHandler
(
BaseHTTP
RequestHandler
):
def
address_string
(
self
):
# Workaround for http://bugs.python.org/issue6085
return
self
.
client_address
[
0
]
def
_dispatch
(
self
,
method
,
params
):
logging
.
debug
(
'%s%r'
,
method
,
params
)
return
self
.
server
.
_dispatch
(
method
,
(
self
,)
+
params
)
def
do_GET
(
self
):
try
:
try
:
path
,
query
=
self
.
path
.
split
(
'?'
,
1
)
except
ValueError
:
path
=
self
.
path
query
=
{}
else
:
query
=
dict
(
parse_qsl
(
query
,
keep_blank_values
=
1
,
strict_parsing
=
1
))
_
,
path
=
path
.
split
(
'/'
)
if
not
_
and
path
[
0
]
!=
'_'
:
return
self
.
server
.
_handle_request
(
self
,
path
,
query
)
except
Exception
:
logging
.
info
(
self
.
requestline
,
exc_info
=
1
)
self
.
send_error
(
httplib
.
BAD_REQUEST
)
def
log_error
(
*
args
):
pass
class
SimpleXMLRPCServer4
(
SimpleXMLRPC
Server
):
class
HTTPServer4
(
ThreadingTCP
Server
):
allow_reuse_address
=
True
daemon_threads
=
True
class
SimpleXMLRPCServer6
(
SimpleXMLRPC
Server4
):
class
HTTPServer6
(
HTTP
Server4
):
address_family
=
socket
.
AF_INET6
def
server_bind
(
self
):
self
.
socket
.
setsockopt
(
SOL_IPV6
,
IPV6_V6ONLY
,
1
)
SimpleXMLRPCServer4
.
server_bind
(
self
)
class
main
(
object
):
def
__init__
(
self
):
self
.
cert_duration
=
365
*
86400
self
.
time_out
=
45000
self
.
refresh_interval
=
600
self
.
last_refresh
=
time
.
time
()
# Command line parsing
parser
=
utils
.
ArgParser
(
fromfile_prefix_chars
=
'@'
,
description
=
"re6stnet registry used to bootstrap nodes"
" and deliver certificates."
)
_
=
parser
.
add_argument
_
(
'--port'
,
type
=
int
,
default
=
80
,
help
=
"Port on which the server will listen."
)
_
(
'-4'
,
dest
=
'bind4'
,
default
=
'0.0.0.0'
,
help
=
"Bind server to this IPv4."
)
_
(
'-6'
,
dest
=
'bind6'
,
default
=
'::'
,
help
=
"Bind server to this IPv6."
)
_
(
'--db'
,
default
=
'/var/lib/re6stnet/registry.db'
,
help
=
"Path to SQLite database file. It is automatically initialized"
" if the file does not exist."
)
_
(
'--ca'
,
required
=
True
,
help
=
parser
.
_ca_help
)
_
(
'--key'
,
required
=
True
,
help
=
"CA private key in .pem format."
)
_
(
'--mailhost'
,
required
=
True
,
help
=
"SMTP host to send confirmation emails. For debugging"
" purpose, it can also be an absolute or existing path to"
" a mailbox file"
)
_
(
'--private'
,
help
=
"re6stnet IP of the node on which runs the registry."
" Required for normal operation."
)
_
(
'--prefix-length'
,
default
=
16
,
type
=
int
,
help
=
"Default length of allocated prefixes."
)
_
(
'--anonymous-prefix-length'
,
type
=
int
,
help
=
"Length of allocated anonymous prefixes."
" If 0 or unset, registration by email is required"
)
_
(
'-l'
,
'--logfile'
,
default
=
'/var/log/re6stnet/registry.log'
,
help
=
"Path to logging file."
)
_
(
'-v'
,
'--verbose'
,
default
=
1
,
type
=
int
,
help
=
"Log level. 0 disables logging."
" Use SIGUSR1 to reopen log."
)
self
.
config
=
parser
.
parse_args
()
utils
.
setupLog
(
self
.
config
.
verbose
,
self
.
config
.
logfile
)
if
self
.
config
.
private
:
self
.
sock
=
socket
.
socket
(
socket
.
AF_INET6
,
socket
.
SOCK_DGRAM
)
else
:
logging
.
warning
(
'You have declared no private address'
', either this is the first start, or you should'
'check you configuration'
)
# Database initializing
utils
.
makedirs
(
os
.
path
.
dirname
(
self
.
config
.
db
))
self
.
db
=
sqlite3
.
connect
(
self
.
config
.
db
,
isolation_level
=
None
)
self
.
db
.
execute
(
"""CREATE TABLE IF NOT EXISTS token (
token text primary key not null,
email text not null,
prefix_len integer not null,
date integer not null)"""
)
try
:
self
.
db
.
execute
(
"""CREATE TABLE cert (
prefix text primary key not null,
email text,
cert text)"""
)
except
sqlite3
.
OperationalError
,
e
:
if
e
.
args
[
0
]
!=
'table cert already exists'
:
raise
RuntimeError
else
:
self
.
db
.
execute
(
"INSERT INTO cert VALUES ('',null,null)"
)
# Loading certificates
with
open
(
self
.
config
.
ca
)
as
f
:
self
.
ca
=
crypto
.
load_certificate
(
crypto
.
FILETYPE_PEM
,
f
.
read
())
with
open
(
self
.
config
.
key
)
as
f
:
self
.
key
=
crypto
.
load_privatekey
(
crypto
.
FILETYPE_PEM
,
f
.
read
())
# Get vpn network prefix
self
.
network
=
bin
(
self
.
ca
.
get_serial_number
())[
3
:]
logging
.
info
(
"Network: %s/%u"
,
utils
.
ipFromBin
(
self
.
network
),
len
(
self
.
network
))
self
.
_email
=
self
.
ca
.
get_subject
().
emailAddress
# Starting server
server_list
=
[]
if
self
.
config
.
bind4
:
server4
=
SimpleXMLRPCServer4
((
self
.
config
.
bind4
,
self
.
config
.
port
),
requestHandler
=
RequestHandler
,
allow_none
=
True
)
server4
.
register_instance
(
self
)
server_list
.
append
(
server4
)
if
self
.
config
.
bind6
:
server6
=
SimpleXMLRPCServer6
((
self
.
config
.
bind6
,
self
.
config
.
port
),
requestHandler
=
RequestHandler
,
allow_none
=
True
)
server6
.
register_instance
(
self
)
server_list
.
append
(
server6
)
if
len
(
server_list
)
==
1
:
server_list
[
0
].
serve_forever
()
else
:
while
True
:
try
:
r
=
select
.
select
(
server_list
[:],
[],
[])[
0
]
except
select
.
error
as
e
:
if
e
.
args
[
0
]
!=
errno
.
EINTR
:
raise
else
:
for
r
in
r
:
r
.
_handle_request_noblock
()
def
requestToken
(
self
,
handler
,
email
):
HTTPServer4
.
server_bind
(
self
)
def
main
():
parser
=
utils
.
ArgParser
(
fromfile_prefix_chars
=
'@'
,
description
=
"re6stnet registry used to bootstrap nodes"
" and deliver certificates."
)
_
=
parser
.
add_argument
_
(
'--port'
,
type
=
int
,
default
=
80
,
help
=
"Port on which the server will listen."
)
_
(
'-4'
,
dest
=
'bind4'
,
default
=
'0.0.0.0'
,
help
=
"Bind server to this IPv4."
)
_
(
'-6'
,
dest
=
'bind6'
,
default
=
'::'
,
help
=
"Bind server to this IPv6."
)
_
(
'--db'
,
default
=
'/var/lib/re6stnet/registry.db'
,
help
=
"Path to SQLite database file. It is automatically initialized"
" if the file does not exist."
)
_
(
'--ca'
,
required
=
True
,
help
=
parser
.
_ca_help
)
_
(
'--key'
,
required
=
True
,
help
=
"CA private key in .pem format."
)
_
(
'--mailhost'
,
required
=
True
,
help
=
"SMTP host to send confirmation emails. For debugging"
" purpose, it can also be an absolute or existing path to"
" a mailbox file"
)
_
(
'--private'
,
help
=
"re6stnet IP of the node on which runs the registry."
" Required for normal operation."
)
_
(
'--prefix-length'
,
default
=
16
,
type
=
int
,
help
=
"Default length of allocated prefixes."
)
_
(
'--anonymous-prefix-length'
,
type
=
int
,
help
=
"Length of allocated anonymous prefixes."
" If 0 or unset, registration by email is required"
)
_
(
'-l'
,
'--logfile'
,
default
=
'/var/log/re6stnet/registry.log'
,
help
=
"Path to logging file."
)
_
(
'-v'
,
'--verbose'
,
default
=
1
,
type
=
int
,
help
=
"Log level. 0 disables logging."
" Use SIGUSR1 to reopen log."
)
config
=
parser
.
parse_args
()
utils
.
setupLog
(
config
.
verbose
,
config
.
logfile
)
server
=
registry
.
RegistryServer
(
config
)
def
requestHandler
(
request
,
client_address
,
_
):
RequestHandler
(
request
,
client_address
,
server
)
server_list
=
[]
if
config
.
bind4
:
server_list
.
append
(
HTTPServer4
((
config
.
bind4
,
config
.
port
),
requestHandler
))
if
config
.
bind6
:
server_list
.
append
(
HTTPServer6
((
config
.
bind6
,
config
.
port
),
requestHandler
))
if
server_list
:
empty_list
=
[]
while
True
:
# Generating token
token
=
''
.
join
(
random
.
sample
(
string
.
ascii_lowercase
,
8
))
args
=
token
,
email
,
self
.
config
.
prefix_length
,
int
(
time
.
time
())
# Updating database
try
:
self
.
db
.
execute
(
"INSERT INTO token VALUES (?,?,?,?)"
,
args
)
break
except
sqlite3
.
IntegrityError
:
pass
# Creating and sending email
msg
=
MIMEText
(
'Hello, your token to join re6st network is: %s
\
n
'
%
token
)
msg
[
'Subject'
]
=
'[re6stnet] Token Request'
if
self
.
_email
:
msg
[
'From'
]
=
self
.
_email
msg
[
'To'
]
=
email
if
os
.
path
.
isabs
(
self
.
config
.
mailhost
)
or
\
os
.
path
.
isfile
(
self
.
config
.
mailhost
):
m
=
mailbox
.
mbox
(
self
.
config
.
mailhost
)
try
:
m
.
add
(
msg
)
finally
:
m
.
close
()
else
:
s
=
smtplib
.
SMTP
(
self
.
config
.
mailhost
)
s
.
sendmail
(
self
.
_email
,
email
,
msg
.
as_string
())
s
.
quit
()
def
_getPrefix
(
self
,
prefix_len
):
max_len
=
128
-
len
(
self
.
network
)
assert
0
<
prefix_len
<=
max_len
try
:
prefix
,
=
self
.
db
.
execute
(
"""SELECT prefix FROM cert WHERE length(prefix) <= ? AND cert is null
ORDER BY length(prefix) DESC"""
,
(
prefix_len
,)).
next
()
except
StopIteration
:
logging
.
error
(
'No more free /%u prefix available'
,
prefix_len
)
raise
while
len
(
prefix
)
<
prefix_len
:
self
.
db
.
execute
(
"UPDATE cert SET prefix = ? WHERE prefix = ?"
,
(
prefix
+
'1'
,
prefix
))
prefix
+=
'0'
self
.
db
.
execute
(
"INSERT INTO cert VALUES (?,null,null)"
,
(
prefix
,))
if
len
(
prefix
)
<
max_len
or
'1'
in
prefix
:
return
prefix
self
.
db
.
execute
(
"UPDATE cert SET cert = 'reserved' WHERE prefix = ?"
,
(
prefix
,))
return
self
.
_getPrefix
(
prefix_len
)
def
requestCertificate
(
self
,
handler
,
token
,
cert_req
):
try
:
req
=
crypto
.
load_certificate_request
(
crypto
.
FILETYPE_PEM
,
cert_req
)
with
self
.
db
:
if
token
is
None
:
prefix_len
=
self
.
config
.
anonymous_prefix_length
if
not
prefix_len
:
return
email
=
None
else
:
try
:
token
,
email
,
prefix_len
,
_
=
self
.
db
.
execute
(
"SELECT * FROM token WHERE token = ?"
,
(
token
,)).
next
()
except
StopIteration
:
return
self
.
db
.
execute
(
"DELETE FROM token WHERE token = ?"
,
(
token
,))
# Get a new prefix
prefix
=
self
.
_getPrefix
(
prefix_len
)
# Create certificate
cert
=
crypto
.
X509
()
cert
.
set_serial_number
(
0
)
# required for libssl < 1.0
cert
.
gmtime_adj_notBefore
(
0
)
cert
.
gmtime_adj_notAfter
(
self
.
cert_duration
)
cert
.
set_issuer
(
self
.
ca
.
get_subject
())
subject
=
req
.
get_subject
()
subject
.
CN
=
"%u/%u"
%
(
int
(
prefix
,
2
),
prefix_len
)
cert
.
set_subject
(
subject
)
cert
.
set_pubkey
(
req
.
get_pubkey
())
cert
.
sign
(
self
.
key
,
'sha1'
)
cert
=
crypto
.
dump_certificate
(
crypto
.
FILETYPE_PEM
,
cert
)
# Insert certificate into db
self
.
db
.
execute
(
"UPDATE cert SET email = ?, cert = ? WHERE prefix = ?"
,
(
email
,
cert
,
prefix
))
return
cert
except
Exception
:
f
=
traceback
.
format_exception
(
*
sys
.
exc_info
())
logging
.
error
(
'%s%s'
,
f
.
pop
(),
''
.
join
(
f
))
raise
def
getCa
(
self
,
handler
):
return
crypto
.
dump_certificate
(
crypto
.
FILETYPE_PEM
,
self
.
ca
)
def
getPrivateAddress
(
self
,
handler
):
return
self
.
config
.
private
def
getBootstrapPeer
(
self
,
handler
,
client_prefix
):
cert
,
=
self
.
db
.
execute
(
"SELECT cert FROM cert WHERE prefix = ?"
,
(
client_prefix
,)).
next
()
address
=
self
.
config
.
private
,
tunnel
.
PORT
self
.
sock
.
sendto
(
'
\
2
'
,
address
)
peer
=
None
while
select
.
select
([
self
.
sock
],
[],
[],
peer
is
None
)[
0
]:
msg
=
self
.
sock
.
recv
(
1
<<
16
)
if
msg
[
0
]
==
'
\
1
'
:
try
:
peer
=
msg
[
1
:].
split
(
'
\
n
'
)[
-
2
]
except
IndexError
:
peer
=
''
if
peer
is
None
:
raise
EnvironmentError
(
"Timeout while querying [%s]:%u"
%
address
)
if
not
peer
or
peer
.
split
()[
0
]
==
client_prefix
:
raise
LookupError
(
"No bootstrap peer found"
)
logging
.
info
(
"Sending bootstrap peer: %s"
,
peer
)
r
,
w
=
os
.
pipe
()
try
:
threading
.
Thread
(
target
=
os
.
write
,
args
=
(
w
,
cert
)).
start
()
p
=
subprocess
.
Popen
((
'openssl'
,
'rsautl'
,
'-encrypt'
,
'-certin'
,
'-inkey'
,
'/proc/self/fd/%u'
%
r
),
stdin
=
subprocess
.
PIPE
,
stdout
=
subprocess
.
PIPE
)
return
xmlrpclib
.
Binary
(
p
.
communicate
(
peer
)[
0
])
finally
:
os
.
close
(
r
)
os
.
close
(
w
)
r
=
select
.
select
(
server_list
[:],
empty_list
,
empty_list
)[
0
]
except
select
.
error
as
e
:
if
e
.
args
[
0
]
!=
errno
.
EINTR
:
raise
else
:
for
r
in
r
:
r
.
_handle_request_noblock
()
def
topology
(
self
,
handler
):
if
handler
.
client_address
[
0
]
in
(
'127.0.0.1'
,
'::'
):
is_registry
=
utils
.
binFromIp
(
self
.
config
.
private
)[
len
(
self
.
network
):].
startswith
peers
=
deque
(
'%u/%u'
%
(
int
(
x
,
2
),
len
(
x
))
for
x
,
in
self
.
db
.
execute
(
"SELECT prefix FROM cert"
)
if
is_registry
(
x
))
assert
len
(
peers
)
==
1
cookie
=
hex
(
random
.
randint
(
0
,
1
<<
32
))[
2
:]
graph
=
dict
.
fromkeys
(
peers
)
asked
=
0
while
True
:
r
,
w
,
_
=
select
.
select
([
self
.
sock
],
[
self
.
sock
]
if
peers
else
[],
[],
1
)
if
r
:
answer
=
self
.
sock
.
recv
(
1
<<
16
)
if
answer
[
0
]
==
'
\
xfe
'
:
answer
=
answer
[
1
:].
split
(
'
\
n
'
)[:
-
1
]
if
len
(
answer
)
>=
3
and
answer
[
0
]
==
cookie
:
x
=
answer
[
3
:]
assert
answer
[
1
]
not
in
x
,
(
answer
,
graph
)
graph
[
answer
[
1
]]
=
x
[:
int
(
answer
[
2
])]
x
=
set
(
x
).
difference
(
graph
)
peers
+=
x
graph
.
update
(
dict
.
fromkeys
(
x
))
if
w
:
x
=
utils
.
binFromSubnet
(
peers
.
popleft
())
x
=
utils
.
ipFromBin
(
self
.
network
+
x
)
try
:
self
.
sock
.
sendto
(
'
\
xff
%s
\
n
'
%
cookie
,
(
x
,
tunnel
.
PORT
))
except
socket
.
error
:
pass
elif
not
r
:
break
return
graph
if
__name__
==
"__main__"
:
main
()
re6st/db.py
View file @
f3ccd7a2
import
logging
,
sqlite3
,
socket
,
subprocess
,
xmlrpclib
,
time
from
urllib
import
splittype
,
splithost
,
splitport
import
utils
import
logging
,
sqlite3
,
socket
,
subprocess
,
time
from
.
import
utils
class
PeerDB
(
object
):
# internal ip = temp arg/attribute
def
__init__
(
self
,
db_path
,
registry
,
key_path
,
prefix
,
db_size
=
200
):
def
__init__
(
self
,
db_path
,
registry
,
key_path
,
network
,
prefix
,
db_size
=
200
):
self
.
_prefix
=
prefix
self
.
_db_size
=
db_size
self
.
_key_path
=
key_path
self
.
_
proxy
=
xmlrpclib
.
ServerProxy
(
registry
)
self
.
_
registry
=
registry
logging
.
info
(
'Initialize cache ...'
)
self
.
_db
=
sqlite3
.
connect
(
db_path
,
isolation_level
=
None
)
...
...
@@ -25,28 +25,36 @@ class PeerDB(object):
value text)"""
)
q
(
'ATTACH DATABASE ":memory:" AS volatile'
)
q
(
"""CREATE TABLE volatile.stat (
peer TEXT PRIMARY KEY
REFERENCES peer(prefix) ON DELETE CASCADE
,
peer TEXT PRIMARY KEY,
try INTEGER NOT NULL DEFAULT 0)"""
)
q
(
"CREATE INDEX volatile.stat_try ON stat(try)"
)
q
(
"INSERT INTO volatile.stat (peer) SELECT prefix FROM peer"
)
try
:
a
=
q
(
"SELECT value FROM config WHERE name='registry'"
).
next
()[
0
]
except
StopIteration
:
logging
.
info
(
"Private IP of registry not in cache."
" Asking registry via its public IP ..."
)
retry
=
1
while
True
:
try
:
a
=
self
.
_proxy
.
getPrivateAddress
()
break
except
socket
.
error
,
e
:
logging
.
warning
(
e
)
time
.
sleep
(
retry
)
retry
=
min
(
60
,
retry
*
2
)
q
(
"INSERT INTO config VALUES ('registry',?)"
,
(
a
,))
self
.
registry_ip
=
utils
.
binFromIp
(
a
)
a
=
self
.
_updateRegistryIP
()
else
:
self
.
registry_ip
=
utils
.
binFromIp
(
a
)
if
not
self
.
registry_ip
.
startswith
(
network
):
a
=
self
.
_updateRegistryIP
()
logging
.
info
(
"Cache initialized. Registry IP is %s"
,
a
)
def
_updateRegistryIP
(
self
):
logging
.
info
(
"Asking registry its private IP..."
)
retry
=
1
while
True
:
try
:
a
=
self
.
_registry
.
getPrivateAddress
(
self
.
_prefix
)
break
except
socket
.
error
,
e
:
logging
.
warning
(
e
)
time
.
sleep
(
retry
)
retry
=
min
(
60
,
retry
*
2
)
self
.
_db
.
execute
(
"INSERT OR REPLACE INTO config VALUES ('registry',?)"
,
(
a
,))
self
.
registry_ip
=
utils
.
binFromIp
(
a
)
return
a
def
log
(
self
):
if
logging
.
getLogger
().
isEnabledFor
(
5
):
logging
.
trace
(
"Cache:"
)
...
...
@@ -83,31 +91,41 @@ class PeerDB(object):
def
getBootstrapPeer
(
self
):
logging
.
info
(
'Getting Boot peer...'
)
try
:
bootpeer
=
self
.
_proxy
.
getBootstrapPeer
(
self
.
_prefix
).
data
except
(
socket
.
error
,
xmlrpclib
.
Fault
),
e
:
bootpeer
=
self
.
_registry
.
getBootstrapPeer
(
self
.
_prefix
)
prefix
,
address
=
utils
.
decrypt
(
self
.
_key_path
,
bootpeer
).
split
()
except
(
socket
.
error
,
subprocess
.
CalledProcessError
,
ValueError
),
e
:
logging
.
warning
(
'Failed to bootstrap (%s)'
,
e
)
else
:
p
=
subprocess
.
Popen
((
'openssl'
,
'rsautl'
,
'-decrypt'
,
'-inkey'
,
self
.
_key_path
),
stdin
=
subprocess
.
PIPE
,
stdout
=
subprocess
.
PIPE
)
bootpeer
=
p
.
communicate
(
bootpeer
)[
0
].
split
()
if
bootpeer
[
0
]
!=
self
.
_prefix
:
self
.
addPeer
(
*
bootpeer
)
return
bootpeer
if
prefix
!=
self
.
_prefix
:
self
.
addPeer
(
prefix
,
address
)
return
prefix
,
address
logging
.
warning
(
'Buggy registry sent us our own address'
)
def
addPeer
(
self
,
prefix
,
address
,
force
=
False
):
def
addPeer
(
self
,
prefix
,
address
,
set_preferred
=
False
):
logging
.
debug
(
'Adding peer %s: %s'
,
prefix
,
address
)
with
self
.
_db
:
q
=
self
.
_db
.
execute
try
:
(
a
,),
=
q
(
"SELECT address FROM peer WHERE prefix=?"
,
(
prefix
,))
a
=
a
!=
address
if
force
else
\
set
(
a
.
split
(
';'
))
!=
set
(
address
.
split
(
';'
))
if
set_preferred
:
preferred
=
address
.
split
(
';'
)
address
=
a
else
:
preferred
=
a
.
split
(
';'
)
def
key
(
a
):
try
:
return
preferred
.
index
(
a
)
except
ValueError
:
return
len
(
preferred
)
address
=
';'
.
join
(
sorted
(
address
.
split
(
';'
),
key
=
key
))
except
ValueError
:
q
(
"DELETE FROM peer WHERE prefix IN (SELECT peer"
" FROM volatile.stat ORDER BY try, RANDOM() LIMIT ?,-1)"
,
(
self
.
_db_size
,))
a
=
True
if
a
:
a
=
q
(
"SELECT peer FROM volatile.stat ORDER BY try, RANDOM()"
" LIMIT ?,-1"
,
(
self
.
_db_size
,)).
fetchall
()
if
a
:
qq
=
self
.
_db
.
executemany
qq
(
"DELETE FROM peer WHERE prefix IN (?)"
,
a
)
qq
(
"DELETE FROM volatile.stat WHERE peer IN (?)"
,
a
)
# 'a != address' will evaluate to True because types differs
if
a
!=
address
:
q
(
"INSERT OR REPLACE INTO peer VALUES (?,?)"
,
(
prefix
,
address
))
q
(
"INSERT OR REPLACE INTO volatile.stat VALUES (?,0)"
,
(
prefix
,))
re6st/registry.py
0 → 100644
View file @
f3ccd7a2
import
base64
,
hmac
,
hashlib
,
httplib
,
inspect
,
logging
,
mailbox
,
os
,
random
import
select
,
smtplib
,
socket
,
sqlite3
,
string
,
struct
,
threading
,
time
from
collections
import
deque
from
BaseHTTPServer
import
HTTPServer
,
BaseHTTPRequestHandler
from
email.mime.text
import
MIMEText
from
OpenSSL
import
crypto
from
urllib
import
splittype
,
splithost
,
splitport
,
urlencode
from
.
import
tunnel
,
utils
HMAC_HEADER
=
"Re6stHMAC"
RENEW_PERIOD
=
30
*
86400
class
getcallargs
(
type
):
def
__init__
(
cls
,
name
,
bases
,
d
):
type
.
__init__
(
cls
,
name
,
bases
,
d
)
for
n
,
f
in
d
.
iteritems
():
if
n
[
0
]
==
'_'
:
continue
try
:
args
,
varargs
,
varkw
,
defaults
=
inspect
.
getargspec
(
f
)
except
TypeError
:
continue
if
varargs
or
varkw
or
defaults
:
continue
f
.
getcallargs
=
eval
(
"lambda %s: locals()"
%
','
.
join
(
args
[
1
:]))
class
RegistryServer
(
object
):
__metaclass__
=
getcallargs
def
__init__
(
self
,
config
):
self
.
config
=
config
self
.
cert_duration
=
365
*
86400
self
.
lock
=
threading
.
Lock
()
self
.
sessions
=
{}
if
self
.
config
.
private
:
self
.
sock
=
socket
.
socket
(
socket
.
AF_INET6
,
socket
.
SOCK_DGRAM
)
else
:
logging
.
warning
(
'You have declared no private address'
', either this is the first start, or you should'
'check you configuration'
)
# Database initializing
utils
.
makedirs
(
os
.
path
.
dirname
(
self
.
config
.
db
))
self
.
db
=
sqlite3
.
connect
(
self
.
config
.
db
,
isolation_level
=
None
,
check_same_thread
=
False
)
self
.
db
.
execute
(
"""CREATE TABLE IF NOT EXISTS token (
token text primary key not null,
email text not null,
prefix_len integer not null,
date integer not null)"""
)
try
:
self
.
db
.
execute
(
"""CREATE TABLE cert (
prefix text primary key not null,
email text,
cert text)"""
)
except
sqlite3
.
OperationalError
,
e
:
if
e
.
args
[
0
]
!=
'table cert already exists'
:
raise
RuntimeError
else
:
self
.
db
.
execute
(
"INSERT INTO cert VALUES ('',null,null)"
)
# Loading certificates
with
open
(
self
.
config
.
ca
)
as
f
:
self
.
ca
=
crypto
.
load_certificate
(
crypto
.
FILETYPE_PEM
,
f
.
read
())
with
open
(
self
.
config
.
key
)
as
f
:
self
.
key
=
crypto
.
load_privatekey
(
crypto
.
FILETYPE_PEM
,
f
.
read
())
# Get vpn network prefix
self
.
network
=
utils
.
networkFromCa
(
self
.
ca
)
logging
.
info
(
"Network: %s/%u"
,
utils
.
ipFromBin
(
self
.
network
),
len
(
self
.
network
))
self
.
_email
=
self
.
ca
.
get_subject
().
emailAddress
def
_handle_request
(
self
,
request
,
method
,
kw
):
m
=
getattr
(
self
,
method
)
if
method
in
(
'topology'
,)
and
\
request
.
client_address
[
0
]
not
in
(
'127.0.0.1'
,
'::'
):
return
request
.
send_error
(
httplib
.
FORBIDDEN
)
key
=
m
.
getcallargs
(
**
kw
).
get
(
'cn'
)
if
key
:
h
=
base64
.
b64decode
(
request
.
headers
[
HMAC_HEADER
])
with
self
.
lock
:
session
=
self
.
sessions
[
key
]
for
key
in
session
:
if
h
==
hmac
.
HMAC
(
key
,
request
.
path
,
hashlib
.
sha1
).
digest
():
break
else
:
raise
Exception
(
"Wrong HMAC"
)
key
=
hashlib
.
sha1
(
key
).
digest
()
session
[:]
=
hashlib
.
sha1
(
key
).
digest
(),
try
:
result
=
m
(
**
kw
)
except
:
logging
.
warning
(
request
.
requestline
,
exc_info
=
1
)
return
request
.
send_error
(
httplib
.
INTERNAL_SERVER_ERROR
)
if
result
:
request
.
send_response
(
httplib
.
OK
)
request
.
send_header
(
"Content-Length"
,
str
(
len
(
result
)))
else
:
request
.
send_response
(
httplib
.
NO_CONTENT
)
if
key
:
request
.
send_header
(
HMAC_HEADER
,
base64
.
b64encode
(
hmac
.
HMAC
(
key
,
result
,
hashlib
.
sha1
).
digest
()))
request
.
end_headers
()
if
result
:
request
.
wfile
.
write
(
result
)
def
hello
(
self
,
client_prefix
):
with
self
.
lock
:
cert
=
self
.
_getCert
(
client_prefix
)
key
=
hashlib
.
sha1
(
struct
.
pack
(
'Q'
,
random
.
getrandbits
(
64
))).
digest
()
self
.
sessions
.
setdefault
(
client_prefix
,
[])[
1
:]
=
key
,
key
=
utils
.
encrypt
(
cert
,
key
)
sign
=
crypto
.
sign
(
self
.
key
,
key
,
'sha1'
)
assert
len
(
key
)
==
len
(
sign
)
return
key
+
sign
def
_getCert
(
self
,
client_prefix
):
assert
self
.
lock
.
locked
()
return
self
.
db
.
execute
(
"SELECT cert FROM cert WHERE prefix = ?"
,
(
client_prefix
,)).
next
()[
0
]
def
requestToken
(
self
,
email
):
while
True
:
# Generating token
token
=
''
.
join
(
random
.
sample
(
string
.
ascii_lowercase
,
8
))
args
=
token
,
email
,
self
.
config
.
prefix_length
,
int
(
time
.
time
())
# Updating database
try
:
self
.
db
.
execute
(
"INSERT INTO token VALUES (?,?,?,?)"
,
args
)
break
except
sqlite3
.
IntegrityError
:
pass
# Creating and sending email
msg
=
MIMEText
(
'Hello, your token to join re6st network is: %s
\
n
'
%
token
)
msg
[
'Subject'
]
=
'[re6stnet] Token Request'
if
self
.
_email
:
msg
[
'From'
]
=
self
.
_email
msg
[
'To'
]
=
email
if
os
.
path
.
isabs
(
self
.
config
.
mailhost
)
or
\
os
.
path
.
isfile
(
self
.
config
.
mailhost
):
with
self
.
lock
:
m
=
mailbox
.
mbox
(
self
.
config
.
mailhost
)
try
:
m
.
add
(
msg
)
finally
:
m
.
close
()
else
:
s
=
smtplib
.
SMTP
(
self
.
config
.
mailhost
)
s
.
sendmail
(
self
.
_email
,
email
,
msg
.
as_string
())
s
.
quit
()
def
_getPrefix
(
self
,
prefix_len
):
max_len
=
128
-
len
(
self
.
network
)
assert
0
<
prefix_len
<=
max_len
try
:
prefix
,
=
self
.
db
.
execute
(
"""SELECT prefix FROM cert WHERE length(prefix) <= ? AND cert is null
ORDER BY length(prefix) DESC"""
,
(
prefix_len
,)).
next
()
except
StopIteration
:
logging
.
error
(
'No more free /%u prefix available'
,
prefix_len
)
raise
while
len
(
prefix
)
<
prefix_len
:
self
.
db
.
execute
(
"UPDATE cert SET prefix = ? WHERE prefix = ?"
,
(
prefix
+
'1'
,
prefix
))
prefix
+=
'0'
self
.
db
.
execute
(
"INSERT INTO cert VALUES (?,null,null)"
,
(
prefix
,))
if
len
(
prefix
)
<
max_len
or
'1'
in
prefix
:
return
prefix
self
.
db
.
execute
(
"UPDATE cert SET cert = 'reserved' WHERE prefix = ?"
,
(
prefix
,))
return
self
.
_getPrefix
(
prefix_len
)
def
requestCertificate
(
self
,
token
,
req
):
req
=
crypto
.
load_certificate_request
(
crypto
.
FILETYPE_PEM
,
req
)
with
self
.
lock
:
with
self
.
db
:
if
token
is
None
:
prefix_len
=
self
.
config
.
anonymous_prefix_length
if
not
prefix_len
:
return
email
=
None
else
:
try
:
token
,
email
,
prefix_len
,
_
=
self
.
db
.
execute
(
"SELECT * FROM token WHERE token = ?"
,
(
token
,)).
next
()
except
StopIteration
:
return
self
.
db
.
execute
(
"DELETE FROM token WHERE token = ?"
,
(
token
,))
prefix
=
self
.
_getPrefix
(
prefix_len
)
self
.
db
.
execute
(
"UPDATE cert SET email = ? WHERE prefix = ?"
,
(
email
,
prefix
))
return
self
.
_createCertificate
(
prefix
,
req
.
get_subject
(),
req
.
get_pubkey
())
def
_createCertificate
(
self
,
client_prefix
,
subject
,
pubkey
):
cert
=
crypto
.
X509
()
cert
.
set_serial_number
(
0
)
# required for libssl < 1.0
cert
.
gmtime_adj_notBefore
(
0
)
cert
.
gmtime_adj_notAfter
(
self
.
cert_duration
)
cert
.
set_issuer
(
self
.
ca
.
get_subject
())
subject
.
CN
=
"%u/%u"
%
(
int
(
client_prefix
,
2
),
len
(
client_prefix
))
cert
.
set_subject
(
subject
)
cert
.
set_pubkey
(
pubkey
)
cert
.
sign
(
self
.
key
,
'sha1'
)
cert
=
crypto
.
dump_certificate
(
crypto
.
FILETYPE_PEM
,
cert
)
self
.
db
.
execute
(
"UPDATE cert SET cert = ? WHERE prefix = ?"
,
(
cert
,
client_prefix
))
return
cert
def
renewCertificate
(
self
,
cn
):
with
self
.
lock
:
with
self
.
db
:
pem
=
self
.
_getCert
(
cn
)
cert
=
crypto
.
load_certificate
(
crypto
.
FILETYPE_PEM
,
pem
)
if
utils
.
notAfter
(
cert
)
-
RENEW_PERIOD
<
time
.
time
():
pem
=
self
.
_createCertificate
(
cn
,
cert
.
get_subject
(),
cert
.
get_pubkey
())
return
pem
def
getCa
(
self
):
return
crypto
.
dump_certificate
(
crypto
.
FILETYPE_PEM
,
self
.
ca
)
def
getPrivateAddress
(
self
,
cn
):
return
self
.
config
.
private
def
getBootstrapPeer
(
self
,
cn
):
with
self
.
lock
:
cert
=
self
.
_getCert
(
cn
)
address
=
self
.
config
.
private
,
tunnel
.
PORT
self
.
sock
.
sendto
(
'
\
2
'
,
address
)
peer
=
None
while
select
.
select
([
self
.
sock
],
[],
[],
peer
is
None
)[
0
]:
msg
=
self
.
sock
.
recv
(
1
<<
16
)
if
msg
[
0
]
==
'
\
1
'
:
try
:
peer
=
msg
[
1
:].
split
(
'
\
n
'
)[
-
2
]
except
IndexError
:
peer
=
''
if
peer
is
None
:
raise
EnvironmentError
(
"Timeout while querying [%s]:%u"
%
address
)
if
not
peer
or
peer
.
split
()[
0
]
==
cn
:
raise
LookupError
(
"No bootstrap peer found"
)
logging
.
info
(
"Sending bootstrap peer: %s"
,
peer
)
return
utils
.
encrypt
(
cert
,
peer
)
def
topology
(
self
):
with
self
.
lock
:
is_registry
=
utils
.
binFromIp
(
self
.
config
.
private
)[
len
(
self
.
network
):].
startswith
peers
=
deque
(
'%u/%u'
%
(
int
(
x
,
2
),
len
(
x
))
for
x
,
in
self
.
db
.
execute
(
"SELECT prefix FROM cert"
)
if
is_registry
(
x
))
assert
len
(
peers
)
==
1
cookie
=
hex
(
random
.
randint
(
0
,
1
<<
32
))[
2
:]
graph
=
dict
.
fromkeys
(
peers
)
asked
=
0
while
True
:
r
,
w
,
_
=
select
.
select
([
self
.
sock
],
[
self
.
sock
]
if
peers
else
[],
[],
1
)
if
r
:
answer
=
self
.
sock
.
recv
(
1
<<
16
)
if
answer
[
0
]
==
'
\
xfe
'
:
answer
=
answer
[
1
:].
split
(
'
\
n
'
)[:
-
1
]
if
len
(
answer
)
>=
3
and
answer
[
0
]
==
cookie
:
x
=
answer
[
3
:]
assert
answer
[
1
]
not
in
x
,
(
answer
,
graph
)
graph
[
answer
[
1
]]
=
x
[:
int
(
answer
[
2
])]
x
=
set
(
x
).
difference
(
graph
)
peers
+=
x
graph
.
update
(
dict
.
fromkeys
(
x
))
if
w
:
x
=
utils
.
binFromSubnet
(
peers
.
popleft
())
x
=
utils
.
ipFromBin
(
self
.
network
+
x
)
try
:
self
.
sock
.
sendto
(
'
\
xff
%s
\
n
'
%
cookie
,
(
x
,
tunnel
.
PORT
))
except
socket
.
error
:
pass
elif
not
r
:
break
return
repr
(
graph
)
class
RegistryClient
(
object
):
_hmac
=
None
def
__init__
(
self
,
url
,
key_path
=
None
,
ca
=
None
,
auto_close
=
True
):
self
.
key_path
=
key_path
self
.
ca
=
ca
self
.
auto_close
=
auto_close
scheme
,
host
=
splittype
(
url
)
host
,
path
=
splithost
(
host
)
host
,
port
=
splitport
(
host
)
self
.
_conn
=
dict
(
http
=
httplib
.
HTTPConnection
,
https
=
httplib
.
HTTPSConnection
,
)[
scheme
](
host
,
port
)
self
.
_path
=
path
.
rstrip
(
'/'
)
def
__getattr__
(
self
,
name
):
getcallargs
=
getattr
(
RegistryServer
,
name
).
getcallargs
def
rpc
(
*
args
,
**
kw
):
kw
=
getcallargs
(
*
args
,
**
kw
)
query
=
'/'
+
name
if
kw
:
if
any
(
type
(
v
)
is
not
str
for
v
in
kw
.
itervalues
()):
raise
TypeError
query
+=
'?'
+
urlencode
(
kw
)
url
=
self
.
_path
+
query
client_prefix
=
kw
.
get
(
'cn'
)
retry
=
True
try
:
while
retry
:
if
client_prefix
:
key
=
self
.
_hmac
if
not
key
:
retry
=
False
h
=
self
.
hello
(
client_prefix
)
n
=
len
(
h
)
//
2
crypto
.
verify
(
self
.
ca
,
h
[
n
:],
h
[:
n
],
'sha1'
)
key
=
utils
.
decrypt
(
self
.
key_path
,
h
[:
n
])
h
=
hmac
.
HMAC
(
key
,
query
,
hashlib
.
sha1
).
digest
()
key
=
hashlib
.
sha1
(
key
).
digest
()
self
.
_hmac
=
hashlib
.
sha1
(
key
).
digest
()
else
:
retry
=
False
self
.
_conn
.
putrequest
(
'GET'
,
url
,
skip_accept_encoding
=
1
)
if
client_prefix
:
self
.
_conn
.
putheader
(
HMAC_HEADER
,
base64
.
b64encode
(
h
))
self
.
_conn
.
endheaders
()
response
=
self
.
_conn
.
getresponse
()
body
=
response
.
read
()
if
response
.
status
in
(
httplib
.
OK
,
httplib
.
NO_CONTENT
)
and
(
not
client_prefix
or
hmac
.
HMAC
(
key
,
body
,
hashlib
.
sha1
).
digest
()
==
base64
.
b64decode
(
response
.
msg
[
HMAC_HEADER
])):
if
self
.
auto_close
and
name
!=
'hello'
:
self
.
_conn
.
close
()
return
body
if
client_prefix
:
self
.
_hmac
=
None
except
Exception
:
logging
.
info
(
url
,
exc_info
=
1
)
else
:
logging
.
info
(
'%s
\
n
Unexpected response %s %s'
,
url
,
response
.
status
,
response
.
reason
)
self
.
_conn
.
close
()
setattr
(
self
,
name
,
rpc
)
return
rpc
re6st/tunnel.py
View file @
f3ccd7a2
...
...
@@ -41,8 +41,8 @@ class MultiGatewayManager(dict):
class
Connection
(
object
):
def
__init__
(
self
,
address
,
iface
,
prefix
):
self
.
address_list
=
list
(
utils
.
parse_address
(
address
))
def
__init__
(
self
,
address
_list
,
iface
,
prefix
):
self
.
address_list
=
address_list
self
.
iface
=
iface
self
.
routes
=
0
self
.
_prefix
=
prefix
...
...
@@ -78,8 +78,7 @@ class Connection(object):
except
TypeError
:
i
=
len
(
self
.
address_list
)
-
1
if
i
:
db
.
addPeer
(
self
.
_prefix
,
utils
.
dump_address
(
self
.
address_list
[
i
:]
+
self
.
address_list
[:
i
]),
True
)
db
.
addPeer
(
self
.
_prefix
,
','
.
join
(
self
.
address_list
[
i
]),
True
)
else
:
db
.
connecting
(
self
.
_prefix
,
0
)
...
...
@@ -106,7 +105,7 @@ class TunnelManager(object):
def
__init__
(
self
,
write_pipe
,
peer_db
,
openvpn_args
,
timeout
,
refresh
,
client_count
,
iface_list
,
network
,
prefix
,
address
,
ip_changed
,
encrypt
,
remote_gateway
):
address
,
ip_changed
,
encrypt
,
remote_gateway
,
disable_proto
):
self
.
_write_pipe
=
write_pipe
self
.
_peer_db
=
peer_db
self
.
_connecting
=
set
()
...
...
@@ -125,6 +124,7 @@ class TunnelManager(object):
self
.
_encrypt
=
encrypt
self
.
_gateway_manager
=
MultiGatewayManager
(
remote_gateway
)
\
if
remote_gateway
else
None
self
.
_disable_proto
=
disable_proto
self
.
_served
=
set
()
self
.
sock
=
socket
.
socket
(
socket
.
AF_INET6
,
socket
.
SOCK_DGRAM
)
...
...
@@ -216,6 +216,11 @@ class TunnelManager(object):
if
prefix
in
self
.
_served
or
prefix
in
self
.
_connection_dict
:
return
False
assert
prefix
!=
self
.
_prefix
,
self
.
__dict__
address
=
[
x
for
x
in
utils
.
parse_address
(
address
)
if
x
[
2
]
not
in
self
.
_disable_proto
]
self
.
_peer_db
.
connecting
(
prefix
,
1
)
if
not
address
:
return
False
logging
.
info
(
'Establishing a connection with %u/%u'
,
int
(
prefix
,
2
),
len
(
prefix
))
iface
=
self
.
getFreeInterface
(
prefix
)
...
...
@@ -224,7 +229,6 @@ class TunnelManager(object):
for
ip
in
c
:
self
.
_gateway_manager
.
add
(
ip
,
True
)
c
.
open
(
self
.
_write_pipe
,
self
.
_timeout
,
self
.
_encrypt
,
self
.
_ovpn_args
)
self
.
_peer_db
.
connecting
(
prefix
,
1
)
return
True
def
_makeNewTunnels
(
self
,
route_counted
):
...
...
@@ -435,10 +439,13 @@ class TunnelManager(object):
return
code
=
ord
(
msg
[
0
])
if
code
==
1
:
# answer
# TODO: do not fail if message contains garbage
# We parse the message in a way to discard a truncated line.
for
peer
in
msg
[
1
:].
split
(
'
\
n
'
)[:
-
1
]:
prefix
,
address
=
peer
.
split
()
try
:
prefix
,
address
=
peer
.
split
()
int
(
prefix
,
2
)
except
ValueError
:
break
if
prefix
!=
self
.
_prefix
:
self
.
_peer_db
.
addPeer
(
prefix
,
address
)
try
:
...
...
re6st/utils.py
View file @
f3ccd7a2
import
argparse
,
errno
,
logging
,
os
,
shlex
,
signal
,
socket
import
struct
,
subprocess
,
textwrap
,
threading
,
time
from
OpenSSL
import
crypto
import
argparse
,
calendar
,
errno
,
logging
,
os
,
shlex
,
signal
,
socket
import
struct
,
subprocess
,
sys
,
textwrap
,
threading
,
time
,
traceback
logging_levels
=
logging
.
WARNING
,
logging
.
INFO
,
logging
.
DEBUG
,
5
...
...
@@ -45,6 +44,10 @@ def setupLog(log_level, filename=None, **kw):
logging
.
addLevelName
(
5
,
'TRACE'
)
logging
.
trace
=
lambda
*
args
,
**
kw
:
logging
.
log
(
5
,
*
args
,
**
kw
)
def
log_exception
():
f
=
traceback
.
format_exception
(
*
sys
.
exc_info
())
logging
.
error
(
'%s%s'
,
f
.
pop
(),
''
.
join
(
f
))
class
HelpFormatter
(
argparse
.
ArgumentDefaultsHelpFormatter
):
...
...
@@ -128,13 +131,14 @@ def ipFromBin(ip, suffix=''):
struct
.
pack
(
'>QQ'
,
int
(
ip
[:
64
],
2
),
int
(
ip
[
64
:],
2
)))
def
networkFromCa
(
ca
):
ca
=
crypto
.
load_certificate
(
crypto
.
FILETYPE_PEM
,
ca
)
return
bin
(
ca
.
get_serial_number
())[
3
:]
def
subnetFromCert
(
cert
):
cert
=
crypto
.
load_certificate
(
crypto
.
FILETYPE_PEM
,
cert
)
return
cert
.
get_subject
().
CN
def
notAfter
(
cert
):
return
calendar
.
timegm
(
time
.
strptime
(
cert
.
get_notAfter
(),
'%Y%m%d%H%M%SZ'
))
def
dump_address
(
address
):
return
';'
.
join
(
map
(
','
.
join
,
address
))
...
...
@@ -150,3 +154,27 @@ def parse_address(address_list):
def
binFromSubnet
(
subnet
):
p
,
l
=
subnet
.
split
(
'/'
)
return
bin
(
int
(
p
))[
2
:].
rjust
(
int
(
l
),
'0'
)
def
decrypt
(
key_path
,
data
):
p
=
subprocess
.
Popen
(
(
'openssl'
,
'rsautl'
,
'-decrypt'
,
'-inkey'
,
key_path
),
stdin
=
subprocess
.
PIPE
,
stdout
=
subprocess
.
PIPE
)
out
,
err
=
p
.
communicate
(
data
)
if
p
.
returncode
:
raise
subprocess
.
CalledProcessError
(
p
.
returncode
,
'openssl'
,
err
)
return
out
def
encrypt
(
cert
,
data
):
r
,
w
=
os
.
pipe
()
try
:
threading
.
Thread
(
target
=
os
.
write
,
args
=
(
w
,
cert
)).
start
()
p
=
subprocess
.
Popen
((
'openssl'
,
'rsautl'
,
'-encrypt'
,
'-certin'
,
'-inkey'
,
'/proc/self/fd/%u'
%
r
),
stdin
=
subprocess
.
PIPE
,
stdout
=
subprocess
.
PIPE
)
out
,
err
=
p
.
communicate
(
data
)
finally
:
os
.
close
(
r
)
os
.
close
(
w
)
if
p
.
returncode
:
raise
subprocess
.
CalledProcessError
(
p
.
returncode
,
'openssl'
,
err
)
return
out
re6stnet
View file @
f3ccd7a2
#!/usr/bin/python
import
atexit
,
errno
,
logging
,
os
,
select
,
signal
import
sqlite3
,
subprocess
,
sys
,
time
,
t
raceback
import
sqlite3
,
subprocess
,
sys
,
time
,
t
hreading
from
collections
import
deque
from
re6st
import
plib
,
utils
,
db
,
tunnel
from
OpenSSL
import
crypto
from
re6st
import
db
,
plib
,
tunnel
,
utils
from
re6st.registry
import
RegistryClient
,
RENEW_PERIOD
class
ReexecException
(
Exception
):
pass
def
getConfig
():
parser
=
utils
.
ArgParser
(
fromfile_prefix_chars
=
'@'
,
...
...
@@ -61,12 +65,14 @@ def getConfig():
" hello interval for Babel to re-establish connection with a"
" node for which the direct connection has been cut."
)
_
(
'--table'
,
type
=
int
,
default
=
42
,
help
=
"Use given table id. Set 0 to use the main table, if:
\
n
"
"- you are a gateway of this network (the default route will be"
" exported)
\
n
"
"- or you want to use the default route of this network for all"
" communications (in this case, make sure you don't already have"
" a default route).
\
n
"
)
help
=
"Use given table id. Set 0 to use the main table, if you want to"
" access internet via this network (in this case, make sure you"
" don't already have a default route). Don't use this option with"
" --gateway (main table is automatically used)."
)
_
(
'--gateway'
,
action
=
'store_true'
,
help
=
"Act as a gateway for this network (the default route will be"
" exported). Do never use it if you don't know what it means."
)
_
=
parser
.
add_argument_group
(
'tunnelling'
).
add_argument
_
(
'-O'
,
dest
=
'openvpn_args'
,
metavar
=
'ARG'
,
action
=
'append'
,
default
=
[],
help
=
"Extra arguments to forward to both server and client OpenVPN"
...
...
@@ -101,6 +107,8 @@ def getConfig():
_
(
'--remote-gateway'
,
action
=
'append'
,
dest
=
'gw_list'
,
help
=
"Force each tunnel to be created through one the given gateways,"
" in a round-robin fashion."
)
_
(
'--disable-proto'
,
action
=
'append'
,
choices
=
(
'udp'
,
'tcp'
),
default
=
[],
help
=
"Do never try to create tunnels using given protocols."
)
_
(
'--client'
,
metavar
=
'HOST,PORT,PROTO[;...]'
,
help
=
"Do not run any OpenVPN server, but only 1 OpenVPN client,"
" with specified remotes. Any other option not required in this"
...
...
@@ -108,14 +116,43 @@ def getConfig():
return
parser
.
parse_args
()
def
maybe_renew
(
path
,
cert
,
info
,
renew
):
while
True
:
next_renew
=
utils
.
notAfter
(
cert
)
-
RENEW_PERIOD
if
time
.
time
()
<
next_renew
:
return
cert
,
next_renew
try
:
pem
=
renew
()
if
not
pem
or
pem
==
crypto
.
dump_certificate
(
crypto
.
FILETYPE_PEM
,
cert
):
exc_info
=
0
break
cert
=
crypto
.
load_certificate
(
crypto
.
FILETYPE_PEM
,
pem
)
except
Exception
:
exc_info
=
1
break
new_path
=
path
+
'.new'
with
open
(
new_path
,
'w'
)
as
f
:
f
.
write
(
pem
)
os
.
rename
(
new_path
,
path
)
logging
.
info
(
"%s renewed until %s UTC"
,
info
,
time
.
asctime
(
time
.
gmtime
(
utils
.
notAfter
(
cert
))))
logging
.
error
(
"%s not renewed. Will retry tomorrow."
,
info
,
exc_info
=
exc_info
)
return
cert
,
time
.
time
()
+
86400
def
exit
(
status
):
exit
.
status
=
status
os
.
kill
(
os
.
getpid
(),
signal
.
SIGTERM
)
def
main
():
# Get arguments
config
=
getConfig
()
with
open
(
config
.
ca
)
as
f
:
network
=
utils
.
networkFromCa
(
f
.
read
())
ca
=
crypto
.
load_certificate
(
crypto
.
FILETYPE_PEM
,
f
.
read
())
with
open
(
config
.
cert
)
as
f
:
prefix
=
utils
.
binFromSubnet
(
utils
.
subnetFromCert
(
f
.
read
()))
cert
=
crypto
.
load_certificate
(
crypto
.
FILETYPE_PEM
,
f
.
read
())
prefix
=
utils
.
binFromSubnet
(
utils
.
subnetFromCert
(
cert
))
config
.
openvpn_args
+=
(
'--ca'
,
config
.
ca
,
'--cert'
,
config
.
cert
,
...
...
@@ -136,7 +173,16 @@ def main():
plib
.
ovpn_log
=
config
.
log
signal
.
signal
(
signal
.
SIGHUP
,
lambda
*
args
:
sys
.
exit
(
-
1
))
signal
.
signal
(
signal
.
SIGTERM
,
lambda
*
args
:
sys
.
exit
())
signal
.
signal
(
signal
.
SIGTERM
,
lambda
*
args
:
sys
.
exit
(
getattr
(
exit
,
'status'
,
None
)))
registry
=
RegistryClient
(
config
.
registry
,
config
.
key
,
ca
)
cert
,
next_renew
=
maybe_renew
(
config
.
cert
,
cert
,
"Certificate"
,
lambda
:
registry
.
renewCertificate
(
prefix
))
ca
,
ca_renew
=
maybe_renew
(
config
.
ca
,
ca
,
"CA Certificate"
,
registry
.
getCa
)
if
next_renew
>
ca_renew
:
next_renew
=
ca_renew
network
=
utils
.
networkFromCa
(
ca
)
if
config
.
max_clients
is
None
:
config
.
max_clients
=
config
.
client_count
*
2
...
...
@@ -220,21 +266,23 @@ def main():
# Init db and tunnels
tunnel_interfaces
=
server_tunnels
.
keys
()
timeout
=
4
*
config
.
hello
cleanup
=
[]
if
config
.
client_count
and
not
config
.
client
:
required
(
'registry'
)
# Create and open read_only pipe to get server events
r_pipe
,
write_pipe
=
os
.
pipe
()
read_pipe
=
os
.
fdopen
(
r_pipe
)
peer_db
=
db
.
PeerDB
(
db_path
,
config
.
registry
,
config
.
key
,
prefix
)
peer_db
=
db
.
PeerDB
(
db_path
,
registry
,
config
.
key
,
network
,
prefix
)
tunnel_manager
=
tunnel
.
TunnelManager
(
write_pipe
,
peer_db
,
config
.
openvpn_args
,
timeout
,
config
.
tunnel_refresh
,
config
.
client_count
,
config
.
iface_list
,
network
,
prefix
,
address
,
ip_changed
,
config
.
encrypt
,
remote_gateway
)
address
,
ip_changed
,
config
.
encrypt
,
remote_gateway
,
config
.
disable_proto
)
cleanup
.
append
(
tunnel_manager
.
sock
.
close
)
tunnel_interfaces
+=
tunnel_manager
.
new_iface_list
else
:
tunnel_manager
=
write_pipe
=
None
cleanup
=
[]
try
:
# Source address selection is defined by RFC 6724, and in most
# applications, it usually works thanks to rule 5 (prefer outgoing
...
...
@@ -277,7 +325,9 @@ def main():
cleanup
.
append
(
lambda
:
subprocess
.
call
(
if_rt
))
x
=
[
my_network
]
if
config
.
table
:
if
config
.
gateway
:
config
.
table
=
0
elif
config
.
table
:
x
+=
'table'
,
str
(
config
.
table
)
try
:
ip
(
'rule'
,
'from'
,
*
x
)
...
...
@@ -292,6 +342,26 @@ def main():
if_rt
+=
x
[
1
:]
call
(
if_rt
[:
3
]
+
[
'add'
,
'proto'
,
'static'
]
+
if_rt
[
4
:])
else
:
def
check_no_default_route
():
try
:
while
True
:
for
route
in
call
((
'ip'
,
'-6'
,
'route'
,
'show'
,
'default'
)).
splitlines
():
if
' proto 42 '
not
in
route
:
logging
.
fatal
(
"Detected default route (%s)"
" whereas you specified --table=0."
" Fix your configuration."
,
route
)
return
time
.
sleep
(
60
)
except
:
utils
.
log_exception
()
finally
:
exit
(
1
)
t
=
threading
.
Thread
(
target
=
check_no_default_route
)
t
.
daemon
=
True
t
.
start
()
# adding tap-windows driver will break others, so we add
# all drivers here
if
sys
.
platform
==
'cygwin'
:
...
...
@@ -314,7 +384,8 @@ def main():
# main loop
if
tunnel_manager
is
None
:
sys
.
exit
(
os
.
WEXITSTATUS
(
os
.
wait
()[
1
]))
time
.
sleep
(
max
(
0
,
next_renew
-
time
.
time
()))
raise
ReexecException
(
"Restart to renew certificate"
)
cleanup
+=
tunnel_manager
.
delInterfaces
,
tunnel_manager
.
killAll
while
True
:
next
=
tunnel_manager
.
next_refresh
...
...
@@ -334,6 +405,8 @@ def main():
t
=
time
.
time
()
if
t
>=
tunnel_manager
.
next_refresh
:
tunnel_manager
.
refresh
()
if
t
>=
next_renew
:
raise
ReexecException
(
"Restart to renew certificate"
)
if
forwarder
and
t
>=
forwarder
.
next_refresh
:
forwarder
.
refresh
()
finally
:
...
...
@@ -345,16 +418,17 @@ def main():
except
sqlite3
.
Error
:
logging
.
exception
(
"Restarting with empty cache"
)
os
.
rename
(
db_path
,
db_path
+
'.bak'
)
try
:
sys
.
exitfunc
()
finally
:
os
.
execvp
(
sys
.
argv
[
0
],
sys
.
argv
)
except
ReexecException
,
e
:
logging
.
info
(
e
)
except
KeyboardInterrupt
:
return
0
except
Exception
:
f
=
traceback
.
format_exception
(
*
sys
.
exc_info
())
logging
.
error
(
'%s%s'
,
f
.
pop
(),
''
.
join
(
f
))
utils
.
log_exception
()
sys
.
exit
(
1
)
try
:
sys
.
exitfunc
()
finally
:
os
.
execvp
(
sys
.
argv
[
0
],
sys
.
argv
)
if
__name__
==
"__main__"
:
main
()
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