Profile graphs

This commit is contained in:
Pascal Engélibert 2025-11-26 17:05:05 +01:00
commit a81a01f394
5 changed files with 115 additions and 56 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
netreplay
__pycache__
rpxy_*

View file

@ -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`

27
exp.py
View file

@ -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:

View file

@ -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)

View file

@ -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])