Compare commits

..

No commits in common. "0335dac6d4de72554a634ecccf3e7ecdfa6e1599" and "fb5adf26f1267a5733e83c24769ddd62c5b1e9c4" have entirely different histories.

4 changed files with 159 additions and 384 deletions

View file

@ -107,11 +107,6 @@ 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
@ -151,13 +146,6 @@ 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
@ -215,8 +203,6 @@ 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`:
``` ```
@ -231,49 +217,14 @@ sudo chmod +s /sbin/sa
``` ```
```bash ```bash
python exp.py make pi -c python exp.py make -c
python exp.py send pi python exp.py send
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 pi --idle python exp.py run --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.

345
exp.py
View file

@ -1,55 +1,13 @@
#!/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",
@ -73,11 +31,12 @@ DOMAINS_ = [
# Peertube video # Peertube video
"videos.domainepublic.net", "videos.domainepublic.net",
] ]
WATTMETER = True
RECORDS = [ RECORDS = [
{ "filename": "youtube", "repeat": 2 }, #{ "filename": "youtube", "repeat": 1000 },
#{ "filename": "peertube", "repeat": 10 }, #{ "filename": "peertube", "repeat": 10 },
#{ "filename": "wikipedia", "repeat": 1 }, { "filename": "wikipedia", "repeat": 100 },
#{ "filename": "apple", "repeat": 1000 }, #{ "filename": "apple", "repeat": 1000 },
#{ "filename": "google", "repeat": 1000 }, #{ "filename": "google", "repeat": 1000 },
] ]
@ -89,9 +48,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)
] ]
@ -133,7 +92,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,
@ -149,7 +108,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,
@ -160,16 +119,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 = []
@ -292,35 +251,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}{p3_suffix}:{p3_port_plain}" }}], set_host = "{domain}"}}] reverse_proxy = [{{ upstream = [{{ location = "{domain}" }}], 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}{p3_suffix}:{p3_port_plain}" }}], set_host = "{domain}"}}] reverse_proxy = [{{ upstream = [{{ location = "{domain}" }}], 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}{p3_suffix}:{p3_port_plain}" }}], set_host = "{domain}"}}] reverse_proxy = [{{ upstream = [{{ location = "{domain}" }}], 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}{p3_suffix}:{p3_port_tls}", tls = true }}], set_host = "{domain}"}}] reverse_proxy = [{{ upstream = [{{ location = "{domain}", 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}{p3_suffix}:{p3_port_tls}", tls = true }}], set_host = "{domain}"}}] reverse_proxy = [{{ upstream = [{{ location = "{domain}", 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}{p3_suffix}:{p3_port_tls}", tls = true }}], set_host = "{domain}"}}] reverse_proxy = [{{ upstream = [{{ location = "{domain}", tls = true }}], set_host = "{domain}"}}]
""" """
}, },
} }
@ -346,67 +305,36 @@ 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, p3_suffix, p3_port_plain, p3_port_tls): def make_rpxy_config(outdir, domains, cryptodir, config_name):
if outdir[-1] != "/": if outdir[-1] != "/":
outdir += "/" outdir += "/"
if cryptodir[-1] != "/": if cryptodir[-1] != "/":
cryptodir += "/" cryptodir += "/"
rpxy_config = RPXY_CONFIGS[config_name] 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(rpxy_config["listen_http"], rpxy_config["listen_https"])) f.write("listen_port = {}\nlisten_port_tls = {}\n".format(config["listen_http"], 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(rpxy_config["app"].format( f.write(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, p3_suffix, p3_port_plain, p3_port_tls): def make_everything(expdir, domains, make_ca):
os.makedirs(expdir, exist_ok=True) os.makedirs(expdir, exist_ok=True)
if expdir[-1] != "/": if expdir[-1] != "/":
expdir += "/" expdir += "/"
@ -419,7 +347,7 @@ def make_everything(expdir, domains, make_ca, p3_suffix, p3_port_plain, p3_port_
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, p3_suffix, p3_port_plain, p3_port_tls) make_rpxy_config(configdir, domains, cryptodir, config_name)
def choose_cert_alg(expdir, alg): def choose_cert_alg(expdir, alg):
if expdir[-1] != "/": if expdir[-1] != "/":
@ -440,7 +368,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": "warning"} env = {"RUST_LOG": "debug"}
if ciphers: if ciphers:
env["CIPHERS"] = ciphers env["CIPHERS"] = ciphers
if kexes: if kexes:
@ -451,54 +379,31 @@ 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)
# Run with or without SSH # https://stackoverflow.com/questions/8775598/how-to-start-a-background-process-with-nohup-using-fabric
def ssh_run(ssh, cmd, env={}, **kwargs): def runbg(ssh, cmd, vars={}):
if ssh == None: print("[SSH]", cmd)
# As long as there is no argument containing a space (escaped or quoted), we're fine strvars = ""
proc = subprocess.Popen(cmd.split(" "), stdout=subprocess.PIPE, env=env) for var in vars:
proc.wait() strvars += f"export {var}="+vars[var]+" && "
return proc.stdout.read().decode() return ssh.run(f"{strvars}dtach -n `mktemp -u /tmp/dtach.XXXX` {cmd}")
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): def get_cpu_stat(ssh):
res = ssh_run(ssh, "/sbin/sa --list-all-names", hide=True) res = ssh.run("/sbin/sa --list-all-names", hide=True)
for line in res.split("\n"): for line in res.stdout.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(ssh, "cat /proc/net/netstat", hide=True) res = ssh.run("cat /proc/net/netstat", hide=True)
items = res.split("\n")[3].split(" ") items = res.stdout.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(expdir, config, only_record=None, idle=False): def run_exp(ssh, expdir, p2_path, exps, 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 config["wattmeter"]: if 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)
@ -508,15 +413,14 @@ def run_exp(expdir, config, only_record=None, idle=False):
exit(1) exit(1)
sh("killall netreplay") sh("killall netreplay")
for expname in config["experiments"]: for expname in exps:
exp = EXPERIMENTS[expname] exp = exps[expname]
for impl in exp["impls"]: for impl in exp["impls"]:
try: try:
ssh_run(ssh, f"killall rpxy_rustls_{impl}") ssh.run(f"killall rpxy_rustls_{impl}")
except invoke.exceptions.UnexpectedExit as e: except invoke.exceptions.UnexpectedExit as e:
pass pass
timestr = str(int(time.time())) logfile_name = "log-"+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")
@ -527,7 +431,7 @@ def run_exp(expdir, config, 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 config["wattmeter"]: if WATTMETER:
energy = wattmeter.get_meter() energy = wattmeter.get_meter()
start = time.time() start = time.time()
@ -535,7 +439,7 @@ def run_exp(expdir, config, only_record=None, idle=False):
end = time.time() end = time.time()
new_energy = 0 new_energy = 0
if config["wattmeter"]: if 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)
@ -565,42 +469,35 @@ def run_exp(expdir, config, 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}")
run_id = 0 for expname in exps:
for expname in config["experiments"]: exp = exps[expname]
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(ssh, f"python {p2_path}/exp.py cert {alg}") ssh.run(f"python {p2_path}/exp.py cert {alg}")
for setup in config["setups"]: for setup in 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", "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": "debug"}
cmd = f"{p2_path}/rpxy_rustls_{impl} --config {expdir}/configs/{p2_rpxy_config}.toml --log-dir /dev/shm"
ssh_run_bg(ssh, cmd, env=vars) runbg(ssh, f"{p2_path}/rpxy_rustls_{impl} --config {expdir}/configs/{p2_rpxy_config}.toml --log-dir /dev/shm", 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 config["wattmeter"]: if WATTMETER:
energy = wattmeter.get_meter() energy = wattmeter.get_meter()
start = time.time() start = time.time()
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) 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)
# TODO detect when netreplay has finished # TODO detect when netreplay has finished
try: try:
@ -608,11 +505,11 @@ def run_exp(expdir, config, only_record=None, idle=False):
except KeyboardInterrupt: except KeyboardInterrupt:
netreplay.kill() netreplay.kill()
try: try:
ssh_run(ssh, f"killall rpxy_rustls_{impl}") ssh.run(f"killall rpxy_rustls_{impl}")
except invoke.exceptions.UnexpectedExit as e: except invoke.exceptions.UnexpectedExit as e:
pass pass
try: try:
ssh_run(ssh, f"killall dtach") ssh.run(f"killall dtach")
except invoke.exceptions.UnexpectedExit as e: except invoke.exceptions.UnexpectedExit as e:
pass pass
exit(0) exit(0)
@ -620,22 +517,21 @@ def run_exp(expdir, config, only_record=None, idle=False):
#time.sleep(30) #time.sleep(30)
#sh("killall netreplay") #sh("killall netreplay")
try: try:
#ssh_run(ssh, f"rm /dev/shm/access.log /dev/shm/rpxy.log") ssh.run(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(ssh, f"killall rpxy_rustls_{impl}") ssh.run(f"killall rpxy_rustls_{impl}")
except invoke.exceptions.UnexpectedExit as e: except invoke.exceptions.UnexpectedExit as e:
pass pass
try: try:
ssh_run(ssh, f"killall dtach") ssh.run(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 config["wattmeter"]: if 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)
@ -656,7 +552,7 @@ def run_exp(expdir, config, 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 config["wattmeter"]: if WATTMETER:
YAPI.FreeAPI() YAPI.FreeAPI()
def update_certs(): def update_certs():
@ -687,15 +583,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(config): def connect_ssh():
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" in config: if P2_PSW != None:
connect_kwargs["password"] = config["p2_psw"] connect_kwargs["password"] = P2_PSW
ssh = fabric.Connection(config["p2_ssh"], connect_kwargs=connect_kwargs) ssh = fabric.Connection(P2_SSH, connect_kwargs=connect_kwargs)
return ssh return ssh
if __name__ == "__main__": if __name__ == "__main__":
@ -703,37 +599,35 @@ 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 <config> Send configs and certs to p2 send Send configs and certs to p2
update-certs Update system's certs update-certs Update system's certs
run <config> Run experiment run 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),
configs = " ".join([i for i in CONFIGS]), impls=" ".join(IMPLS),
)) ))
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, config["p3_suffix"], config["p3_port_plain"], config["p3_port_tls"]) make_everything(EXPDIR, DOMAINS, make_ca)
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:
@ -741,21 +635,19 @@ 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(config) ssh = connect_ssh()
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 config["experiments"]: for expname in EXPERIMENTS:
exp = config["experiments"][expname] exp = 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(config["setups"])) print("Experiments to make:", exps * len(SETUPS))
exit(0) exit(0)
import time import time
@ -763,12 +655,13 @@ Run options:
import re import re
import fabric import fabric
if config["wattmeter"]: if 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 *
run_exp(EXPDIR, config, only_record=getargv("--record", None), idle="--idle" in sys.argv) ssh = connect_ssh()
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,71 +59,67 @@ 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}_{record}.gnuplot".format(plots_dir=PLOTS_DIR, **kwargs), "w") f = open("{plots_dir}/{object}_by_{criterion}_{side}.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}_{record}.png" set output "{plots_dir}/{object}_by_{criterion}_{side}.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} ({record}, {side} side) ({unit})" set title font "CMU Sans Serif,12" "{object_title} by {criterion_title} ({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 key fixed right top vertical Right noreverse noenhanced autotitle nobox
#set key fixed right top vertical Right noreverse noenhanced autotitle nobox
set key fixed left top vertical Left reverse noenhanced autotitle nobox
set colorbox vertical origin screen 0.9, 0.2 size screen 0.05, 0.6 front noinvert bdefault set colorbox vertical origin screen 0.9, 0.2 size screen 0.05, 0.6 front noinvert bdefault
set xrange [ * : * ] noreverse writeback set xrange [ * : * ] noreverse writeback
set yrange [ 0 : * ]
set grid y lt 1 lw .75 lc "gray"
plot \ plot \
newhistogram "", "{plots_dir}/{object}_by_{criterion}_{side}_{record}.dat" using 2:xticlabels(1) notitle col, \ newhistogram "", "{plots_dir}/{object}_by_{criterion}_{side}.dat" using 2:xticlabels(1) notitle col, \
newhistogram "", "{plots_dir}/{object}_by_{criterion}_{side}_{record}.dat" using 3:xticlabels(1) title col{cluster} newhistogram "", "{plots_dir}/{object}_by_{criterion}_{side}.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}_{record}.gnuplot".format(plots_dir=PLOTS_DIR, **kwargs)) os.system("gnuplot {plots_dir}/{object}_by_{criterion}_{side}.gnuplot".format(plots_dir=PLOTS_DIR, **kwargs))
def make_plot(logs, exp, criterion, side, obj, record): def make_plot(records, exp, criterion, side, obj):
f = open(f"/dev/shm/plots/{obj}_by_{criterion}_{side}_{record}.dat", "w") f = open(f"/dev/shm/plots/{obj}_by_{criterion}_{side}.dat", "w")
ciphers = {} ciphers = {}
impls = [] impls = []
plain_line = None plain_line = None
idle_val = None idle_val = None
for log in logs: for record in records:
if log["exp"] == "idle": if record["exp"] == "idle":
idle_val = float(log[COL[obj]]) / float(log["time"]) idle_val = float(record[COL[obj]]) / float(record["time"])
if log["exp"] != exp or log["record"] != record: if record["exp"] != exp:
continue continue
if log["setup"] == "none": if record["setup"] == "none":
plain_line = "plain {}".format(float(log[COL[obj]]) - idle_val * float(log["time"])) plain_line = "plain {}".format(float(record[COL[obj]]) - idle_val * float(record["time"]))
if plain_line == None: if plain_line == None:
return return
for log in logs: for record in records:
if log["exp"] != exp or log["record"] != record: if record["exp"] != exp:
continue continue
elif log["setup"] == side: elif record["setup"] == side:
if log[COL[criterion]] not in ciphers: if record[COL[criterion]] not in ciphers:
ciphers[log[COL[criterion]]] = {} ciphers[record[COL[criterion]]] = {}
ciphers[log[COL[criterion]]][log["impl"]] = float(log[COL[obj]]) - idle_val * float(log["time"]) ciphers[record[COL[criterion]]][record["impl"]] = float(record[COL[obj]]) - idle_val * float(record["time"])
if log["impl"] not in impls: if record["impl"] not in impls:
impls.append(log["impl"]) impls.append(record["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[log["cipher"]], VER_LABEL[record["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), record=record) 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))
if __name__ == "__main__": if __name__ == "__main__":
logfile_name = sys.argv[1] logfile_name = sys.argv[1]
@ -133,24 +129,20 @@ if __name__ == "__main__":
colnames = lines[0].removesuffix("\n").split(" ") colnames = lines[0].removesuffix("\n").split(" ")
logs = [] records = []
records = {}
for line in lines[1:]: for line in lines[1:]:
cols = line.removesuffix("\n").split(" ") cols = line.removesuffix("\n").split(" ")
log = {} record = {}
for col in range(len(cols)): for col in range(len(cols)):
log[colnames[col]] = cols[col] record[colnames[col]] = cols[col]
if log["record"] != "-": records.append(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"]:
for record in records: make_plot(records, "impl-cipher-ver", "cipher", side, "cpu")
make_plot(logs, "impl-cipher-ver", "cipher", side, "cpu", record) make_plot(records, "impl-cipher-ver", "cipher", side, "energy")
make_plot(logs, "impl-cipher-ver", "cipher", side, "energy", record) make_plot(records, "impl-cert-ver", "cert", side, "cpu")
make_plot(logs, "impl-cert-ver", "cert", side, "cpu", record) make_plot(records, "impl-cert-ver", "cert", side, "energy")
make_plot(logs, "impl-cert-ver", "cert", side, "energy", record) make_plot(records, "impl-kex-ver", "kex", side, "cpu")
make_plot(logs, "impl-kex-ver", "kex", side, "cpu", record) make_plot(records, "impl-kex-ver", "kex", side, "energy")
make_plot(logs, "impl-kex-ver", "kex", side, "energy", record)

View file

@ -1,61 +0,0 @@
# 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])