diff --git a/.gitignore b/.gitignore index 2079428..56b09c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ netreplay +__pycache__ rpxy_* diff --git a/README.md b/README.md index 6a055be..e9e7cd4 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,11 @@ sudo chmod +s /sbin/sa Install OpenSSL with debug symbols: ```bash -./Configure --release -g +#./Configure --release -g +# Options from Debian build +# Debian package libssl3t64 -> Developer Information -> buildd reproducibility -> trixie rbuild +# https://tests.reproducible-builds.org/debian/rb-pkg/trixie/amd64/openssl.html +/usr/bin/perl ./Configure --release -g --prefix=/usr --openssldir=/usr/lib/ssl --libdir=lib/x86_64-linux-gnu shared no-idea no-mdc2 no-rc5 no-ssl3 no-ssl3-method enable-rfc3779 enable-cms no-capieng no-rdrand enable-tfo enable-zstd enable-zlib enable-fips enable-ec_nistp_64_gcc_128 ``` Backup your system's `libcrypto.so` and `libssl.so` and replace them with the new ones. @@ -272,40 +276,12 @@ python exp.py make local -c python exp.py send local python exp.py update-certs python exp.py run local + ``` ## Problems -### Youtube gives 502 bad gateway. - -* Works with bare curl. (not hiding we're a bot) -* We have same JA3 fingerprint as Firefox. -* HTTP request is intact. -* JA4 fingerprint different from Firefox but existing for some browsers. - -### Modèle d'expérience à revoir - -* Le relai d'une vidéo streaming avec TLS prend max 3% d'un cœur sur le Pi3, soit pas beaucoup plus que le bruit. -* Il faudrait spammer avec plusieurs connexions pour voir un effet significatif. -* On peut spammer le streaming vidéo, mais pas le reste (antibots). -* On ne devrait pas spammer sur Renater... -* Il faut donc tout faire en local. - -Solutions : - -* Copier les sites en statique et les servir avec Apache. => OK pour des sites propres genre Wikipedia, WordPress (à voir pour la pub) -* Installer des instances => Peertube, WordPress -* Copier le trafic et le rejouer => risque de demander beaucoup de dev - ### Youtube Youtube utilise des trucs aléatoires en `RANDOM.googlevideo.com` pour la vidéo. Cependant il y a quelques domaines utilisés qui ne changent pas, du moins sur un même navigateur avec la même vidéo et sur une courte période. Avant d'enregistrer le trafic, il faut observer les domaines utilisés puis générer les certificats et les redirections en fonction. - -## TODO - -* Partie serveur sans TLS de netreplay -> sans SNI, il faut parser le HTTP >.< -* exp.py: détecter la fin du replay -* yoctowatt -* mesures CPU, mémoire, bande passante sur p2 - * CPU: paquet acct, commande `sa -m` diff --git a/exp.py b/exp.py index 849c803..0132c4f 100644 --- a/exp.py +++ b/exp.py @@ -33,13 +33,14 @@ CONFIGS = { "setups": [ "none-local", "client-local", - #"server-local", + "server-local", ], "p2_hostname": "localhost", "p2_addr": "127.0.0.1", "p2_repodir": "/home/tuxmain/reps/tlsbench", "wattmeter": False, "perf": True, + "perf_dir": "/home/tuxmain/.cache/exp", "p3_suffix": ".localhost", "p3_port_plain": 8080, "p3_port_tls": 8443, @@ -75,7 +76,7 @@ DOMAINS_ = [ ] RECORDS = [ - { "filename": "youtube", "repeat": 2 }, + { "filename": "youtube", "repeat": 1 }, #{ "filename": "peertube", "repeat": 10 }, #{ "filename": "wikipedia", "repeat": 1 }, #{ "filename": "apple", "repeat": 1000 }, @@ -90,8 +91,8 @@ CERT_SIGN_ALGS = [ IMPLS = [ "aws_lc_rs", # Amazon's Rust crypto widely used in Rust stuff #"boring", # Google's fork of OpenSSL used in Chrome and Android - "openssl", # widely used - "ring", # used in most Rust stuff + #"openssl", # widely used + #"ring", # used in most Rust stuff #"symcrypt", # Microsoft's crypto #"wolfcrypt" # used in embedded (won't build with rpxy for now) ] @@ -519,9 +520,14 @@ def run_exp(expdir, config, only_record=None, idle=False): logfile_name = "log-"+timestr logfile_path = expdir+"/"+logfile_name logfile = open(logfile_path, "w") - logfile.write("exp impl alg kex cipher setup record time cpu bytes_in bytes_out Wh\n") + logfile.write("exp impl alg kex cipher setup record time cpu bytes_in bytes_out Wh 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...") rpxy_cpu = get_cpu_stat(ssh) @@ -547,7 +553,7 @@ def run_exp(expdir, config, only_record=None, idle=False): while True: try: with open(logfile_path, "a") as logfile: - logfile.write(f"idle - - - - - - {time_diff} {rpxy_cpu_diff} {p2_bytes_in_diff} {p2_bytes_out_diff} {energy_diff}\n") + logfile.write(f"idle - - - - - - {time_diff} {rpxy_cpu_diff} {p2_bytes_in_diff} {p2_bytes_out_diff} {energy_diff} -\n") logfile.close() break except Exception as e: @@ -582,14 +588,17 @@ def run_exp(expdir, config, only_record=None, idle=False): for record in exp["records"]: print(f"EXPERIMENT {expname}: {impl} {alg} {kex} {cipher} {setup}") p2_rpxy_config = SETUPS[setup]["rpxy_config"] - vars = {"CIPHERS": cipher, "KEXES": kex, "RUST_LOG": "debug", "LD_PRELOAD": "/dev/shm/openssl-3.6.0/libssl.so.3 /dev/shm/openssl-3.6.0/libcrypto.so.3"} + vars = {"CIPHERS": cipher, "KEXES": kex, "RUST_LOG": "warning"} cmd = f"{p2_path}/rpxy_rustls_{impl} --config {expdir}/configs/{p2_rpxy_config}.toml --log-dir /dev/shm" + #cmd = f"{p2_path}/rpxy_rustls_{impl} --config {expdir}/configs/{p2_rpxy_config}.toml" ssh_run_bg(ssh, cmd, env=vars) time.sleep(1) + prof_filename = "-" if config["perf"]: + prof_filename = f"{perf_dir}/perf-{timestr}-{run_id}.data" rpxy_pid = ssh_run(ssh, f"pidof rpxy_rustls_{impl}").removesuffix("\n") - ssh_run_bg(ssh, f"perf record -F 997 --call-graph dwarf,64000 -g -o {expdir}/perf-{timestr}-{run_id}.data -p {rpxy_pid}") + ssh_run_bg(ssh, f"perf record -F 997 --call-graph dwarf,64000 -g -o {prof_filename} -p {rpxy_pid}") run_id += 1 @@ -648,7 +657,7 @@ def run_exp(expdir, config, only_record=None, idle=False): while True: try: with open(logfile_path, "a") as logfile: - logfile.write(f"{expname} {impl} {alg} {kex} {cipher} {setup} {record_filename} {time_diff} {rpxy_cpu_diff} {p2_bytes_in_diff} {p2_bytes_out_diff} {energy_diff}\n") + logfile.write(f"{expname} {impl} {alg} {kex} {cipher} {setup} {record_filename} {time_diff} {rpxy_cpu_diff} {p2_bytes_in_diff} {p2_bytes_out_diff} {energy_diff} {prof_filename}\n") logfile.close() break except Exception as e: diff --git a/plots.py b/plots.py index 5daac1f..221193e 100644 --- a/plots.py +++ b/plots.py @@ -1,4 +1,5 @@ import os, sys +import profile # Nice labels for algorithms of all kinds ALG_LABEL = { @@ -30,7 +31,8 @@ VER_LABEL = { # Titles for measured quantities OBJ_TITLE = { "cpu": "CPU time", - "energy": "energy consumption" + "energy": "energy consumption", + "profile": "time profile", } # Logfile column names COL = { @@ -43,7 +45,8 @@ COL = { # Physical units by object UNIT = { "cpu": "s", - "energy": "W" + "energy": "W", + "profile": "samples", } # Titles for criteria CRITERION_TITLE = { @@ -83,7 +86,31 @@ plot \ f.close() os.system("gnuplot {plots_dir}/{object}_by_{criterion}_{side}_{record}.gnuplot".format(plots_dir=PLOTS_DIR, **kwargs)) -def make_plot(logs, exp, criterion, side, obj, record): +def gnuplot_stacked_histogram(**kwargs): + cluster = "" + #for i in range(kwargs["nb_impls"]-1): + # cluster += """, "" using {}:xticlabels(1) title col""".format(i+4) + f = open("{plots_dir}/{object}_by_{criterion}_{side}_{record}.gnuplot".format(plots_dir=PLOTS_DIR, **kwargs), "w") + f.write("""\ +set terminal pngcairo enhanced font "CMU Sans Serif,11" fontscale 1.0 size 800, 600 +set output "{plots_dir}/{object}_by_{criterion}_{side}_{record}.png" +set boxwidth 0.9 absolute +set style fill solid 1.0 border lt -1 +set style histogram rowstacked +set style data histograms +set title font "CMU Sans Serif,12" "{object_title} by {criterion_title} ({record}, {side} side) ({unit})" +set xtics border in scale 0,0 nomirror noenhanced rotate by -15 autojustify +set key fixed right top vertical Right noenhanced autotitle nobox invert +set colorbox vertical origin screen 0.9, 0.2 size screen 0.05, 0.6 front noinvert bdefault +set xrange [ * : * ] noreverse writeback +set yrange [ 0 : * ] +set grid y lt 1 lw .75 lc "gray" +plot for [i=2:{nb_functions}] "{plots_dir}/{object}_by_{criterion}_{side}_{record}.dat" using i:xticlabels(1) title col +""".format(plots_dir=PLOTS_DIR, cluster=cluster, **kwargs)) + f.close() + os.system("gnuplot {plots_dir}/{object}_by_{criterion}_{side}_{record}.gnuplot".format(plots_dir=PLOTS_DIR, **kwargs)) + +def make_log_plot(logs, exp, criterion, side, obj, record): f = open(f"/dev/shm/plots/{obj}_by_{criterion}_{side}_{record}.dat", "w") ciphers = {} impls = [] @@ -102,9 +129,7 @@ def make_plot(logs, exp, criterion, side, obj, record): return for log in logs: - if log["exp"] != exp or log["record"] != record: - continue - elif log["setup"] == side: + if log["exp"] == exp and log["record"] == record and log["setup"] == side: if log[COL[criterion]] not in ciphers: ciphers[log[COL[criterion]]] = {} ciphers[log[COL[criterion]]][log["impl"]] = float(log[COL[obj]]) - idle_val * float(log["time"]) @@ -125,8 +150,43 @@ def make_plot(logs, exp, criterion, side, obj, record): f.close() gnuplot_histogram(object=obj, criterion=criterion, side=side, object_title=OBJ_TITLE[obj], criterion_title=CRITERION_TITLE[criterion], unit=UNIT[obj], nb_impls=len(impls), record=record) +def make_profile_plot(logs, exp, criterion, side, record, no_flamegraph=False): + f = open(f"/dev/shm/plots/profile_by_{criterion}_{side}_{record}.dat", "w") + runs = [] + functions = [] + + for log in logs: + if log["exp"] == exp and log["record"] == record and log["setup"] == side: + svg_filename = log["prof"] + ".svg" + if not no_flamegraph: + os.system("flamegraph --perfdata {} -o {}".format(log["prof"], svg_filename)) + profile_results = profile.extract_from_file(svg_filename) + print(profile_results) + for function in profile_results: + if function not in functions: + functions.append(function) + runs.append({ + criterion: log[COL[criterion]], + "impl": log["impl"], + "functions": profile_results, + }) + f.write("{} {}\n".format(criterion, " ".join(functions))) + for run in runs: + f.write("\"{} {}({})\" {}\n".format( + run["impl"], + ALG_LABEL[run[criterion]], + VER_LABEL[log["cipher"]], + " ".join([ + str(run["functions"][function][0]) + for function in functions + ]), + )) + f.close() + gnuplot_stacked_histogram(object="profile", criterion=criterion, side=side, object_title=OBJ_TITLE["profile"], criterion_title=CRITERION_TITLE[criterion], unit=UNIT["profile"], record=record, nb_functions=len(functions)+1) + if __name__ == "__main__": - logfile_name = sys.argv[1] + cmd = sys.argv[1] + logfile_name = sys.argv[2] logfile = open(logfile_name, "r") lines = logfile.readlines() logfile.close() @@ -146,11 +206,20 @@ if __name__ == "__main__": os.makedirs("/dev/shm/plots", exist_ok=True) - for side in ["client", "server"]: - for record in records: - make_plot(logs, "impl-cipher-ver", "cipher", side, "cpu", record) - make_plot(logs, "impl-cipher-ver", "cipher", side, "energy", record) - make_plot(logs, "impl-cert-ver", "cert", side, "cpu", record) - make_plot(logs, "impl-cert-ver", "cert", side, "energy", record) - make_plot(logs, "impl-kex-ver", "kex", side, "cpu", record) - make_plot(logs, "impl-kex-ver", "kex", side, "energy", record) + no_flamegraph = "-f" in sys.argv + + if cmd == "log": + for side in ["client", "server"]: + for record in records: + make_log_plot(logs, "impl-cipher-ver", "cipher", side, "cpu", record) + make_log_plot(logs, "impl-cipher-ver", "cipher", side, "energy", record) + make_log_plot(logs, "impl-cert-ver", "cert", side, "cpu", record) + make_log_plot(logs, "impl-cert-ver", "cert", side, "energy", record) + make_log_plot(logs, "impl-kex-ver", "kex", side, "cpu", record) + make_log_plot(logs, "impl-kex-ver", "kex", side, "energy", record) + elif cmd == "prof": + for side in ["client-local", "server-local"]: + for record in records: + make_profile_plot(logs, "impl-cipher-ver", "cipher", side, record, no_flamegraph=no_flamegraph) + make_profile_plot(logs, "impl-cert-ver", "cert", side, record, no_flamegraph=no_flamegraph) + make_profile_plot(logs, "impl-kex-ver", "kex", side, record, no_flamegraph=no_flamegraph) diff --git a/profile.py b/profile.py index cebedf2..cbe773d 100644 --- a/profile.py +++ b/profile.py @@ -45,8 +45,7 @@ def extract_function(data, name): percents += float(match.group(2)) return (samples, percents) -if __name__ == "__main__": - filename = sys.argv[1] +def extract_from_file(filename): f = open(filename, "r") c = f.read() results = {} @@ -57,5 +56,10 @@ if __name__ == "__main__": results[title] = [0, 0.0] results[title][0] += samples results[title][1] += percents + return results + +if __name__ == "__main__": + filename = sys.argv[1] + results = extract_from_file(filename) for title in results: print(title, results[title])