tlsbench/exp.py

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,
"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,
"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,
"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",
},
"pi3-local": {
"experiments": [
"impl-cipher-ver",
"impl-cert-ver",
"impl-kex-ver",
],
"sides": [
"client",
"server",
],
"tls": [
False,
True,
],
"records": [
{ "filename": "wikipedia", "repeat": 5 },
],
"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,
"perf_dir": "/home/exp/.cache/exp",
"listen_port": 8080,
"notify_listen": ("127.0.0.1", 8090),
"notify_addr": "127.0.0.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,
"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,
"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",
],
"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.42",
"remote_ssh": "exp@192.168.3.42",
"remote_psw": None,
"remote_repo_dir": "/home/exp/exp",
"wattmeter": True,
"perf": False,
"rapl": True,
"listen_port": 8080,
"idle": "idle - - - - - - - - - 600.000194311142 0.0 1822 6541 1.3880000000000052 304283035 -",
"notify_listen": ("0.0.0.0", 8090),
"notify_addr": "192.168.3.1:8090",
},
"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,
"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": 400 },
],
"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@nova-6",
"remote_psw": None,
"remote_repo_dir": "/home/pengelib/tlsbench",
"wattmeter": False,
"perf": False,
"rapl": False,
"listen_port": 8080,
"idle": "idle - - - - - - - - - 600.000194311142 0.0 1822 6541 1.3880000000000052 304283035 -",#TODO
"notify_listen": ("0.0.0.0", 8090),
"notify_addr": "TODO:8090",
},
}
# 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
"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 impl != "openssl" and impl != "aws-lc" and impl != "graviola":
return False
if kex == "SECP256R1MLKEM768" and impl == "openssl":
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 impl == "openssl":
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):
if exp_dir[-1] != "/":
exp_dir += "/"
repo_dir = repo_dir.removesuffix("/")
env = {"RUST_LOG": "warning", "SSLKEYLOGFILE": "/dev/shm/netreplay.keys.txt"}
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):
if exp_dir[-1] != "/":
exp_dir += "/"
repo_dir = repo_dir.removesuffix("/")
env = {"RUST_LOG": "warning", "SSLKEYLOGFILE": "/dev/shm/netreplay.keys.txt", "EARLYDATA": earlydata}
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]
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):
if ssh == None:
# As long as there is no argument containing a space (escaped or quoted), we're fine
print(cmd)
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 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 - - - - - - - - - {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,
)
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"],
)
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,
)
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"],
)
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 = 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 = 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} {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)