diff --git a/.gitignore b/.gitignore index 56b09c2..b21a6e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ netreplay __pycache__ rpxy_* +records/ diff --git a/README.md b/README.md index 7341b38..a36deab 100644 --- a/README.md +++ b/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 +``` diff --git a/exp.py b/exp.py index c4d9acf..657940a 100644 --- a/exp.py +++ b/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,54 +763,73 @@ 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"]: - if not alg_filter(kex, alg, cipher, impl): - continue - for setup in config["setups"]: - if SETUPS[setup]["tls_invariant"] and not first_set: + for earlydata in exp["earlydata"]: + if not alg_filter(kex, alg, cipher, impl): continue - setupdir = expdir+"setups/"+setup - for record in config["records"]: - print(f"EXPERIMENT {expname}: {impl} {alg} {kex} {cipher} {setup}") - p2_rpxy_config = SETUPS[setup]["rpxy_config"] - vars = {"CIPHERS": cipher, "KEXES": kex, "RUST_LOG": "warning"} - cmd = f"{p2_path}/rpxy_rustls_{impl} --config {expdir}/configs/{p2_rpxy_config}.toml --log-dir /dev/shm" - #cmd = f"{p2_path}/rpxy_rustls_{impl} --config {expdir}/configs/{p2_rpxy_config}.toml" - ssh_run_bg(ssh, cmd, env=vars) - time.sleep(1) + for setup in config["setups"]: + if SETUPS[setup]["tls_invariant"] and not first_set: + continue + setupdir = expdir+"setups/"+setup + for record in config["records"]: + 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", "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) + time.sleep(1) - prof_filename = "-" - if config["perf"]: - prof_filename = f"{perf_dir}/perf-{timestr}-{run_id}.data" - rpxy_pid = ssh_run(ssh, f"pidof rpxy_rustls_{impl}").removesuffix("\n") - ssh_run_bg(ssh, f"perf record -F 997 --call-graph dwarf,64000 -g -o {prof_filename} -p {rpxy_pid}") - - run_id += 1 - - rpxy_cpu = get_cpu_stat(ssh) - p2_bytes_in, p2_bytes_out = get_net_stat(ssh) - energy = 0 - if config["wattmeter"]: - energy = wattmeter.get_meter() - - start = time.time() - netreplay = run_netreplay( - config["expdir"], - config["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 - try: - netreplay.wait() - except KeyboardInterrupt: - netreplay.kill() + prof_filename = "-" + if config["perf"]: + prof_filename = f"{perf_dir}/perf-{timestr}-{run_id}.data" + rpxy_pid = ssh_run(ssh, f"pidof rpxy_rustls_{impl}").removesuffix("\n") + ssh_run_bg(ssh, f"perf record -F 997 --call-graph dwarf,64000 -g -o {prof_filename} -p {rpxy_pid}") + + run_id += 1 + + rpxy_cpu = get_cpu_stat(ssh) + p2_bytes_in, p2_bytes_out = get_net_stat(ssh) + energy = 0 + if config["wattmeter"]: + energy = wattmeter.get_meter() + + start = time.time() + netreplay = run_netreplay( + config["expdir"], + config["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, + earlydata=earlydata, + ) + + # TODO detect when netreplay has finished + try: + netreplay.wait() + except KeyboardInterrupt: + netreplay.kill() + try: + ssh_run(ssh, f"killall rpxy_rustls_{impl}") + except invoke.exceptions.UnexpectedExit as e: + pass + try: + ssh_run(ssh, f"killall dtach") + except invoke.exceptions.UnexpectedExit as e: + pass + exit(0) + + #time.sleep(30) + #sh("killall netreplay") + try: + #ssh_run(ssh, f"rm /dev/shm/access.log /dev/shm/rpxy.log") + pass + except invoke.exceptions.UnexpectedExit as e: + pass try: ssh_run(ssh, f"killall rpxy_rustls_{impl}") except invoke.exceptions.UnexpectedExit as e: @@ -748,47 +838,30 @@ def run_exp(config, only_record=None, idle=False): ssh_run(ssh, f"killall dtach") except invoke.exceptions.UnexpectedExit as e: pass - exit(0) - - #time.sleep(30) - #sh("killall netreplay") - try: - #ssh_run(ssh, f"rm /dev/shm/access.log /dev/shm/rpxy.log") - pass - except invoke.exceptions.UnexpectedExit as e: - pass - try: - ssh_run(ssh, f"killall rpxy_rustls_{impl}") - except invoke.exceptions.UnexpectedExit as e: - pass - try: - ssh_run(ssh, f"killall dtach") - except invoke.exceptions.UnexpectedExit as e: - pass - end = time.time() - - new_energy = 0 - if config["wattmeter"]: - new_energy = wattmeter.get_meter() - new_p2_bytes_in, new_p2_bytes_out = get_net_stat(ssh) - new_rpxy_cpu = get_cpu_stat(ssh) - record_filename = record["filename"] - rpxy_cpu_diff = new_rpxy_cpu - rpxy_cpu - p2_bytes_in_diff = new_p2_bytes_in - p2_bytes_in - p2_bytes_out_diff = new_p2_bytes_out - p2_bytes_out - energy_diff = new_energy - energy - time_diff = end - start - 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.close() - break - except Exception as e: - print("Can't open log file:", e) - time.sleep(1) - sh(f"cp {logfile_path} {log_backup_dir}/{logfile_name}") - first_set = False + end = time.time() + + new_energy = 0 + if config["wattmeter"]: + new_energy = wattmeter.get_meter() + new_p2_bytes_in, new_p2_bytes_out = get_net_stat(ssh) + new_rpxy_cpu = get_cpu_stat(ssh) + record_filename = record["filename"] + rpxy_cpu_diff = new_rpxy_cpu - rpxy_cpu + p2_bytes_in_diff = new_p2_bytes_in - p2_bytes_in + p2_bytes_out_diff = new_p2_bytes_out - p2_bytes_out + energy_diff = new_energy - energy + time_diff = end - start + while True: + try: + with open(logfile_path, "a") as logfile: + 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: + print("Can't open log file:", e) + time.sleep(1) + sh(f"cp {logfile_path} {log_backup_dir}/{logfile_name}") + first_set = False if config["wattmeter"]: YAPI.FreeAPI() @@ -841,7 +914,6 @@ if __name__ == "__main__": send Send configs and certs to p2 update-certs Update system's certs run 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) diff --git a/plots.py b/plots.py index 736191a..5e7786d 100644 --- a/plots.py +++ b/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: diff --git a/records/apple b/records/apple deleted file mode 100644 index 3ed9121..0000000 Binary files a/records/apple and /dev/null differ diff --git a/records/google b/records/google deleted file mode 100644 index 1d4cb5d..0000000 Binary files a/records/google and /dev/null differ diff --git a/records/wikipedia b/records/wikipedia deleted file mode 100644 index 3d03970..0000000 Binary files a/records/wikipedia and /dev/null differ diff --git a/records/youtube b/records/youtube deleted file mode 100644 index 1b1d8d0..0000000 Binary files a/records/youtube and /dev/null differ