This commit is contained in:
Pascal Engélibert 2025-11-21 17:05:59 +01:00
commit 0335dac6d4
4 changed files with 378 additions and 157 deletions

View file

@ -107,6 +107,11 @@ Most of the implementations can be used through RusTLS.
However RusTLS clients won't enable to force TLS1.2 if 1.3 is available. However RusTLS clients won't enable to force TLS1.2 if 1.3 is available.
* séparer conso crypto / trafic
* mesure avec debugger : quelles fonctions consomment
* mesure mémoire
* mesure overhead
### WolfSSL ### WolfSSL
```bash ```bash
@ -146,6 +151,13 @@ sudo make install
* https://github.com/MarcT0K/privacy-carbon-experiments * https://github.com/MarcT0K/privacy-carbon-experiments
* https://davidtnaylor.com/CostOfTheS.pdf * https://davidtnaylor.com/CostOfTheS.pdf
* 2014 * 2014
* https://www.researchgate.net/publication/359906722_Performance_Analysis_of_SSLTLS_Crypto_Libraries_Based_on_Operating_Platform
* 2022
* throughput & cpu
* OpenSSL, GnuTLS, Boring, S2n, NSS, Cryptlib
* https://www.haproxy.com/blog/state-of-ssl-stacks
* 2025
* OpenSSL
## Sources ## Sources
@ -203,6 +215,8 @@ Just browse. Any traffic to and from the selected names will be recorded. Termin
### Measure ### Measure
Measure resource cost on a different machine.
Add p2 the `/etc/hosts`: Add p2 the `/etc/hosts`:
``` ```
@ -217,14 +231,49 @@ sudo chmod +s /sbin/sa
``` ```
```bash ```bash
python exp.py make -c python exp.py make pi -c
python exp.py send python exp.py send pi
python exp.py update-certs # also do this command on p2 python exp.py update-certs # also do this command on p2
python exp.py run --idle python exp.py run pi --idle
``` ```
On Debian, update-certs says 0 certs added even if it has actually updated some certs. This step is still needed. On Debian, update-certs says 0 certs added even if it has actually updated some certs. This step is still needed.
### Profile
Profile code execution on the local machine.
Add the domains the `/etc/hosts`:
```
127.0.0.1 google.com.localhost
127.0.0.1 www.google.com.localhost
# etc.
```
Install sa:
```bash
sudo apt install acct
sudo chmod +s /sbin/sa
```
Install OpenSSL with debug symbols:
```bash
./Configure --release -g
```
Backup your system's `libcrypto.so` and `libssl.so` and replace them with the new ones.
It would be simpler with `LD_PRELOAD` but Rust loads dynamic libraries in a particuliar way so it doesn't work.
```bash
python exp.py make local -c
python exp.py send local
python exp.py update-certs
python exp.py run local
```
## Problems ## Problems
### Youtube gives 502 bad gateway. ### Youtube gives 502 bad gateway.

335
exp.py
View file

