realistic

This commit is contained in:
Pascal Engélibert 2026-02-26 16:40:44 +01:00
commit e184b429ed
6 changed files with 256 additions and 28 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
*.json
netreplay* netreplay*
powercap powercap
__pycache__ __pycache__

View file

@ -1,6 +1,7 @@
import json,re, subprocess, sys, threading, time import json,re, subprocess, sys, threading, time
BATCH = 50 WRITE_INTERVAL = 1000
BATCH = 200
TIMEOUT = 4 TIMEOUT = 4
CAPTURES = { CAPTURES = {
"read_bytes": "SSL handshake has read (\\d+) bytes and written \\d+ bytes\n", "read_bytes": "SSL handshake has read (\\d+) bytes and written \\d+ bytes\n",
@ -21,7 +22,9 @@ def probe(domain, ossl):
ossl.stdin.write(b"") ossl.stdin.write(b"")
ossl.stdin.close() ossl.stdin.close()
lock.acquire()
results[domain] = output = ossl.stdout.read().decode() results[domain] = output = ossl.stdout.read().decode()
lock.release()
ossl.kill() ossl.kill()
@ -29,7 +32,15 @@ if __name__ == "__main__":
if sys.argv[1] == "crawl": if sys.argv[1] == "crawl":
threads = [] threads = []
f = open(sys.argv[2], "r") f = open(sys.argv[2], "r")
i = 0
for line in f.readlines()[1:]: for line in f.readlines()[1:]:
if i % WRITE_INTERVAL == 0:
f = open(f"/dev/shm/crawl-{i}.json", "w")
lock.acquire()
json.dump(results, f)
lock.release()
f.close()
i += 1
#line = line.removesuffix("\n") #line = line.removesuffix("\n")
line = line[:line.find(",")-1] line = line[:line.find(",")-1]
print("start", line) print("start", line)
@ -37,7 +48,7 @@ if __name__ == "__main__":
ossl = subprocess.Popen(["openssl", "s_client", "-no-interactive", addr], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) ossl = subprocess.Popen(["openssl", "s_client", "-no-interactive", addr], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
t = threading.Thread(target=probe, args=(line, ossl,)) t = threading.Thread(target=probe, args=(line, ossl,))
threads.append((t, ossl, time.time())) threads.append((t, ossl, time.time()))
time.sleep(0.05) time.sleep(0.02)
t.start() t.start()
if len(threads) >= BATCH: if len(threads) >= BATCH:
print("wait") print("wait")
@ -73,9 +84,40 @@ if __name__ == "__main__":
for r in CAPTURES: for r in CAPTURES:
regexes[r] = re.compile(CAPTURES[r]) regexes[r] = re.compile(CAPTURES[r])
stats = {} stats = {}
summary = {
"cert": {
"none": 0,
"secp256r1": 0,
"secp384r1": 0,
"secp521r1": 0,
"rsa512": 0,
"rsa1024": 0,
"rsa2048": 0,
"rsa3072": 0,
"rsa4096": 0,
},
"cipher": {
"none": 0,
"aes128": 0,
"aes256": 0,
"chacha20": 0,
},
"kx": {
"none": 0,
"x25519mlkem768": 0,
"x25519": 0,
"rsa": 0,
},
"version": {
"none": 0,
"1.2": 0,
"1.3": 0,
}
}
f = open(sys.argv[2], "r") f = open(sys.argv[2], "r")
c = json.load(f) c = json.load(f)
for domain in c: for domain in c:
domain_stats = {}
for r in CAPTURES: for r in CAPTURES:
try: try:
val = regexes[r].finditer(c[domain]).__next__().group(1) val = regexes[r].finditer(c[domain]).__next__().group(1)
@ -88,7 +130,54 @@ if __name__ == "__main__":
stats[r][val] = 1 stats[r][val] = 1
else: else:
stats[r][val] += 1 stats[r][val] += 1
domain_stats[r] = val
except StopIteration: except StopIteration:
pass pass
#print("Not found:", line, r) #print("Not found:", line, r)
if "cert_sig" not in domain_stats:
summary["cert"]["none"] += 1
elif domain_stats["cert_sig"] == "ecdsa_secp256r1_sha256":
summary["cert"]["secp256r1"] += 1
elif domain_stats["cert_sig"] == "ecdsa_secp384r1_sha384":
summary["cert"]["secp384r1"] += 1
elif domain_stats["cert_sig"] == "ecdsa_secp521r1_sha512":
summary["cert"]["secp521r1"] += 1
elif "rsa" in domain_stats["cert_sig"]:
summary["cert"]["rsa{}".format(domain_stats["cert_pk_size"])] += 1
if "cipher" not in domain_stats:
summary["cipher"]["none"] += 1
elif "AES_128" in domain_stats["cipher"] or "AES128" in domain_stats["cipher"]:
summary["cipher"]["aes128"] += 1
elif "AES_256" in domain_stats["cipher"] or "AES256" in domain_stats["cipher"]:
summary["cipher"]["aes256"] += 1
elif "CHACHA20" in domain_stats["cipher"]:
summary["cipher"]["chacha20"] += 1
if "kx" not in domain_stats:
summary["kx"]["none"] += 1
elif domain_stats["kx"] == "X25519MLKEM768":
summary["kx"]["x25519mlkem768"] += 1
elif domain_stats["kx"] == "X25519" or domain_stats["kx"] == "ECDH":
summary["kx"]["x25519"] += 1
elif domain_stats["kx"] == "DH":
summary["kx"]["rsa"] += 1
if "protocol" not in domain_stats:
summary["version"]["none"] += 1
elif domain_stats["protocol"] == "TLSv1.3":
summary["version"]["1.3"] += 1
elif domain_stats["protocol"] == "TLSv1.2":
summary["version"]["1.2"] += 1
#if "kx" in domain_stats and domain_stats["kx"] == "DH":
# print(c[domain])
# exit(0)
if "-t" in sys.argv: # text output
for cat in stats:
print(f"{cat}:")
for item in stats[cat]:
print(" {}:\t{}".format(item, stats[cat][item]))
elif "-s" in sys.argv: # summary
for cat in summary:
print(f"{cat}:")
for item in summary[cat]:
print(" {}:\t{}".format(item, summary[cat][item]))
else:
print(stats) print(stats)

51
exp.py
View file

@ -51,7 +51,7 @@ CONFIGS = {
True, True,
], ],
"records": [ "records": [
{ "filename": "wikipedia", "repeat": 10 }, { "filename": "wikipedia", "repeat": 5 },
], ],
"repo_dir": "/home/tuxmain/reps/tlsbench", "repo_dir": "/home/tuxmain/reps/tlsbench",
"exp_dir": "/dev/shm/exp", "exp_dir": "/dev/shm/exp",
@ -73,7 +73,8 @@ CONFIGS = {
"impl-cipher-ver", "impl-cipher-ver",
"impl-cert-ver", "impl-cert-ver",
"impl-kex-ver", "impl-kex-ver",
"zrtt" "zrtt",
#"realistic"
], ],
"sides": [ "sides": [
"client", "client",
@ -139,6 +140,8 @@ CONFIGS = {
"impl-cipher-ver", "impl-cipher-ver",
"impl-cert-ver", "impl-cert-ver",
"impl-kex-ver", "impl-kex-ver",
"zrtt",
#"realistic",
], ],
"sides": [ "sides": [
"client", "client",
@ -149,8 +152,7 @@ CONFIGS = {
True, True,
], ],
"records": [ "records": [
{ "filename": "wikipedia", "repeat": 400 }, { "filename": "wikipedia", "repeat": 2000 },
{ "filename": "youtube", "repeat": 100 },
], ],
"repo_dir": "/home/tuxmain/reps/tlsbench", "repo_dir": "/home/tuxmain/reps/tlsbench",
"exp_dir": "/dev/shm/exp", "exp_dir": "/dev/shm/exp",
@ -164,7 +166,7 @@ CONFIGS = {
"perf": False, "perf": False,
"rapl": False, "rapl": False,
"listen_port": 8080, "listen_port": 8080,
"idle": "idle - - - - - - - - - 600.0001013278961 0.0 735 4942 1.7759999999999962 0 -", "idle": "idle - - - - - - - - - 1200.0000903606415 0 589 4764 3.478999999999999 0 -",
"notify_listen": ("0.0.0.0", 8090), "notify_listen": ("0.0.0.0", 8090),
"notify_addr": "192.168.3.1:8090", "notify_addr": "192.168.3.1:8090",
}, },
@ -388,9 +390,9 @@ EXPERIMENTS = {
"AES_128_GCM_SHA256", "AES_128_GCM_SHA256",
"AES_256_GCM_SHA384", "AES_256_GCM_SHA384",
"CHACHA20_POLY1305_SHA256", "CHACHA20_POLY1305_SHA256",
#"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_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_AES_256_GCM_SHA384,ECDHE_RSA_WITH_AES_256_GCM_SHA384",
#"ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
], ],
"kexes": ["X25519"], "kexes": ["X25519"],
"cert": ["prime256v1"], "cert": ["prime256v1"],
@ -401,7 +403,7 @@ EXPERIMENTS = {
"impls": IMPLS, "impls": IMPLS,
"ciphers": [ "ciphers": [
"AES_128_GCM_SHA256", "AES_128_GCM_SHA256",
#"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_SHA256", "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_SHA256",
], ],
"kexes": ["X25519"], "kexes": ["X25519"],
"cert": [ "cert": [
@ -418,7 +420,7 @@ EXPERIMENTS = {
"impls": IMPLS, "impls": IMPLS,
"ciphers": [ "ciphers": [
"AES_128_GCM_SHA256", "AES_128_GCM_SHA256",
#"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_SHA256", "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_SHA256",
], ],
"kexes": [ "kexes": [
"X25519", "X25519",
@ -453,6 +455,33 @@ EXPERIMENTS = {
"cert": ["prime256v1"], "cert": ["prime256v1"],
"earlydata": ["0"], "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 # Some algorithms are not available in all implementations
@ -893,7 +922,7 @@ def run_exp(config, only_record=None, idle=False, shutdown=False, debug=False):
start = time.time() start = time.time()
# Wait for the client to terminate # Wait for the client to terminate
signal.alarm(600) signal.alarm(3600)
try: try:
notify_socket.recv(4) notify_socket.recv(4)
except Timeout: except Timeout:

View file

@ -9,8 +9,8 @@ CERTS_DIR = "/dev/shm/exp/certs/"
ALGS = ["prime256v1", "secp384r1", "rsa2048", "rsa3072", "rsa4096"] ALGS = ["prime256v1", "secp384r1", "rsa2048", "rsa3072", "rsa4096"]
DOMAINS = [ DOMAINS = [
#"txmn.tk", #"txmn.tk",
"wikipedia.org", #"wikipedia.org",
#"youtube.com" "youtube.com"
] ]
def sh(cmds): def sh(cmds):
@ -135,11 +135,11 @@ def replace_keys(cert, key, cas):
# Sign # Sign
digest = None digest = None
sig_alg = cert.get_signature_algorithm() sig_alg = cert.get_signature_algorithm()
if b"SHA384" in sig_alg: if b"SHA384" in sig_alg or b"sha384" in sig_alg:
digest = "sha384" digest = "sha384"
elif b"SHA256" in sig_alg: elif b"SHA256" in sig_alg or b"sha256" in sig_alg:
digest = "sha256" digest = "sha256"
elif b"SHA512" in sig_alg: elif b"SHA512" in sig_alg or b"sha512" in sig_alg:
digest = "sha512" digest = "sha512"
if digest == None: if digest == None:
print("Unknown signature algorithm:", sig_alg) print("Unknown signature algorithm:", sig_alg)

125
plots.py
View file

@ -118,9 +118,6 @@ def gnuplot_stacked_histogram(**kwargs):
titleline = "" titleline = ""
if kwargs["maketitle"]: if kwargs["maketitle"]:
titleline = 'set title font "CMU Sans Serif,12" "{object_title} by {criterion_title} ({record}, {side}{machine}) ({unit})"'.format(**kwargs) titleline = 'set title font "CMU Sans Serif,12" "{object_title} by {criterion_title} ({record}, {side}{machine}) ({unit})"'.format(**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 = open("{plots_dir}/{object}_by_{criterion}_{side}_{record}.gnuplot".format(plots_dir=PLOTS_DIR, **kwargs), "w")
f.write("""\ f.write("""\
set terminal pngcairo enhanced font "CMU Sans Serif,11" fontscale 1.0 size 800, 600 set terminal pngcairo enhanced font "CMU Sans Serif,11" fontscale 1.0 size 800, 600
@ -146,10 +143,63 @@ set term pict2e font ",10"
set output "{plots_dir}/{object}_by_{criterion}_{side}_{record}.tex" set output "{plots_dir}/{object}_by_{criterion}_{side}_{record}.tex"
set key font ",10" spacing 0.8 set key font ",10" spacing 0.8
replot replot
""".format(plots_dir=PLOTS_DIR, cluster=cluster, titleline=titleline, **kwargs).replace("aws_lc", "aws-lc")) """.format(plots_dir=PLOTS_DIR, titleline=titleline, **kwargs).replace("aws_lc", "aws-lc"))
f.close() f.close()
os.system("gnuplot {plots_dir}/{object}_by_{criterion}_{side}_{record}.gnuplot".format(plots_dir=PLOTS_DIR, **kwargs)) os.system("gnuplot {plots_dir}/{object}_by_{criterion}_{side}_{record}.gnuplot".format(plots_dir=PLOTS_DIR, **kwargs))
def gnuplot_stacked_histogram_grouped(**kwargs):
if "machine" in kwargs and kwargs["machine"] != None:
kwargs["machine"] = ", " + kwargs["machine"]
else:
kwargs["machine"] = ""
titleline = ""
if kwargs["maketitle"]:
titleline = 'set title font "CMU Sans Serif,12" "{object_title} by {criterion_title} ({record}, {side}{machine}) ({unit})"'.format(**kwargs)
cluster = ""
cluster_i = 0
for group in kwargs["groups"]:
if cluster_i > 0:
cluster += ",\\\n\t"
cluster += f' newhistogram "{group}"'
for i in range(3, kwargs["nb_functions"]+2):
if i == 3:
cluster += ',\\\n\t "{plots_dir}/{object}_by_{criterion}_{side}_{record}_{group}.dat"'.format(group=group, plots_dir=PLOTS_DIR, **kwargs)
else:
cluster += ',\\\n\t ""'
if cluster_i == 0:
cluster += f' using {i}:xticlabels(2) title col'
else:
cluster += f' using {i}:xticlabels(2) notitle'
cluster_i += 1
f = open("{plots_dir}/{object}_by_{criterion}_{side}_{record}_{group_by}.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}_{group_by}.png"
set boxwidth 0.9 absolute
set style fill solid 1.0 border lt -1
set style histogram rowstacked gap 0
set style data histograms
{titleline}
set xtics border in scale 0,0 nomirror noenhanced rotate by 30 right
set lmargin 9
set rmargin 1
set bmargin 5
set tmargin 2.5
set key fixed left top vertical Left noenhanced nobox invert reverse opaque
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{cluster}
#set term cairolatex pdf
set term pict2e font ",10"
set output "{plots_dir}/{object}_by_{criterion}_{side}_{record}_{group_by}.tex"
set key font ",10" spacing 0.8
replot
""".format(plots_dir=PLOTS_DIR, cluster=cluster, titleline=titleline, **kwargs).replace("aws_lc", "aws-lc"))
f.close()
os.system("gnuplot {plots_dir}/{object}_by_{criterion}_{side}_{record}_{group_by}.gnuplot".format(plots_dir=PLOTS_DIR, **kwargs))
def make_log_plot(logs, exp, criterion, side, obj, record, machine=None, version=None, maketitle=True): def make_log_plot(logs, exp, criterion, side, obj, record, machine=None, version=None, maketitle=True):
f = open(f"/dev/shm/plots/{obj}_by_{criterion}_{side}_{record}.dat", "w") f = open(f"/dev/shm/plots/{obj}_by_{criterion}_{side}_{record}.dat", "w")
ciphers = {} ciphers = {}
@ -283,6 +333,64 @@ def make_profile_plot(logs, exp, criterion, side, record, no_flamegraph=False, m
maketitle=maketitle maketitle=maketitle
) )
def make_profile_plot_grouped(logs, exp, criterion, side, record, group_by, no_flamegraph=False, machine=None, version=None, maketitle=False):
runs = []
functions = []
files = {}
for log in logs:
if version != None and VER_LABEL[log["cipher"]] != version:
continue
if log["exp"] == exp and log["record"] == record and log["side"] == side and log["tls"] == "1":
svg_filename = log["prof"] + ".svg"
if not no_flamegraph:
os.system("flamegraph --perfdata {} -o {}".format(log["prof"], svg_filename))
try:
profile_results = profile.extract_from_file(svg_filename)
except FileNotFoundError:
print(f"Cannot read {svg_filename}")
continue
print(profile_results)
for function in profile_results:
if function not in functions:
functions.append(function)
log_group = log[group_by]
runs.append({
criterion: log[COL[criterion]],
"impl": log["impl"],
"group": log_group,
"functions": profile_results,
})
if log_group not in files:
files[log_group] = open(f"/dev/shm/plots/profile_by_{criterion}_{side}_{record}_{log_group}.dat", "w")
for group in files:
files[group].write("{} {} {}\n".format(group_by, criterion, " ".join(functions)))
for run in runs:
files[run["group"]].write("{} {} {}\n".format(
run["group"],
ALG_LABEL[run[criterion]],
" ".join([
str(run["functions"][function][0])
for function in functions
]),
))
for group in files:
files[group].close()
gnuplot_stacked_histogram_grouped(
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,
machine=machine,
maketitle=maketitle,
group_by=group_by,
groups=[group for group in files]
)
# Are CPU and energy proportional # Are CPU and energy proportional
def make_linear_regression(logs): def make_linear_regression(logs):
idle_cpu = None idle_cpu = None
@ -418,9 +526,12 @@ if __name__ == "__main__":
elif cmd == "prof": elif cmd == "prof":
for side in ["client", "server"]: for side in ["client", "server"]:
for record in records: for record in records:
make_profile_plot(logs, "impl-cipher-ver", "cipher", side, record, no_flamegraph=no_flamegraph, machine=machine, maketitle=maketitle, version="1.3") #make_profile_plot(logs, "impl-cipher-ver", "cipher", side, record, no_flamegraph=no_flamegraph, machine=machine, maketitle=maketitle, version="1.3")
make_profile_plot(logs, "impl-cert-ver", "cert", side, record, no_flamegraph=no_flamegraph, machine=machine, maketitle=maketitle, version="1.3") #make_profile_plot(logs, "impl-cert-ver", "cert", side, record, no_flamegraph=no_flamegraph, machine=machine, maketitle=maketitle, version="1.3")
make_profile_plot(logs, "impl-kex-ver", "kex", side, record, no_flamegraph=no_flamegraph, machine=machine, maketitle=maketitle, version="1.3") #make_profile_plot(logs, "impl-kex-ver", "kex", side, record, no_flamegraph=no_flamegraph, machine=machine, maketitle=maketitle, version="1.3")
make_profile_plot_grouped(logs, "impl-cipher-ver", "cipher", side, record, "impl", no_flamegraph=no_flamegraph, machine=machine, maketitle=maketitle, version="1.3")
make_profile_plot_grouped(logs, "impl-cert-ver", "cert", side, record, "impl", no_flamegraph=no_flamegraph, machine=machine, maketitle=maketitle, version="1.3")
make_profile_plot_grouped(logs, "impl-kex-ver", "kex", side, record, "impl", no_flamegraph=no_flamegraph, machine=machine, maketitle=maketitle, version="1.3")
elif cmd == "correl": elif cmd == "correl":
from scipy import stats from scipy import stats
import matplotlib.pyplot as plt import matplotlib.pyplot as plt

View file

@ -1,2 +0,0 @@
domain_name,output_rank,date
google.com.,1,2025-12-18