1114 lines
32 KiB
Python
1114 lines
32 KiB
Python
#!/usr/bin/python3
|
|
import os, sys, subprocess, socket, signal
|
|
|
|
CONFIGS = {
|
|
"debug": {
|
|
"experiments": [
|
|
"impl-cipher-ver",
|
|
"impl-cert-ver",
|
|
"impl-kex-ver",
|
|
"zrtt",
|
|
],
|
|
"sides": [
|
|
"client",
|
|
"server",
|
|
],
|
|
"tls": [
|
|
False,
|
|
True,
|
|
],
|
|
"records": [
|
|
{ "filename": "youtube", "repeat": 1 },
|
|
],
|
|
"repo_dir": "/home/tuxmain/reps/tlsbench",
|
|
"exp_dir": "/dev/shm/exp",
|
|
"log_backup_dir": "/home/tuxmain",
|
|
"local_addr": "127.0.0.1",
|
|
"remote_addr": "127.0.0.1",
|
|
"remote_repo_dir": "/home/tuxmain/reps/tlsbench",
|
|
"wattmeter": False,
|
|
"perf": False,
|
|
"rapl": False,
|
|
"sa": True,
|
|
"perf_dir": "/home/tuxmain/.cache/exp",
|
|
"listen_port": 8080,
|
|
"notify_listen": ("127.0.0.1", 8090),
|
|
"notify_addr": "127.0.0.1:8090",
|
|
},
|
|
# i7-4790 local
|
|
"local": {
|
|
"experiments": [
|
|
"impl-cipher-ver",
|
|
"impl-cert-ver",
|
|
"impl-kex-ver",
|
|
"zrtt",
|
|
],
|
|
"sides": [
|
|
"client",
|
|
"server",
|
|
],
|
|
"tls": [
|
|
#False,
|
|
True,
|
|
],
|
|
"records": [
|
|
{ "filename": "wikipedia", "repeat": 5 },
|
|
],
|
|
"repo_dir": "/home/tuxmain/reps/tlsbench",
|
|
"exp_dir": "/dev/shm/exp",
|
|
"log_backup_dir": "/home/tuxmain",
|
|
"local_addr": "127.0.0.1",
|
|
"remote_addr": "127.0.0.1",
|
|
"remote_repo_dir": "/home/tuxmain/reps/tlsbench",
|
|
"wattmeter": False,
|
|
"perf": True,
|
|
"rapl": False,
|
|
"sa": True,
|
|
"perf_dir": "/home/tuxmain/.cache/exp",
|
|
"listen_port": 8080,
|
|
"notify_listen": ("127.0.0.1", 8090),
|
|
"notify_addr": "127.0.0.1:8090",
|
|
},
|
|
# i7-4790 -> pi3
|
|
"pi3": {
|
|
"experiments": [
|
|
#"impl-cipher-ver",
|
|
#"impl-cert-ver",
|
|
#"impl-kex-ver",
|
|
#"zrtt",
|
|
"realistic"
|
|
],
|
|
"sides": [
|
|
"client",
|
|
"server",
|
|
],
|
|
"tls": [
|
|
False,
|
|
True,
|
|
],
|
|
"records": [
|
|
{ "filename": "wikipedia", "repeat": 1000 },
|
|
],
|
|
"repo_dir": "/home/tuxmain/reps/tlsbench",
|
|
"exp_dir": "/dev/shm/exp",
|
|
"log_backup_dir": "/home/tuxmain",
|
|
"local_addr": "192.168.3.1",
|
|
"remote_addr": "192.168.3.14",
|
|
"remote_ssh": "exp@p2",
|
|
"remote_psw": "exp",
|
|
"remote_repo_dir": "/home/exp/exp",
|
|
"wattmeter": True,
|
|
"perf": False,
|
|
"rapl": False,
|
|
"sa": True,
|
|
"listen_port": 8080,
|
|
"idle": "idle - - - - - - - - - 600.000081539154 0.0 896 4792 0.5399999999999991 0 -",
|
|
"notify_listen": ("0.0.0.0", 8090),
|
|
"notify_addr": "192.168.3.1:8090",
|
|
},
|
|
# i7-4790 -> core2
|
|
"core2": {
|
|
"experiments": [
|
|
"impl-cipher-ver",
|
|
"impl-cert-ver",
|
|
"impl-kex-ver",
|
|
"zrtt",
|
|
#"realistic",
|
|
],
|
|
"sides": [
|
|
"client",
|
|
"server",
|
|
],
|
|
"tls": [
|
|
False,
|
|
True,
|
|
],
|
|
"records": [
|
|
{ "filename": "wikipedia", "repeat": 2000 },
|
|
],
|
|
"repo_dir": "/home/tuxmain/reps/tlsbench",
|
|
"exp_dir": "/dev/shm/exp",
|
|
"log_backup_dir": "/home/tuxmain",
|
|
"local_addr": "192.168.3.1",
|
|
"remote_addr": "192.168.3.3",
|
|
"remote_ssh": "exp@192.168.3.3",
|
|
"remote_psw": None,
|
|
"remote_repo_dir": "/home/exp/exp",
|
|
"wattmeter": True,
|
|
"perf": False,
|
|
"rapl": False,
|
|
"sa": True,
|
|
"listen_port": 8080,
|
|
"idle": "idle - - - - - - - - - 1200.0000903606415 0 589 4764 3.478999999999999 0 -",
|
|
"notify_listen": ("0.0.0.0", 8090),
|
|
"notify_addr": "192.168.3.1:8090",
|
|
},
|
|
"core2-local": {
|
|
"experiments": [
|
|
"impl-cipher-ver",
|
|
"impl-cert-ver",
|
|
"impl-kex-ver",
|
|
],
|
|
"sides": [
|
|
"client",
|
|
"server",
|
|
],
|
|
"tls": [
|
|
False,
|
|
True,
|
|
],
|
|
"records": [
|
|
{ "filename": "wikipedia", "repeat": 10 },
|
|
],
|
|
"repo_dir": "/home/exp/exp",
|
|
"exp_dir": "/dev/shm/exp",
|
|
"log_backup_dir": "/home/exp",
|
|
"local_addr": "127.0.0.1",
|
|
"remote_addr": "127.0.0.1",
|
|
"remote_repo_dir": "/home/exp/exp",
|
|
"wattmeter": False,
|
|
"perf": True,
|
|
"rapl": False,
|
|
"sa": True,
|
|
"perf_dir": "/home/exp/.cache/exp",
|
|
"listen_port": 8080,
|
|
},
|
|
# i7-4790 -> i5-7300HQ
|
|
"i5": {
|
|
"experiments": [
|
|
#"impl-cipher-ver",
|
|
#"impl-cert-ver",
|
|
#"impl-kex-ver",
|
|
#"zrtt",
|
|
"realistic",
|
|
],
|
|
"sides": [
|
|
"client",
|
|
"server",
|
|
],
|
|
"tls": [
|
|
False,
|
|
True,
|
|
],
|
|
"records": [
|
|
{ "filename": "wikipedia", "repeat": 1000 },
|
|
],
|
|
"repo_dir": "/home/tuxmain/reps/tlsbench",
|
|
"exp_dir": "/dev/shm/exp",
|
|
"log_backup_dir": "/home/tuxmain",
|
|
"local_addr": "192.168.3.1",
|
|
"remote_addr": "192.168.3.42",
|
|
"remote_ssh": "exp@192.168.3.42",
|
|
"remote_psw": None,
|
|
"remote_repo_dir": "/home/exp/exp",
|
|
"wattmeter": True,
|
|
"perf": False,
|
|
"rapl": True,
|
|
"sa": True,
|
|
"listen_port": 8080,
|
|
"idle": "idle - - - - - - - - - 1772446308.8950071 1772447508.8950853 1200.000078201294 0 1747 6555 2.4309999999999974 564883014 -",
|
|
"notify_listen": ("0.0.0.0", 8090),
|
|
"notify_addr": "192.168.3.1:8090",
|
|
"ld_preload": {
|
|
"openssl": "/home/tuxmain/reps/tlsbench/libssl.so:/home/tuxmain/reps/tlsbench/libcrypto.so",
|
|
}
|
|
},
|
|
"i5-local": {
|
|
"experiments": [
|
|
"impl-cipher-ver",
|
|
"impl-cert-ver",
|
|
"impl-kex-ver",
|
|
],
|
|
"sides": [
|
|
"client",
|
|
"server",
|
|
],
|
|
"tls": [
|
|
False,
|
|
True,
|
|
],
|
|
"records": [
|
|
{ "filename": "wikipedia", "repeat": 100 },
|
|
],
|
|
"repo_dir": "/home/exp/exp",
|
|
"exp_dir": "/dev/shm/exp",
|
|
"log_backup_dir": "/home/exp",
|
|
"local_addr": "127.0.0.1",
|
|
"remote_addr": "127.0.0.1",
|
|
"remote_repo_dir": "/home/exp/exp",
|
|
"wattmeter": False,
|
|
"perf": True,
|
|
"rapl": False,
|
|
"sa": True,
|
|
"perf_dir": "/home/exp/.cache/exp",
|
|
"listen_port": 8080,
|
|
},
|
|
"g5k": {
|
|
"experiments": [
|
|
"impl-cipher-ver",
|
|
"impl-cert-ver",
|
|
"impl-kex-ver",
|
|
"zrtt",
|
|
],
|
|
"sides": [
|
|
"client",
|
|
"server",
|
|
],
|
|
"tls": [
|
|
False,
|
|
True,
|
|
],
|
|
"records": [
|
|
{ "filename": "wikipedia", "repeat": 12000 },
|
|
],
|
|
"repo_dir": "/home/pengelib/tlsbench",
|
|
"exp_dir": "/dev/shm/exp",
|
|
"log_backup_dir": "/home/pengelib",
|
|
"local_addr": "TODO",
|
|
"remote_addr": "172.16.52.6",
|
|
"remote_ssh": "pengelib@gros-69",
|
|
"remote_psw": None,
|
|
"remote_repo_dir": "/home/pengelib/tlsbench",
|
|
"wattmeter": False,
|
|
"perf": False,
|
|
"rapl": False,
|
|
"sa": False,
|
|
"listen_port": 8080,
|
|
"idle": "idle - - - - - - - - - 1772205368.593937 1772206568.6941307 1200.1001937389374 0 298843 2217803 16.6 0 -",
|
|
"notify_listen": ("0.0.0.0", 8090),
|
|
"notify_addr": "TODO:8090",
|
|
"ld_preload": {
|
|
"openssl": "/home/pengelib/openssl-openssl-3.6.1/libssl.so.3:/home/pengelib/openssl-openssl-3.6.1/libcrypto.so.3",
|
|
}
|
|
},
|
|
}
|
|
|
|
# Wildcard subdomains are used only for certificates.
|
|
# This is useful for recording, because some domains may be unknown at that time.
|
|
# Proxy config ignores them so individual subdomains must be added as well for replaying.
|
|
DOMAINS_ = [
|
|
# Youtube video
|
|
"youtube.com", "www.youtube.com",
|
|
"*.ytimg.com",
|
|
"i.ytimg.com",
|
|
"i1.ytimg.com",
|
|
"i9.ytimg.com",
|
|
"fonts.gstatic.com", "www.gstatic.com",
|
|
"www.google.com", "accounts.google.com",
|
|
"yt3.ggpht.com",
|
|
"*.googlevideo.com",
|
|
"rr3---sn-gx1v2vax-atne.googlevideo.com",
|
|
"rr4---sn-hgn7yn7l.googlevideo.com",
|
|
"rr3---sn-gx1v2vax-atne.googlevideo.com",
|
|
"rr2---sn-gx1v2vax-atne.googlevideo.com",
|
|
"rr1---sn-hgn7yn7l.googlevideo.com",
|
|
"rr4---sn-hgn7yn76.googlevideo.com",
|
|
"rr3---sn-hgn7rn7r.googlevideo.com",
|
|
"rr1---sn-hgn7rn7y.googlevideo.com",
|
|
"rr1---sn-hgn7rnee.googlevideo.com",
|
|
"rr2---sn-aigl6ney.googlevideo.com",
|
|
"rr3---sn-q4fzenee.googlevideo.com",
|
|
"googleads.g.doubleclick.net",
|
|
"static.doubleclick.net",
|
|
"ad.doubleclick.net",
|
|
"yt3.googleusercontent.com",
|
|
"suggestqueries-clients6.youtube.com",
|
|
"pagead2.googlesyndication.com",
|
|
"tpc.googlesyndication.com",
|
|
"encrypted-tbn0.gstatic.com",
|
|
"encrypted-tbn1.gstatic.com",
|
|
"encrypted-tbn2.gstatic.com",
|
|
"encrypted-tbn3.gstatic.com",
|
|
"fonts.googleapis.com",
|
|
"consent.youtube.com",
|
|
# Wikipedia article
|
|
"en.wikipedia.org", "auth.wikimedia.org", "upload.wikimedia.org", "meta.wikimedia.org", "intake-analytics.wikimedia.org",
|
|
# Google search
|
|
"www.google.com", "www.googleadservices.com", "www.gstatic.com", "csp.withgoogle.com", "ogads-pa.clients6.google.com", "play.google.com", "ssl.gstatic.com", "fonts.gstatic.com", "ogs.google.com",
|
|
]
|
|
|
|
CERT_SIGN_ALGS = [
|
|
"prime256v1", # widely used
|
|
"secp384r1", # rarely used but supported by browsers because it's NIST standard
|
|
#"secp521r1", # not supported by browsers because NIST said it was not needed
|
|
"rsa2048", "rsa3072", "rsa4096", # widely used
|
|
]
|
|
IMPLS = [
|
|
#"aws-lc", # Amazon's crypto widely used in Rust stuff
|
|
#"boring", # Google's fork of OpenSSL used in Chrome and Android
|
|
#"graviola", # New crypto in Rust
|
|
"openssl", # widely used
|
|
#"openssl-static",
|
|
#"ring", # used in most Rust stuff
|
|
#"symcrypt", # Microsoft's crypto
|
|
#"wolfcrypt" # used in embedded (won't build with rpxy for now)
|
|
]
|
|
# Symmetric ciphers
|
|
# They also allow to choose the TLS version.
|
|
CIPHERS = [
|
|
# TLS 1.3
|
|
"AES_256_GCM_SHA384",
|
|
"AES_128_GCM_SHA256",
|
|
"CHACHA20_POLY1305_SHA256",
|
|
# TLS 1.2
|
|
# ECDSA vs RSA refers to the certificate signature algorithm.
|
|
# DH is EC in either case, using the group specified below.
|
|
"ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
|
"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
|
"ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
|
|
]
|
|
KEXES = [
|
|
"X25519",
|
|
"SECP256R1",
|
|
"SECP384R1",
|
|
"X25519MLKEM768",
|
|
"SECP256R1MLKEM768",
|
|
"MLKEM768",
|
|
]
|
|
|
|
# Testing all combinations would be too much. Instead we isolate independent parts.
|
|
EXPERIMENTS = {
|
|
# Compare ciphers among implementations and TLS versions
|
|
"impl-cipher-ver": {
|
|
"impls": IMPLS,
|
|
"ciphers": [
|
|
"AES_128_GCM_SHA256",
|
|
"AES_256_GCM_SHA384",
|
|
"CHACHA20_POLY1305_SHA256",
|
|
"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
|
"ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
|
"ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
|
|
],
|
|
"kexes": ["X25519"],
|
|
"cert": ["prime256v1"],
|
|
"earlydata": ["0"],
|
|
},
|
|
# Compare signatures among implementations and TLS versions
|
|
"impl-cert-ver": {
|
|
"impls": IMPLS,
|
|
"ciphers": [
|
|
"AES_128_GCM_SHA256",
|
|
"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
|
],
|
|
"kexes": ["X25519"],
|
|
"cert": [
|
|
"prime256v1",
|
|
"secp384r1",
|
|
"rsa2048",
|
|
"rsa3072",
|
|
"rsa4096",
|
|
],
|
|
"earlydata": ["0"],
|
|
},
|
|
# Compare key exchange groups among implementations and TLS versions
|
|
"impl-kex-ver": {
|
|
"impls": IMPLS,
|
|
"ciphers": [
|
|
"AES_128_GCM_SHA256",
|
|
"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
|
],
|
|
"kexes": [
|
|
"X25519",
|
|
"SECP256R1",
|
|
"SECP384R1",
|
|
"X25519MLKEM768",
|
|
"SECP256R1MLKEM768",
|
|
"MLKEM768",
|
|
],
|
|
"cert": ["prime256v1"],
|
|
"earlydata": ["0"],
|
|
},
|
|
# Compare 0-RTT with no early data
|
|
"zrtt": {
|
|
"impls": IMPLS,
|
|
"ciphers": [
|
|
"AES_128_GCM_SHA256",
|
|
"AES_256_GCM_SHA384",
|
|
"CHACHA20_POLY1305_SHA256",
|
|
],
|
|
"kexes": ["X25519"],
|
|
"cert": ["prime256v1"],
|
|
"earlydata": ["0", "1"],
|
|
},
|
|
"debug": {
|
|
"impls": IMPLS,
|
|
"ciphers": [
|
|
"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
|
#"ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
|
|
],
|
|
"kexes": ["X25519"],
|
|
"cert": ["prime256v1"],
|
|
"earlydata": ["0"],
|
|
},
|
|
"realistic": {
|
|
"impls": IMPLS,
|
|
"ciphers": [
|
|
#"AES_128_GCM_SHA256",
|
|
"AES_256_GCM_SHA384",
|
|
#"CHACHA20_POLY1305_SHA256",
|
|
#"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
|
#"ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
|
#"ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
|
|
],
|
|
"kexes": [
|
|
#"X25519",
|
|
#"SECP256R1",
|
|
#"SECP384R1",
|
|
"X25519MLKEM768",
|
|
#"SECP256R1MLKEM768",
|
|
#"MLKEM768",
|
|
],
|
|
"cert": [
|
|
#"prime256v1",
|
|
#"secp384r1",
|
|
"rsa2048",
|
|
#"rsa3072",
|
|
#"rsa4096",
|
|
],
|
|
"earlydata": ["0"],
|
|
},
|
|
}
|
|
|
|
# Some algorithms are not available in all implementations
|
|
def alg_filter(kex, cert, cipher, impl):
|
|
if "MLKEM" in kex and "WITH" in cipher:
|
|
# WITH means TLS1.2
|
|
return False
|
|
if "MLKEM" in kex and "openssl" not in impl and impl != "aws-lc" and impl != "graviola":
|
|
return False
|
|
if kex == "SECP256R1MLKEM768" and "openssl" in impl:
|
|
return False
|
|
if cert == "secp384r1" and impl == "boring":
|
|
return False
|
|
if kex == "SECP256R1MLKEM768" and impl == "graviola":
|
|
return False
|
|
if kex == "MLKEM768" and impl == "graviola":
|
|
return False
|
|
if cipher == "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,ECDHE_RSA_WITH_AES_256_GCM_SHA384" and "openssl" in impl:
|
|
return False
|
|
return True
|
|
|
|
DOMAINS = []
|
|
for domain in DOMAINS_:
|
|
if not domain in DOMAINS:
|
|
DOMAINS.append(domain)
|
|
|
|
for config in CONFIGS:
|
|
CONFIGS[config]["name"] = config
|
|
|
|
def getargv(arg:str, default="", n:int=1, args:list=sys.argv):
|
|
if arg in args and len(args) > args.index(arg)+n:
|
|
return args[args.index(arg)+n]
|
|
else:
|
|
return default
|
|
|
|
def sh(cmds):
|
|
if type(cmds) == list:
|
|
for cmd in cmds:
|
|
print(cmd)
|
|
os.system(cmd)
|
|
elif type(cmds) == str:
|
|
print(cmds)
|
|
os.system(cmds)
|
|
else:
|
|
raise TypeError
|
|
|
|
def make_sk(outpath, alg):
|
|
sh({
|
|
"ed25519": [
|
|
f"openssl genpkey -out {outpath}.sec1 -algorithm ed25519",
|
|
f"openssl pkcs8 -topk8 -nocrypt -in {outpath}.sec1 -out {outpath} -outform PEM"
|
|
],
|
|
"prime256v1": [
|
|
f"openssl ecparam -genkey -name prime256v1 -noout -out {outpath}.sec1",
|
|
f"openssl pkcs8 -topk8 -nocrypt -in {outpath}.sec1 -out {outpath} -outform PEM"
|
|
],
|
|
"rsa2048": [
|
|
f"openssl genrsa -out {outpath} 2048"
|
|
],
|
|
"rsa3072": [
|
|
f"openssl genrsa -out {outpath} 3072"
|
|
],
|
|
"rsa4096": [
|
|
f"openssl genrsa -out {outpath} 4096"
|
|
],
|
|
"secp384r1": [
|
|
f"openssl ecparam -genkey -name secp384r1 -noout -out {outpath}.sec1",
|
|
f"openssl pkcs8 -topk8 -nocrypt -in {outpath}.sec1 -out {outpath} -outform PEM"
|
|
],
|
|
}[alg])
|
|
|
|
def make_ca_cert(outpath, skpath):
|
|
sh(f"openssl req -x509 -new -key {skpath} -sha256 -days 730 -out {outpath} -subj '/CN=Foobar Root CA/C=AT/ST=Lyon/L=Lyon/O=Foobar'")
|
|
|
|
def make_cert(outpath, skpath, capath, caskpath, name, domains):
|
|
sh(f"openssl req -new -key {skpath} -sha256 -out /tmp/tmp.csr -subj '/CN={name}/C=AT/ST=Lyon/L=Lyon/O={name}'")
|
|
ext = open("/tmp/tmp.v3.ext", "w")
|
|
ext.write("""authorityKeyIdentifier=keyid,issuer
|
|
basicConstraints=CA:FALSE
|
|
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
|
|
subjectAltName = @alt_names
|
|
[alt_names]
|
|
""")
|
|
i = 1
|
|
for domain in domains:
|
|
ext.write(f"DNS.{i} = {domain}\n")
|
|
i += 1
|
|
ext.write(f"DNS.{i} = {domain}.localhost\n")
|
|
i += 1
|
|
ext.write(f"DNS.{i} = {domain}.p2\n")
|
|
i += 1
|
|
ext.write(f"DNS.{i} = {domain}.p3\n")
|
|
i += 1
|
|
ext.close()
|
|
|
|
sh(f"openssl x509 -req -in /tmp/tmp.csr -CA {capath} -CAkey {caskpath} -CAcreateserial -out {outpath} -days 365 -sha256 -extfile /tmp/tmp.v3.ext")
|
|
|
|
def get_domain_root(domain):
|
|
last_dot = domain.rfind(".")
|
|
penultimate_dot = domain.rfind(".", 0, last_dot)
|
|
return domain[penultimate_dot+1:]
|
|
|
|
# Issue secret keys, CA cert and signed certs for given domains
|
|
# All using the same algorithm
|
|
def make_certs(outdir, domains, alg, make_ca):
|
|
if outdir[-1] != "/":
|
|
outdir += "/"
|
|
|
|
if make_ca:
|
|
make_sk(outdir+"ca.key", alg)
|
|
make_ca_cert(outdir+"ca.crt", outdir+"ca.key")
|
|
|
|
# Only make certs for root domains, and include subdomains
|
|
roots = {}
|
|
for domain in domains:
|
|
root = domain
|
|
if domain.count(".") > 1:
|
|
root = get_domain_root(domain)
|
|
if root in roots:
|
|
roots[root].append(domain)
|
|
else:
|
|
roots[root] = [domain]
|
|
for root in roots:
|
|
make_sk(outdir+root+".key", alg)
|
|
make_cert(outdir+root+".crt", outdir+root+".key", outdir+"ca.crt", outdir+"ca.key", root, roots[root])
|
|
|
|
# Make a cert for all domains because choosing a certificate as a proxy is a real pain
|
|
# It is used by netreplay when recording. Rpxy will use specific certs.
|
|
make_sk(outdir+"all.key", alg)
|
|
make_cert(outdir+"all.crt", outdir+"all.key", outdir+"ca.crt", outdir+"ca.key", "wikipedia.org", domains)
|
|
|
|
def make_everything(exp_dir, domains, make_ca):
|
|
if exp_dir[-1] != "/":
|
|
exp_dir += "/"
|
|
os.makedirs(exp_dir, exist_ok=True)
|
|
for alg in CERT_SIGN_ALGS:
|
|
algdir = exp_dir+"certs/"+alg
|
|
os.makedirs(algdir, exist_ok=True)
|
|
make_certs(algdir, domains, alg, make_ca)
|
|
|
|
def run_netreplay_server(ssh, exp_dir, repo_dir, record, listen_addr, listen_port, tls, impl, certs_dir, only_record=None, ciphers=None, kexes=None, debug=False, ld_preload=None):
|
|
if exp_dir[-1] != "/":
|
|
exp_dir += "/"
|
|
repo_dir = repo_dir.removesuffix("/")
|
|
env = {"RUST_LOG": "warning", "SSLKEYLOGFILE": "/dev/shm/netreplay.keys.txt"}
|
|
if ld_preload and impl in ld_preload:
|
|
env["LD_PRELOAD"] = ld_preload[impl]
|
|
if ciphers:
|
|
env["CIPHERS"] = ciphers
|
|
if kexes:
|
|
env["KEXES"] = kexes
|
|
cmd = [repo_dir+"/netreplay"+("" if impl == None else ("-"+impl)), repo_dir+"/records/"+record["filename"], "server", str(listen_port), certs_dir]
|
|
if debug:
|
|
cmd.append("-d")
|
|
if tls:
|
|
cmd.append("--tls")
|
|
cmdline = " ".join(cmd)
|
|
#print(cmdline)
|
|
return ssh_run_bg(ssh, cmdline, env)
|
|
|
|
def run_netreplay_client(ssh, exp_dir, repo_dir, record, remote_addr, remote_port, tls, impl, certs_dir, only_record=None, ciphers=None, kexes=None, earlydata="0", debug=False, notify_addr=None, ld_preload=None, skip_verif=False):
|
|
if exp_dir[-1] != "/":
|
|
exp_dir += "/"
|
|
repo_dir = repo_dir.removesuffix("/")
|
|
env = {"RUST_LOG": "warning", "SSLKEYLOGFILE": "/dev/shm/netreplay.keys.txt", "EARLYDATA": earlydata}
|
|
if ld_preload and impl in ld_preload:
|
|
env["LD_PRELOAD"] = ld_preload[impl]
|
|
if ciphers:
|
|
env["CIPHERS"] = ciphers
|
|
if kexes:
|
|
env["KEXES"] = kexes
|
|
cmd = [repo_dir+"/netreplay"+("" if impl == None else ("-"+impl)), repo_dir+"/records/"+record["filename"], "client", remote_addr, str(remote_port), "-r", str(record["repeat"]), "--certs", certs_dir]
|
|
if debug:
|
|
cmd.append("-d")
|
|
if tls:
|
|
cmd.append("--tls")
|
|
if only_record != None:
|
|
cmd += ["--record", only_record]
|
|
if notify_addr != None:
|
|
cmd += ["-n", notify_addr]
|
|
if skip_verif:
|
|
cmd.append("-s")
|
|
cmdline = " ".join(cmd)
|
|
#print(cmdline)
|
|
return ssh_run_bg(ssh, cmdline, env)
|
|
|
|
# Run with or without SSH
|
|
def ssh_run(ssh, cmd, env={}, **kwargs):
|
|
if ssh == None:
|
|
# As long as there is no argument containing a space (escaped or quoted), we're fine
|
|
proc = subprocess.Popen(cmd.split(" "), stdout=subprocess.PIPE, env=env)
|
|
proc.wait()
|
|
return proc.stdout.read().decode()
|
|
else:
|
|
strvars = ""
|
|
for var in env:
|
|
strvars += var+"="+env[var]+" "
|
|
return ssh.run(strvars+cmd, **kwargs).stdout
|
|
|
|
# Run with or without SSH
|
|
def ssh_run_bg(ssh, cmd, env={}, **kwargs):
|
|
print(cmd)
|
|
if ssh == None:
|
|
# As long as there is no argument containing a space (escaped or quoted), we're fine
|
|
return subprocess.Popen(cmd.split(" "), env=env)
|
|
else:
|
|
# https://stackoverflow.com/questions/8775598/how-to-start-a-background-process-with-nohup-using-fabric
|
|
strvars = ""
|
|
for var in env:
|
|
strvars += "export "+var+"="+env[var].replace(" ", "\\ ")+" && "
|
|
return ssh.run(f"{strvars}dtach -n `mktemp -u /tmp/dtach.XXXX` {cmd}", **kwargs)
|
|
|
|
def get_cpu_stat(ssh, process_names):
|
|
res = ssh_run(ssh, "/sbin/sa --list-all-names", hide=True)
|
|
s = 0.0
|
|
for line in res.split("\n"):
|
|
for process_name in process_names:
|
|
if process_name[:15] in line:
|
|
s += float(re.finditer("\\s(\\d+\\.\\d+)cp\\s", line).__next__().group(1))
|
|
return s
|
|
|
|
def get_net_stat(ssh):
|
|
res = ssh_run(ssh, "cat /proc/net/netstat", hide=True)
|
|
items = res.split("\n")[3].split(" ")
|
|
bytes_in = int(items[7])
|
|
bytes_out = int(items[8])
|
|
return (bytes_in, bytes_out)
|
|
|
|
def get_rapl_energy(ssh, repo_dir):
|
|
res = ssh_run(ssh, repo_dir+"/powercap", hide=True)
|
|
items = res.split("\n")
|
|
energy = 0
|
|
for item in items:
|
|
if item != "" and item != "-1":
|
|
energy += int(item)
|
|
return energy
|
|
|
|
class Timeout(Exception):
|
|
pass
|
|
|
|
def timeout_handler(signum, frame):
|
|
raise Timeout
|
|
|
|
signal.signal(signal.SIGALRM, timeout_handler)
|
|
|
|
def run_exp(config, only_record=None, idle=False, shutdown=False, debug=False):
|
|
ssh = None
|
|
if "remote_ssh" in config:
|
|
ssh = connect_ssh(config)
|
|
|
|
exp_dir = config["exp_dir"]
|
|
log_backup_dir = config["log_backup_dir"]
|
|
remote_path = config["remote_repo_dir"]
|
|
config_name = config["name"]
|
|
wattmeter = None
|
|
if config["wattmeter"]:
|
|
errmsg = YRefParam()
|
|
if YAPI.RegisterHub("usb", errmsg) != YAPI.SUCCESS:
|
|
sys.exit("init error" + errmsg.value)
|
|
wattmeter = YPower.FirstPower()
|
|
if wattmeter is None or not wattmeter.isOnline():
|
|
print("No YoctoWatt connected")
|
|
exit(1)
|
|
|
|
killed = []
|
|
for expname in config["experiments"]:
|
|
exp = EXPERIMENTS[expname]
|
|
for impl in exp["impls"]:
|
|
if impl not in killed:
|
|
killed.append(impl)
|
|
try:
|
|
ssh_run(ssh, f"killall netreplay-{impl}")
|
|
except invoke.exceptions.UnexpectedExit as e:
|
|
pass
|
|
timestr = str(int(time.time()))
|
|
logfile_name = "log-"+timestr
|
|
logfile_path = exp_dir+"/"+logfile_name
|
|
logfile = open(logfile_path, "w")
|
|
logfile.write("exp impl alg kex cipher ed side tls record n start stop time cpu bytes_in bytes_out Wh Wh_rapl prof\n")
|
|
logfile.close()
|
|
|
|
perf_dir = ""
|
|
if config["perf"]:
|
|
perf_dir = config["perf_dir"]
|
|
os.makedirs(perf_dir, exist_ok=True)
|
|
|
|
if idle:
|
|
print("Measuring idle...")
|
|
remote_bytes_in, remote_bytes_out = get_net_stat(ssh)
|
|
energy = 0
|
|
energy_rapl = 0
|
|
if config["wattmeter"]:
|
|
energy = wattmeter.get_meter()
|
|
if config["rapl"]:
|
|
energy_rapl = get_rapl_energy(ssh, remote_path)
|
|
start = time.time()
|
|
|
|
time.sleep(1200)
|
|
|
|
end = time.time()
|
|
new_energy = 0
|
|
new_energy_rapl = 0
|
|
if config["wattmeter"]:
|
|
new_energy = wattmeter.get_meter()
|
|
if config["rapl"]:
|
|
new_energy_rapl = get_rapl_energy(ssh, remote_path)
|
|
new_remote_bytes_in, new_remote_bytes_out = get_net_stat(ssh)
|
|
remote_bytes_in_diff = new_remote_bytes_in - remote_bytes_in
|
|
remote_bytes_out_diff = new_remote_bytes_out - remote_bytes_out
|
|
energy_diff = new_energy - energy
|
|
energy_rapl_diff = new_energy_rapl - energy_rapl
|
|
time_diff = end - start
|
|
while True:
|
|
try:
|
|
with open(logfile_path, "a") as logfile:
|
|
logfile.write(f"idle - - - - - - - - - {start} {end} {time_diff} 0 {remote_bytes_in_diff} {remote_bytes_out_diff} {energy_diff} {energy_rapl_diff} -\n")
|
|
logfile.close()
|
|
break
|
|
except Exception as e:
|
|
print("Can't open log file:", e)
|
|
time.sleep(1)
|
|
elif "idle" in config:
|
|
while True:
|
|
try:
|
|
with open(logfile_path, "a") as logfile:
|
|
logfile.write(config["idle"]+"\n")
|
|
logfile.close()
|
|
break
|
|
except Exception as e:
|
|
print("Can't open log file:", e)
|
|
time.sleep(1)
|
|
sh(f"cp {logfile_path} {log_backup_dir}/{logfile_name}")
|
|
|
|
notify_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
notify_socket.bind(config["notify_listen"])
|
|
notify_socket.setblocking(True)
|
|
|
|
run_id = 0
|
|
for expname in config["experiments"]:
|
|
exp = EXPERIMENTS[expname]
|
|
first_set = True
|
|
for impl in exp["impls"]:
|
|
for alg in exp["cert"]:
|
|
certs_dir = exp_dir+"/certs/"+alg
|
|
for kex in exp["kexes"]:
|
|
for cipher in exp["ciphers"]:
|
|
for earlydata in exp["earlydata"]:
|
|
if not alg_filter(kex, alg, cipher, impl):
|
|
continue
|
|
for tls in config["tls"]:
|
|
tls_int = int(tls)
|
|
if not tls:
|
|
# Without TLS, TLS parameters don't matter so we skip redundant experiments
|
|
if first_set:
|
|
first_set = False
|
|
else:
|
|
continue
|
|
for side in config["sides"]:
|
|
for record in config["records"]:
|
|
print(f"EXPERIMENT {expname}: {impl} {alg} {kex} {cipher} ED={earlydata} {side} TLS={tls}")
|
|
|
|
run_id += 1
|
|
|
|
if side == "client":
|
|
netreplay = run_netreplay_server(
|
|
None,
|
|
config["exp_dir"],
|
|
config["repo_dir"],
|
|
record,
|
|
"0.0.0.0",
|
|
config["listen_port"],
|
|
tls,
|
|
None,
|
|
certs_dir,
|
|
only_record=only_record,
|
|
ciphers=cipher,
|
|
kexes=kex,
|
|
debug=debug,
|
|
ld_preload=config["ld_preload"] if "ld_preload" in config else None,
|
|
)
|
|
netreplay = run_netreplay_client(
|
|
ssh,
|
|
config["exp_dir"],
|
|
config["remote_repo_dir"],
|
|
record,
|
|
config["local_addr"],
|
|
config["listen_port"],
|
|
tls,
|
|
impl,
|
|
certs_dir,
|
|
only_record=only_record,
|
|
ciphers=cipher,
|
|
kexes=kex,
|
|
earlydata=earlydata,
|
|
debug=debug,
|
|
notify_addr=config["notify_addr"],
|
|
ld_preload=config["ld_preload"] if "ld_preload" in config else None,
|
|
)
|
|
else:
|
|
netreplay = run_netreplay_server(
|
|
ssh,
|
|
config["exp_dir"],
|
|
config["remote_repo_dir"],
|
|
record,
|
|
"0.0.0.0",
|
|
config["listen_port"],
|
|
tls,
|
|
impl,
|
|
certs_dir,
|
|
only_record=only_record,
|
|
ciphers=cipher,
|
|
kexes=kex,
|
|
debug=debug,
|
|
ld_preload=config["ld_preload"] if "ld_preload" in config else None,
|
|
)
|
|
netreplay = run_netreplay_client(
|
|
None,
|
|
config["exp_dir"],
|
|
config["repo_dir"],
|
|
record,
|
|
config["remote_addr"],
|
|
config["listen_port"],
|
|
tls,
|
|
None,
|
|
certs_dir,
|
|
only_record=only_record,
|
|
ciphers=cipher,
|
|
kexes=kex,
|
|
earlydata=earlydata,
|
|
debug=debug,
|
|
notify_addr=config["notify_addr"],
|
|
ld_preload=config["ld_preload"] if "ld_preload" in config else None,
|
|
skip_verif=True,
|
|
)
|
|
|
|
prof_filename = "-"
|
|
if config["perf"]:
|
|
prof_filename = f"{perf_dir}/perf-{timestr}-{run_id}.data"
|
|
process_pid = ssh_run(ssh, "pidof netreplay-"+impl).removesuffix("\n")
|
|
ssh_run_bg(ssh, f"perf record -F 997 --call-graph dwarf,64000 -g -o {prof_filename} -p {process_pid}")
|
|
|
|
# Measure
|
|
cpu = 0
|
|
if config["sa"]:
|
|
cpu = get_cpu_stat(ssh, ["netreplay-"+impl, "tokio-runtime-w"])
|
|
remote_bytes_in, remote_bytes_out = get_net_stat(ssh)
|
|
energy = 0
|
|
energy_rapl = 0
|
|
if config["wattmeter"]:
|
|
energy = wattmeter.get_meter()
|
|
if config["rapl"]:
|
|
energy_rapl = get_rapl_energy(ssh, remote_path)
|
|
start = time.time()
|
|
|
|
# Wait for the client to terminate
|
|
signal.alarm(3600)
|
|
try:
|
|
notify_socket.recv(4)
|
|
except Timeout:
|
|
print("TIMEOUT: stop")
|
|
|
|
# Measure
|
|
end = time.time()
|
|
new_energy = 0
|
|
new_energy_rapl = 0
|
|
if config["wattmeter"]:
|
|
new_energy = wattmeter.get_meter()
|
|
if config["rapl"]:
|
|
new_energy_rapl = get_rapl_energy(ssh, remote_path)
|
|
new_remote_bytes_in, new_remote_bytes_out = get_net_stat(ssh)
|
|
|
|
# Kill server
|
|
if side == "client":
|
|
try:
|
|
ssh_run(None, "killall netreplay")
|
|
except invoke.exceptions.UnexpectedExit as e:
|
|
pass
|
|
else:
|
|
try:
|
|
ssh_run(ssh, "killall netreplay-"+impl)
|
|
except invoke.exceptions.UnexpectedExit as e:
|
|
pass
|
|
|
|
# Measure CPU after (as it may update only after the process is killed)
|
|
new_cpu = 0
|
|
if config["sa"]:
|
|
new_cpu = get_cpu_stat(ssh, ["netreplay-"+impl, "tokio-runtime-w"])
|
|
|
|
record_filename = record["filename"]
|
|
cpu_diff = new_cpu - cpu
|
|
remote_bytes_in_diff = new_remote_bytes_in - remote_bytes_in
|
|
remote_bytes_out_diff = new_remote_bytes_out - remote_bytes_out
|
|
energy_diff = new_energy - energy
|
|
energy_rapl_diff = new_energy_rapl - energy_rapl
|
|
time_diff = end - start
|
|
repeats = record["repeat"]
|
|
while True:
|
|
try:
|
|
with open(logfile_path, "a") as logfile:
|
|
logfile.write(f"{expname} {impl} {alg} {kex} {cipher} {earlydata} {side} {tls_int} {record_filename} {repeats} {start} {end} {time_diff} {cpu_diff} {remote_bytes_in_diff} {remote_bytes_out_diff} {energy_diff} {energy_rapl_diff} {prof_filename}\n")
|
|
logfile.close()
|
|
break
|
|
except Exception as e:
|
|
print("Can't open log file:", e)
|
|
time.sleep(1)
|
|
sh(f"cp {logfile_path} {log_backup_dir}/{logfile_name}")
|
|
if config["wattmeter"]:
|
|
YAPI.FreeAPI()
|
|
if shutdown and ssh:
|
|
ssh_run(ssh, f"python3 {remote_path}/exp.py --shutdown")
|
|
|
|
def update_certs(config):
|
|
info = platform.freedesktop_os_release()
|
|
dist = info.get("ID_LIKE", info["ID"])
|
|
exp_dir = config["exp_dir"]
|
|
if dist == "debian":
|
|
for alg in CERT_SIGN_ALGS:
|
|
sh([
|
|
f"sudo cp {exp_dir}/certs/{alg}/ca.crt /usr/local/share/ca-certificates/ca-{alg}.crt",
|
|
f"sudo chmod 644 /usr/local/share/ca-certificates/ca-{alg}.crt",
|
|
f"sudo chown root:root /usr/local/share/ca-certificates/ca-{alg}.crt"
|
|
])
|
|
sh("sudo update-ca-certificates")
|
|
elif dist == "arch":
|
|
for alg in CERT_SIGN_ALGS:
|
|
sh([
|
|
f"sudo cp {exp_dir}/certs/{alg}/ca.crt /etc/ca-certificates/trust-source/anchors/ca-{alg}.crt",
|
|
f"sudo chmod 644 /etc/ca-certificates/trust-source/anchors/ca-{alg}.crt",
|
|
f"sudo chown root:root /etc/ca-certificates/trust-source/anchors/ca-{alg}.crt"
|
|
])
|
|
sh("sudo update-ca-trust extract")
|
|
|
|
# copy local dir src to remote parent dir dst (src's copy will be a subdir of dst)
|
|
def upload_dir(ssh, src, dst):
|
|
src = src.removesuffix("/")
|
|
src_name = src.split("/")[-1]
|
|
os.chdir(f"{src}/..")
|
|
os.system(f"tar -czf {src}/../tmp.tar.gz {src_name}")
|
|
print(ssh.put(src+"/../tmp.tar.gz", dst))
|
|
print(ssh.run(f"cd {dst} && tar -xf tmp.tar.gz"))
|
|
|
|
def connect_ssh(config):
|
|
ssh_passphrase = "--passphrase" in sys.argv
|
|
connect_kwargs = {}
|
|
if ssh_passphrase:
|
|
import getpass
|
|
connect_kwargs["passphrase"] = getpass.getpass("Enter passphrase for SSH key: ")
|
|
if "remote_psw" in config:
|
|
connect_kwargs["password"] = config["remote_psw"]
|
|
ssh = fabric.Connection(config["remote_ssh"], connect_kwargs=connect_kwargs)
|
|
return ssh
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2 or sys.argv[1] in ["h", "help", "?", "-h", "-help", "--help", "/?"]:
|
|
print("""Options:
|
|
make <config> [-c] [-o] Create everything
|
|
send <config> Send configs and certs to p2
|
|
update-certs <config> Update system's certs
|
|
run <config> Run experiment
|
|
shutdown Shutdown the system
|
|
|
|
Make options:
|
|
-c Make CA cert (otherwise use already existing one)
|
|
-o Make only configs (do not make certs)
|
|
|
|
Send options:
|
|
<config> One of: {configs}
|
|
|
|
Run options:
|
|
<config> One of: {configs}
|
|
--passphrase Prompt SSH key decryption passphrase (when using pubkey login)
|
|
--count Do not run experiments but display number of experiments
|
|
--record <id> Only play this record
|
|
--idle Also measure when idle
|
|
--shutdown Shutdown host and target when finished
|
|
--debug Print netreplay's debug
|
|
""".format(
|
|
sig_algs = " ".join(CERT_SIGN_ALGS),
|
|
configs = " ".join([i for i in CONFIGS]),
|
|
))
|
|
exit()
|
|
|
|
opt = sys.argv[1]
|
|
if opt == "make":
|
|
config = CONFIGS[sys.argv[2]]
|
|
make_ca = "-c" in sys.argv
|
|
config_only = "-o" in sys.argv
|
|
make_everything(config["exp_dir"], DOMAINS, make_ca)
|
|
elif opt == "send":
|
|
config = CONFIGS[sys.argv[2]]
|
|
import fabric
|
|
ssh = connect_ssh(config)
|
|
upload_dir(ssh, config["exp_dir"], "/dev/shm")
|
|
elif opt == "update-certs":
|
|
import platform
|
|
config = CONFIGS[sys.argv[2]]
|
|
update_certs(config)
|
|
elif opt == "run":
|
|
config = CONFIGS[sys.argv[2]]
|
|
if "--count" in sys.argv:
|
|
exps = 0
|
|
for expname in config["experiments"]:
|
|
exp = config["experiments"][expname]
|
|
exps += len(exp["impls"]) * len(exp["cert"]) * len(exp["kexes"]) * len(exp["ciphers"]) * len(config["records"])
|
|
print("Experiments to make:", exps * len(config["setups"]))
|
|
exit(0)
|
|
|
|
import time
|
|
import re
|
|
if "remote_ssh" in config:
|
|
import invoke
|
|
import fabric
|
|
|
|
if config["wattmeter"]:
|
|
import yoctopuce
|
|
from yoctopuce.yocto_api import *
|
|
from yoctopuce.yocto_power import *
|
|
|
|
bus = None
|
|
bus_proxy = None
|
|
shutdown = "--shutdown" in sys.argv
|
|
if shutdown:
|
|
from pydbus import SystemBus
|
|
bus = SystemBus()
|
|
bus_proxy = bus.get('org.freedesktop.login1', '/org/freedesktop/login1')
|
|
if bus_proxy.CanPowerOff() != 'yes':
|
|
print("Cannot power off")
|
|
exit(1)
|
|
|
|
debug = "--debug" in sys.argv
|
|
run_exp(config, only_record=getargv("--record", None), idle="--idle" in sys.argv, shutdown=shutdown, debug=debug)
|
|
|
|
if shutdown:
|
|
bus_proxy.PowerOff(False)
|
|
elif opt == "shutdown":
|
|
from pydbus import SystemBus
|
|
bus = SystemBus()
|
|
bus_proxy = bus.get('org.freedesktop.login1', '/org/freedesktop/login1')
|
|
if bus_proxy.CanPowerOff() != 'yes':
|
|
print("Cannot power off")
|
|
exit(1)
|
|
bus_proxy.PowerOff(False)
|
|
else:
|
|
print("Unknown command, use help for help")
|
|
exit(1)
|