@ -1,13 +1,55 @@
#!/usr/bin/python3 #!/usr/bin/python3
import os, sys, subprocess import os, sys, subprocess
CONFIGS = {
"pi": {
"experiments": [
"impl-cipher-ver",
"impl-cert-ver",
"impl-kex-ver",
],
"setups": [
"none",
"client",
"server",
],
"p2_hostname": "p2",
"p2_addr": "192.168.3.14",
"p2_ssh": "exp@p2",
"p2_psw": "exp",
"p2_repodir": "/home/exp/exp",
"wattmeter": True,
"perf": False,
"p3_suffix": "",
"p3_port_plain": 80,
"p3_port_tls": 443,
},
"local": {
"experiments": [
#"impl-cipher-ver",
"impl-cert-ver",
#"impl-kex-ver",
],
"setups": [
"none-local",
"client-local",
#"server-local",
],
"p2_hostname": "localhost",
"p2_addr": "127.0.0.1",
"p2_repodir": "/home/tuxmain/reps/tlsbench",
"wattmeter": False,
"perf": True,
"p3_suffix": ".localhost",
"p3_port_plain": 8080,
"p3_port_tls": 8443,
}
}
REPODIR = "/home/tuxmain/reps/tlsbench" REPODIR = "/home/tuxmain/reps/tlsbench"
P2_SSH = "exp@p2"
P2_PSW = "exp"
P2_REPODIR = "/home/exp/exp" P2_REPODIR = "/home/exp/exp"
EXPDIR = "/dev/shm/exp" EXPDIR = "/dev/shm/exp"
LOG_BACKUP_DIR = "/home/tuxmain" LOG_BACKUP_DIR = "/home/tuxmain"
P2_ADDR = "192.168.3.14"
DOMAINS_ = [ DOMAINS_ = [
# Apple # Apple
"apple.com", "www.apple.com", "graffiti-tags.apple.com", "securemetrics.apple.com", "apple.com", "www.apple.com", "graffiti-tags.apple.com", "securemetrics.apple.com",
@ -31,12 +73,11 @@ DOMAINS_ = [
# Peertube video # Peertube video
"videos.domainepublic.net", "videos.domainepublic.net",
] ]
WATTMETER = True
RECORDS = [ RECORDS = [
#{ "filename": "youtube", "repeat": 1000 }, { "filename": "youtube", "repeat": 2 },
#{ "filename": "peertube", "repeat": 10 }, #{ "filename": "peertube", "repeat": 10 },
{ "filename": "wikipedia", "repeat": 100 }, #{ "filename": "wikipedia", "repeat": 1 },
#{ "filename": "apple", "repeat": 1000 }, #{ "filename": "apple", "repeat": 1000 },
#{ "filename": "google", "repeat": 1000 }, #{ "filename": "google", "repeat": 1000 },
] ]
@ -48,9 +89,9 @@ CERT_SIGN_ALGS = [
] ]
IMPLS = [ IMPLS = [
"aws_lc_rs", # Amazon's Rust crypto widely used in Rust stuff "aws_lc_rs", # Amazon's Rust crypto widely used in Rust stuff
"boring", # Google's fork of OpenSSL used in Chrome and Android #"boring", # Google's fork of OpenSSL used in Chrome and Android
"openssl", # widely used "openssl", # widely used
#"ring", # used in most Rust stuff "ring", # used in most Rust stuff
#"symcrypt", # Microsoft's crypto #"symcrypt", # Microsoft's crypto
#"wolfcrypt" # used in embedded (won't build with rpxy for now) #"wolfcrypt" # used in embedded (won't build with rpxy for now)
] ]
@ -92,7 +133,7 @@ EXPERIMENTS = {
"kexes": ["X25519"], "kexes": ["X25519"],
"cert": ["prime256v1"], "cert": ["prime256v1"],
}, },
## Compare signatures among implementations and TLS versions # Compare signatures among implementations and TLS versions
"impl-cert-ver": { "impl-cert-ver": {
"impls": IMPLS, "impls": IMPLS,
"records": RECORDS, "records": RECORDS,
@ -108,7 +149,7 @@ EXPERIMENTS = {
"rsa3072", "rsa4096" "rsa3072", "rsa4096"
], ],
}, },
## Compare key exchange groups among implementations and TLS versions # Compare key exchange groups among implementations and TLS versions
"impl-kex-ver": { "impl-kex-ver": {
"impls": IMPLS, "impls": IMPLS,
"records": RECORDS, "records": RECORDS,
@ -119,16 +160,16 @@ EXPERIMENTS = {
"kexes": ["X25519", "SECP256R1", "SECP384R1"], "kexes": ["X25519", "SECP256R1", "SECP384R1"],
"cert": ["prime256v1"], "cert": ["prime256v1"],
}, },
#"debug": { "debug": {
# "impls": IMPLS, "impls": IMPLS,
# "records": RECORDS, "records": RECORDS,
# "ciphers": [ "ciphers": [
# "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_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"],
#}, },
} }
DOMAINS = [] DOMAINS = []
@ -251,35 +292,35 @@ RPXY_CONFIGS = {
"listen_http": 80, "listen_http": 80,
"listen_https": 443, "listen_https": 443,
"app": """[apps.{app}] "app": """[apps.{app}]
server_name = "{domain}" server_name = "{domain}"
tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }}
reverse_proxy = [{{ upstream = [{{ location = "{domain}" }}], set_host = "{domain}"}}] reverse_proxy = [{{ upstream = [{{ location = "{domain}{p3_suffix}:{p3_port_plain}" }}], set_host = "{domain}"}}]
[apps.{app}_localhost] [apps.{app}_localhost]
server_name = "{domain}.localhost" server_name = "{domain}.localhost"
tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }}
reverse_proxy = [{{ upstream = [{{ location = "{domain}" }}], set_host = "{domain}"}}] reverse_proxy = [{{ upstream = [{{ location = "{domain}{p3_suffix}:{p3_port_plain}" }}], set_host = "{domain}"}}]
[apps.{app}_p2] [apps.{app}_p2]
server_name = "{domain}.p2" server_name = "{domain}.p2"
tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }}
reverse_proxy = [{{ upstream = [{{ location = "{domain}" }}], set_host = "{domain}"}}] reverse_proxy = [{{ upstream = [{{ location = "{domain}{p3_suffix}:{p3_port_plain}" }}], set_host = "{domain}"}}]
""" """
}, },
"tls": { "tls": {
"listen_http": 80, "listen_http": 80,
"listen_https": 443, "listen_https": 443,
"app": """[apps.{app}] "app": """[apps.{app}]
server_name = "{domain}" server_name = "{domain}"
tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }}
reverse_proxy = [{{ upstream = [{{ location = "{domain}", tls = true }}], set_host = "{domain}"}}] reverse_proxy = [{{ upstream = [{{ location = "{domain}{p3_suffix}:{p3_port_tls}", tls = true }}], set_host = "{domain}"}}]
[apps.{app}_localhost] [apps.{app}_localhost]
server_name = "{domain}.localhost" server_name = "{domain}.localhost"
tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }}
reverse_proxy = [{{ upstream = [{{ location = "{domain}", tls = true }}], set_host = "{domain}"}}] reverse_proxy = [{{ upstream = [{{ location = "{domain}{p3_suffix}:{p3_port_tls}", tls = true }}], set_host = "{domain}"}}]
[apps.{app}_p2] [apps.{app}_p2]
server_name = "{domain}.p2" server_name = "{domain}.p2"
tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }}
reverse_proxy = [{{ upstream = [{{ location = "{domain}", tls = true }}], set_host = "{domain}"}}] reverse_proxy = [{{ upstream = [{{ location = "{domain}{p3_suffix}:{p3_port_tls}", tls = true }}], set_host = "{domain}"}}]
""" """
}, },
} }
@ -305,36 +346,67 @@ SETUPS = {
"listen_port": 80, "listen_port": 80,
"tls_invariant": False, "tls_invariant": False,
}, },
#"both": { "both": {
# "rpxy_config": "tls", "rpxy_config": "tls",
# "netreplay_tls_mode": "both", "netreplay_tls_mode": "both",
# "p2_port": 443, "p2_port": 443,
# "listen_port": 443, "listen_port": 443,
# "tls_invariant": False, "tls_invariant": False,
#}, },
"none-local": {
"rpxy_config": "plain",
"netreplay_tls_mode": "none",
"p2_port": 80,
"listen_port": 8080,
"tls_invariant": True,
},
"client-local": {
"rpxy_config": "tls",
"netreplay_tls_mode": "server",
"p2_port": 80,
"listen_port": 8443,
"tls_invariant": False,
},
"server-local": {
"rpxy_config": "plain",
"netreplay_tls_mode": "client",
"p2_port": 443,
"listen_port": 8080,
"tls_invariant": False,
},
"both-local": {
"rpxy_config": "tls",
"netreplay_tls_mode": "both",
"p2_port": 443,
"listen_port": 8443,
"tls_invariant": False,
},
} }
def make_rpxy_config(outdir, domains, cryptodir, config_name): def make_rpxy_config(outdir, domains, cryptodir, config_name, p3_suffix, p3_port_plain, p3_port_tls):
if outdir[-1] != "/": if outdir[-1] != "/":
outdir += "/" outdir += "/"
if cryptodir[-1] != "/": if cryptodir[-1] != "/":
cryptodir += "/" cryptodir += "/"
config = RPXY_CONFIGS[config_name] rpxy_config = RPXY_CONFIGS[config_name]
f = open(outdir+config_name+".toml", "w") f = open(outdir+config_name+".toml", "w")
f.write("listen_port = {}\nlisten_port_tls = {}\n".format(config["listen_http"], config["listen_https"])) f.write("listen_port = {}\nlisten_port_tls = {}\n".format(rpxy_config["listen_http"], rpxy_config["listen_https"]))
for domain in domains: for domain in domains:
app = domain.replace(".", "_") app = domain.replace(".", "_")
root = get_domain_root(domain) root = get_domain_root(domain)
f.write(config["app"].format( f.write(rpxy_config["app"].format(
app=app, app=app,
domain=domain, domain=domain,
cert=cryptodir+root+".crt", cert=cryptodir+root+".crt",
key=cryptodir+root+".key", key=cryptodir+root+".key",
p3_suffix=p3_suffix,
p3_port_plain=p3_port_plain,
p3_port_tls=p3_port_tls,
)) ))
f.close() f.close()
def make_everything(expdir, domains, make_ca): def make_everything(expdir, domains, make_ca, p3_suffix, p3_port_plain, p3_port_tls):
os.makedirs(expdir, exist_ok=True) os.makedirs(expdir, exist_ok=True)
if expdir[-1] != "/": if expdir[-1] != "/":
expdir += "/" expdir += "/"
@ -347,7 +419,7 @@ def make_everything(expdir, domains, make_ca):
configdir = expdir+"configs/" configdir = expdir+"configs/"
os.makedirs(configdir, exist_ok=True) os.makedirs(configdir, exist_ok=True)
for config_name in RPXY_CONFIGS: for config_name in RPXY_CONFIGS:
make_rpxy_config(configdir, domains, cryptodir, config_name) make_rpxy_config(configdir, domains, cryptodir, config_name, p3_suffix, p3_port_plain, p3_port_tls)
def choose_cert_alg(expdir, alg): def choose_cert_alg(expdir, alg):
if expdir[-1] != "/": if expdir[-1] != "/":
@ -368,7 +440,7 @@ def run_netreplay(expdir, repodir, record, p2_addr, p2_port, listen_port, tls_mo
if expdir[-1] != "/": if expdir[-1] != "/":
expdir += "/" expdir += "/"
repodir = repodir.removesuffix("/") repodir = repodir.removesuffix("/")
env = {"RUST_LOG": "debug"} env = {"RUST_LOG": "warning"}
if ciphers: if ciphers:
env["CIPHERS"] = ciphers env["CIPHERS"] = ciphers
if kexes: if kexes:
@ -379,31 +451,54 @@ def run_netreplay(expdir, repodir, record, p2_addr, p2_port, listen_port, tls_mo
print(" ".join(cmd)) print(" ".join(cmd))
return subprocess.Popen(cmd, env=env) return subprocess.Popen(cmd, env=env)
# https://stackoverflow.com/questions/8775598/how-to-start-a-background-process-with-nohup-using-fabric # Run with or without SSH
def runbg(ssh, cmd, vars={}): def ssh_run(ssh, cmd, env={}, **kwargs):
print("[SSH]", cmd) 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 = "" strvars = ""
for var in vars: for var in env:
strvars += f"export {var}="+vars[var]+" && " strvars += var+"="+env[var]+" "
return ssh.run(f"{strvars}dtach -n `mktemp -u /tmp/dtach.XXXX` {cmd}") 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): def get_cpu_stat(ssh):
res = ssh.run("/sbin/sa --list-all-names", hide=True) res = ssh_run(ssh, "/sbin/sa --list-all-names", hide=True)
for line in res.stdout.split("\n"): for line in res.split("\n"):
if "rpxy" in line: if "rpxy" in line:
return float(re.finditer("\\s(\\d+\\.\\d+)cp\\s", line).__next__().group(1)) return float(re.finditer("\\s(\\d+\\.\\d+)cp\\s", line).__next__().group(1))
return 0.0 return 0.0
def get_net_stat(ssh): def get_net_stat(ssh):
res = ssh.run("cat /proc/net/netstat", hide=True) res = ssh_run(ssh, "cat /proc/net/netstat", hide=True)
items = res.stdout.split("\n")[3].split(" ") items = res.split("\n")[3].split(" ")
bytes_in = int(items[7]) bytes_in = int(items[7])
bytes_out = int(items[8]) bytes_out = int(items[8])
return (bytes_in, bytes_out) return (bytes_in, bytes_out)
def run_exp(ssh, expdir, p2_path, exps, only_record=None, idle=False): def run_exp(expdir, config, only_record=None, idle=False):
ssh = None
if "p2_ssh" in config:
ssh = connect_ssh(config)
p2_path = config["p2_repodir"]
wattmeter = None wattmeter = None
if WATTMETER: if config["wattmeter"]:
errmsg = YRefParam() errmsg = YRefParam()
if YAPI.RegisterHub("usb", errmsg) != YAPI.SUCCESS: if YAPI.RegisterHub("usb", errmsg) != YAPI.SUCCESS:
sys.exit("init error" + errmsg.value) sys.exit("init error" + errmsg.value)
@ -413,14 +508,15 @@ def run_exp(ssh, expdir, p2_path, exps, only_record=None, idle=False):
exit(1) exit(1)
sh("killall netreplay") sh("killall netreplay")
for expname in exps: for expname in config["experiments"]:
exp = exps[expname] exp = EXPERIMENTS[expname]
for impl in exp["impls"]: for impl in exp["impls"]:
try: try:
ssh.run(f"killall rpxy_rustls_{impl}") ssh_run(ssh, f"killall rpxy_rustls_{impl}")
except invoke.exceptions.UnexpectedExit as e: except invoke.exceptions.UnexpectedExit as e:
pass pass
logfile_name = "log-"+str(int(time.time())) timestr = str(int(time.time()))
logfile_name = "log-"+timestr
logfile_path = expdir+"/"+logfile_name logfile_path = expdir+"/"+logfile_name
logfile = open(logfile_path, "w") 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\n")
@ -431,7 +527,7 @@ def run_exp(ssh, expdir, p2_path, exps, only_record=None, idle=False):
rpxy_cpu = get_cpu_stat(ssh) rpxy_cpu = get_cpu_stat(ssh)
p2_bytes_in, p2_bytes_out = get_net_stat(ssh) p2_bytes_in, p2_bytes_out = get_net_stat(ssh)
energy = 0 energy = 0
if WATTMETER: if config["wattmeter"]:
energy = wattmeter.get_meter() energy = wattmeter.get_meter()
start = time.time() start = time.time()
@ -439,7 +535,7 @@ def run_exp(ssh, expdir, p2_path, exps, only_record=None, idle=False):
end = time.time() end = time.time()
new_energy = 0 new_energy = 0
if WATTMETER: if config["wattmeter"]:
new_energy = wattmeter.get_meter() new_energy = wattmeter.get_meter()
new_p2_bytes_in, new_p2_bytes_out = get_net_stat(ssh) new_p2_bytes_in, new_p2_bytes_out = get_net_stat(ssh)
new_rpxy_cpu = get_cpu_stat(ssh) new_rpxy_cpu = get_cpu_stat(ssh)
@ -469,35 +565,42 @@ def run_exp(ssh, expdir, p2_path, exps, only_record=None, idle=False):
time.sleep(1) time.sleep(1)
sh(f"cp {logfile_path} {LOG_BACKUP_DIR}/{logfile_name}") sh(f"cp {logfile_path} {LOG_BACKUP_DIR}/{logfile_name}")
for expname in exps: run_id = 0
exp = exps[expname] for expname in config["experiments"]:
exp = EXPERIMENTS[expname]
first_set = True first_set = True
for impl in exp["impls"]: for impl in exp["impls"]:
for alg in exp["cert"]: for alg in exp["cert"]:
for kex in exp["kexes"]: for kex in exp["kexes"]:
for cipher in exp["ciphers"]: for cipher in exp["ciphers"]:
choose_cert_alg(expdir, alg) choose_cert_alg(expdir, alg)
ssh.run(f"python {p2_path}/exp.py cert {alg}") ssh_run(ssh, f"python {p2_path}/exp.py cert {alg}")
for setup in SETUPS: for setup in config["setups"]:
if SETUPS[setup]["tls_invariant"] and not first_set: if SETUPS[setup]["tls_invariant"] and not first_set:
continue continue
setupdir = expdir+"setups/"+setup setupdir = expdir+"setups/"+setup
for record in exp["records"]: for record in exp["records"]:
print(f"EXPERIMENT {expname}: {impl} {alg} {kex} {cipher} {setup}") print(f"EXPERIMENT {expname}: {impl} {alg} {kex} {cipher} {setup}")
p2_rpxy_config = SETUPS[setup]["rpxy_config"] p2_rpxy_config = SETUPS[setup]["rpxy_config"]
vars = {"CIPHERS": cipher, "KEXES": kex, "RUST_LOG": "debug"} 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"}
cmd = f"{p2_path}/rpxy_rustls_{impl} --config {expdir}/configs/{p2_rpxy_config}.toml --log-dir /dev/shm"
runbg(ssh, f"{p2_path}/rpxy_rustls_{impl} --config {expdir}/configs/{p2_rpxy_config}.toml --log-dir /dev/shm", vars) ssh_run_bg(ssh, cmd, env=vars)
time.sleep(1) time.sleep(1)
if config["perf"]:
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}")
run_id += 1
rpxy_cpu = get_cpu_stat(ssh) rpxy_cpu = get_cpu_stat(ssh)
p2_bytes_in, p2_bytes_out = get_net_stat(ssh) p2_bytes_in, p2_bytes_out = get_net_stat(ssh)
energy = 0 energy = 0
if WATTMETER: if config["wattmeter"]:
energy = wattmeter.get_meter() energy = wattmeter.get_meter()
start = time.time() start = time.time()
netreplay = run_netreplay(expdir, REPODIR, record, P2_ADDR, SETUPS[setup]["p2_port"], SETUPS[setup]["listen_port"], SETUPS[setup]["netreplay_tls_mode"], only_record=only_record, ciphers=cipher, kexes=kex) netreplay = run_netreplay(expdir, REPODIR, record, config["p2_addr"], SETUPS[setup]["p2_port"], SETUPS[setup]["listen_port"], SETUPS[setup]["netreplay_tls_mode"], only_record=only_record, ciphers=cipher, kexes=kex)
# TODO detect when netreplay has finished # TODO detect when netreplay has finished
try: try:
@ -505,11 +608,11 @@ def run_exp(ssh, expdir, p2_path, exps, only_record=None, idle=False):
except KeyboardInterrupt: except KeyboardInterrupt:
netreplay.kill() netreplay.kill()
try: try:
ssh.run(f"killall rpxy_rustls_{impl}") ssh_run(ssh, f"killall rpxy_rustls_{impl}")
except invoke.exceptions.UnexpectedExit as e: except invoke.exceptions.UnexpectedExit as e:
pass pass
try: try:
ssh.run(f"killall dtach") ssh_run(ssh, f"killall dtach")
except invoke.exceptions.UnexpectedExit as e: except invoke.exceptions.UnexpectedExit as e:
pass pass
exit(0) exit(0)
@ -517,21 +620,22 @@ def run_exp(ssh, expdir, p2_path, exps, only_record=None, idle=False):
#time.sleep(30) #time.sleep(30)
#sh("killall netreplay") #sh("killall netreplay")
try: try:
ssh.run(f"rm /dev/shm/access.log /dev/shm/rpxy.log") #ssh_run(ssh, f"rm /dev/shm/access.log /dev/shm/rpxy.log")
pass
except invoke.exceptions.UnexpectedExit as e: except invoke.exceptions.UnexpectedExit as e:
pass pass
try: try:
ssh.run(f"killall rpxy_rustls_{impl}") ssh_run(ssh, f"killall rpxy_rustls_{impl}")
except invoke.exceptions.UnexpectedExit as e: except invoke.exceptions.UnexpectedExit as e:
pass pass
try: try:
ssh.run(f"killall dtach") ssh_run(ssh, f"killall dtach")
except invoke.exceptions.UnexpectedExit as e: except invoke.exceptions.UnexpectedExit as e:
pass pass
end = time.time() end = time.time()
new_energy = 0 new_energy = 0
if WATTMETER: if config["wattmeter"]:
new_energy = wattmeter.get_meter() new_energy = wattmeter.get_meter()
new_p2_bytes_in, new_p2_bytes_out = get_net_stat(ssh) new_p2_bytes_in, new_p2_bytes_out = get_net_stat(ssh)
new_rpxy_cpu = get_cpu_stat(ssh) new_rpxy_cpu = get_cpu_stat(ssh)
@ -552,7 +656,7 @@ def run_exp(ssh, expdir, p2_path, exps, only_record=None, idle=False):
time.sleep(1) time.sleep(1)
sh(f"cp {logfile_path} {LOG_BACKUP_DIR}/{logfile_name}") sh(f"cp {logfile_path} {LOG_BACKUP_DIR}/{logfile_name}")
first_set = False first_set = False
if WATTMETER: if config["wattmeter"]:
YAPI.FreeAPI() YAPI.FreeAPI()
def update_certs(): def update_certs():
@ -583,15 +687,15 @@ def upload_dir(ssh, src, dst):
print(ssh.put(src+"/../tmp.tar.gz", dst)) print(ssh.put(src+"/../tmp.tar.gz", dst))
print(ssh.run(f"cd {dst} && tar -xf tmp.tar.gz")) print(ssh.run(f"cd {dst} && tar -xf tmp.tar.gz"))
def connect_ssh(): def connect_ssh(config):
ssh_passphrase = "--passphrase" in sys.argv ssh_passphrase = "--passphrase" in sys.argv
connect_kwargs = {} connect_kwargs = {}
if ssh_passphrase: if ssh_passphrase:
import getpass import getpass
connect_kwargs["passphrase"] = getpass.getpass("Enter passphrase for SSH key: ") connect_kwargs["passphrase"] = getpass.getpass("Enter passphrase for SSH key: ")
if P2_PSW != None: if "p2_psw" in config:
connect_kwargs["password"] = P2_PSW connect_kwargs["password"] = config["p2_psw"]
ssh = fabric.Connection(P2_SSH, connect_kwargs=connect_kwargs) ssh = fabric.Connection(config["p2_ssh"], connect_kwargs=connect_kwargs)
return ssh return ssh
if __name__ == "__main__": if __name__ == "__main__":
@ -599,35 +703,37 @@ if __name__ == "__main__":
print("""Options: print("""Options:
make [-c] Create everything make [-c] Create everything
cert <alg> Select cert signature algorithm cert <alg> Select cert signature algorithm
send Send configs and certs to p2 send <config> Send configs and certs to p2
update-certs Update system's certs update-certs Update system's certs
run Run experiment run <config> Run experiment
script Print Firefox script to override DNS script Print Firefox script to override DNS
Signature algorithms:
{sig_algs}
Implementations:
{impls}
Make options: Make options:
-c Make CA cert (otherwise use already existing one) -c Make CA cert (otherwise use already existing one)
Cert options:
<alg> One of: {sig_algs}
Send options:
<config> One of: {configs}
Run options: Run options:
<config> One of: {configs}
--passphrase Prompt SSH key decryption passphrase (when using pubkey login) --passphrase Prompt SSH key decryption passphrase (when using pubkey login)
--count Do not run experiments but display number of experiments --count Do not run experiments but display number of experiments
--record <id> Only play this record --record <id> Only play this record
--idle Also measure when idle --idle Also measure when idle
""".format( """.format(
sig_algs=" ".join(CERT_SIGN_ALGS), sig_algs = " ".join(CERT_SIGN_ALGS),
impls=" ".join(IMPLS), configs = " ".join([i for i in CONFIGS]),
)) ))
exit() exit()
opt = sys.argv[1] opt = sys.argv[1]
if opt == "make": if opt == "make":
config = CONFIGS[sys.argv[2]]
make_ca = "-c" in sys.argv make_ca = "-c" in sys.argv
make_everything(EXPDIR, DOMAINS, make_ca) make_everything(EXPDIR, DOMAINS, make_ca, config["p3_suffix"], config["p3_port_plain"], config["p3_port_tls"])
elif opt == "cert": elif opt == "cert":
alg = sys.argv[2] alg = sys.argv[2]
if not alg in CERT_SIGN_ALGS: if not alg in CERT_SIGN_ALGS:
@ -635,19 +741,21 @@ Run options:
exit(1) exit(1)
choose_cert_alg(EXPDIR, alg) choose_cert_alg(EXPDIR, alg)
elif opt == "send": elif opt == "send":
config = CONFIGS[sys.argv[2]]
import fabric import fabric
ssh = connect_ssh() ssh = connect_ssh(config)
upload_dir(ssh, EXPDIR, "/dev/shm") upload_dir(ssh, EXPDIR, "/dev/shm")
elif opt == "update-certs": elif opt == "update-certs":
import platform import platform
update_certs() update_certs()
elif opt == "run": elif opt == "run":
config = CONFIGS[sys.argv[2]]
if "--count" in sys.argv: if "--count" in sys.argv:
exps = 0 exps = 0
for expname in EXPERIMENTS: for expname in config["experiments"]:
exp = EXPERIMENTS[expname] exp = config["experiments"][expname]
exps += len(exp["impls"]) * len(exp["cert"]) * len(exp["kexes"]) * len(exp["ciphers"]) * len(exp["records"]) exps += len(exp["impls"]) * len(exp["cert"]) * len(exp["kexes"]) * len(exp["ciphers"]) * len(exp["records"])
print("Experiments to make:", exps * len(SETUPS)) print("Experiments to make:", exps * len(config["setups"]))
exit(0) exit(0)
import time import time
@ -655,13 +763,12 @@ Run options:
import re import re
import fabric import fabric
if WATTMETER: if config["wattmeter"]:
import yoctopuce import yoctopuce
from yoctopuce.yocto_api import * from yoctopuce.yocto_api import *
from yoctopuce.yocto_power import * from yoctopuce.yocto_power import *
ssh = connect_ssh() run_exp(EXPDIR, config, only_record=getargv("--record", None), idle="--idle" in sys.argv)
run_exp(ssh, EXPDIR, P2_REPODIR, EXPERIMENTS, only_record=getargv("--record", None), idle="--idle" in sys.argv)
elif opt == "script": elif opt == "script":
print(SCRIPT_FIREFOX_HOSTS) print(SCRIPT_FIREFOX_HOSTS)
else: else:

