0-RTT
This commit is contained in:
parent
f5145f80ea
commit
c9dc2a306e
8 changed files with 230 additions and 134 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
|||
netreplay
|
||||
__pycache__
|
||||
rpxy_*
|
||||
records/
|
||||
|
|
|
|||
57
README.md
57
README.md
|
|
@ -136,6 +136,7 @@ sudo make install
|
|||
* OpenSSL
|
||||
* ML-KEM
|
||||
* https://www.semanticscholar.org/paper/Energy-Profiling-and-Comparison-of-TLS-Protocols-Gatram-Reddy/9c061fe57a0008574b85919bc70fc803c6e66f06
|
||||
* Energy Profiling and Comparison of TLS Protocols for Embedded Devices: Experimental Study
|
||||
* 2024
|
||||
* RaspberryPi
|
||||
* TLS PQ, TLS KEM, TLS
|
||||
|
|
@ -185,6 +186,15 @@ sudo make install
|
|||
* https://hal.science/hal-04197885/document
|
||||
* Empreinte carbone de la transmission de données sur le backbone RENATER
|
||||
* 2021
|
||||
* https://ieeexplore.ieee.org/document/10971851/
|
||||
* Optimizing TLS/SSL for IoT Devices: Performance Enhancements and Security Considerations
|
||||
* 2024
|
||||
* https://ieeexplore.ieee.org/document/10060762/
|
||||
* Performance Evaluation of Quantum-Resistant TLS for Consumer IoT Devices
|
||||
* 2023
|
||||
*
|
||||
* Evaluating the Energy Profile of Tasks Managed by Build Automation Tools in Continuous Integration Workflows: The Case of Apache Maven and Gradle
|
||||
* 2025
|
||||
|
||||
## Sources
|
||||
|
||||
|
|
@ -207,29 +217,21 @@ firefox -P tlsbench
|
|||
|
||||
In settings, disable DNS security.
|
||||
|
||||
In `about:config`, set `devtools.chrome.enabled` to `true`.
|
||||
In `about:config`, set:
|
||||
* `devtools.chrome.enabled` to `true`
|
||||
* `network.dns.forceResolve` to `127.0.0.1`
|
||||
|
||||
In the `about:config` tab, open the console, execute this script to override DNS for the selected names, and redirect them to localhost:
|
||||
|
||||
```js
|
||||
const gOverride = Cc["@mozilla.org/network/native-dns-override;1"].getService(Ci.nsINativeDNSResolverOverride);
|
||||
gOverride.clearOverrides();
|
||||
var names = [
|
||||
"apple.com", "www.apple.com",
|
||||
"yt3.ggpht.com",
|
||||
"accounts.google.com", "www.google.com",
|
||||
"fonts.gstatic.com", "www.gstatic.com",
|
||||
"mzstatic.com",
|
||||
"wikimedia.org", "intake-analytics.wikimedia.org", "meta.wikimedia.org", "upload.wikimedia.org",
|
||||
"wikipedia.org", "fr.wikipedia.org",
|
||||
"youtube.com", "www.youtube.com",
|
||||
"i.ytimg.com"
|
||||
];
|
||||
for(var i in names) {
|
||||
gOverride.addIPOverride(names[i], "127.0.0.1");
|
||||
}
|
||||
Run the shell commands:
|
||||
|
||||
```bash
|
||||
python exp.py make debug -c
|
||||
python exp.py update-certs debug
|
||||
```
|
||||
|
||||
In Firefox, go to security settings, Certificates, import `/dev/shm/exp/certs/prime256v1/ca.crt` and trust it for identifying websites.
|
||||
|
||||
Stop anything running on ports 80 or 443.
|
||||
|
||||
Start the record proxy:
|
||||
|
|
@ -325,7 +327,13 @@ Install OpenSSL with debug symbols:
|
|||
/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-zlib enable-ec_nistp_64_gcc_128 linux-x86_64
|
||||
```
|
||||
|
||||
Backup your system's `libcrypto.so` and `libssl.so` and replace them with the new ones.
|
||||
To build rpxy with this openssl:
|
||||
|
||||
```bash
|
||||
OPENSSL_LIB_DIR=/home/pi/reps/openssl-openssl-3.6.0/ OPENSSL_DIR=/home/pi/reps/openssl-openssl-3.6.0/ cargo build --release
|
||||
```
|
||||
|
||||
Or: 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.
|
||||
|
||||
Authorize non-root users to use perf:
|
||||
|
|
@ -372,3 +380,14 @@ Get the most used domains here https://www.akamai.com/fr/security-research/akara
|
|||
python crawler.py crawl /dev/shm/top1K.csv
|
||||
python crawler.py stat /dev/shm/crawl.json
|
||||
```
|
||||
|
||||
## 0-RTT
|
||||
|
||||
```bash
|
||||
echo "hello world" > /dev/shm/ed
|
||||
openssl s_server -port 8000 -cert /dev/shm/exp/certs/prime256v1/wikipedia.org.crt -key /dev/shm/exp/certs/prime256v1/wikipedia.org.key -early_data
|
||||
# First req, without early data
|
||||
echo | openssl s_client -no-interactive -keylogfile /dev/shm/client.txt -sess_out sessions 127.0.0.1:8000
|
||||
# Second req, using 0-RTT for early data
|
||||
echo | openssl s_client -no-interactive -early_data /dev/shm/ed -keylogfile /dev/shm/client.txt -sess_in sessions 127.0.0.1:8000
|
||||
```
|
||||
|
|
|
|||
130
exp.py
130
exp.py
|
|
@ -2,6 +2,31 @@
|
|||
import os, sys, subprocess
|
||||
|
||||
CONFIGS = {
|
||||
"debug": {
|
||||
"experiments": [
|
||||
"zrtt",
|
||||
],
|
||||
"setups": [
|
||||
#"none-local",
|
||||
"client-local",
|
||||
#"server-local",
|
||||
],
|
||||
"records": [
|
||||
{ "filename": "youtube", "repeat": 1 },
|
||||
],
|
||||
"repodir": "/home/tuxmain/reps/tlsbench",
|
||||
"expdir": "/dev/shm/exp",
|
||||
"log_backup_dir": "/home/tuxmain",
|
||||
"p2_hostname": "localhost",
|
||||
"p2_addr": "127.0.0.1",
|
||||
"p2_repodir": "/home/tuxmain/reps/tlsbench",
|
||||
"wattmeter": False,
|
||||
"perf": False,
|
||||
"perf_dir": "/home/tuxmain/.cache/exp",
|
||||
"p3_suffix": ".localhost",
|
||||
"p3_port_plain": 8080,
|
||||
"p3_port_tls": 8443,
|
||||
},
|
||||
# i7-4790 local
|
||||
"local": {
|
||||
"experiments": [
|
||||
|
|
@ -33,9 +58,10 @@ CONFIGS = {
|
|||
# i7-4790 -> pi3
|
||||
"pi3": {
|
||||
"experiments": [
|
||||
#"impl-cipher-ver",
|
||||
"impl-cipher-ver",
|
||||
"impl-cert-ver",
|
||||
#"impl-kex-ver",
|
||||
"impl-kex-ver",
|
||||
"zrtt"
|
||||
],
|
||||
"setups": [
|
||||
"none",
|
||||
|
|
@ -43,7 +69,7 @@ CONFIGS = {
|
|||
"server",
|
||||
],
|
||||
"records": [
|
||||
{ "filename": "youtube", "repeat": 100 },
|
||||
{ "filename": "youtube", "repeat": 1 },
|
||||
],
|
||||
"repodir": "/home/tuxmain/reps/tlsbench",
|
||||
"expdir": "/dev/shm/exp",
|
||||
|
|
@ -58,7 +84,7 @@ CONFIGS = {
|
|||
"p3_suffix": "",
|
||||
"p3_port_plain": 80,
|
||||
"p3_port_tls": 443,
|
||||
"idle": "idle - - - - - - 600.000081539154 0.0 896 4792 0.5399999999999991 -",
|
||||
"idle": "idle - - - - - - - 600.000081539154 0.0 896 4792 0.5399999999999991 -",
|
||||
},
|
||||
"pi3-local": {
|
||||
"experiments": [
|
||||
|
|
@ -116,7 +142,7 @@ CONFIGS = {
|
|||
"p3_suffix": "",
|
||||
"p3_port_plain": 80,
|
||||
"p3_port_tls": 443,
|
||||
"idle": "idle - - - - - - 600.0001013278961 0.0 735 4942 1.7759999999999962 -",
|
||||
"idle": "idle - - - - - - - 600.0001013278961 0.0 735 4942 1.7759999999999962 -",
|
||||
},
|
||||
"core2-local": {
|
||||
"experiments": [
|
||||
|
|
@ -147,6 +173,9 @@ CONFIGS = {
|
|||
},
|
||||
}
|
||||
|
||||
# Wildcard subdomains are used only for certificates.
|
||||
# This is useful for recording, because some domains may be unknown at that time.
|
||||
# Proxy config ignores them so individual subdomains must be added as well for replaying.
|
||||
DOMAINS_ = [
|
||||
# Apple
|
||||
"apple.com", "www.apple.com", "graffiti-tags.apple.com", "securemetrics.apple.com",
|
||||
|
|
@ -154,13 +183,38 @@ DOMAINS_ = [
|
|||
"mzstatic.com", "is1-ssl.mzstatic.com",
|
||||
# Youtube video
|
||||
"youtube.com", "www.youtube.com",
|
||||
"*.ytimg.com",
|
||||
"i.ytimg.com",
|
||||
"i1.ytimg.com",
|
||||
"i9.ytimg.com",
|
||||
"fonts.gstatic.com", "www.gstatic.com",
|
||||
"www.google.com", "accounts.google.com",
|
||||
"yt3.ggpht.com",
|
||||
"rr1---sn-gxo5uxg-jqbl.googlevideo.com",
|
||||
"rr2---sn-gxo5uxg-jqbl.googlevideo.com",
|
||||
"rr4---sn-q4fl6nds.googlevideo.com",
|
||||
"*.googlevideo.com",
|
||||
"rr3---sn-gx1v2vax-atne.googlevideo.com",
|
||||
"rr4---sn-hgn7yn7l.googlevideo.com",
|
||||
"rr3---sn-gx1v2vax-atne.googlevideo.com",
|
||||
"rr2---sn-gx1v2vax-atne.googlevideo.com",
|
||||
"rr1---sn-hgn7yn7l.googlevideo.com",
|
||||
"rr4---sn-hgn7yn76.googlevideo.com",
|
||||
"rr3---sn-hgn7rn7r.googlevideo.com",
|
||||
"rr1---sn-hgn7rn7y.googlevideo.com",
|
||||
"rr1---sn-hgn7rnee.googlevideo.com",
|
||||
"rr2---sn-aigl6ney.googlevideo.com",
|
||||
"rr3---sn-q4fzenee.googlevideo.com",
|
||||
"googleads.g.doubleclick.net",
|
||||
"static.doubleclick.net",
|
||||
"ad.doubleclick.net",
|
||||
"yt3.googleusercontent.com",
|
||||
"suggestqueries-clients6.youtube.com",
|
||||
"pagead2.googlesyndication.com",
|
||||
"tpc.googlesyndication.com",
|
||||
"encrypted-tbn0.gstatic.com",
|
||||
"encrypted-tbn1.gstatic.com",
|
||||
"encrypted-tbn2.gstatic.com",
|
||||
"encrypted-tbn3.gstatic.com",
|
||||
"fonts.googleapis.com",
|
||||
"consent.youtube.com",
|
||||
# Amazon
|
||||
"amazon.com", "www.amazon.com",
|
||||
# Wikipedia article
|
||||
|
|
@ -182,7 +236,7 @@ IMPLS = [
|
|||
"boring", # Google's fork of OpenSSL used in Chrome and Android
|
||||
"openssl", # widely used
|
||||
"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)
|
||||
]
|
||||
# Symmetric ciphers
|
||||
|
|
@ -223,6 +277,7 @@ EXPERIMENTS = {
|
|||
],
|
||||
"kexes": ["X25519"],
|
||||
"cert": ["prime256v1"],
|
||||
"earlydata": ["0"],
|
||||
},
|
||||
# Compare signatures among implementations and TLS versions
|
||||
"impl-cert-ver": {
|
||||
|
|
@ -239,6 +294,7 @@ EXPERIMENTS = {
|
|||
"rsa3072",
|
||||
"rsa4096",
|
||||
],
|
||||
"earlydata": ["0"],
|
||||
},
|
||||
# Compare key exchange groups among implementations and TLS versions
|
||||
"impl-kex-ver": {
|
||||
|
|
@ -256,6 +312,25 @@ EXPERIMENTS = {
|
|||
"MLKEM768",
|
||||
],
|
||||
"cert": ["prime256v1"],
|
||||
"earlydata": ["0"],
|
||||
},
|
||||
# Compare 0-RTT with no early data
|
||||
"zrtt": {
|
||||
"impls": [
|
||||
"aws_lc",
|
||||
#"ring"
|
||||
],
|
||||
"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"],
|
||||
"cert": ["prime256v1"],
|
||||
"earlydata": ["0", "1"],
|
||||
},
|
||||
"debug": {
|
||||
"impls": IMPLS,
|
||||
|
|
@ -265,6 +340,7 @@ EXPERIMENTS = {
|
|||
],
|
||||
"kexes": ["X25519"],
|
||||
"cert": ["prime256v1"],
|
||||
"earlydata": ["0"],
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -283,15 +359,6 @@ for domain in DOMAINS_:
|
|||
if not domain in DOMAINS:
|
||||
DOMAINS.append(domain)
|
||||
|
||||
# JS to redirect the target domains to local (bypass DNS without altering system's config or webpages or packets)
|
||||
SCRIPT_FIREFOX_HOSTS = """const gOverride = Cc["@mozilla.org/network/native-dns-override;1"].getService(Ci.nsINativeDNSResolverOverride);
|
||||
gOverride.clearOverrides();
|
||||
var names = """+str(DOMAINS)+""";
|
||||
for(var i in names) {
|
||||
gOverride.addIPOverride(names[i], "127.0.0.1");
|
||||
}
|
||||
"""
|
||||
|
||||
for config in CONFIGS:
|
||||
CONFIGS[config]["name"] = config
|
||||
|
||||
|
|
@ -502,6 +569,8 @@ def make_rpxy_config(outdir, domains, cryptodir, config_name, p3_suffix, p3_port
|
|||
f = open(outdir+config_name+".toml", "w")
|
||||
f.write("listen_port = {}\nlisten_port_tls = {}\n".format(rpxy_config["listen_http"], rpxy_config["listen_https"]))
|
||||
for domain in domains:
|
||||
if "*" in domain:
|
||||
continue
|
||||
app = domain.replace(".", "_")
|
||||
root = get_domain_root(domain)
|
||||
f.write(rpxy_config["app"].format(
|
||||
|
|
@ -545,16 +614,18 @@ def choose_impl(expdir, p, impl):
|
|||
expdir += "/"
|
||||
os.symlink(os.getcwd()+"/rpxy_rustls_"+impl, expdir+str(p)+"_rpxy", False)
|
||||
|
||||
def run_netreplay(expdir, repodir, record, p2_addr, p2_port, listen_port, tls_mode, only_record=None, ciphers=None, kexes=None):
|
||||
def run_netreplay(expdir, repodir, record, p2_addr, p2_port, listen_port, tls_mode, only_record=None, ciphers=None, kexes=None, earlydata="0"):
|
||||
if expdir[-1] != "/":
|
||||
expdir += "/"
|
||||
repodir = repodir.removesuffix("/")
|
||||
env = {"RUST_LOG": "warning"}
|
||||
env = {"RUST_LOG": "warning", "EARLYDATA": earlydata}
|
||||
if ciphers:
|
||||
env["CIPHERS"] = ciphers
|
||||
if kexes:
|
||||
env["KEXES"] = kexes
|
||||
cmd = [repodir+"/netreplay", repodir+"/records/"+record["filename"], "play", p2_addr, str(p2_port), str(listen_port), expdir+"current_certs", tls_mode, "-r", str(record["repeat"])]
|
||||
cmd = [repodir+"/netreplay", repodir+"/records/"+record["filename"], "play", p2_addr, str(p2_port), str(listen_port), expdir+"current_certs", tls_mode, "-r", str(record["repeat"]),
|
||||
#"--record", "21"
|
||||
]
|
||||
if only_record != None:
|
||||
cmd += ["--record", only_record]
|
||||
print(" ".join(cmd))
|
||||
|
|
@ -631,7 +702,7 @@ def run_exp(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 prof\n")
|
||||
logfile.write("exp impl alg kex cipher ed setup record time cpu bytes_in bytes_out Wh prof\n")
|
||||
logfile.close()
|
||||
|
||||
perf_dir = ""
|
||||
|
|
@ -664,7 +735,7 @@ def run_exp(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:
|
||||
|
|
@ -692,6 +763,7 @@ def run_exp(config, only_record=None, idle=False):
|
|||
ssh_run(ssh, f"python {p2_path}/exp.py cert {config_name} {alg}")
|
||||
for kex in exp["kexes"]:
|
||||
for cipher in exp["ciphers"]:
|
||||
for earlydata in exp["earlydata"]:
|
||||
if not alg_filter(kex, alg, cipher, impl):
|
||||
continue
|
||||
for setup in config["setups"]:
|
||||
|
|
@ -699,9 +771,9 @@ def run_exp(config, only_record=None, idle=False):
|
|||
continue
|
||||
setupdir = expdir+"setups/"+setup
|
||||
for record in config["records"]:
|
||||
print(f"EXPERIMENT {expname}: {impl} {alg} {kex} {cipher} {setup}")
|
||||
print(f"EXPERIMENT {expname}: {impl} {alg} {kex} {cipher} ED={earlydata} {setup}")
|
||||
p2_rpxy_config = SETUPS[setup]["rpxy_config"]
|
||||
vars = {"CIPHERS": cipher, "KEXES": kex, "RUST_LOG": "warning"}
|
||||
vars = {"CIPHERS": cipher, "KEXES": kex, "RUST_LOG": "warning", "EARLYDATA": earlydata}
|
||||
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)
|
||||
|
|
@ -732,7 +804,8 @@ def run_exp(config, only_record=None, idle=False):
|
|||
SETUPS[setup]["netreplay_tls_mode"],
|
||||
only_record=only_record,
|
||||
ciphers=cipher,
|
||||
kexes=kex
|
||||
kexes=kex,
|
||||
earlydata=earlydata,
|
||||
)
|
||||
|
||||
# TODO detect when netreplay has finished
|
||||
|
|
@ -781,7 +854,7 @@ def run_exp(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} {prof_filename}\n")
|
||||
logfile.write(f"{expname} {impl} {alg} {kex} {cipher} {earlydata} {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:
|
||||
|
|
@ -841,7 +914,6 @@ if __name__ == "__main__":
|
|||
send <config> Send configs and certs to p2
|
||||
update-certs <config> Update system's certs
|
||||
run <config> Run experiment
|
||||
script Print Firefox script to override DNS
|
||||
|
||||
Make options:
|
||||
-c Make CA cert (otherwise use already existing one)
|
||||
|
|
@ -907,8 +979,6 @@ Run options:
|
|||
from yoctopuce.yocto_power import *
|
||||
|
||||
run_exp(config, only_record=getargv("--record", None), idle="--idle" in sys.argv)
|
||||
elif opt == "script":
|
||||
print(SCRIPT_FIREFOX_HOSTS)
|
||||
else:
|
||||
print("Unknown command, use help for help")
|
||||
exit(1)
|
||||
|
|
|
|||
10
plots.py
10
plots.py
|
|
@ -20,6 +20,8 @@ ALG_LABEL = {
|
|||
"X25519MLKEM768": "x25519mlkem",
|
||||
"SECP256R1MLKEM768": "p256mlkem",
|
||||
"MLKEM768": "mlkem",
|
||||
"0": "Off",
|
||||
"1": "On",
|
||||
}
|
||||
|
||||
# Nice labels for TLS versions using ciphers
|
||||
|
|
@ -44,7 +46,8 @@ COL = {
|
|||
"energy": "Wh",
|
||||
"cipher": "cipher",
|
||||
"cert": "alg",
|
||||
"kex": "kex"
|
||||
"kex": "kex",
|
||||
"ed": "ed",
|
||||
}
|
||||
# Physical units by object
|
||||
UNIT = {
|
||||
|
|
@ -56,7 +59,8 @@ UNIT = {
|
|||
CRITERION_TITLE = {
|
||||
"cipher": "cipher",
|
||||
"cert": "signature algorithm",
|
||||
"kex": "key exchange"
|
||||
"kex": "key exchange",
|
||||
"ed": "0-RTT",
|
||||
}
|
||||
|
||||
def impl_title(impl):
|
||||
|
|
@ -335,6 +339,8 @@ if __name__ == "__main__":
|
|||
make_log_plot(logs, "impl-cert-ver", "cert", side, "energy", record, machine=machine)
|
||||
make_log_plot(logs, "impl-kex-ver", "kex", side, "cpu", record, machine=machine)
|
||||
make_log_plot(logs, "impl-kex-ver", "kex", side, "energy", record, machine=machine)
|
||||
make_log_plot(logs, "zrtt", "ed", side, "cpu", record, machine=machine)
|
||||
make_log_plot(logs, "zrtt", "ed", side, "energy", record, machine=machine)
|
||||
elif cmd == "prof":
|
||||
for side in ["client-local", "server-local"]:
|
||||
for record in records:
|
||||
|
|
|
|||
BIN
records/apple
BIN
records/apple
Binary file not shown.
BIN
records/google
BIN
records/google
Binary file not shown.
Binary file not shown.
BIN
records/youtube
BIN
records/youtube
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue