#!/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 [-c] [-o] Create everything send Send configs and certs to p2 update-certs Update system's certs run 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: One of: {configs} Run options: One of: {configs} --passphrase Prompt SSH key decryption passphrase (when using pubkey login) --count Do not run experiments but display number of experiments --record 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)