View file

@ -59,15 +59,15 @@ def gnuplot_histogram(**kwargs):
cluster = "" cluster = ""
for i in range(kwargs["nb_impls"]-1): for i in range(kwargs["nb_impls"]-1):
cluster += """, "" using {}:xticlabels(1) title col""".format(i+4) cluster += """, "" using {}:xticlabels(1) title col""".format(i+4)
f = open("{plots_dir}/{object}_by_{criterion}_{side}.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
set output "{plots_dir}/{object}_by_{criterion}_{side}.png" set output "{plots_dir}/{object}_by_{criterion}_{side}_{record}.png"
set boxwidth 0.9 absolute set boxwidth 0.9 absolute
set style fill solid 1.0 border lt -1 set style fill solid 1.0 border lt -1
set style histogram clustered gap 1 title textcolor lt -1 set style histogram clustered gap 1 title textcolor lt -1
set style data histograms set style data histograms
set title font "CMU Sans Serif,12" "{object_title} by {criterion_title} ({side} side) ({unit})" set title font "CMU Sans Serif,12" "{object_title} by {criterion_title} ({record}, {side} side) ({unit})"
#set xtics border in scale 0,0 nomirror rotate by -45 autojustify #set xtics border in scale 0,0 nomirror rotate by -45 autojustify
set xtics border in scale 0,0 nomirror autojustify set xtics border in scale 0,0 nomirror autojustify
#set key fixed right top vertical Right noreverse noenhanced autotitle nobox #set key fixed right top vertical Right noreverse noenhanced autotitle nobox
@ -77,53 +77,53 @@ set xrange [ * : * ] noreverse writeback
set yrange [ 0 : * ] set yrange [ 0 : * ]
set grid y lt 1 lw .75 lc "gray" set grid y lt 1 lw .75 lc "gray"
plot \ plot \
newhistogram "", "{plots_dir}/{object}_by_{criterion}_{side}.dat" using 2:xticlabels(1) notitle col, \ newhistogram "", "{plots_dir}/{object}_by_{criterion}_{side}_{record}.dat" using 2:xticlabels(1) notitle col, \
newhistogram "", "{plots_dir}/{object}_by_{criterion}_{side}.dat" using 3:xticlabels(1) title col{cluster} newhistogram "", "{plots_dir}/{object}_by_{criterion}_{side}_{record}.dat" using 3:xticlabels(1) title col{cluster}
""".format(plots_dir=PLOTS_DIR, cluster=cluster, **kwargs)) """.format(plots_dir=PLOTS_DIR, cluster=cluster, **kwargs))
f.close() f.close()
os.system("gnuplot {plots_dir}/{object}_by_{criterion}_{side}.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 make_plot(records, exp, criterion, side, obj): def make_plot(logs, exp, criterion, side, obj, record):
f = open(f"/dev/shm/plots/{obj}_by_{criterion}_{side}.dat", "w") f = open(f"/dev/shm/plots/{obj}_by_{criterion}_{side}_{record}.dat", "w")
ciphers = {} ciphers = {}
impls = [] impls = []
plain_line = None plain_line = None
idle_val = None idle_val = None
for record in records: for log in logs:
if record["exp"] == "idle": if log["exp"] == "idle":
idle_val = float(record[COL[obj]]) / float(record["time"]) idle_val = float(log[COL[obj]]) / float(log["time"])
if record["exp"] != exp: if log["exp"] != exp or log["record"] != record:
continue continue
if record["setup"] == "none": if log["setup"] == "none":
plain_line = "plain {}".format(float(record[COL[obj]]) - idle_val * float(record["time"])) plain_line = "plain {}".format(float(log[COL[obj]]) - idle_val * float(log["time"]))
if plain_line == None: if plain_line == None:
return return
for record in records: for log in logs:
if record["exp"] != exp: if log["exp"] != exp or log["record"] != record:
continue continue
elif record["setup"] == side: elif log["setup"] == side:
if record[COL[criterion]] not in ciphers: if log[COL[criterion]] not in ciphers:
ciphers[record[COL[criterion]]] = {} ciphers[log[COL[criterion]]] = {}
ciphers[record[COL[criterion]]][record["impl"]] = float(record[COL[obj]]) - idle_val * float(record["time"]) ciphers[log[COL[criterion]]][log["impl"]] = float(log[COL[obj]]) - idle_val * float(log["time"])
if record["impl"] not in impls: if log["impl"] not in impls:
impls.append(record["impl"]) impls.append(log["impl"])
impls.sort() impls.sort()
f.write("{} none {}\n".format(criterion, " ".join(impls))) f.write("{} none {}\n".format(criterion, " ".join(impls)))
f.write(plain_line+" -"*len(impls)+"\n") f.write(plain_line+" -"*len(impls)+"\n")
for cipher in ciphers: for cipher in ciphers:
f.write("{}({}) - {}\n".format( f.write("{}({}) - {}\n".format(
ALG_LABEL[cipher], ALG_LABEL[cipher],
VER_LABEL[record["cipher"]], VER_LABEL[log["cipher"]],
" ".join([ " ".join([
str(ciphers[cipher][impl]) str(ciphers[cipher][impl])
for impl in impls for impl in impls
]), ]),
)) ))
f.close() 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)) 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)
if __name__ == "__main__": if __name__ == "__main__":
logfile_name = sys.argv[1] logfile_name = sys.argv[1]
@ -133,20 +133,24 @@ if __name__ == "__main__":
colnames = lines[0].removesuffix("\n").split(" ") colnames = lines[0].removesuffix("\n").split(" ")
records = [] logs = []
records = {}
for line in lines[1:]: for line in lines[1:]:
cols = line.removesuffix("\n").split(" ") cols = line.removesuffix("\n").split(" ")
record = {} log = {}
for col in range(len(cols)): for col in range(len(cols)):
record[colnames[col]] = cols[col] log[colnames[col]] = cols[col]
records.append(record) if log["record"] != "-":
records[log["record"]] = ()
logs.append(log)
os.makedirs("/dev/shm/plots", exist_ok=True) os.makedirs("/dev/shm/plots", exist_ok=True)
for side in ["client", "server"]: for side in ["client", "server"]:
make_plot(records, "impl-cipher-ver", "cipher", side, "cpu") for record in records:
make_plot(records, "impl-cipher-ver", "cipher", side, "energy") make_plot(logs, "impl-cipher-ver", "cipher", side, "cpu", record)
make_plot(records, "impl-cert-ver", "cert", side, "cpu") make_plot(logs, "impl-cipher-ver", "cipher", side, "energy", record)
make_plot(records, "impl-cert-ver", "cert", side, "energy") make_plot(logs, "impl-cert-ver", "cert", side, "cpu", record)
make_plot(records, "impl-kex-ver", "kex", side, "cpu") make_plot(logs, "impl-cert-ver", "cert", side, "energy", record)
make_plot(records, "impl-kex-ver", "kex", side, "energy") make_plot(logs, "impl-kex-ver", "kex", side, "cpu", record)
make_plot(logs, "impl-kex-ver", "kex", side, "energy", record)

61
profile.py Normal file
View file

@ -0,0 +1,61 @@
# Extract some data from profile flamegraphs produced by cargo flamegraph
import re, sys
FUNCTIONS = {
"rustls::record_layer::RecordLayer::decrypt_incoming": "decrypt",
"<[a-zA-Z0-9_:]+ as rustls::crypto::cipher::MessageEncrypter>::encrypt": "encrypt",
"<[a-zA-Z0-9_:]+ as rustls::crypto::tls13::Hkdf>::expander_for_okm": "hkdf",
"<[a-zA-Z0-9_:]+ as rustls::crypto::SecureRandom>::fill": "rand",
"<[a-zA-Z0-9_:]+ as rustls::crypto::SupportedKxGroup>::start": "kx",
"<[a-zA-Z0-9_:]+ as rustls::crypto::hash::Hash>::start": "hash",
"<[a-zA-Z0-9_:]+ as rustls::crypto::hash::Context>::finish": "hash",
"<[a-zA-Z0-9_:]+ as rustls::crypto::hash::Context>::update": "hash",
"<[a-zA-Z0-9_:]+ as rustls::crypto::hash::Context>::fork_finish": "hash",
"<[a-zA-Z0-9_:]+ as rustls::crypto::tls13::Hkdf>::extract_from_secret": "hkdf",
"<[a-zA-Z0-9_:]+ as rustls::crypto::ActiveKeyExchange>::complete": "kx",
"<[a-zA-Z0-9_:]+ as rustls::crypto::tls13::HkdfExpander>::hash_len": "hkdf",
"<[a-zA-Z0-9_:]+ as rustls::crypto::tls13::HkdfExpander>::expand_slice": "hkdf",
"<[a-zA-Z0-9_:]+ as rustls::crypto::tls13::Hkdf>::extract_from_secret": "hkdf",
"<[a-zA-Z0-9_:]+ as rustls::crypto::tls13::Hkdf>::hmac_sign": "hkdf",
"ring::hkdf::fill_okm": "hkdf",
"aws_lc_0_32_2_HKDF": "hkdf",
"rustls_openssl::tls13::<impl rustls::crypto::cipher::Tls13AeadAlgorithm for rustls_openssl::aead::Algorithm>::encrypter": "encrypt",
"rustls::crypto::aws_lc_rs::tls13::AeadAlgorithm::encrypter": "encrypt",
"rustls::crypto::aws_lc_rs::tls13::AeadAlgorithm::decrypter": "decrypt",
}
def extract_function(data, name):
r = re.compile(name.replace("<", "&lt;").replace(">", "&gt;") + " \\(([0-9,]+) samples, ([0-9.]+)%\\)")
samples = 0
percents = 0.0
for match in r.finditer(data):
samples += int(match.group(1).replace(",", ""))
percents += float(match.group(2))
return (samples, percents)
if __name__ == "__main__":
filename = sys.argv[1]
f = open(filename, "r")
c = f.read()
results = {}
for function in FUNCTIONS:
samples, percents = extract_function(c, function)
title = FUNCTIONS[function]
if title not in results:
results[title] = [0, 0.0]
results[title][0] += samples
results[title][1] += percents
for title in results:
print(title, results[title])