├── .DS_Store ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── central_service ├── .gitignore ├── README.md ├── agent.py ├── centraltrainer │ ├── basic_thread.py │ ├── collector.py │ └── request_handler.py ├── environment │ ├── count_objs.py │ ├── environment.py │ ├── experiences │ │ ├── core │ │ │ ├── __init__.py │ │ │ ├── core.py │ │ │ ├── generate_topo.py │ │ │ └── generate_xp.py │ │ ├── quic_dualfile_offline.py │ │ └── quic_web_browse.py │ ├── generate_topos.py │ └── split_data_test.py ├── evaluation │ ├── move_folders.py │ ├── mplstyle │ ├── plot_data.py │ ├── plot_data_scenarios.py │ ├── plot_pdf.py │ ├── read_batch_run.py │ ├── read_batch_run_rl.py │ ├── read_batch_run_scenario.py │ ├── rl_testing │ │ ├── batch_run_rl_test.py │ │ ├── experiences │ │ │ ├── core │ │ │ │ ├── __init__.py │ │ │ │ ├── core.py │ │ │ │ ├── generate_topo.py │ │ │ │ └── generate_xp.py │ │ │ └── quic_web_browse.py │ │ └── single_run_rl_test.py │ ├── stream_testing │ │ ├── batch_run_stream_test.py │ │ └── experiences │ │ │ ├── core │ │ │ ├── __init__.py │ │ │ ├── core.py │ │ │ ├── generate_topo.py │ │ │ └── generate_xp.py │ │ │ └── quic_web_browse.py │ └── vanilla_testing │ │ ├── batch_run_vanilla_test.py │ │ └── experiences │ │ ├── core │ │ ├── __init__.py │ │ ├── core.py │ │ ├── generate_topo.py │ │ └── generate_xp.py │ │ └── quic_web_browse.py ├── minitopo │ ├── https.py │ ├── mpConfig.py │ ├── mpExperience.py │ ├── mpExperienceHTTPS.py │ ├── mpExperienceNone.py │ ├── mpExperienceQUIC.py │ ├── mpExperienceQUICReqres.py │ ├── mpLinkCharacteristics.py │ ├── mpMininetBuilder.py │ ├── mpMultiInterfaceConfig.py │ ├── mpMultiInterfaceTopo.py │ ├── mpNetemAt.py │ ├── mpParam.py │ ├── mpParamTopo.py │ ├── mpParamXp.py │ ├── mpPerf.py │ ├── mpTopo.py │ ├── mpValidations.py │ ├── mpXpRunner.py │ └── server.pem ├── requirements.txt ├── scripts │ ├── cleanup.sh │ ├── install-libzmq3-dev.txt │ ├── move.sh │ └── run_tensorboard.txt ├── training │ └── a3c.py └── utils │ ├── data_transf.py │ ├── logger.py │ └── queue_ops.py ├── middleware ├── interface.go ├── middleware.go ├── pubsub.go ├── transfer.sh └── zserver.go ├── nn_testing ├── a3c.py └── nn_inference.py └── rlmp-quic ├── --progress ├── Changelog.md ├── LICENSE ├── README.md ├── ackhandler ├── _gen.go ├── ackhandler_suite_test.go ├── interfaces.go ├── packet.go ├── packet_linkedlist.go ├── packet_test.go ├── received_packet_handler.go ├── received_packet_handler_test.go ├── received_packet_history.go ├── received_packet_history_test.go ├── retransmittable.go ├── retransmittable_test.go ├── sent_packet_handler.go ├── sent_packet_handler_test.go ├── stop_waiting_manager.go └── stop_waiting_manager_test.go ├── appveyor.yml ├── benchmark ├── benchmark_suite_test.go └── benchmark_test.go ├── buffer_pool.go ├── buffer_pool_test.go ├── client.go ├── client.sublime-workspace ├── client_test.go ├── codecov.yml ├── congestion ├── bandwidth.go ├── bandwidth_test.go ├── bdw_stats.go ├── clock.go ├── congestion_suite_test.go ├── cubic.go ├── cubic_sender.go ├── cubic_sender_test.go ├── cubic_test.go ├── hybrid_slow_start.go ├── hybrid_slow_start_test.go ├── interface.go ├── olia.go ├── olia_sender.go ├── prr_sender.go ├── prr_sender_test.go ├── rtt_stats.go ├── rtt_stats_test.go └── stats.go ├── conn.go ├── conn_test.go ├── docs ├── quic.png ├── quic.sketch └── quic.svg ├── example ├── Dockerfile ├── Readme.md ├── client │ └── main.go ├── client_benchmarker │ ├── main.go │ └── main_test.go ├── client_benchmarker_cached │ └── main.go ├── client_browse_deptree │ ├── client_browse_deptree │ └── main.go ├── client_browse_wprof │ ├── browse_test.go │ ├── main.go │ └── web_browse ├── echo │ └── echo.go ├── fullchain.pem ├── main.go ├── privkey.pem ├── reqres │ ├── client │ │ └── reqres.go │ └── reqres.go └── server_main_test.go ├── h2quic ├── client.go ├── client_test.go ├── fs_extend.go ├── gzipreader.go ├── h2quic_suite_test.go ├── request.go ├── request_body.go ├── request_body_test.go ├── request_test.go ├── request_writer.go ├── request_writer_test.go ├── response.go ├── response_writer.go ├── response_writer_test.go ├── roundtrip.go ├── roundtrip_test.go ├── server.go └── server_test.go ├── integrationtests ├── chrome │ ├── chrome_suite_test.go │ └── chrome_test.go ├── gquic │ ├── drop_test.go │ ├── gquic_suite_test.go │ ├── integration_test.go │ ├── random_rtt_test.go │ ├── rtt_test.go │ └── server_test.go ├── self │ ├── client_test.go │ ├── handshake_rtt_test.go │ └── self_suite_test.go └── tools │ ├── proxy │ ├── proxy.go │ ├── proxy_suite_test.go │ └── proxy_test.go │ ├── testlog │ └── testlog.go │ └── testserver │ └── server.go ├── interface.go ├── internal ├── crypto │ ├── AEAD.go │ ├── aesgcm12_aead.go │ ├── aesgcm12_aead_test.go │ ├── aesgcm_aead.go │ ├── aesgcm_aead_test.go │ ├── cert_cache.go │ ├── cert_cache_test.go │ ├── cert_chain.go │ ├── cert_chain_test.go │ ├── cert_compression.go │ ├── cert_compression_test.go │ ├── cert_dict.go │ ├── cert_manager.go │ ├── cert_manager_test.go │ ├── cert_sets.go │ ├── chacha20poly1305_aead.go │ ├── chacha20poly1305_aead_test.go │ ├── crypto_suite_test.go │ ├── curve_25519.go │ ├── curve_25519_test.go │ ├── key_derivation.go │ ├── key_derivation_quic_crypto.go │ ├── key_derivation_quic_crypto_test.go │ ├── key_derivation_test.go │ ├── key_exchange.go │ ├── null_aead.go │ ├── null_aead_fnv128a.go │ ├── null_aead_fnv128a_test.go │ ├── null_aead_fnv64a.go │ ├── null_aead_fnv64a_test.go │ ├── null_aead_test.go │ ├── server_proof.go │ ├── server_proof_test.go │ ├── source_address_token.go │ └── source_address_token_test.go ├── flowcontrol │ ├── flow_control_manager.go │ ├── flow_control_manager.go~ │ ├── flow_control_manager_test.go │ ├── flow_controller.go │ ├── flow_controller_test.go │ ├── flowcontrol_suite_test.go │ └── interface.go ├── handshake │ ├── connection_parameters_manager.go │ ├── connection_parameters_manager_test.go │ ├── cookie_generator.go │ ├── cookie_generator_test.go │ ├── crypto_setup_client.go │ ├── crypto_setup_client_test.go │ ├── crypto_setup_server.go │ ├── crypto_setup_server_test.go │ ├── crypto_setup_tls.go │ ├── crypto_setup_tls_test.go │ ├── data_test.go │ ├── ephermal_cache.go │ ├── ephermal_cache_test.go │ ├── handshake_message.go │ ├── handshake_message_test.go │ ├── handshake_suite_test.go │ ├── interface.go │ ├── mint_utils.go │ ├── server_config.go │ ├── server_config_client.go │ ├── server_config_client_test.go │ ├── server_config_test.go │ └── tags.go ├── mocks │ ├── cpm.go │ ├── gen.go │ └── mocks_fc │ │ └── flow_control_manager.go ├── protocol │ ├── encryption_level.go │ ├── encryption_level_test.go │ ├── packet_number.go │ ├── packet_number_test.go │ ├── perspective.go │ ├── protocol.go │ ├── protocol_suite_test.go │ ├── server_parameters.go │ ├── version.go │ └── version_test.go ├── testdata │ └── cert.go ├── utils │ ├── _gen.go │ ├── atomic_bool.go │ ├── atomic_bool_test.go │ ├── byteinterval_linkedlist.go │ ├── byteoder_big_endian_test.go │ ├── byteorder.go │ ├── byteorder_big_endian.go │ ├── byteorder_little_endian.go │ ├── byteorder_little_endian_test.go │ ├── byteorder_test.go │ ├── connection_id.go │ ├── connection_id_test.go │ ├── float16.go │ ├── float16_test.go │ ├── host.go │ ├── host_test.go │ ├── log.go │ ├── log_test.go │ ├── minmax.go │ ├── minmax_test.go │ ├── packet_interval.go │ ├── packetinterval_linkedlist.go │ ├── streamframe_interval.go │ ├── timer.go │ ├── timer_test.go │ └── utils_suite_test.go └── wire │ ├── ack_frame.go │ ├── ack_frame_test.go │ ├── ack_range.go │ ├── add_address_frame.go │ ├── blocked_frame.go │ ├── blocked_frame_test.go │ ├── close_path_frame.go │ ├── close_path_frame_test.go │ ├── connection_close_frame.go │ ├── connection_close_frame_test.go │ ├── frame.go │ ├── goaway_frame.go │ ├── goaway_frame_test.go │ ├── log.go │ ├── log_test.go │ ├── paths_frame.go │ ├── ping_frame.go │ ├── ping_frame_test.go │ ├── public_header.go │ ├── public_header_test.go │ ├── public_reset.go │ ├── public_reset_test.go │ ├── rst_stream_frame.go │ ├── rst_stream_frame_test.go │ ├── stop_waiting_frame.go │ ├── stop_waiting_frame_test.go │ ├── stream_frame.go │ ├── stream_frame_test.go │ ├── version_negotiation.go │ ├── version_negotiation_test.go │ ├── window_update_frame.go │ ├── window_update_frame_test.go │ └── wire_suite_test.go ├── packet_number_generator.go ├── packet_number_generator_test.go ├── packet_packer.go ├── packet_packer_test.go ├── packet_unpacker.go ├── packet_unpacker_test.go ├── path.go ├── path_manager.go ├── pconn_manager.go ├── qerr ├── error_codes.go ├── errorcode_string.go ├── errorcodes_test.go ├── errors_suite_test.go ├── quic_error.go └── quic_error_test.go ├── quic_suite_test.go ├── scheduler.go ├── server.go ├── server_test.go ├── session.go ├── session_test.go ├── stream.go ├── streamToPath.go ├── streamToPath_test.go ├── stream_frame_sorter.go ├── stream_frame_sorter_test.go ├── stream_framer.go ├── stream_framer_test.go ├── stream_test.go ├── stream_tree.go ├── stream_tree_test.go ├── streams_map.go ├── streams_map_test.go ├── transfer.sh ├── zclient.go └── zpublisher.go /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vunetsys/mpquic-rl/f9f9ce7a199c359baf0d5cdee063fbd5186f82c5/.DS_Store -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.linting.pylintEnabled": true, 3 | "python.linting.enabled": true 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 NetMSys 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## DRL Scheduler for Multipath - QUIC 3 | 4 | ### Marios Evangelos Kanakis - MSc Thesis Project 5 | 6 | #### Instalation Guide (WIP) 7 | 8 | Following steps are the initial VM setup for running any kind of experiments with Multipath QUIC protocol, courtesy of the original MP-QUIC authors (put refs;). The environment/experiments setup is from PStream (put refs;), as well as the stream scheduling code (`mpquic-rl`) which is used as the basis for building our RL agent. 9 | 10 | The list should be sufficient to get you started on the VM and the projects' structure but is not __*runnable complete*__. 11 | 12 | __*Information*__ on how to run the agent, should be available under `central_service/README.md`. 13 | 14 | _Steps_: 15 | 16 | 1. Grab the original MP-QUIC VM image from https://multipath-quic.org/2017/12/09/artifacts-available.html 17 | 18 | i. Start up the VM with KEMU 19 | 20 | ii. SSH Into the VM (user: mininet, pass: mininet) 21 | 22 | iii. Place your ssh public keys under ~/.ssh/authorized_keys (from link above this is step 3 from end) 23 | 24 | 2. Copy the contents of 'minitopo/' under VM's '~/git/minitopo/src/' 25 | 26 | 3. Move (as in mv) the original quic-go directory under '\~/go/src/github.com/lucas-clemente/quic-go/' to '~/go/src/github.com/lucas-clemente/mp-quic' 27 | 28 | 4. Under the original '~/go/src/github.com/lucas-clemente/quic-go/' copy all contents of 'mpquic-rl' 29 | 30 | 5. Under '~/go/src/github.com/mkanakis/test-zmq/reply/' place the contents of 'middleware' (This path will change in a subsequent version) 31 | 32 | 6. Create a directory 'client' under '~/go/src/github.com/lucas-clemente/' 33 | 34 | i. Inside the client directory copy the folder 'dependency_graphs' 35 | 36 | 7. Create a directory 'server' under '~/go/src/github.com/lucas-clemente/' 37 | 38 | i. Inside the server directory extract the server.tar.gz with the `missing` and `pages` directories respectively 39 | 40 | 8. Everytime you start up the VM, remember to run the './mount_tmpfs.sh' under '~/' 41 | 42 | 43 | The dependency_graphs and the server files for step 6. and 7. can be downloaded from [here (in the section 'Dependency Graph')]( http://wprof.cs.washington.edu/spdy/tool/). 44 | -------------------------------------------------------------------------------- /central_service/centraltrainer/basic_thread.py: -------------------------------------------------------------------------------- 1 | import threading, queue 2 | import time 3 | import zmq 4 | import logging 5 | import sys 6 | import json 7 | 8 | sys.path.append("") 9 | 10 | 11 | from utils.logger import config_logger 12 | # from logger import config_logger 13 | 14 | class BasicThread(threading.Thread): 15 | ''' 16 | This is BasicThread configuration that will be implemented 17 | in middleware threads (request handler, collector) 18 | ''' 19 | def __init__(self, threadID: int, threadName: str, queue: queue.Queue): 20 | threading.Thread.__init__(self) 21 | 22 | # Threading variables 23 | self._threadID = threadID 24 | self._threadName = threadName 25 | self._queue = queue 26 | self._stoprequest = threading.Event() 27 | 28 | self.__logger = config_logger(name=self._threadName, filepath='./logs/{}.log'.format(self._threadName)) 29 | 30 | def run(self): 31 | self.run() 32 | 33 | def getresponse(self): 34 | while not self._stoprequest.isSet(): 35 | try: 36 | resp = self._queue.get(True, 0.05) 37 | return resp 38 | except queue.Empty: 39 | self.pinfo("Queue is empty") 40 | continue 41 | 42 | def putrequest(self, data): 43 | while not self._stoprequest.isSet(): 44 | try: 45 | self._queue.put(data, True, 0.05) 46 | break 47 | except Exception as ex: 48 | self.pinfo("Cannot put item into Queue") 49 | self.pdebug(ex) 50 | 51 | def pdebug(self, msg): 52 | self.__logger.debug(msg) 53 | 54 | def pinfo(self, msg): 55 | self.__logger.info(msg) 56 | 57 | def stophandler(self): 58 | self._stoprequest.set() 59 | 60 | def close(self): 61 | self.close() 62 | 63 | -------------------------------------------------------------------------------- /central_service/centraltrainer/collector.py: -------------------------------------------------------------------------------- 1 | import threading, queue 2 | import time 3 | import zmq 4 | import logging 5 | import sys 6 | import json 7 | 8 | 9 | from .basic_thread import BasicThread 10 | 11 | class Collector(BasicThread): 12 | ''' RequestHandler will receive requests from MPQUIC 13 | Pass on the requests to the CentralTrainer 14 | Obtain a response (scheduling-related) and send it back 15 | ''' 16 | def __init__(self, threadID: int, threadName: str, queue: queue.Queue, host:str="localhost", port:str="5555"): 17 | super().__init__(threadID, threadName, queue) 18 | 19 | # Stream times 20 | self._all_streams = [] 21 | 22 | # ZMQ context 23 | self.__host = host 24 | self.__port = port 25 | 26 | self.__context = zmq.Context() 27 | self._subscriber = self.__context.socket(zmq.SUB) 28 | self._subscriber.connect("tcp://%s:%s" % (self.__host, self.__port)) 29 | self._subscriber.subscribe("") 30 | 31 | self.__poller = zmq.Poller() 32 | self.__poller.register(self._subscriber, zmq.POLLIN) 33 | 34 | def start(self): 35 | super().start() 36 | 37 | def run(self): 38 | self.pinfo("Run Collector Thread") 39 | 40 | while not self._stoprequest.isSet(): 41 | try: 42 | # Poll for a reply => time is ms 43 | if (self.__poller.poll(timeout=10)): 44 | # Receive request from middleware 45 | try: 46 | data = self._subscriber.recv_multipart(zmq.NOBLOCK) 47 | except Exception as ex: 48 | self.pdebug(ex) 49 | 50 | json_data = json.loads(data[1]) 51 | self.pinfo(json_data) 52 | 53 | self._all_streams.append(json_data) 54 | 55 | # put stream info on the Queue (blocking operation) 56 | self.putrequest(json_data) 57 | except Exception as ex: 58 | self.pdebug(ex) 59 | self.close() 60 | 61 | def close(self): 62 | self._subscriber.close() 63 | self.__context.term() 64 | self.pinfo("RequestHandler closing gracefully...") 65 | 66 | 67 | 68 | if __name__ == "__main__": 69 | tqueue = queue.Queue() 70 | 71 | cthread = Collector(1, 'collector', queue=tqueue) 72 | cthread.start() 73 | cthread.join() -------------------------------------------------------------------------------- /central_service/environment/count_objs.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Simple script file that helps on selecting training "samples" for the environment 3 | ''' 4 | import os 5 | import json 6 | 7 | PATH = './environment/dependency_graphs/' 8 | 9 | lisdir = os.listdir(PATH) 10 | print ("-------------------------------------------") 11 | print ("Number of objects in dependency graph: {}".format(len(lisdir))) 12 | print ("-------------------------------------------") 13 | 14 | 15 | # Create an array that contains dict: "graph", "obj - size" 16 | sizeof_graphs = [] 17 | for item in lisdir: 18 | sp = PATH + item + '/' + item + '.json' 19 | with open(sp, 'r') as fp: 20 | data = json.load(fp) 21 | 22 | sizeof_graphs.append({ 23 | "file": item, 24 | "size": len(data['objs']) 25 | }) 26 | 27 | # Order files by size 28 | order_by_max = sorted(sizeof_graphs, key=lambda k: k['size'] ) 29 | 30 | # Save files ordered by size in ./logs/objs.json 31 | with open('./objs.json', 'w') as fp: 32 | json.dump(order_by_max, fp, ensure_ascii=True, indent=4, sort_keys=True) 33 | 34 | 35 | # count_object value that contains >= 32 objects/stream and less than 100 36 | lar32 = [elem for elem in order_by_max if elem['size'] >= 32 and elem['size'] <= 100] 37 | print ("---------------------------------------------------------") 38 | print ("Objects in dependency graph that contain >= 32 files: {}".format(len(lar32))) 39 | print ("---------------------------------------------------------") 40 | 41 | 42 | # count_total_objects from graphs that contain >= 32 files and less than 100 43 | totalsize32 = 0 44 | for elem in lar32: 45 | totalsize32 += elem['size'] 46 | print ("--------------------------------------------------------------") 47 | print ("Total number of files in graphs that contain >= 32 files: {}".format(totalsize32)) 48 | print ("--------------------------------------------------------------") 49 | 50 | 51 | # print objects >= 32 one by one for manual validation 52 | print ("--------------------------------------------------------------") 53 | for index, elem in enumerate(lar32): 54 | print ("{}, \t {}\t\t, size: {}".format(index+1, elem['file'], elem['size'])) 55 | print ("--------------------------------------------------------------") 56 | 57 | 58 | # save graphs for training 59 | with open('./graphs.json', 'w') as fp: 60 | json.dump(lar32, fp, ensure_ascii=True, indent=4, sort_keys=True) -------------------------------------------------------------------------------- /central_service/environment/experiences/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vunetsys/mpquic-rl/f9f9ce7a199c359baf0d5cdee063fbd5186f82c5/central_service/environment/experiences/core/__init__.py -------------------------------------------------------------------------------- /central_service/environment/split_data_test.py: -------------------------------------------------------------------------------- 1 | # 2 | ''' 3 | Simple script file thats help on spliting our dependency graphs 4 | ''' 5 | from sklearn.model_selection import train_test_split 6 | import json 7 | 8 | with open('./environment/graphs.json', 'r') as fp: 9 | data = json.load(fp) 10 | 11 | x_train, x_test = train_test_split(data, test_size=0.2, random_state=42, shuffle=True) 12 | 13 | # Save results 14 | with open('./environment/train_graphs.json', 'w') as fp: 15 | json.dump(x_train, fp, ensure_ascii=True, indent=4, sort_keys=True) 16 | 17 | with open('./environment/test_graphs.json', 'w') as fp: 18 | json.dump(x_test, fp, ensure_ascii=True, indent=4, sort_keys=True) -------------------------------------------------------------------------------- /central_service/evaluation/move_folders.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | 5 | ROOT_FOLDER = '' 6 | DEST_FOLDER = '' 7 | 8 | NAME_FOLDER_SUFFIX = '' 9 | 10 | # returns folders not client.log files 11 | def retrieve_clogs_folders(folder): 12 | subfolders = [ f.path for f in sorted(os.scandir(folder), key=os.path.getmtime) if f.is_dir() ] 13 | 14 | curated_file_list = [] 15 | for s in subfolders: 16 | if 'https_quic_' in s: 17 | dir_0 = os.listdir(s) 18 | for d in dir_0: 19 | if '0_d' in d: 20 | curated_file_list.append(s) 21 | return curated_file_list 22 | 23 | 24 | 25 | clogs = retrieve_clogs_folders(ROOT_FOLDER) 26 | assert len(clogs) == 200 27 | 28 | for i in range(10): 29 | 30 | d_path = DEST_FOLDER + str(i+1) + NAME_FOLDER_SUFFIX 31 | os.mkdir(d_path) 32 | 33 | sub_list = clogs[(i*20):(i*20)+20] 34 | for s in sub_list: 35 | shutil.move(s, d_path) 36 | -------------------------------------------------------------------------------- /central_service/evaluation/mplstyle: -------------------------------------------------------------------------------- 1 | font.family: serif 2 | axes.labelsize: 8 3 | font.size: 8 4 | legend.fontsize: 8 5 | xtick.labelsize: 6 6 | ytick.labelsize: 6 7 | 8 | -------------------------------------------------------------------------------- /central_service/evaluation/rl_testing/experiences/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vunetsys/mpquic-rl/f9f9ce7a199c359baf0d5cdee063fbd5186f82c5/central_service/evaluation/rl_testing/experiences/core/__init__.py -------------------------------------------------------------------------------- /central_service/evaluation/stream_testing/experiences/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vunetsys/mpquic-rl/f9f9ce7a199c359baf0d5cdee063fbd5186f82c5/central_service/evaluation/stream_testing/experiences/core/__init__.py -------------------------------------------------------------------------------- /central_service/evaluation/vanilla_testing/batch_run_vanilla_test.py: -------------------------------------------------------------------------------- 1 | # global imports 2 | import json 3 | import random 4 | import time 5 | import os 6 | 7 | # local imports 8 | from experiences.quic_web_browse import launchTests 9 | 10 | 11 | # global vars 12 | TOPOS_FP = './../topos.json' 13 | GRAPHS_FP = './../test_graphs.json' 14 | TG_PAIRS = [''] 15 | 16 | 17 | random.seed(42) 18 | 19 | 20 | def getNetemToTuple(topo): 21 | '''in json -> tuple (0 0 loss 1.69%) is stored as [0, 0, loss 1.69%] 22 | revert it back to tuple, otherwise error is produced 23 | ''' 24 | topo[0]['netem'][0] = (topo[0]['netem'][0][0], topo[0]['netem'][0][1], topo[0]['netem'][0][2]) 25 | topo[0]['netem'][1] = (topo[0]['netem'][1][0], topo[0]['netem'][1][1], topo[0]['netem'][1][2]) 26 | return topo 27 | 28 | 29 | def load_or_generate_pairs(file): 30 | if not os.path.isfile(file): 31 | with open(TOPOS_FP, 'r') as fp: 32 | topos = json.load(fp) 33 | with open(GRAPHS_FP, 'r') as fp: 34 | graphs = json.load(fp) 35 | 36 | tuple_list = [] 37 | for i in range(len(topos)): 38 | for j in range(len(graphs)): 39 | tuple_list.append((i, j)) 40 | 41 | pairs = random.sample(tuple_list, 20) 42 | output = [] 43 | for (t, g) in pairs: 44 | pair = { 45 | 'graph': graphs[g], 46 | 'topo': topos[t] 47 | } 48 | output.append(pair) 49 | with open(file, 'w') as fp: 50 | json.dump(output, fp, indent=4) 51 | return output 52 | else: 53 | with open(file, 'r') as fp: 54 | pairs = json.load(fp) 55 | return pairs 56 | 57 | 58 | def main(): 59 | counter = 1 60 | with open('', 'w') as fp: 61 | for p in pairs: 62 | graph = p['graph']['file'] 63 | topo = getNetemToTuple([p['topo']]) 64 | 65 | 66 | fp.write("{},\t{},\t{}\n".format(counter, graph, p['topo'])) 67 | counter += 1 68 | 69 | for _ in range(10): 70 | try: 71 | start = time.time() 72 | launchTests(topo, graph) 73 | end = time.time() 74 | 75 | diff = int(start - end) 76 | print("runtime: {}s".format(diff)) 77 | except Exception as ex: 78 | print (ex) 79 | 80 | if __name__ == "__main__": 81 | main() -------------------------------------------------------------------------------- /central_service/evaluation/vanilla_testing/experiences/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vunetsys/mpquic-rl/f9f9ce7a199c359baf0d5cdee063fbd5186f82c5/central_service/evaluation/vanilla_testing/experiences/core/__init__.py -------------------------------------------------------------------------------- /central_service/minitopo/https.py: -------------------------------------------------------------------------------- 1 | ''' 2 | From : 3 | http://code.activestate.com/recipes/442473-simple-http-server-supporting-ssl-secure-communica/ 4 | 5 | SimpleSecureHTTPServer.py - simple HTTP server supporting SSL. 6 | 7 | - replace fpem with the location of your .pem server file. 8 | - the default port is 443. 9 | 10 | usage: python SimpleSecureHTTPServer.py 11 | ''' 12 | 13 | import socket, os 14 | from SocketServer import BaseServer 15 | from BaseHTTPServer import HTTPServer 16 | from SimpleHTTPServer import SimpleHTTPRequestHandler 17 | from OpenSSL import SSL 18 | 19 | 20 | class SecureHTTPServer(HTTPServer): 21 | def __init__(self, server_address, HandlerClass): 22 | BaseServer.__init__(self, server_address, HandlerClass) 23 | ctx = SSL.Context(SSL.SSLv23_METHOD) 24 | #server.pem's location (containing the server private key and 25 | #the server certificate). 26 | fpem = os.path.dirname(os.path.abspath(__file__)) + "/server.pem" 27 | ctx.use_privatekey_file (fpem) 28 | ctx.use_certificate_file(fpem) 29 | self.socket = SSL.Connection(ctx, socket.socket(self.address_family, 30 | self.socket_type)) 31 | self.server_bind() 32 | self.server_activate() 33 | 34 | def shutdown_request(self,request): 35 | request.shutdown() 36 | 37 | class SecureHTTPRequestHandler(SimpleHTTPRequestHandler): 38 | def setup(self): 39 | self.connection = self.request 40 | self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) 41 | self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) 42 | 43 | 44 | def test(HandlerClass = SecureHTTPRequestHandler, 45 | ServerClass = SecureHTTPServer): 46 | server_address = ('', 443) # (address, port) 47 | httpd = ServerClass(server_address, HandlerClass) 48 | sa = httpd.socket.getsockname() 49 | print("Serving HTTPS on", sa[0], "port", sa[1], "...") 50 | httpd.serve_forever() 51 | 52 | 53 | if __name__ == '__main__': 54 | test() 55 | -------------------------------------------------------------------------------- /central_service/minitopo/mpConfig.py: -------------------------------------------------------------------------------- 1 | class MpConfig: 2 | 3 | PING_OUTPUT = "ping.log" 4 | 5 | def __init__(self, topo, param): 6 | self.topo = topo 7 | self.param = param 8 | 9 | def configureNetwork(self): 10 | print("Configure interfaces....Generic call ?") 11 | self.configureInterfaces() 12 | self.configureRoute() 13 | 14 | def getMidL2RInterface(self, id): 15 | "get Middle link, left to right interface" 16 | pass 17 | 18 | def getMidR2LInterface(self, id): 19 | pass 20 | 21 | def getMidLeftName(self, i): 22 | "get Middle link, left box name" 23 | pass 24 | 25 | def getMidRightName(self, i): 26 | pass 27 | 28 | def configureInterfaces(self): 29 | pass 30 | 31 | def getClientInterfaceCount(self): 32 | raise Exception("To be implemented") 33 | 34 | def interfaceBUPCommand(self, interfaceName): 35 | s = "/home/mininet/git/iproute-mptcp/ip/ip link set dev " + interfaceName + " multipath backup " 36 | print(s) 37 | return s 38 | 39 | def interfaceUpCommand(self, interfaceName, ip, subnet): 40 | s = "ifconfig " + interfaceName + " " + ip + " netmask " + \ 41 | subnet 42 | print(s) 43 | return s 44 | 45 | def addRouteTableCommand(self, fromIP, id): 46 | s = "ip rule add from " + fromIP + " table " + str(id + 1) 47 | print(s) 48 | return s 49 | 50 | def addRouteScopeLinkCommand(self, network, interfaceName, id): 51 | s = "ip route add " + network + " dev " + interfaceName + \ 52 | " scope link table " + str(id + 1) 53 | print(s) 54 | return s 55 | 56 | def addRouteDefaultCommand(self, via, id): 57 | s = "ip route add default via " + via + " table " + str(id + 1) 58 | print(s) 59 | return s 60 | 61 | def addRouteDefaultGlobalCommand(self, via, interfaceName): 62 | s = "ip route add default scope global nexthop via " + via + \ 63 | " dev " + interfaceName 64 | print(s) 65 | return s 66 | 67 | def arpCommand(self, ip, mac): 68 | s = "arp -s " + ip + " " + mac 69 | print(s) 70 | return s 71 | 72 | def addRouteDefaultSimple(self, via): 73 | s = "ip route add default via " + via 74 | print(s) 75 | return s 76 | 77 | def pingCommand(self, fromIP, toIP, n=5): 78 | s = "ping -c " + str(n) + " -I " + fromIP + " " + toIP + \ 79 | " >> " + MpConfig.PING_OUTPUT 80 | print(s) 81 | return s 82 | -------------------------------------------------------------------------------- /central_service/minitopo/mpExperienceNone.py: -------------------------------------------------------------------------------- 1 | from mpExperience import MpExperience 2 | from mpParamXp import MpParamXp 3 | 4 | class MpExperienceNone(MpExperience): 5 | def __init__(self, xpParamFile, mpTopo, mpConfig): 6 | MpExperience.__init__(self, xpParamFile, mpTopo, mpConfig) 7 | MpExperience.classicRun(self) 8 | 9 | def prepare(self): 10 | MpExperience.prepare(self) 11 | 12 | def clean(self): 13 | MpExperience.clean(self) 14 | 15 | def run(self): 16 | self.mpTopo.getCLI() 17 | -------------------------------------------------------------------------------- /central_service/minitopo/mpMininetBuilder.py: -------------------------------------------------------------------------------- 1 | from mininet.topo import Topo 2 | from mininet.net import Mininet 3 | from mininet.link import TCLink 4 | from mininet.cli import CLI 5 | from subprocess import Popen, PIPE 6 | 7 | class MpMininetBuilder(Topo): 8 | def __init__(self): 9 | Topo.__init__( self ) 10 | self.net = None 11 | 12 | def commandTo(self, who, cmd): 13 | return who.cmd(cmd) 14 | 15 | def notNSCommand(self, cmd): 16 | p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) 17 | stdout, stderr = p.communicate() 18 | if stderr: 19 | return "Error" 20 | return stdout 21 | 22 | def startNetwork(self): 23 | self.net = Mininet(topo=self,link=TCLink) 24 | self.net.start() 25 | 26 | def getCLI(self): 27 | if self.net is None: 28 | print("Can not get the CLI") 29 | else: 30 | CLI(self.net) 31 | 32 | def getHost(self, who): 33 | if self.net is None: 34 | print("Network not available....") 35 | raise Exception("Network not ready"); 36 | else: 37 | return self.net.getNodeByName(who) 38 | 39 | def stopNetwork(self): 40 | if self.net is None: 41 | print("Could not stop network... Nothing to stop)") 42 | else: 43 | self.net.stop() 44 | -------------------------------------------------------------------------------- /central_service/minitopo/mpMultiInterfaceTopo.py: -------------------------------------------------------------------------------- 1 | from mpTopo import MpTopo 2 | 3 | class MpMultiInterfaceTopo(MpTopo): 4 | def __init__(self, topoBuilder, parameterFile): 5 | MpTopo.__init__(self,topoBuilder, parameterFile) 6 | print("Hello from topo multi if") 7 | self.client = self.addHost(MpTopo.clientName) 8 | self.server = self.addHost(MpTopo.serverName) 9 | self.router = self.addHost(MpTopo.routerName) 10 | self.switch = [] 11 | for l in self.topoParam.linkCharacteristics: 12 | self.switch.append(self.addOneSwitchPerLink(l)) 13 | self.addLink(self.client,self.switch[-1]) 14 | self.addLink(self.switch[-1],self.router, **l.asDict()) 15 | self.addLink(self.router, self.server) 16 | 17 | def addOneSwitchPerLink(self, link): 18 | return self.addSwitch(MpMultiInterfaceTopo.switchNamePrefix + 19 | str(link.id)) 20 | 21 | def __str__(self): 22 | s = "Simple multiple interface topolgy \n" 23 | i = 0 24 | n = len(self.topoParam.linkCharacteristics) 25 | for p in self.topoParam.linkCharacteristics: 26 | if i == n // 2: 27 | if n % 2 == 0: 28 | s = s + "c r-----s\n" 29 | s = s + "|-----sw-----|\n" 30 | else: 31 | s = s + "c-----sw-----r-----s\n" 32 | else: 33 | s = s + "|-----sw-----|\n" 34 | 35 | i = i + 1 36 | return s 37 | 38 | -------------------------------------------------------------------------------- /central_service/minitopo/mpNetemAt.py: -------------------------------------------------------------------------------- 1 | class MpNetemAt: 2 | def __init__(self, at, cmd): 3 | self.at = at 4 | self.cmd = cmd 5 | self.delta = 0 6 | 7 | def __str__(self): 8 | return "Netem... at " + str(self.at) + "(" + str(self.delta) + \ 9 | ") will be " + self.cmd 10 | -------------------------------------------------------------------------------- /central_service/minitopo/mpParam.py: -------------------------------------------------------------------------------- 1 | 2 | class MpParam: 3 | def __init__(self, paramFile): 4 | self.paramDic = {} 5 | print("Create the param Object") 6 | if paramFile is None: 7 | print("default param...") 8 | else: 9 | self.loadParamFile(paramFile) 10 | 11 | def loadParamFile(self, paramFile): 12 | f = open(paramFile) 13 | i = 0 14 | for l in f: 15 | i = i + 1 16 | if l.startswith("#"): 17 | continue 18 | 19 | tab = l.split(":") 20 | if len(tab) == 2: 21 | k = tab[0] 22 | val = tab[1][:-1] 23 | if k in self.paramDic: 24 | if not isinstance(self.paramDic[k], list): 25 | self.paramDic[k] = [self.paramDic[k]] 26 | self.paramDic[k].append(val) 27 | else: 28 | self.paramDic[k] = val 29 | else: 30 | print("Ignored Line " + str(i)) 31 | print(l), 32 | print("In file " + paramFile) 33 | f.close() 34 | 35 | def getParam(self, key): 36 | if key in self.paramDic: 37 | return self.paramDic[key] 38 | return None 39 | 40 | def __str__(self): 41 | s = self.paramDic.__str__() 42 | return s 43 | -------------------------------------------------------------------------------- /central_service/minitopo/mpParamTopo.py: -------------------------------------------------------------------------------- 1 | from mpLinkCharacteristics import MpLinkCharacteristics 2 | from mpParam import MpParam 3 | from mpNetemAt import MpNetemAt 4 | 5 | class MpParamTopo(MpParam): 6 | LSUBNET = "leftSubnet" 7 | RSUBNET = "rightSubnet" 8 | netemAt = "netemAt_" 9 | changeNetem = "changeNetem" 10 | defaultValue = {} 11 | defaultValue[LSUBNET] = "10.1." 12 | defaultValue[RSUBNET] = "10.2." 13 | defaultValue[changeNetem] = "false" 14 | 15 | def __init__(self, paramFile): 16 | MpParam.__init__(self, paramFile) 17 | self.linkCharacteristics = [] 18 | self.loadLinkCharacteristics() 19 | self.loadNetemAt() 20 | print(self.__str__()) 21 | 22 | def loadNetemAt(self): 23 | if not self.getParam(MpParamTopo.changeNetem) == "yes": 24 | return 25 | for k in sorted(self.paramDic): 26 | if k.startswith(MpParamTopo.netemAt): 27 | i = int(k[len(MpParamTopo.netemAt):]) 28 | val = self.paramDic[k] 29 | if not isinstance(val, list): 30 | tmp = val 31 | val = [] 32 | val.append(tmp) 33 | self.loadNetemAtList(i, val) 34 | 35 | def loadNetemAtList(self, id, nlist): 36 | for n in nlist: 37 | tab = n.split(",") 38 | if len(tab)==2: 39 | o = MpNetemAt(float(tab[0]), tab[1]) 40 | if id < len(self.linkCharacteristics): 41 | self.linkCharacteristics[id].addNetemAt(o) 42 | else: 43 | print("Error can't set netem for link " + str(id)) 44 | else: 45 | print("Netem wrong line : " + n) 46 | print(self.linkCharacteristics[id].netemAt) 47 | 48 | def loadLinkCharacteristics(self): 49 | i = 0 50 | for k in sorted(self.paramDic): 51 | if k.startswith("path"): 52 | tab = self.paramDic[k].split(",") 53 | bup = False 54 | loss = "0.0" 55 | if len(tab) == 5: 56 | loss = tab[3] 57 | bup = tab[4] == 'True' 58 | if len(tab) == 4: 59 | try: 60 | loss = float(tab[3]) 61 | loss = tab[3] 62 | except ValueError: 63 | bup = tab[3] == 'True' 64 | if len(tab) == 3 or len(tab) == 4 or len(tab) == 5: 65 | path = MpLinkCharacteristics(i,tab[0], 66 | tab[1], tab[2], loss, bup) 67 | self.linkCharacteristics.append(path) 68 | i = i + 1 69 | else: 70 | print("Ignored path :") 71 | print(self.paramDic[k]) 72 | 73 | def getParam(self, key): 74 | val = MpParam.getParam(self, key) 75 | if val is None: 76 | if key in MpParamTopo.defaultValue: 77 | return MpParamTopo.defaultValue[key] 78 | else: 79 | raise Exception("Param not found " + key) 80 | else: 81 | return val 82 | 83 | def __str__(self): 84 | s = MpParam.__str__(self) 85 | s = s + "\n" 86 | for p in self.linkCharacteristics[:-1]: 87 | s = s + p.__str__() + "\n" 88 | s = s + self.linkCharacteristics[-1].__str__() 89 | return s 90 | -------------------------------------------------------------------------------- /central_service/minitopo/mpPerf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys, getopt 4 | from mpXpRunner import MpXpRunner 5 | from mpTopo import MpTopo 6 | 7 | topoParamFile = None 8 | xpParamFile = None 9 | topoBuilder = "mininet" 10 | 11 | def printHelp(): 12 | print("Help Menu") 13 | 14 | def parseArgs(argv): 15 | global topoParamFile 16 | global xpParamFile 17 | try: 18 | opts, args = getopt.getopt(argv, "ht:x:", ["topoParam=","xp="]) 19 | except getopt.GetoptError: 20 | printHelp() 21 | sys.exit(1) 22 | for opt, arg in opts: 23 | if opt == "-h": 24 | printHelp() 25 | sys.exit(1) 26 | elif opt in ("-x","--xp"): 27 | xpParamFile = arg 28 | elif opt in ("-t","--topoParam"): 29 | print("hey") 30 | topoParamFile = arg 31 | if topoParamFile is None: 32 | print("Missing the topo...") 33 | printHelp() 34 | sys.exit(1) 35 | 36 | if __name__ == '__main__': 37 | parseArgs(sys.argv[1:]) 38 | MpXpRunner(MpTopo.mininetBuilder, topoParamFile, xpParamFile) 39 | -------------------------------------------------------------------------------- /central_service/minitopo/mpTopo.py: -------------------------------------------------------------------------------- 1 | from mpParamTopo import MpParamTopo 2 | 3 | class MpTopo: 4 | mininetBuilder = "mininet" 5 | multiIfTopo = "MultiIf" 6 | topoAttr = "topoType" 7 | switchNamePrefix = "s" 8 | routerNamePrefix = "r" 9 | clientName = "Client" 10 | serverName = "Server" 11 | routerName = "Router" 12 | cmdLog = "command.log" 13 | 14 | """Simple MpTopo""" 15 | def __init__(self, topoBuilder, topoParam): 16 | self.topoBuilder = topoBuilder 17 | self.topoParam = topoParam 18 | self.changeNetem = topoParam.getParam(MpParamTopo.changeNetem) 19 | self.logFile = open(MpTopo.cmdLog, 'w') 20 | 21 | def getLinkCharacteristics(self): 22 | return self.topoParam.linkCharacteristics 23 | 24 | def commandTo(self, who, cmd): 25 | self.logFile.write(who.__str__() + " : " + cmd + "\n") 26 | return self.topoBuilder.commandTo(who, cmd) 27 | 28 | def notNSCommand(self, cmd): 29 | """ 30 | mainly use for not namespace sysctl. 31 | """ 32 | self.logFile.write("Not_NS" + " : " + cmd + "\n") 33 | return self.topoBuilder.notNSCommand(cmd) 34 | 35 | def getHost(self, who): 36 | return self.topoBuilder.getHost(who) 37 | 38 | def addHost(self, host): 39 | return self.topoBuilder.addHost(host) 40 | 41 | def addSwitch(self, switch): 42 | return self.topoBuilder.addSwitch(switch) 43 | 44 | def addLink(self, fromA, toB, **kwargs): 45 | self.topoBuilder.addLink(fromA,toB,**kwargs) 46 | 47 | def getCLI(self): 48 | self.topoBuilder.getCLI() 49 | 50 | def startNetwork(self): 51 | self.topoBuilder.startNetwork() 52 | 53 | def closeLogFile(self): 54 | self.logFile.close() 55 | 56 | def stopNetwork(self): 57 | self.topoBuilder.stopNetwork() 58 | -------------------------------------------------------------------------------- /central_service/minitopo/mpXpRunner.py: -------------------------------------------------------------------------------- 1 | from mpTopo import MpTopo 2 | from mpParamTopo import MpParamTopo 3 | from mpParamXp import MpParamXp 4 | from mpMultiInterfaceTopo import MpMultiInterfaceTopo 5 | from mpMultiInterfaceConfig import MpMultiInterfaceConfig 6 | from mpMininetBuilder import MpMininetBuilder 7 | from mpExperienceHTTPS import MpExperienceHTTPS 8 | from mpExperienceQUIC import MpExperienceQUIC 9 | from mpExperienceQUICReqres import MpExperienceQUICReqres 10 | from mpExperienceNone import MpExperienceNone 11 | from mpExperience import MpExperience 12 | 13 | class MpXpRunner: 14 | def __init__(self, builderType, topoParamFile, xpParamFile): 15 | self.defParamXp(xpParamFile) 16 | self.topoParam = MpParamTopo(topoParamFile) 17 | self.defBuilder(builderType) 18 | self.defTopo() 19 | self.defConfig() 20 | self.startTopo() 21 | self.runXp() 22 | self.stopTopo() 23 | 24 | def defParamXp(self, xpParamFile): 25 | self.xpParam = MpParamXp(xpParamFile) 26 | 27 | def defBuilder(self, builderType): 28 | if builderType == MpTopo.mininetBuilder: 29 | self.topoBuilder = MpMininetBuilder() 30 | else: 31 | raise Exception("I can not find the builder " + 32 | builderType) 33 | def defTopo(self): 34 | t = self.topoParam.getParam(MpTopo.topoAttr) 35 | if t == MpTopo.multiIfTopo: 36 | self.mpTopo = MpMultiInterfaceTopo(self.topoBuilder, 37 | self.topoParam) 38 | else: 39 | raise Exception("Unfound Topo" + t) 40 | print(self.mpTopo) 41 | 42 | def defConfig(self): 43 | t = self.topoParam.getParam(MpTopo.topoAttr) 44 | if t == MpTopo.multiIfTopo: 45 | self.mpTopoConfig = MpMultiInterfaceConfig(self.mpTopo, 46 | self.topoParam) 47 | else: 48 | raise Exception("Unfound Topo" + t) 49 | 50 | def startTopo(self): 51 | self.mpTopo.startNetwork() 52 | self.mpTopoConfig.configureNetwork() 53 | 54 | def runXp(self): 55 | xp = self.xpParam.getParam(MpParamXp.XPTYPE) 56 | if xp == MpExperience.NONE: 57 | MpExperienceNone(self.xpParam, self.mpTopo, 58 | self.mpTopoConfig) 59 | elif xp == MpExperience.HTTPS: 60 | MpExperienceHTTPS(self.xpParam, self.mpTopo, 61 | self.mpTopoConfig) 62 | elif xp == MpExperience.QUIC: 63 | MpExperienceQUIC(self.xpParam, self.mpTopo, self.mpTopoConfig) 64 | elif xp == MpExperience.QUICREQRES: 65 | MpExperienceQUICReqres(self.xpParam, self.mpTopo, self.mpTopoConfig) 66 | else: 67 | print("Unfound xp type..." + xp) 68 | 69 | def stopTopo(self): 70 | self.mpTopo.stopNetwork() 71 | -------------------------------------------------------------------------------- /central_service/requirements.txt: -------------------------------------------------------------------------------- 1 | absl-py==0.9.0 2 | astor==0.8.1 3 | astroid==2.3.3 4 | cycler==0.10.0 5 | gast==0.3.3 6 | google-pasta==0.2.0 7 | grpcio==1.27.2 8 | h5py==2.10.0 9 | isort==4.3.21 10 | joblib==0.14.1 11 | Keras-Applications==1.0.8 12 | Keras-Preprocessing==1.1.0 13 | kiwisolver==1.1.0 14 | lazy-object-proxy==1.4.3 15 | Markdown==3.2.1 16 | matplotlib==3.2.1 17 | mccabe==0.6.1 18 | numpy==1.18.4 19 | Pillow==7.0.0 20 | pkg-resources==0.0.0 21 | protobuf==3.11.3 22 | pylint==2.4.4 23 | pyparsing==2.4.6 24 | python-dateutil==2.8.1 25 | pyzmq==19.0.0 26 | scikit-learn==0.23.0 27 | scipy==1.4.1 28 | six==1.14.0 29 | tensorboard==1.14.0 30 | tensorflow==1.14.0 31 | tensorflow-estimator==1.14.0 32 | termcolor==1.1.0 33 | tflearn==0.3.2 34 | threadpoolctl==2.0.0 35 | typed-ast==1.4.1 36 | Werkzeug==1.0.0 37 | wrapt==1.11.2 38 | -------------------------------------------------------------------------------- /central_service/scripts/cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Cleaning up..." 4 | find . -type d -name "https_quic_*" -exec rm -rf {} + 5 | 6 | # find . -type f -name "events.out.tfevents.*" -exec rm -rf {} + -------------------------------------------------------------------------------- /central_service/scripts/install-libzmq3-dev.txt: -------------------------------------------------------------------------------- 1 | 2 | # On the guest Mininet OS 3 | # You need to install libzmq3 for using the pebbe/zmq4 golang package 4 | # To do so, ssh to the machine and run the following commands 5 | 6 | 7 | # NOTE: If you want you can directly use the last command 8 | 9 | sudo sh -c "echo 'deb http://download.opensuse.org/repositories/network:/messaging:/zeromq:/release-stable/xUbuntu_14.04/ /' > /etc/apt/sources.list.d/network:messaging:zeromq:release-stable.list" 10 | wget -nv https://download.opensuse.org/repositories/network:messaging:zeromq:release-stable/xUbuntu_14.04/Release.key -O Release.key 11 | sudo apt-key add - < Release.key 12 | sudo apt-get update 13 | sudo apt-get install libzmq3-dev -------------------------------------------------------------------------------- /central_service/scripts/move.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Moving all https_quic_* folders under experiments/" 4 | find . -iname 'https_quic_*' -type d -exec mv '{}' experiments/ \; -------------------------------------------------------------------------------- /central_service/scripts/run_tensorboard.txt: -------------------------------------------------------------------------------- 1 | # command to spawn it from venv (tensorflow is installed at venv) 2 | python -m tensorboard.main --logdir $PWD/logs -------------------------------------------------------------------------------- /central_service/utils/data_transf.py: -------------------------------------------------------------------------------- 1 | # Some useful operations on data we need for our training 2 | 3 | def allUnique(x, debug=False): 4 | ''' 5 | Simplest way to check if all items in a list are unique 6 | with early exit 7 | ''' 8 | # return len(x) == len(set(x)) 9 | if debug: 10 | seen = set() 11 | for i in x: 12 | if i in seen: 13 | print("Not unique: {}".format(i)) 14 | else: 15 | seen.add(i) 16 | print ("allUnique len(x) = {} and len(seen) = {}".format(len(x), len(seen))) 17 | return len(x) == len(seen) 18 | 19 | seen = set() 20 | return not any(i in seen or seen.add(i) for i in x) 21 | 22 | 23 | 24 | def getTrainingVariables(request): 25 | ''' 26 | Return all necessary state/training variables from the request 27 | They might come in random order so rotate them 28 | ''' 29 | if request['Path1']['PathID'] == 1: 30 | return request['Path1']['SmoothedRTT'], request['Path1']['Bandwidth'], request['Path1']['Packets'], \ 31 | request['Path1']['Retransmissions'], request['Path1']['Losses'], \ 32 | request['Path2']['SmoothedRTT'], request['Path2']['Bandwidth'], request['Path2']['Packets'], \ 33 | request['Path2']['Retransmissions'], request['Path2']['Losses'] 34 | else: 35 | return request['Path2']['SmoothedRTT'], request['Path2']['Bandwidth'], request['Path2']['Packets'], \ 36 | request['Path2']['Retransmissions'], request['Path2']['Losses'], \ 37 | request['Path1']['SmoothedRTT'], request['Path1']['Bandwidth'], request['Path1']['Packets'], \ 38 | request['Path1']['Retransmissions'], request['Path1']['Losses'] 39 | 40 | 41 | def arrangeStateStreamsInfo(states, stream_info): 42 | ''' 43 | Concurrency in MPQUIC results in slightly different ordering of stream_ids, 44 | Since we only care about the server side of events (for training and validation) 45 | We rearrange the stream_info based on the order of states received 46 | As a matching value we have the request-path (e.g. /index.html == /index.html) 47 | ''' 48 | assert (len(states) == len(stream_info)) 49 | 50 | for i, state in enumerate(states): 51 | for j, stream in enumerate(stream_info): 52 | if stream['Path'] == state['RequestPath']: 53 | stream['StreamID'] = state['StreamID'] 54 | # reorder streams 55 | if i != j: 56 | stream_info[i], stream_info[j] = stream_info[j], stream_info[i] 57 | 58 | return stream_info -------------------------------------------------------------------------------- /central_service/utils/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | def config_logger(name='', filepath='./logs/debug.log'): 5 | if os.path.isfile(filepath): 6 | os.remove(filepath) 7 | # create logger with 'spam_application' 8 | logger = logging.getLogger(name) 9 | logger.setLevel(logging.DEBUG) 10 | # create file handler which logs even debug messages 11 | fh = logging.FileHandler(filepath) 12 | fh.setLevel(logging.DEBUG) 13 | # create formatter and add it to the handlers 14 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 15 | fh.setFormatter(formatter) 16 | # add the handlers to the logger 17 | logger.addHandler(fh) 18 | return logger -------------------------------------------------------------------------------- /central_service/utils/queue_ops.py: -------------------------------------------------------------------------------- 1 | # Simple queue operations 2 | # get or put a request to queue 3 | # blocking operation with a small timeout 4 | import queue 5 | from threading import Event 6 | import multiprocessing as mp 7 | 8 | def get_request(queue: queue.Queue, logger, end_of_run: mp.Event = None): 9 | # logger.info("Waiting for request...") 10 | while not end_of_run.is_set(): 11 | try: 12 | req, evt = queue.get(timeout=0.05) 13 | return req, evt 14 | except Exception as ex: 15 | # logger.error(ex) 16 | continue 17 | return None, None 18 | 19 | def put_response(response, queue: queue.Queue, logger): 20 | # logger.info("Putting response...") 21 | try: 22 | queue.put(response) 23 | except Exception as ex: 24 | logger.error(ex) -------------------------------------------------------------------------------- /middleware/interface.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import zmq "github.com/pebbe/zmq4" 4 | 5 | // BasicConfig ... 6 | type BasicConfig struct { 7 | socket *zmq.Socket 8 | poller *zmq.Poller 9 | } 10 | 11 | // BasicOperations ... 12 | type BasicOperations interface { 13 | NewConfig(zmq.Type, string) *zmq.Socket 14 | bind(string) 15 | Send(*Message) error 16 | RecvMessage() (*Message, error) 17 | Close() 18 | } 19 | 20 | // Message is a basic message struct 21 | type Message struct { 22 | ID int 23 | Data []string 24 | } 25 | -------------------------------------------------------------------------------- /middleware/middleware.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "sync" 7 | 8 | zmq "github.com/pebbe/zmq4" 9 | ) 10 | 11 | func listenAndForward(serverAddrs *string, clientAddrs *string, wg *sync.WaitGroup) { 12 | server := NewServer(zmq.REP, *serverAddrs) 13 | client := NewServer(zmq.REQ, *clientAddrs) 14 | 15 | request := &Message{} 16 | response := &Message{} 17 | var err error 18 | 19 | defer server.Close() 20 | defer client.Close() 21 | defer wg.Done() 22 | 23 | fmt.Println("ListenAndForward") 24 | 25 | for { 26 | // Get initial request from GOServer 27 | request, err = server.RecvMessage() 28 | if err != nil { 29 | fmt.Println(err.Error()) 30 | break 31 | } 32 | 33 | // PASS on request to agent 34 | err = client.Request(request) 35 | if err != nil { 36 | fmt.Println(err.Error()) 37 | break 38 | } 39 | 40 | // Get response from agent 41 | response, err = client.RecvMessage() 42 | if err != nil { 43 | fmt.Println(err.Error()) 44 | break 45 | } 46 | 47 | // Forward response back to GOServer 48 | err = server.Request(response) 49 | if err != nil { 50 | fmt.Println(err.Error()) 51 | break 52 | } 53 | } 54 | } 55 | 56 | func subscribeAndForward(pubAddrs *string, subAddrs *string, wg *sync.WaitGroup) { 57 | publisher := NewConfig(zmq.PUB, *pubAddrs) 58 | subscriber := NewConfig(zmq.SUB, *subAddrs) 59 | 60 | message := &Message{} 61 | var err error 62 | 63 | // subscribe to all 64 | subscriber.socket.SetSubscribe("") 65 | 66 | defer subscriber.Close() 67 | defer publisher.Close() 68 | defer wg.Done() 69 | 70 | fmt.Println("SubscribeAndForward") 71 | 72 | for { 73 | message, err = subscriber.RecvMessage() 74 | if err != nil { 75 | fmt.Println(err.Error()) 76 | break 77 | } 78 | 79 | err = publisher.Send(message) 80 | if err != nil { 81 | fmt.Println(err.Error()) 82 | break 83 | } 84 | } 85 | } 86 | 87 | func main() { 88 | server := flag.String("sv", "ipc:///tmp/zmq", "Server listenAndForward") 89 | client := flag.String("cl", "tcp://*:5555", "Client listenAndForward") 90 | publisher := flag.String("pub", "tcp://*:5556", "Publisher Subscribe&Forward") 91 | subscriber := flag.String("sub", "ipc:///tmp/pubsub", "Subscriber Subscribe&Forward") 92 | 93 | flag.Parse() 94 | 95 | fmt.Println(*server) 96 | fmt.Println(*client) 97 | fmt.Println(*publisher) 98 | fmt.Println(*subscriber) 99 | 100 | var wg sync.WaitGroup 101 | 102 | go listenAndForward(server, client, &wg) 103 | wg.Add(1) 104 | go subscribeAndForward(publisher, subscriber, &wg) 105 | wg.Add(1) 106 | 107 | wg.Wait() 108 | } 109 | -------------------------------------------------------------------------------- /middleware/pubsub.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | zmq "github.com/pebbe/zmq4" 8 | ) 9 | 10 | // PubSub ... 11 | type PubSub = BasicConfig 12 | 13 | // NewConfig instantiates a new server 14 | func NewConfig(servertype zmq.Type, endpoint string) (pubsub *PubSub) { 15 | pubsub = &BasicConfig{} 16 | var err error 17 | 18 | pubsub.socket, err = zmq.NewSocket(servertype) 19 | 20 | if err != nil { 21 | fmt.Println("Error in NewSocket") 22 | fmt.Println(err.Error()) 23 | } 24 | 25 | pubsub.bind(endpoint) 26 | pubsub.setLingerInfinite() 27 | 28 | pubsub.poller = zmq.NewPoller() 29 | pubsub.poller.Add(pubsub.socket, zmq.POLLIN) 30 | 31 | return 32 | } 33 | 34 | // SetLingerInfinite 35 | func (pubsub *PubSub) setLingerInfinite() { 36 | pubsub.socket.SetLinger(-1) 37 | } 38 | 39 | // Bind to an endpoint 40 | func (pubsub *PubSub) bind(endpoint string) { 41 | err := pubsub.socket.Bind(endpoint) 42 | 43 | if err != nil { 44 | fmt.Println("Error binding") 45 | fmt.Println(err.Error()) 46 | } 47 | } 48 | 49 | // Close ... 50 | func (pubsub *PubSub) Close() { 51 | pubsub.socket.Close() 52 | } 53 | 54 | // Send sends a message 55 | func (pubsub *PubSub) Send(message *Message) (err error) { 56 | bsent, err := pubsub.socket.SendMessage(message.ID, message.Data) 57 | 58 | if err != nil || bsent == 0 { 59 | fmt.Println(err.Error()) 60 | } 61 | return 62 | } 63 | 64 | // RecvMessage polls and receives a message 65 | func (pubsub *PubSub) RecvMessage() (message *Message, err error) { 66 | message = &Message{} 67 | rawMessage := []string{} 68 | 69 | for { 70 | polled, err := pubsub.poller.Poll(requestTimeout) 71 | //fmt.Println("subscriber polling") 72 | if err == nil && len(polled) > 0 { 73 | // reply 74 | rawMessage, _ = pubsub.socket.RecvMessage(0) 75 | if len(rawMessage) != 2 { 76 | panic("len(reply) != 2") 77 | } 78 | 79 | message.ID, _ = strconv.Atoi(rawMessage[0]) 80 | message.Data = rawMessage[1:] 81 | break 82 | } 83 | //time.Sleep(10 * time.Millisecond) 84 | } 85 | return 86 | } 87 | -------------------------------------------------------------------------------- /middleware/transfer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | User="mininet" 4 | Host="192.168.122.157" 5 | SendDir="~/go/src/github.com/mkanakis/middleware/" 6 | 7 | scp interface.go pubsub.go middleware.go zserver.go $User@$Host:$SendDir 8 | -------------------------------------------------------------------------------- /middleware/zserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "time" 7 | 8 | zmq "github.com/pebbe/zmq4" 9 | ) 10 | 11 | const ( 12 | globalTimeout = 2500 * time.Millisecond 13 | requestTimeout = 10 * time.Millisecond 14 | maxRetries = 3 // before we abandon 15 | ) 16 | 17 | // ZServer ... 18 | type ZServer struct { 19 | socket *zmq.Socket 20 | poller *zmq.Poller 21 | } 22 | 23 | // Message ... 24 | // type Message struct { 25 | // ID int 26 | // Data []string 27 | // } 28 | 29 | // NewServer instantiates a new server 30 | func NewServer(servertype zmq.Type, endpoint string) (s *ZServer) { 31 | s = &ZServer{} 32 | var err error 33 | 34 | s.socket, err = zmq.NewSocket(servertype) 35 | s.bind(endpoint) 36 | 37 | if err != nil { 38 | fmt.Println(err.Error()) 39 | } 40 | 41 | s.poller = zmq.NewPoller() 42 | s.poller.Add(s.socket, zmq.POLLIN) 43 | 44 | return 45 | } 46 | 47 | // Bind to an endpoint 48 | func (s *ZServer) bind(endpoint string) { 49 | err := s.socket.Bind(endpoint) 50 | 51 | if err != nil { 52 | fmt.Println(err.Error()) 53 | } 54 | } 55 | 56 | // Close ... 57 | func (s *ZServer) Close() { 58 | s.socket.Close() 59 | } 60 | 61 | // Request sends a message 62 | func (s *ZServer) Request(request *Message) (err error) { 63 | bsent, err := s.socket.SendMessage(request.ID, request.Data) 64 | 65 | if err != nil || bsent == 0 { 66 | fmt.Println(err.Error()) 67 | } 68 | return 69 | } 70 | 71 | // RecvMessage polls and receives a message 72 | func (s *ZServer) RecvMessage() (request *Message, err error) { 73 | request = &Message{} 74 | rawRequest := []string{} 75 | 76 | for { 77 | polled, err := s.poller.Poll(requestTimeout) 78 | //fmt.Println("zserver polling") 79 | if err == nil && len(polled) > 0 { 80 | // reply 81 | rawRequest, _ = s.socket.RecvMessage(0) 82 | if len(rawRequest) != 2 { 83 | panic("len(reply) != 2") 84 | } 85 | 86 | request.ID, _ = strconv.Atoi(rawRequest[0]) 87 | request.Data = rawRequest[1:] 88 | break 89 | } 90 | //time.Sleep(10 * time.Millisecond) 91 | } 92 | return 93 | } 94 | -------------------------------------------------------------------------------- /rlmp-quic/--progress: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vunetsys/mpquic-rl/f9f9ce7a199c359baf0d5cdee063fbd5186f82c5/rlmp-quic/--progress -------------------------------------------------------------------------------- /rlmp-quic/Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.6.0 (unreleased) 4 | 5 | - Add support for QUIC 38 and 39, drop support for QUIC 35 and 36 6 | - Added `quic.Config` options for maximal flow control windows 7 | - Add a `quic.Config` option for QUIC versions 8 | - Add a `quic.Config` option to request truncation of the connection ID from a server 9 | - Add a `quic.Config` option to configure the source address validation 10 | - Add a `quic.Config` option to configure the handshake timeout 11 | - Add a `quic.Config` option to configure the idle timeout 12 | - Add a `quic.Config` option to configure keep-alive 13 | - Rename the STK to Cookie 14 | - Implement `net.Conn`-style deadlines for streams 15 | - Remove the `tls.Config` from the `quic.Config`. The `tls.Config` must now be passed to the `Dial` and `Listen` functions as a separate parameter. See the [Godoc](https://godoc.org/github.com/lucas-clemente/quic-go) for details. 16 | - Changed the log level environment variable to only accept strings ("DEBUG", "INFO", "ERROR"), see [the wiki](https://github.com/lucas-clemente/quic-go/wiki/Logging) for more details. 17 | - Rename the `h2quic.QuicRoundTripper` to `h2quic.RoundTripper` 18 | - Changed `h2quic.Server.Serve()` to accept a `net.PacketConn` 19 | - Drop support for Go 1.7 and 1.8. 20 | - Various bugfixes 21 | -------------------------------------------------------------------------------- /rlmp-quic/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 the quic-go authors & Google, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rlmp-quic/ackhandler/_gen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "github.com/clipperhouse/linkedlist" 5 | _ "github.com/clipperhouse/slice" 6 | _ "github.com/clipperhouse/stringer" 7 | ) 8 | -------------------------------------------------------------------------------- /rlmp-quic/ackhandler/ackhandler_suite_test.go: -------------------------------------------------------------------------------- 1 | package ackhandler 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestCrypto(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "AckHandler Suite") 13 | } 14 | -------------------------------------------------------------------------------- /rlmp-quic/ackhandler/interfaces.go: -------------------------------------------------------------------------------- 1 | package ackhandler 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | "github.com/lucas-clemente/quic-go/internal/wire" 8 | ) 9 | 10 | // SentPacketHandler handles ACKs received for outgoing packets 11 | type SentPacketHandler interface { 12 | // SentPacket may modify the packet 13 | SentPacket(packet *Packet) error 14 | ReceivedAck(ackFrame *wire.AckFrame, withPacketNumber protocol.PacketNumber, recvTime time.Time) error 15 | 16 | // Specific to multipath operation 17 | ReceivedClosePath(f *wire.ClosePathFrame, withPacketNumber protocol.PacketNumber, recvTime time.Time) error 18 | SetInflightAsLost() 19 | 20 | SendingAllowed() bool 21 | GetStopWaitingFrame(force bool) *wire.StopWaitingFrame 22 | ShouldSendRetransmittablePacket() bool 23 | DequeuePacketForRetransmission() (packet *Packet) 24 | GetLeastUnacked() protocol.PacketNumber 25 | 26 | GetAlarmTimeout() time.Time 27 | OnAlarm() 28 | 29 | DuplicatePacket(packet *Packet) 30 | 31 | GetStatistics() (uint64, uint64, uint64) 32 | } 33 | 34 | // ReceivedPacketHandler handles ACKs needed to send for incoming packets 35 | type ReceivedPacketHandler interface { 36 | ReceivedPacket(packetNumber protocol.PacketNumber, shouldInstigateAck bool) error 37 | SetLowerLimit(protocol.PacketNumber) 38 | 39 | GetAlarmTimeout() time.Time 40 | GetAckFrame() *wire.AckFrame 41 | 42 | GetClosePathFrame() *wire.ClosePathFrame 43 | 44 | GetStatistics() uint64 45 | } 46 | -------------------------------------------------------------------------------- /rlmp-quic/ackhandler/packet.go: -------------------------------------------------------------------------------- 1 | package ackhandler 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | "github.com/lucas-clemente/quic-go/internal/wire" 8 | ) 9 | 10 | // A Packet is a packet 11 | // +gen linkedlist 12 | type Packet struct { 13 | PacketNumber protocol.PacketNumber 14 | Frames []wire.Frame 15 | Length protocol.ByteCount 16 | EncryptionLevel protocol.EncryptionLevel 17 | 18 | SendTime time.Time 19 | } 20 | 21 | // GetFramesForRetransmission gets all the frames for retransmission 22 | func (p *Packet) GetFramesForRetransmission() []wire.Frame { 23 | var fs []wire.Frame 24 | for _, frame := range p.Frames { 25 | switch frame.(type) { 26 | case *wire.AckFrame: 27 | continue 28 | case *wire.StopWaitingFrame: 29 | continue 30 | } 31 | fs = append(fs, frame) 32 | } 33 | return fs 34 | } 35 | 36 | func (p *Packet) IsRetransmittable() bool { 37 | for _, f := range p.Frames { 38 | switch f.(type) { 39 | case *wire.StreamFrame: 40 | return true 41 | case *wire.RstStreamFrame: 42 | return true 43 | case *wire.WindowUpdateFrame: 44 | return true 45 | case *wire.BlockedFrame: 46 | return true 47 | case *wire.PingFrame: 48 | return true 49 | case *wire.GoawayFrame: 50 | return true 51 | case *wire.AddAddressFrame: 52 | return true 53 | case *wire.PathsFrame: 54 | return true 55 | } 56 | } 57 | return false 58 | } 59 | -------------------------------------------------------------------------------- /rlmp-quic/ackhandler/packet_test.go: -------------------------------------------------------------------------------- 1 | package ackhandler 2 | 3 | import ( 4 | "github.com/lucas-clemente/quic-go/internal/wire" 5 | . "github.com/onsi/ginkgo" 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | var _ = Describe("Packet", func() { 10 | Context("getting frames for retransmission", func() { 11 | ackFrame := &wire.AckFrame{LargestAcked: 13} 12 | stopWaitingFrame := &wire.StopWaitingFrame{LeastUnacked: 7331} 13 | windowUpdateFrame := &wire.WindowUpdateFrame{StreamID: 999} 14 | 15 | streamFrame := &wire.StreamFrame{ 16 | StreamID: 5, 17 | Data: []byte{0x13, 0x37}, 18 | } 19 | 20 | rstStreamFrame := &wire.RstStreamFrame{ 21 | StreamID: 555, 22 | ErrorCode: 1337, 23 | } 24 | 25 | It("returns nil if there are no retransmittable frames", func() { 26 | packet := &Packet{ 27 | Frames: []wire.Frame{ackFrame, stopWaitingFrame}, 28 | } 29 | Expect(packet.GetFramesForRetransmission()).To(BeNil()) 30 | }) 31 | 32 | It("returns all retransmittable frames", func() { 33 | packet := &Packet{ 34 | Frames: []wire.Frame{ 35 | windowUpdateFrame, 36 | ackFrame, 37 | stopWaitingFrame, 38 | streamFrame, 39 | rstStreamFrame, 40 | }, 41 | } 42 | fs := packet.GetFramesForRetransmission() 43 | Expect(fs).To(ContainElement(streamFrame)) 44 | Expect(fs).To(ContainElement(rstStreamFrame)) 45 | Expect(fs).To(ContainElement(windowUpdateFrame)) 46 | Expect(fs).ToNot(ContainElement(stopWaitingFrame)) 47 | Expect(fs).ToNot(ContainElement(ackFrame)) 48 | }) 49 | 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /rlmp-quic/ackhandler/retransmittable.go: -------------------------------------------------------------------------------- 1 | package ackhandler 2 | 3 | import "github.com/lucas-clemente/quic-go/internal/wire" 4 | 5 | // Returns a new slice with all non-retransmittable frames deleted. 6 | func stripNonRetransmittableFrames(fs []wire.Frame) []wire.Frame { 7 | res := make([]wire.Frame, 0, len(fs)) 8 | for _, f := range fs { 9 | if IsFrameRetransmittable(f) { 10 | res = append(res, f) 11 | } 12 | } 13 | return res 14 | } 15 | 16 | // IsFrameRetransmittable returns true if the frame should be retransmitted. 17 | func IsFrameRetransmittable(f wire.Frame) bool { 18 | switch f.(type) { 19 | case *wire.StopWaitingFrame: 20 | return false 21 | case *wire.AckFrame: 22 | return false 23 | default: 24 | return true 25 | } 26 | } 27 | 28 | // HasRetransmittableFrames returns true if at least one frame is retransmittable. 29 | func HasRetransmittableFrames(fs []wire.Frame) bool { 30 | for _, f := range fs { 31 | if IsFrameRetransmittable(f) { 32 | return true 33 | } 34 | } 35 | return false 36 | } 37 | -------------------------------------------------------------------------------- /rlmp-quic/ackhandler/retransmittable_test.go: -------------------------------------------------------------------------------- 1 | package ackhandler 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/wire" 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | var _ = Describe("retransmittable frames", func() { 12 | for fl, el := range map[wire.Frame]bool{ 13 | &wire.AckFrame{}: false, 14 | &wire.StopWaitingFrame{}: false, 15 | &wire.BlockedFrame{}: true, 16 | &wire.ConnectionCloseFrame{}: true, 17 | &wire.GoawayFrame{}: true, 18 | &wire.PingFrame{}: true, 19 | &wire.RstStreamFrame{}: true, 20 | &wire.StreamFrame{}: true, 21 | &wire.WindowUpdateFrame{}: true, 22 | } { 23 | f := fl 24 | e := el 25 | fName := reflect.ValueOf(f).Elem().Type().Name() 26 | 27 | It("works for "+fName, func() { 28 | Expect(IsFrameRetransmittable(f)).To(Equal(e)) 29 | }) 30 | 31 | It("stripping non-retransmittable frames works for "+fName, func() { 32 | s := []wire.Frame{f} 33 | if e { 34 | Expect(stripNonRetransmittableFrames(s)).To(Equal([]wire.Frame{f})) 35 | } else { 36 | Expect(stripNonRetransmittableFrames(s)).To(BeEmpty()) 37 | } 38 | }) 39 | 40 | It("HasRetransmittableFrames works for "+fName, func() { 41 | Expect(HasRetransmittableFrames([]wire.Frame{f})).To(Equal(e)) 42 | }) 43 | } 44 | }) 45 | -------------------------------------------------------------------------------- /rlmp-quic/ackhandler/stop_waiting_manager.go: -------------------------------------------------------------------------------- 1 | package ackhandler 2 | 3 | import ( 4 | "github.com/lucas-clemente/quic-go/internal/protocol" 5 | "github.com/lucas-clemente/quic-go/internal/wire" 6 | ) 7 | 8 | // This stopWaitingManager is not supposed to satisfy the StopWaitingManager interface, which is a remnant of the legacy AckHandler, and should be remove once we drop support for QUIC 33 9 | type stopWaitingManager struct { 10 | largestLeastUnackedSent protocol.PacketNumber 11 | nextLeastUnacked protocol.PacketNumber 12 | 13 | lastStopWaitingFrame *wire.StopWaitingFrame 14 | } 15 | 16 | func (s *stopWaitingManager) GetStopWaitingFrame(force bool) *wire.StopWaitingFrame { 17 | if s.nextLeastUnacked <= s.largestLeastUnackedSent { 18 | if force { 19 | if s.lastStopWaitingFrame == nil && s.largestLeastUnackedSent > 0 { 20 | // This case is possible when no previous SWF were sent (lost first packet) 21 | swf := &wire.StopWaitingFrame{ 22 | LeastUnacked: s.nextLeastUnacked, 23 | } 24 | s.lastStopWaitingFrame = swf 25 | } 26 | return s.lastStopWaitingFrame 27 | } 28 | return nil 29 | } 30 | 31 | s.largestLeastUnackedSent = s.nextLeastUnacked 32 | swf := &wire.StopWaitingFrame{ 33 | LeastUnacked: s.nextLeastUnacked, 34 | } 35 | s.lastStopWaitingFrame = swf 36 | return swf 37 | } 38 | 39 | func (s *stopWaitingManager) ReceivedAck(ack *wire.AckFrame) { 40 | if ack.LargestAcked >= s.nextLeastUnacked { 41 | s.nextLeastUnacked = ack.LargestAcked + 1 42 | } 43 | } 44 | 45 | func (s *stopWaitingManager) QueuedRetransmissionForPacketNumber(p protocol.PacketNumber) { 46 | if p >= s.nextLeastUnacked { 47 | s.nextLeastUnacked = p + 1 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /rlmp-quic/ackhandler/stop_waiting_manager_test.go: -------------------------------------------------------------------------------- 1 | package ackhandler 2 | 3 | import ( 4 | "github.com/lucas-clemente/quic-go/internal/wire" 5 | . "github.com/onsi/ginkgo" 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | var _ = Describe("StopWaitingManager", func() { 10 | var manager *stopWaitingManager 11 | BeforeEach(func() { 12 | manager = &stopWaitingManager{} 13 | }) 14 | 15 | It("returns nil in the beginning", func() { 16 | Expect(manager.GetStopWaitingFrame(false)).To(BeNil()) 17 | Expect(manager.GetStopWaitingFrame(true)).To(BeNil()) 18 | }) 19 | 20 | It("returns a StopWaitingFrame, when a new ACK arrives", func() { 21 | manager.ReceivedAck(&wire.AckFrame{LargestAcked: 10}) 22 | Expect(manager.GetStopWaitingFrame(false)).To(Equal(&wire.StopWaitingFrame{LeastUnacked: 11})) 23 | }) 24 | 25 | It("does not decrease the LeastUnacked", func() { 26 | manager.ReceivedAck(&wire.AckFrame{LargestAcked: 10}) 27 | manager.ReceivedAck(&wire.AckFrame{LargestAcked: 9}) 28 | Expect(manager.GetStopWaitingFrame(false)).To(Equal(&wire.StopWaitingFrame{LeastUnacked: 11})) 29 | }) 30 | 31 | It("does not send the same StopWaitingFrame twice", func() { 32 | manager.ReceivedAck(&wire.AckFrame{LargestAcked: 10}) 33 | Expect(manager.GetStopWaitingFrame(false)).ToNot(BeNil()) 34 | Expect(manager.GetStopWaitingFrame(false)).To(BeNil()) 35 | }) 36 | 37 | It("gets the same StopWaitingFrame twice, if forced", func() { 38 | manager.ReceivedAck(&wire.AckFrame{LargestAcked: 10}) 39 | Expect(manager.GetStopWaitingFrame(false)).ToNot(BeNil()) 40 | Expect(manager.GetStopWaitingFrame(true)).ToNot(BeNil()) 41 | Expect(manager.GetStopWaitingFrame(true)).ToNot(BeNil()) 42 | }) 43 | 44 | It("increases the LeastUnacked when a retransmission is queued", func() { 45 | manager.ReceivedAck(&wire.AckFrame{LargestAcked: 10}) 46 | manager.QueuedRetransmissionForPacketNumber(20) 47 | Expect(manager.GetStopWaitingFrame(false)).To(Equal(&wire.StopWaitingFrame{LeastUnacked: 21})) 48 | }) 49 | 50 | It("does not decrease the LeastUnacked when a retransmission is queued", func() { 51 | manager.ReceivedAck(&wire.AckFrame{LargestAcked: 10}) 52 | manager.QueuedRetransmissionForPacketNumber(9) 53 | Expect(manager.GetStopWaitingFrame(false)).To(Equal(&wire.StopWaitingFrame{LeastUnacked: 11})) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /rlmp-quic/appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | os: Windows Server 2012 R2 4 | 5 | environment: 6 | GOPATH: c:\gopath 7 | CGO_ENABLED: 0 8 | TIMESCALE_FACTOR: 20 9 | matrix: 10 | - GOARCH: 386 11 | - GOARCH: amd64 12 | 13 | clone_folder: c:\gopath\src\github.com\lucas-clemente\quic-go 14 | 15 | install: 16 | - rmdir c:\go /s /q 17 | - appveyor DownloadFile https://storage.googleapis.com/golang/go1.9.windows-amd64.zip 18 | - 7z x go1.9.windows-amd64.zip -y -oC:\ > NUL 19 | - set PATH=%PATH%;%GOPATH%\bin\windows_%GOARCH%;%GOPATH%\bin 20 | - echo %PATH% 21 | - echo %GOPATH% 22 | - git submodule update --init --recursive 23 | - go get github.com/onsi/ginkgo/ginkgo 24 | - go get github.com/onsi/gomega 25 | - go version 26 | - go env 27 | - go get -v -t ./... 28 | 29 | build_script: 30 | - ginkgo -r -v -randomizeAllSpecs -randomizeSuites -trace -skipPackage benchmark,integrationtests 31 | - ginkgo -randomizeAllSpecs -randomizeSuites -trace benchmark -- -samples=1 32 | 33 | test: off 34 | 35 | deploy: off 36 | -------------------------------------------------------------------------------- /rlmp-quic/benchmark/benchmark_suite_test.go: -------------------------------------------------------------------------------- 1 | package benchmark 2 | 3 | import ( 4 | "flag" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | 9 | "testing" 10 | ) 11 | 12 | func TestBenchmark(t *testing.T) { 13 | RegisterFailHandler(Fail) 14 | RunSpecs(t, "Benchmark Suite") 15 | } 16 | 17 | var ( 18 | size int // file size in MB, will be read from flags 19 | samples int // number of samples for Measure, will be read from flags 20 | ) 21 | 22 | func init() { 23 | flag.IntVar(&size, "size", 50, "data length (in MB)") 24 | flag.IntVar(&samples, "samples", 6, "number of samples") 25 | flag.Parse() 26 | } 27 | -------------------------------------------------------------------------------- /rlmp-quic/buffer_pool.go: -------------------------------------------------------------------------------- 1 | package quic 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | ) 8 | 9 | var bufferPool sync.Pool 10 | 11 | func getPacketBuffer() []byte { 12 | return bufferPool.Get().([]byte) 13 | } 14 | 15 | func putPacketBuffer(buf []byte) { 16 | if cap(buf) != int(protocol.MaxReceivePacketSize) { 17 | panic("putPacketBuffer called with packet of wrong size!") 18 | } 19 | bufferPool.Put(buf[:0]) 20 | } 21 | 22 | func init() { 23 | bufferPool.New = func() interface{} { 24 | return make([]byte, 0, protocol.MaxReceivePacketSize) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /rlmp-quic/buffer_pool_test.go: -------------------------------------------------------------------------------- 1 | package quic 2 | 3 | import ( 4 | "github.com/lucas-clemente/quic-go/internal/protocol" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("Buffer Pool", func() { 11 | It("returns buffers of correct len and cap", func() { 12 | buf := getPacketBuffer() 13 | Expect(buf).To(HaveLen(0)) 14 | Expect(buf).To(HaveCap(int(protocol.MaxReceivePacketSize))) 15 | }) 16 | 17 | It("zeroes put buffers' length", func() { 18 | for i := 0; i < 1000; i++ { 19 | buf := getPacketBuffer() 20 | putPacketBuffer(buf[0:10]) 21 | buf = getPacketBuffer() 22 | Expect(buf).To(HaveLen(0)) 23 | Expect(buf).To(HaveCap(int(protocol.MaxReceivePacketSize))) 24 | } 25 | }) 26 | 27 | It("panics if wrong-sized buffers are passed", func() { 28 | Expect(func() { 29 | putPacketBuffer([]byte{0}) 30 | }).To(Panic()) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /rlmp-quic/codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | round: nearest 3 | ignore: 4 | - ackhandler/packet_linkedlist.go 5 | - h2quic/gzipreader.go 6 | - h2quic/response.go 7 | - internal/utils/byteinterval_linkedlist.go 8 | - internal/utils/packetinterval_linkedlist.go 9 | status: 10 | project: 11 | default: 12 | threshold: 0.5 13 | patch: false 14 | -------------------------------------------------------------------------------- /rlmp-quic/congestion/bandwidth.go: -------------------------------------------------------------------------------- 1 | package congestion 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | ) 8 | 9 | // Bandwidth of a connection 10 | type Bandwidth uint64 11 | 12 | const ( 13 | // BitsPerSecond is 1 bit per second 14 | BitsPerSecond Bandwidth = 1 15 | // BytesPerSecond is 1 byte per second 16 | BytesPerSecond = 8 * BitsPerSecond 17 | ) 18 | 19 | // BandwidthFromDelta calculates the bandwidth from a number of bytes and a time delta 20 | func BandwidthFromDelta(bytes protocol.ByteCount, delta time.Duration) Bandwidth { 21 | return Bandwidth(bytes) * Bandwidth(time.Second) / Bandwidth(delta) * BytesPerSecond 22 | } 23 | -------------------------------------------------------------------------------- /rlmp-quic/congestion/bandwidth_test.go: -------------------------------------------------------------------------------- 1 | package congestion 2 | 3 | import ( 4 | "time" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("Bandwidth", func() { 11 | It("converts from time delta", func() { 12 | Expect(BandwidthFromDelta(1, time.Millisecond)).To(Equal(1000 * BytesPerSecond)) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /rlmp-quic/congestion/bdw_stats.go: -------------------------------------------------------------------------------- 1 | package congestion 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | ) 8 | 9 | // BDWStats provides estimated bandwidth statistics 10 | type BDWStats struct { 11 | bandwidth Bandwidth //SHI: bit per second 12 | compareWindow [10]Bandwidth 13 | roundRobinIndex uint8 //SHI: resume where ended 14 | } 15 | 16 | // NewBDWStats makes a properly initialized BDWStats object 17 | func NewBDWStats(bandwidth Bandwidth) *BDWStats { 18 | return &BDWStats{ 19 | bandwidth: bandwidth, 20 | } 21 | } 22 | 23 | //GetBandwidth returns estimated bandwidth in Mbps 24 | func (b *BDWStats) GetBandwidth() Bandwidth { return b.bandwidth / Bandwidth(1048576) } 25 | 26 | // UpdateBDW updates the bandwidth based on a new sample. 27 | func (b *BDWStats) UpdateBDW(sentDelta protocol.ByteCount, sentDelay time.Duration) { 28 | disable := true 29 | if !disable { 30 | //bit per second 31 | // if utils.Debug() { 32 | // utils.Debugf("UpdateBDW In test begin: sentDelta = %d, sentDelay = %s", sentDelta, sentDelay.String()) 33 | // } 34 | 35 | bdw := Bandwidth(sentDelta) * Bandwidth(time.Second) / Bandwidth(sentDelay) * BytesPerSecond 36 | size := uint8(len(b.compareWindow)) 37 | startIndex := b.roundRobinIndex 38 | b.compareWindow[(startIndex)%size] = bdw 39 | 40 | // if utils.Debug() { 41 | // utils.Debugf("UpdateBDW In test: now changed compareWindow[%d] = %d, bdw = %d", startIndex, b.compareWindow[(startIndex)%size], bdw) 42 | // } 43 | 44 | b.roundRobinIndex = (b.roundRobinIndex + 1) % size 45 | // if utils.Debug() { 46 | // utils.Debugf("UpdateBDW In test: increased roundRobinIndex = %d", b.roundRobinIndex) 47 | // } 48 | for i := uint8(0); i < size; i++ { 49 | // if utils.Debug() { 50 | // utils.Debugf("UpdateBDW In test: compareWindow = %d bps", b.compareWindow[i]) 51 | // } 52 | if b.bandwidth < b.compareWindow[i] { 53 | b.bandwidth = b.compareWindow[i] 54 | } 55 | } 56 | // if utils.Debug() { 57 | // utils.Debugf("UpdateBDW In test: sentDelta %d, sentDelay %s, fullbandwidth %d Mbps", sentDelta, sentDelay.String(), b.bandwidth/1048576) 58 | // } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /rlmp-quic/congestion/clock.go: -------------------------------------------------------------------------------- 1 | package congestion 2 | 3 | import "time" 4 | 5 | // A Clock returns the current time 6 | type Clock interface { 7 | Now() time.Time 8 | } 9 | 10 | // DefaultClock implements the Clock interface using the Go stdlib clock. 11 | type DefaultClock struct{} 12 | 13 | var _ Clock = DefaultClock{} 14 | 15 | // Now gets the current time 16 | func (DefaultClock) Now() time.Time { 17 | return time.Now() 18 | } 19 | -------------------------------------------------------------------------------- /rlmp-quic/congestion/congestion_suite_test.go: -------------------------------------------------------------------------------- 1 | package congestion 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestCongestion(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Congestion Suite") 13 | } 14 | -------------------------------------------------------------------------------- /rlmp-quic/congestion/hybrid_slow_start_test.go: -------------------------------------------------------------------------------- 1 | package congestion 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | var _ = Describe("Hybrid slow start", func() { 12 | var ( 13 | slowStart HybridSlowStart 14 | ) 15 | 16 | BeforeEach(func() { 17 | slowStart = HybridSlowStart{} 18 | }) 19 | 20 | It("works in a simple case", func() { 21 | packet_number := protocol.PacketNumber(1) 22 | end_packet_number := protocol.PacketNumber(3) 23 | slowStart.StartReceiveRound(end_packet_number) 24 | 25 | packet_number++ 26 | Expect(slowStart.IsEndOfRound(packet_number)).To(BeFalse()) 27 | 28 | // Test duplicates. 29 | Expect(slowStart.IsEndOfRound(packet_number)).To(BeFalse()) 30 | 31 | packet_number++ 32 | Expect(slowStart.IsEndOfRound(packet_number)).To(BeFalse()) 33 | packet_number++ 34 | Expect(slowStart.IsEndOfRound(packet_number)).To(BeTrue()) 35 | 36 | // Test without a new registered end_packet_number; 37 | packet_number++ 38 | Expect(slowStart.IsEndOfRound(packet_number)).To(BeTrue()) 39 | 40 | end_packet_number = 20 41 | slowStart.StartReceiveRound(end_packet_number) 42 | for packet_number < end_packet_number { 43 | packet_number++ 44 | Expect(slowStart.IsEndOfRound(packet_number)).To(BeFalse()) 45 | } 46 | packet_number++ 47 | Expect(slowStart.IsEndOfRound(packet_number)).To(BeTrue()) 48 | }) 49 | 50 | It("works with delay", func() { 51 | rtt := 60 * time.Millisecond 52 | // We expect to detect the increase at +1/8 of the RTT; hence at a typical 53 | // RTT of 60ms the detection will happen at 67.5 ms. 54 | const kHybridStartMinSamples = 8 // Number of acks required to trigger. 55 | 56 | end_packet_number := protocol.PacketNumber(1) 57 | end_packet_number++ 58 | slowStart.StartReceiveRound(end_packet_number) 59 | 60 | // Will not trigger since our lowest RTT in our burst is the same as the long 61 | // term RTT provided. 62 | for n := 0; n < kHybridStartMinSamples; n++ { 63 | Expect(slowStart.ShouldExitSlowStart(rtt+time.Duration(n)*time.Millisecond, rtt, 100)).To(BeFalse()) 64 | } 65 | end_packet_number++ 66 | slowStart.StartReceiveRound(end_packet_number) 67 | for n := 1; n < kHybridStartMinSamples; n++ { 68 | Expect(slowStart.ShouldExitSlowStart(rtt+(time.Duration(n)+10)*time.Millisecond, rtt, 100)).To(BeFalse()) 69 | } 70 | // Expect to trigger since all packets in this burst was above the long term 71 | // RTT provided. 72 | Expect(slowStart.ShouldExitSlowStart(rtt+10*time.Millisecond, rtt, 100)).To(BeTrue()) 73 | }) 74 | 75 | }) 76 | -------------------------------------------------------------------------------- /rlmp-quic/congestion/interface.go: -------------------------------------------------------------------------------- 1 | package congestion 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | ) 8 | 9 | // A SendAlgorithm performs congestion control and calculates the congestion window 10 | type SendAlgorithm interface { 11 | TimeUntilSend(now time.Time, bytesInFlight protocol.ByteCount) time.Duration 12 | OnPacketSent(sentTime time.Time, bytesInFlight protocol.ByteCount, packetNumber protocol.PacketNumber, bytes protocol.ByteCount, isRetransmittable bool) bool 13 | GetCongestionWindow() protocol.ByteCount 14 | MaybeExitSlowStart() 15 | OnPacketAcked(number protocol.PacketNumber, ackedBytes protocol.ByteCount, bytesInFlight protocol.ByteCount) 16 | OnPacketLost(number protocol.PacketNumber, lostBytes protocol.ByteCount, bytesInFlight protocol.ByteCount) 17 | SetNumEmulatedConnections(n int) 18 | OnRetransmissionTimeout(packetsRetransmitted bool) 19 | OnConnectionMigration() 20 | RetransmissionDelay() time.Duration 21 | SmoothedRTT() time.Duration 22 | 23 | // Experiments 24 | SetSlowStartLargeReduction(enabled bool) 25 | } 26 | 27 | // SendAlgorithmWithDebugInfo adds some debug functions to SendAlgorithm 28 | type SendAlgorithmWithDebugInfo interface { 29 | SendAlgorithm 30 | BandwidthEstimate() Bandwidth 31 | 32 | // Stuff only used in testing 33 | 34 | HybridSlowStart() *HybridSlowStart 35 | SlowstartThreshold() protocol.PacketNumber 36 | RenoBeta() float32 37 | InRecovery() bool 38 | } 39 | -------------------------------------------------------------------------------- /rlmp-quic/congestion/olia.go: -------------------------------------------------------------------------------- 1 | package congestion 2 | 3 | import ( 4 | "github.com/lucas-clemente/quic-go/internal/protocol" 5 | "github.com/lucas-clemente/quic-go/internal/utils" 6 | ) 7 | 8 | const scale uint = 10 9 | 10 | // Olia implements the olia algorithm from MPTCP 11 | type Olia struct { 12 | // Total number of bytes acked two losses ago 13 | loss1 protocol.ByteCount 14 | // Total number of bytes acked at the last loss 15 | loss2 protocol.ByteCount 16 | // Current number of bytes acked 17 | loss3 protocol.ByteCount 18 | epsilonNum int 19 | epsilonDen uint32 20 | sndCwndCnt int 21 | // We need to keep a reference to all paths 22 | } 23 | 24 | func NewOlia(ackedBytes protocol.ByteCount) *Olia { 25 | o := &Olia{ 26 | loss1: ackedBytes, 27 | loss2: ackedBytes, 28 | loss3: ackedBytes, 29 | epsilonNum: 0, 30 | epsilonDen: 1, 31 | sndCwndCnt: 0, 32 | } 33 | return o 34 | } 35 | 36 | func oliaScale(val uint64, scale uint) uint64 { 37 | return uint64(val) << scale 38 | } 39 | 40 | func (o *Olia) Reset() { 41 | o.loss1 = 0 42 | o.loss2 = 0 43 | o.loss3 = 0 44 | o.epsilonNum = 0 45 | o.epsilonDen = 1 46 | o.sndCwndCnt = 0 47 | } 48 | 49 | func (o *Olia) SmoothedBytesBetweenLosses() protocol.ByteCount { 50 | return utils.MaxByteCount(o.loss3 - o.loss2, o.loss2 - o.loss1) 51 | } 52 | 53 | func (o *Olia) UpdateAckedSinceLastLoss(ackedBytes protocol.ByteCount) { 54 | o.loss3 += ackedBytes 55 | } 56 | 57 | func (o *Olia) OnPacketLost() { 58 | // TODO should we add so many if check? Not done here 59 | o.loss1 = o.loss2 60 | o.loss2 = o.loss3 61 | } 62 | 63 | func (o *Olia) CongestionWindowAfterAck(currentCongestionWindow protocol.PacketNumber, rate protocol.ByteCount, cwndScaled uint64) protocol.PacketNumber { 64 | newCongestionWindow := currentCongestionWindow 65 | incDen := uint64(o.epsilonDen) * uint64(currentCongestionWindow) * uint64(rate) 66 | if incDen == 0 { 67 | incDen = 1 68 | } 69 | 70 | // calculate the increasing term, scaling is used to reduce the rounding effect 71 | if o.epsilonNum == -1 { 72 | if uint64(o.epsilonDen) * cwndScaled * cwndScaled < uint64(rate) { 73 | incNum := uint64(rate) - uint64(o.epsilonDen) * cwndScaled * cwndScaled 74 | o.sndCwndCnt -= int(oliaScale(incNum, scale) / uint64(incDen)) 75 | } else { 76 | incNum := uint64(o.epsilonDen) * cwndScaled * cwndScaled - uint64(rate) 77 | o.sndCwndCnt += int(oliaScale(incNum, scale) / uint64(incDen)) 78 | } 79 | } else { 80 | incNum := uint64(o.epsilonNum) * uint64(rate) + uint64(o.epsilonDen) * cwndScaled * cwndScaled 81 | o.sndCwndCnt += int(oliaScale(incNum, scale) / uint64(incDen)) 82 | } 83 | 84 | if o.sndCwndCnt >= (1 << scale) - 1 { 85 | newCongestionWindow++ 86 | o.sndCwndCnt = 0 87 | } else if o.sndCwndCnt <= 0 - (1 << scale) + 1 { 88 | newCongestionWindow = utils.MaxPacketNumber(1, currentCongestionWindow - 1) 89 | o.sndCwndCnt = 0 90 | } 91 | return newCongestionWindow 92 | } 93 | -------------------------------------------------------------------------------- /rlmp-quic/congestion/prr_sender.go: -------------------------------------------------------------------------------- 1 | package congestion 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | "github.com/lucas-clemente/quic-go/internal/utils" 8 | ) 9 | 10 | // PrrSender implements the Proportional Rate Reduction (PRR) per RFC 6937 11 | type PrrSender struct { 12 | bytesSentSinceLoss protocol.ByteCount 13 | bytesDeliveredSinceLoss protocol.ByteCount 14 | ackCountSinceLoss protocol.ByteCount 15 | bytesInFlightBeforeLoss protocol.ByteCount 16 | } 17 | 18 | // OnPacketSent should be called after a packet was sent 19 | func (p *PrrSender) OnPacketSent(sentBytes protocol.ByteCount) { 20 | p.bytesSentSinceLoss += sentBytes 21 | } 22 | 23 | // OnPacketLost should be called on the first loss that triggers a recovery 24 | // period and all other methods in this class should only be called when in 25 | // recovery. 26 | func (p *PrrSender) OnPacketLost(bytesInFlight protocol.ByteCount) { 27 | p.bytesSentSinceLoss = 0 28 | p.bytesInFlightBeforeLoss = bytesInFlight 29 | p.bytesDeliveredSinceLoss = 0 30 | p.ackCountSinceLoss = 0 31 | } 32 | 33 | // OnPacketAcked should be called after a packet was acked 34 | func (p *PrrSender) OnPacketAcked(ackedBytes protocol.ByteCount) { 35 | p.bytesDeliveredSinceLoss += ackedBytes 36 | p.ackCountSinceLoss++ 37 | } 38 | 39 | // TimeUntilSend calculates the time until a packet can be sent 40 | func (p *PrrSender) TimeUntilSend(congestionWindow, bytesInFlight, slowstartThreshold protocol.ByteCount) time.Duration { 41 | // Return QuicTime::Zero In order to ensure limited transmit always works. 42 | if p.bytesSentSinceLoss == 0 || bytesInFlight < protocol.DefaultTCPMSS { 43 | return 0 44 | } 45 | if congestionWindow > bytesInFlight { 46 | // During PRR-SSRB, limit outgoing packets to 1 extra MSS per ack, instead 47 | // of sending the entire available window. This prevents burst retransmits 48 | // when more packets are lost than the CWND reduction. 49 | // limit = MAX(prr_delivered - prr_out, DeliveredData) + MSS 50 | if p.bytesDeliveredSinceLoss+p.ackCountSinceLoss*protocol.DefaultTCPMSS <= p.bytesSentSinceLoss { 51 | return utils.InfDuration 52 | } 53 | return 0 54 | } 55 | // Implement Proportional Rate Reduction (RFC6937). 56 | // Checks a simplified version of the PRR formula that doesn't use division: 57 | // AvailableSendWindow = 58 | // CEIL(prr_delivered * ssthresh / BytesInFlightAtLoss) - prr_sent 59 | if p.bytesDeliveredSinceLoss*slowstartThreshold > p.bytesSentSinceLoss*p.bytesInFlightBeforeLoss { 60 | return 0 61 | } 62 | return utils.InfDuration 63 | } 64 | -------------------------------------------------------------------------------- /rlmp-quic/congestion/stats.go: -------------------------------------------------------------------------------- 1 | package congestion 2 | 3 | import "github.com/lucas-clemente/quic-go/internal/protocol" 4 | 5 | type connectionStats struct { 6 | slowstartPacketsLost protocol.PacketNumber 7 | slowstartBytesLost protocol.ByteCount 8 | } 9 | -------------------------------------------------------------------------------- /rlmp-quic/conn.go: -------------------------------------------------------------------------------- 1 | package quic 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | ) 7 | 8 | type connection interface { 9 | Write([]byte) error 10 | Read([]byte) (int, net.Addr, error) 11 | Close() error 12 | LocalAddr() net.Addr 13 | RemoteAddr() net.Addr 14 | SetCurrentRemoteAddr(net.Addr) 15 | } 16 | 17 | type conn struct { 18 | mutex sync.RWMutex 19 | 20 | pconn net.PacketConn 21 | currentAddr net.Addr 22 | } 23 | 24 | var _ connection = &conn{} 25 | 26 | func (c *conn) Write(p []byte) error { 27 | _, err := c.pconn.WriteTo(p, c.currentAddr) 28 | return err 29 | } 30 | 31 | func (c *conn) Read(p []byte) (int, net.Addr, error) { 32 | return c.pconn.ReadFrom(p) 33 | } 34 | 35 | func (c *conn) SetCurrentRemoteAddr(addr net.Addr) { 36 | c.mutex.Lock() 37 | c.currentAddr = addr 38 | c.mutex.Unlock() 39 | } 40 | 41 | func (c *conn) LocalAddr() net.Addr { 42 | return c.pconn.LocalAddr() 43 | } 44 | 45 | func (c *conn) RemoteAddr() net.Addr { 46 | c.mutex.RLock() 47 | addr := c.currentAddr 48 | c.mutex.RUnlock() 49 | return addr 50 | } 51 | 52 | func (c *conn) Close() error { 53 | return c.pconn.Close() 54 | } 55 | -------------------------------------------------------------------------------- /rlmp-quic/docs/quic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vunetsys/mpquic-rl/f9f9ce7a199c359baf0d5cdee063fbd5186f82c5/rlmp-quic/docs/quic.png -------------------------------------------------------------------------------- /rlmp-quic/docs/quic.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vunetsys/mpquic-rl/f9f9ce7a199c359baf0d5cdee063fbd5186f82c5/rlmp-quic/docs/quic.sketch -------------------------------------------------------------------------------- /rlmp-quic/example/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | VOLUME /certs 4 | VOLUME /www 5 | EXPOSE 6121 6 | 7 | ADD main /main 8 | 9 | CMD ["/main", "-bind=0.0.0.0", "-certpath=/certs/", "-www=/www"] 10 | -------------------------------------------------------------------------------- /rlmp-quic/example/Readme.md: -------------------------------------------------------------------------------- 1 | # About the certificate 2 | 3 | Yes, this folder contains a private key and a certificate for quic.clemente.io. 4 | 5 | Unfortunately we need a valid certificate for the integration tests with Chrome and `quic_client`. No important data is served on the "real" `quic.clemente.io` (only a test page), and the MITM problem is imho negligible. 6 | 7 | If you figure out a way to test with Chrome without having a cert and key here, let us now in an issue. 8 | -------------------------------------------------------------------------------- /rlmp-quic/example/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "io" 7 | "log" 8 | "net/http" 9 | "os" 10 | "sync" 11 | 12 | quic "github.com/lucas-clemente/quic-go" 13 | 14 | "github.com/lucas-clemente/quic-go/h2quic" 15 | "github.com/lucas-clemente/quic-go/internal/utils" 16 | ) 17 | 18 | func main() { 19 | verbose := flag.Bool("v", false, "verbose") 20 | multipath := flag.Bool("m", false, "multipath") 21 | output := flag.String("o", "", "logging output") 22 | flag.Parse() 23 | urls := flag.Args() 24 | 25 | if *verbose { 26 | utils.SetLogLevel(utils.LogLevelDebug) 27 | } else { 28 | utils.SetLogLevel(utils.LogLevelInfo) 29 | } 30 | utils.SetLogTimeFormat("") 31 | 32 | if *output != "" { 33 | logfile, err := os.Create(*output) 34 | if err != nil { 35 | panic(err) 36 | } 37 | defer logfile.Close() 38 | log.SetOutput(logfile) 39 | } 40 | 41 | quicConfig := &quic.Config{ 42 | CreatePaths: *multipath, 43 | } 44 | 45 | hclient := &http.Client{ 46 | Transport: &h2quic.RoundTripper{QuicConfig: quicConfig}, 47 | } 48 | 49 | var wg sync.WaitGroup 50 | wg.Add(len(urls)) 51 | for _, addr := range urls { 52 | utils.Infof("GET %s", addr) 53 | go func(addr string) { 54 | rsp, err := hclient.Get(addr) 55 | if err != nil { 56 | panic(err) 57 | } 58 | utils.Infof("Got response for %s: %#v", addr, rsp) 59 | 60 | body := &bytes.Buffer{} 61 | _, err = io.Copy(body, rsp.Body) 62 | if err != nil { 63 | panic(err) 64 | } 65 | utils.Infof("Request Body:") 66 | utils.Infof("%s", body.Bytes()) 67 | wg.Done() 68 | }(addr) 69 | } 70 | wg.Wait() 71 | } 72 | -------------------------------------------------------------------------------- /rlmp-quic/example/client_benchmarker/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestMainProgram(t *testing.T) { 9 | os.Args = []string{"./main -m -v -c", 10 | " https://10.1.0.1:6121/random1 200 0", 11 | " https://10.1.0.1:6121/random2 10 5"} 12 | main() 13 | } 14 | -------------------------------------------------------------------------------- /rlmp-quic/example/client_benchmarker_cached/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "flag" 7 | "io" 8 | "log" 9 | "net/http" 10 | "os" 11 | "strings" 12 | "sync" 13 | "time" 14 | 15 | quic "github.com/lucas-clemente/quic-go" 16 | 17 | "github.com/lucas-clemente/quic-go/h2quic" 18 | "github.com/lucas-clemente/quic-go/internal/utils" 19 | ) 20 | 21 | func main() { 22 | verbose := flag.Bool("v", false, "verbose") 23 | multipath := flag.Bool("m", false, "multipath") 24 | output := flag.String("o", "", "logging output") 25 | flag.Parse() 26 | urls := flag.Args() 27 | hostnameDelimiter := strings.LastIndex(urls[0], "/") 28 | hostname := urls[0][:hostnameDelimiter] 29 | 30 | if *verbose { 31 | utils.SetLogLevel(utils.LogLevelDebug) 32 | } else { 33 | utils.SetLogLevel(utils.LogLevelInfo) 34 | } 35 | 36 | if *output != "" { 37 | logfile, err := os.Create(*output) 38 | if err != nil { 39 | panic(err) 40 | } 41 | defer logfile.Close() 42 | log.SetOutput(logfile) 43 | } 44 | 45 | quicConfig := &quic.Config{ 46 | CreatePaths: *multipath, 47 | CacheHandshake: true, 48 | } 49 | 50 | hclient := &http.Client{ 51 | Transport: &h2quic.RoundTripper{QuicConfig: quicConfig, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, 52 | } 53 | 54 | var wg sync.WaitGroup 55 | // First pass: cache crypto handshake info 56 | wg.Add(1) 57 | go func() { 58 | rsp, err := hclient.Get(hostname + "/hziodhozdouhozahoazhd") 59 | if err != nil { 60 | panic(err) 61 | } 62 | 63 | body := &bytes.Buffer{} 64 | _, err = io.Copy(body, rsp.Body) 65 | if err != nil { 66 | panic(err) 67 | } 68 | wg.Done() 69 | }() 70 | wg.Wait() 71 | 72 | hclient = &http.Client{ 73 | Transport: &h2quic.RoundTripper{QuicConfig: quicConfig, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, 74 | } 75 | 76 | // Now perform connections in 1-RTT 77 | wg.Add(len(urls)) 78 | for _, addr := range urls { 79 | utils.Infof("GET %s", addr) 80 | go func(addr string) { 81 | start := time.Now() 82 | rsp, err := hclient.Get(addr) 83 | if err != nil { 84 | panic(err) 85 | } 86 | 87 | body := &bytes.Buffer{} 88 | _, err = io.Copy(body, rsp.Body) 89 | if err != nil { 90 | panic(err) 91 | } 92 | elapsed := time.Since(start) 93 | utils.Infof("%s", elapsed) 94 | wg.Done() 95 | }(addr) 96 | } 97 | wg.Wait() 98 | } 99 | -------------------------------------------------------------------------------- /rlmp-quic/example/client_browse_deptree/client_browse_deptree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vunetsys/mpquic-rl/f9f9ce7a199c359baf0d5cdee063fbd5186f82c5/rlmp-quic/example/client_browse_deptree/client_browse_deptree -------------------------------------------------------------------------------- /rlmp-quic/example/client_browse_wprof/browse_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | ) 8 | 9 | func TestMainProgram(t *testing.T) { 10 | absPath, _ := filepath.Abs("/Users/shixiang/Desktop/request_website_code/epload/emulator/tests/test.json") 11 | os.Args = []string{"./web_browse -m -v -c ", 12 | absPath} 13 | main() 14 | } 15 | -------------------------------------------------------------------------------- /rlmp-quic/example/client_browse_wprof/web_browse: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vunetsys/mpquic-rl/f9f9ce7a199c359baf0d5cdee063fbd5186f82c5/rlmp-quic/example/client_browse_wprof/web_browse -------------------------------------------------------------------------------- /rlmp-quic/example/echo/echo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "encoding/pem" 9 | "fmt" 10 | "io" 11 | "log" 12 | "math/big" 13 | 14 | quic "github.com/lucas-clemente/quic-go" 15 | ) 16 | 17 | const addr = "localhost:4242" 18 | 19 | const message = "foobar" 20 | 21 | // We start a server echoing data on the first stream the client opens, 22 | // then connect with a client, send the message, and wait for its receipt. 23 | func main() { 24 | go func() { log.Fatal(echoServer()) }() 25 | 26 | err := clientMain() 27 | if err != nil { 28 | panic(err) 29 | } 30 | } 31 | 32 | // Start a server that echos all data on the first stream opened by the client 33 | func echoServer() error { 34 | listener, err := quic.ListenAddr(addr, generateTLSConfig(), nil) 35 | if err != nil { 36 | return err 37 | } 38 | sess, err := listener.Accept() 39 | if err != nil { 40 | return err 41 | } 42 | stream, err := sess.AcceptStream() 43 | if err != nil { 44 | panic(err) 45 | } 46 | // Echo through the loggingWriter 47 | _, err = io.Copy(loggingWriter{stream}, stream) 48 | return err 49 | } 50 | 51 | func clientMain() error { 52 | session, err := quic.DialAddr(addr, &tls.Config{InsecureSkipVerify: true}, nil) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | stream, err := session.OpenStreamSync() 58 | if err != nil { 59 | return err 60 | } 61 | 62 | fmt.Printf("Client: Sending '%s'\n", message) 63 | _, err = stream.Write([]byte(message)) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | buf := make([]byte, len(message)) 69 | _, err = io.ReadFull(stream, buf) 70 | if err != nil { 71 | return err 72 | } 73 | fmt.Printf("Client: Got '%s'\n", buf) 74 | 75 | return nil 76 | } 77 | 78 | // A wrapper for io.Writer that also logs the message. 79 | type loggingWriter struct{ io.Writer } 80 | 81 | func (w loggingWriter) Write(b []byte) (int, error) { 82 | fmt.Printf("Server: Got '%s'\n", string(b)) 83 | return w.Writer.Write(b) 84 | } 85 | 86 | // Setup a bare-bones TLS config for the server 87 | func generateTLSConfig() *tls.Config { 88 | key, err := rsa.GenerateKey(rand.Reader, 1024) 89 | if err != nil { 90 | panic(err) 91 | } 92 | template := x509.Certificate{SerialNumber: big.NewInt(1)} 93 | certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) 94 | if err != nil { 95 | panic(err) 96 | } 97 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) 98 | certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) 99 | 100 | tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) 101 | if err != nil { 102 | panic(err) 103 | } 104 | return &tls.Config{Certificates: []tls.Certificate{tlsCert}} 105 | } 106 | -------------------------------------------------------------------------------- /rlmp-quic/example/privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4xixIS9iBFy/k 3 | N8AxF1AKFQqK06aWQ4Sjku677az6on2q8gDCIiup7uaAiyUzmSgPRZRrcBESApw7 4 | w15XN/K17ZQb4Bw7Xp0O7rzKhtwH8ugz+Qs8ceK4ayKTCT/PoPmKnr+sL9Soo0LJ 5 | 8XFvNdY/v3cq6eScnqztOGJV3RSAd34e/EBVkiSOIeXgu8rjtGOxSuuWS52n6NBx 6 | uZrSjP/JClqV26I1NyH+psfoO+zZ8/cJUoC1clalsDhDfX33rPEHJ4KOvB6rAAh9 7 | VcFQtTfuOTIySzPuPMfarhq14lbt+pRjvEMru1uaQYL+t/OWrX/tLc/990G/rVmv 8 | Xfed9/yDAgMBAAECggEAN48PHaYAscBBHERPO/Ogk4eEJf5CJwiiR3UU59ktnCdj 9 | 1hTyeW1A59X35UrxorQ4wW7QlAWcfGfghm/WXC9sgZuwXzliA9ANNcI/bj5ixtkZ 10 | TRdjc4di/sToHoI3d70Vi8L0K1guf46ntIUu8JulkoGF2Zd+sEFeCe5cUyko0v+Y 11 | SGd1WHmMm526Iw07FkS642Vqdx9DcQZNeSTi8girFGOoLEWkP5JgrUqoLvokBPDW 12 | c/sa4zLsscFCYwafG2olQZLrHlyXlMv6BRKWa/5T62DF1+uVatYOgQgH9/RIu9Jh 13 | jmMgxnA+enXOyGdj/b0lYm0uV/IEUyDtPMSeYDAgcQKBgQDgGX2WQANbxHvvOwO+ 14 | 0radIIsxvQBsO/fyc0NaKNXVgGBfoXomg4iLb2qkDHQkVQS4xiOwfZ6PZxLTa1eT 15 | wZPL749qqPPEwuIJYiJ+6fNNhqLb/qC4UbIQpG7yb+G7O6yjPwnLmHoX9qfcF4He 16 | NojNHB9ltQIe3SgGG96CG3maZwKBgQDTE5s4xyH1OkaPnm0dCgEqDHHpcNLqVNH+ 17 | gSwf/eBBDmclv26Y6HGa9GrPeVSk9kGxF4n5AXm57tnTeMFc8nNzCqcgvaTwRahq 18 | 6ur79PJmKRDTX6/RWK8Srh7Sox5L7jr1Dn/qyOT2579B9eovZRCthZjw21XJGjW2 19 | i5nrdQ3zhQKBgQCjqToUns9VF5vDTQAhPlXrTrcZLgS/BtS/lfocQDJaaBT6Aj3p 20 | Hqp72nSxNf8kAYsfPmUWIcfIxufyyzP8TqUXjO7aYGUWz5SwcaDruwPbHHaX38+U 21 | jOVUTiJQn/DlAmHEHueSbtrL4XEZxXksxfsGgIFVj+nqjG0MeRH5RwN6BQKBgBdv 22 | GdCX6yE6sxLG1/5dWfu9Hfh42jHB8P58gNWcbgVLABCkzDaVt+coM6ONKOSXonty 23 | zZKjo0wNRInB4lXbZQ3kpOFxrJowYZ5dLnGCpFbLQF73RKHNYsKEKk/gZECx1kHW 24 | tkTuwNzYpddA4hsY8V0SdARplYCaNFRr80681CuxAoGASqAbFhJk6YhvBcAEDZit 25 | h92qsH1GCYdqsnbvnJtZIDYch+uyURNnO7sjomm8NkAqjvlC+YfvPdEXUliXfdg9 26 | MkwkZJrCwH/RlHg429agimrUnsFdO26rxjzLFBaUOrfAkYru3YvpHbpjVTTt7O0C 27 | 7QNPy6556B/57VNoAToQOhM= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /rlmp-quic/example/server_main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestMainProgram(t *testing.T) { 9 | os.Args = []string{"./main -www .", 10 | " -certpath ~/go/src/github.com/lucas-clemente/quic-go/example/", 11 | " -bind 0.0.0.0:6121"} 12 | main() 13 | } 14 | -------------------------------------------------------------------------------- /rlmp-quic/h2quic/gzipreader.go: -------------------------------------------------------------------------------- 1 | package h2quic 2 | 3 | // copied from net/transport.go 4 | 5 | // gzipReader wraps a response body so it can lazily 6 | // call gzip.NewReader on the first call to Read 7 | import ( 8 | "compress/gzip" 9 | "io" 10 | ) 11 | 12 | // call gzip.NewReader on the first call to Read 13 | type gzipReader struct { 14 | body io.ReadCloser // underlying Response.Body 15 | zr *gzip.Reader // lazily-initialized gzip reader 16 | zerr error // sticky error 17 | } 18 | 19 | func (gz *gzipReader) Read(p []byte) (n int, err error) { 20 | if gz.zerr != nil { 21 | return 0, gz.zerr 22 | } 23 | if gz.zr == nil { 24 | gz.zr, err = gzip.NewReader(gz.body) 25 | if err != nil { 26 | gz.zerr = err 27 | return 0, err 28 | } 29 | } 30 | return gz.zr.Read(p) 31 | } 32 | 33 | func (gz *gzipReader) Close() error { 34 | return gz.body.Close() 35 | } 36 | -------------------------------------------------------------------------------- /rlmp-quic/h2quic/h2quic_suite_test.go: -------------------------------------------------------------------------------- 1 | package h2quic 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestH2quic(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "H2quic Suite") 13 | } 14 | -------------------------------------------------------------------------------- /rlmp-quic/h2quic/request.go: -------------------------------------------------------------------------------- 1 | package h2quic 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "net/http" 7 | "net/url" 8 | "strconv" 9 | "strings" 10 | 11 | "golang.org/x/net/http2/hpack" 12 | ) 13 | 14 | func requestFromHeaders(headers []hpack.HeaderField) (*http.Request, error) { 15 | var path, authority, method, contentLengthStr string 16 | httpHeaders := http.Header{} 17 | 18 | for _, h := range headers { 19 | switch h.Name { 20 | case ":path": 21 | path = h.Value 22 | case ":method": 23 | method = h.Value 24 | case ":authority": 25 | authority = h.Value 26 | case "content-length": 27 | contentLengthStr = h.Value 28 | default: 29 | if !h.IsPseudo() { 30 | httpHeaders.Add(h.Name, h.Value) 31 | } 32 | } 33 | } 34 | 35 | // concatenate cookie headers, see https://tools.ietf.org/html/rfc6265#section-5.4 36 | if len(httpHeaders["Cookie"]) > 0 { 37 | httpHeaders.Set("Cookie", strings.Join(httpHeaders["Cookie"], "; ")) 38 | } 39 | 40 | if len(path) == 0 || len(authority) == 0 || len(method) == 0 { 41 | return nil, errors.New(":path, :authority and :method must not be empty") 42 | } 43 | 44 | u, err := url.Parse(path) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | var contentLength int64 50 | if len(contentLengthStr) > 0 { 51 | contentLength, err = strconv.ParseInt(contentLengthStr, 10, 64) 52 | if err != nil { 53 | return nil, err 54 | } 55 | } 56 | 57 | return &http.Request{ 58 | Method: method, 59 | URL: u, 60 | Proto: "HTTP/2.0", 61 | ProtoMajor: 2, 62 | ProtoMinor: 0, 63 | Header: httpHeaders, 64 | Body: nil, 65 | ContentLength: contentLength, 66 | Host: authority, 67 | RequestURI: path, 68 | TLS: &tls.ConnectionState{}, 69 | }, nil 70 | } 71 | 72 | func hostnameFromRequest(req *http.Request) string { 73 | if len(req.Host) > 0 { 74 | return req.Host 75 | } 76 | if req.URL != nil { 77 | return req.URL.Host 78 | } 79 | return "" 80 | } 81 | -------------------------------------------------------------------------------- /rlmp-quic/h2quic/request_body.go: -------------------------------------------------------------------------------- 1 | package h2quic 2 | 3 | import ( 4 | "io" 5 | 6 | quic "github.com/lucas-clemente/quic-go" 7 | ) 8 | 9 | type requestBody struct { 10 | requestRead bool 11 | dataStream quic.Stream 12 | } 13 | 14 | // make sure the requestBody can be used as a http.Request.Body 15 | var _ io.ReadCloser = &requestBody{} 16 | 17 | func newRequestBody(stream quic.Stream) *requestBody { 18 | return &requestBody{dataStream: stream} 19 | } 20 | 21 | func (b *requestBody) Read(p []byte) (int, error) { 22 | b.requestRead = true 23 | return b.dataStream.Read(p) 24 | } 25 | 26 | func (b *requestBody) Close() error { 27 | // stream's Close() closes the write side, not the read side 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /rlmp-quic/h2quic/request_body_test.go: -------------------------------------------------------------------------------- 1 | package h2quic 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("Request body", func() { 9 | var ( 10 | stream *mockStream 11 | rb *requestBody 12 | ) 13 | 14 | BeforeEach(func() { 15 | stream = &mockStream{} 16 | stream.dataToRead.Write([]byte("foobar")) // provides data to be read 17 | rb = newRequestBody(stream) 18 | }) 19 | 20 | It("reads from the stream", func() { 21 | b := make([]byte, 10) 22 | n, _ := stream.Read(b) 23 | Expect(n).To(Equal(6)) 24 | Expect(b[0:6]).To(Equal([]byte("foobar"))) 25 | }) 26 | 27 | It("saves if the stream was read from", func() { 28 | Expect(rb.requestRead).To(BeFalse()) 29 | rb.Read(make([]byte, 1)) 30 | Expect(rb.requestRead).To(BeTrue()) 31 | }) 32 | 33 | It("doesn't close the stream when closing the request body", func() { 34 | Expect(stream.closed).To(BeFalse()) 35 | err := rb.Close() 36 | Expect(err).ToNot(HaveOccurred()) 37 | Expect(stream.closed).To(BeFalse()) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /rlmp-quic/integrationtests/chrome/chrome_test.go: -------------------------------------------------------------------------------- 1 | package chrome_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | 8 | . "github.com/onsi/ginkgo" 9 | ) 10 | 11 | var _ = Describe("Chrome tests", func() { 12 | for i := range protocol.SupportedVersions { 13 | version = protocol.SupportedVersions[i] 14 | 15 | Context(fmt.Sprintf("with quic version %s", version), func() { 16 | It("downloads a small file", func() { 17 | chromeTest( 18 | version, 19 | fmt.Sprintf("https://quic.clemente.io/downloadtest?num=1&len=%d", dataLen), 20 | waitForDone, 21 | ) 22 | }) 23 | 24 | It("downloads a large file", func() { 25 | chromeTest( 26 | version, 27 | fmt.Sprintf("https://quic.clemente.io/downloadtest?num=1&len=%d", dataLongLen), 28 | waitForDone, 29 | ) 30 | }) 31 | 32 | It("loads a large number of files", func() { 33 | chromeTest( 34 | version, 35 | "https://quic.clemente.io/downloadtest?num=4&len=100", 36 | waitForDone, 37 | ) 38 | }) 39 | 40 | It("uploads a small file", func() { 41 | chromeTest( 42 | version, 43 | fmt.Sprintf("https://quic.clemente.io/uploadtest?num=1&len=%d", dataLen), 44 | waitForNUploaded(1), 45 | ) 46 | }) 47 | 48 | It("uploads a large file", func() { 49 | chromeTest( 50 | version, 51 | fmt.Sprintf("https://quic.clemente.io/uploadtest?num=1&len=%d", dataLongLen), 52 | waitForNUploaded(1), 53 | ) 54 | }) 55 | 56 | It("uploads many small files", func() { 57 | num := protocol.MaxStreamsPerConnection + 20 58 | chromeTest( 59 | version, 60 | fmt.Sprintf("https://quic.clemente.io/uploadtest?num=%d&len=%d", num, dataLen), 61 | waitForNUploaded(num), 62 | ) 63 | }) 64 | }) 65 | } 66 | }) 67 | -------------------------------------------------------------------------------- /rlmp-quic/integrationtests/gquic/gquic_suite_test.go: -------------------------------------------------------------------------------- 1 | package gquic_test 2 | 3 | import ( 4 | "fmt" 5 | mrand "math/rand" 6 | "path/filepath" 7 | "runtime" 8 | 9 | _ "github.com/lucas-clemente/quic-go/integrationtests/tools/testlog" 10 | "github.com/lucas-clemente/quic-go/integrationtests/tools/testserver" 11 | 12 | . "github.com/onsi/ginkgo" 13 | . "github.com/onsi/gomega" 14 | 15 | "testing" 16 | ) 17 | 18 | var ( 19 | clientPath string 20 | serverPath string 21 | ) 22 | 23 | func TestIntegration(t *testing.T) { 24 | RegisterFailHandler(Fail) 25 | RunSpecs(t, "GQuic Tests Suite") 26 | } 27 | 28 | var _ = BeforeSuite(func() { 29 | mrand.Seed(GinkgoRandomSeed()) 30 | }) 31 | 32 | var _ = JustBeforeEach(func() { 33 | testserver.StartQuicServer(nil) 34 | }) 35 | 36 | var _ = AfterEach(testserver.StopQuicServer) 37 | 38 | func init() { 39 | _, thisfile, _, ok := runtime.Caller(0) 40 | if !ok { 41 | panic("Failed to get current path") 42 | } 43 | clientPath = filepath.Join(thisfile, fmt.Sprintf("../../../../quic-clients/client-%s-debug", runtime.GOOS)) 44 | serverPath = filepath.Join(thisfile, fmt.Sprintf("../../../../quic-clients/server-%s-debug", runtime.GOOS)) 45 | } 46 | -------------------------------------------------------------------------------- /rlmp-quic/integrationtests/gquic/rtt_test.go: -------------------------------------------------------------------------------- 1 | package gquic_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os/exec" 7 | "strconv" 8 | "time" 9 | 10 | _ "github.com/lucas-clemente/quic-clients" // download clients 11 | "github.com/lucas-clemente/quic-go/integrationtests/tools/proxy" 12 | "github.com/lucas-clemente/quic-go/integrationtests/tools/testserver" 13 | "github.com/lucas-clemente/quic-go/internal/protocol" 14 | 15 | . "github.com/onsi/ginkgo" 16 | . "github.com/onsi/gomega" 17 | . "github.com/onsi/gomega/gexec" 18 | ) 19 | 20 | var _ = Describe("non-zero RTT", func() { 21 | var proxy *quicproxy.QuicProxy 22 | 23 | runRTTTest := func(rtt time.Duration, version protocol.VersionNumber) { 24 | var err error 25 | proxy, err = quicproxy.NewQuicProxy("localhost:", version, &quicproxy.Opts{ 26 | RemoteAddr: "localhost:" + testserver.Port(), 27 | DelayPacket: func(_ quicproxy.Direction, _ uint64) time.Duration { 28 | return rtt / 2 29 | }, 30 | }) 31 | Expect(err).ToNot(HaveOccurred()) 32 | 33 | command := exec.Command( 34 | clientPath, 35 | "--quic-version="+strconv.Itoa(int(version)), 36 | "--host=127.0.0.1", 37 | "--port="+strconv.Itoa(proxy.LocalPort()), 38 | "https://quic.clemente.io/prdata", 39 | ) 40 | 41 | session, err := Start(command, nil, GinkgoWriter) 42 | Expect(err).NotTo(HaveOccurred()) 43 | defer session.Kill() 44 | Eventually(session, 20).Should(Exit(0)) 45 | Expect(bytes.Contains(session.Out.Contents(), testserver.PRData)).To(BeTrue()) 46 | } 47 | 48 | AfterEach(func() { 49 | err := proxy.Close() 50 | Expect(err).ToNot(HaveOccurred()) 51 | time.Sleep(time.Millisecond) 52 | }) 53 | 54 | for i := range protocol.SupportedVersions { 55 | version := protocol.SupportedVersions[i] 56 | 57 | Context(fmt.Sprintf("with QUIC version %s", version), func() { 58 | roundTrips := [...]int{10, 50, 100, 200} 59 | for _, rtt := range roundTrips { 60 | It(fmt.Sprintf("gets a 500kB file with %dms RTT", rtt), func() { 61 | runRTTTest(time.Duration(rtt)*time.Millisecond, version) 62 | }) 63 | } 64 | }) 65 | } 66 | }) 67 | -------------------------------------------------------------------------------- /rlmp-quic/integrationtests/self/self_suite_test.go: -------------------------------------------------------------------------------- 1 | package self_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | _ "github.com/lucas-clemente/quic-go/integrationtests/tools/testlog" 8 | 9 | "testing" 10 | ) 11 | 12 | func TestSelf(t *testing.T) { 13 | RegisterFailHandler(Fail) 14 | RunSpecs(t, "Self integration tests") 15 | } 16 | -------------------------------------------------------------------------------- /rlmp-quic/integrationtests/tools/proxy/proxy_suite_test.go: -------------------------------------------------------------------------------- 1 | package quicproxy 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestQuicGo(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "QUIC Proxy") 13 | } 14 | -------------------------------------------------------------------------------- /rlmp-quic/integrationtests/tools/testlog/testlog.go: -------------------------------------------------------------------------------- 1 | package testlog 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | 8 | "github.com/lucas-clemente/quic-go/internal/utils" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | var ( 15 | logFileName string // the log file set in the ginkgo flags 16 | logFile *os.File 17 | ) 18 | 19 | // read the logfile command line flag 20 | // to set call ginkgo -- -logfile=log.txt 21 | func init() { 22 | flag.StringVar(&logFileName, "logfile", "", "log file") 23 | } 24 | 25 | var _ = BeforeEach(func() { 26 | log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds) 27 | 28 | if len(logFileName) > 0 { 29 | var err error 30 | logFile, err = os.Create(logFileName) 31 | Expect(err).ToNot(HaveOccurred()) 32 | log.SetOutput(logFile) 33 | utils.SetLogLevel(utils.LogLevelDebug) 34 | } 35 | }) 36 | 37 | var _ = AfterEach(func() { 38 | if len(logFileName) > 0 { 39 | _ = logFile.Close() 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/AEAD.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import "github.com/lucas-clemente/quic-go/internal/protocol" 4 | 5 | // An AEAD implements QUIC's authenticated encryption and associated data 6 | type AEAD interface { 7 | Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) 8 | Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte 9 | Overhead() int 10 | } 11 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/aesgcm12_aead.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto/cipher" 5 | "encoding/binary" 6 | "errors" 7 | 8 | "github.com/lucas-clemente/aes12" 9 | 10 | "github.com/lucas-clemente/quic-go/internal/protocol" 11 | ) 12 | 13 | type aeadAESGCM12 struct { 14 | otherIV []byte 15 | myIV []byte 16 | encrypter cipher.AEAD 17 | decrypter cipher.AEAD 18 | } 19 | 20 | var _ AEAD = &aeadAESGCM12{} 21 | 22 | // NewAEADAESGCM12 creates a AEAD using AES-GCM with 12 bytes tag size 23 | // 24 | // AES-GCM support is a bit hacky, since the go stdlib does not support 12 byte 25 | // tag size, and couples the cipher and aes packages closely. 26 | // See https://github.com/lucas-clemente/aes12. 27 | func NewAEADAESGCM12(otherKey []byte, myKey []byte, otherIV []byte, myIV []byte) (AEAD, error) { 28 | if len(myKey) != 16 || len(otherKey) != 16 || len(myIV) != 4 || len(otherIV) != 4 { 29 | return nil, errors.New("AES-GCM: expected 16-byte keys and 4-byte IVs") 30 | } 31 | encrypterCipher, err := aes12.NewCipher(myKey) 32 | if err != nil { 33 | return nil, err 34 | } 35 | encrypter, err := aes12.NewGCM(encrypterCipher) 36 | if err != nil { 37 | return nil, err 38 | } 39 | decrypterCipher, err := aes12.NewCipher(otherKey) 40 | if err != nil { 41 | return nil, err 42 | } 43 | decrypter, err := aes12.NewGCM(decrypterCipher) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return &aeadAESGCM12{ 48 | otherIV: otherIV, 49 | myIV: myIV, 50 | encrypter: encrypter, 51 | decrypter: decrypter, 52 | }, nil 53 | } 54 | 55 | func (aead *aeadAESGCM12) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) { 56 | return aead.decrypter.Open(dst, aead.makeNonce(aead.otherIV, packetNumber), src, associatedData) 57 | } 58 | 59 | func (aead *aeadAESGCM12) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { 60 | return aead.encrypter.Seal(dst, aead.makeNonce(aead.myIV, packetNumber), src, associatedData) 61 | } 62 | 63 | func (aead *aeadAESGCM12) makeNonce(iv []byte, packetNumber protocol.PacketNumber) []byte { 64 | res := make([]byte, 12) 65 | copy(res[0:4], iv) 66 | binary.LittleEndian.PutUint64(res[4:12], uint64(packetNumber)) 67 | return res 68 | } 69 | 70 | func (aead *aeadAESGCM12) Overhead() int { 71 | return aead.encrypter.Overhead() 72 | } 73 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/aesgcm12_aead_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto/rand" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("AES-GCM", func() { 11 | var ( 12 | alice, bob AEAD 13 | keyAlice, keyBob, ivAlice, ivBob []byte 14 | ) 15 | 16 | BeforeEach(func() { 17 | keyAlice = make([]byte, 16) 18 | keyBob = make([]byte, 16) 19 | ivAlice = make([]byte, 4) 20 | ivBob = make([]byte, 4) 21 | rand.Reader.Read(keyAlice) 22 | rand.Reader.Read(keyBob) 23 | rand.Reader.Read(ivAlice) 24 | rand.Reader.Read(ivBob) 25 | var err error 26 | alice, err = NewAEADAESGCM12(keyBob, keyAlice, ivBob, ivAlice) 27 | Expect(err).ToNot(HaveOccurred()) 28 | bob, err = NewAEADAESGCM12(keyAlice, keyBob, ivAlice, ivBob) 29 | Expect(err).ToNot(HaveOccurred()) 30 | }) 31 | 32 | It("seals and opens", func() { 33 | b := alice.Seal(nil, []byte("foobar"), 42, []byte("aad")) 34 | text, err := bob.Open(nil, b, 42, []byte("aad")) 35 | Expect(err).ToNot(HaveOccurred()) 36 | Expect(text).To(Equal([]byte("foobar"))) 37 | }) 38 | 39 | It("seals and opens reverse", func() { 40 | b := bob.Seal(nil, []byte("foobar"), 42, []byte("aad")) 41 | text, err := alice.Open(nil, b, 42, []byte("aad")) 42 | Expect(err).ToNot(HaveOccurred()) 43 | Expect(text).To(Equal([]byte("foobar"))) 44 | }) 45 | 46 | It("has the proper length", func() { 47 | b := bob.Seal(nil, []byte("foobar"), 42, []byte("aad")) 48 | Expect(b).To(HaveLen(6 + bob.Overhead())) 49 | }) 50 | 51 | It("fails with wrong aad", func() { 52 | b := alice.Seal(nil, []byte("foobar"), 42, []byte("aad")) 53 | _, err := bob.Open(nil, b, 42, []byte("aad2")) 54 | Expect(err).To(HaveOccurred()) 55 | }) 56 | 57 | It("rejects wrong key and iv sizes", func() { 58 | var err error 59 | e := "AES-GCM: expected 16-byte keys and 4-byte IVs" 60 | _, err = NewAEADAESGCM12(keyBob[1:], keyAlice, ivBob, ivAlice) 61 | Expect(err).To(MatchError(e)) 62 | _, err = NewAEADAESGCM12(keyBob, keyAlice[1:], ivBob, ivAlice) 63 | Expect(err).To(MatchError(e)) 64 | _, err = NewAEADAESGCM12(keyBob, keyAlice, ivBob[1:], ivAlice) 65 | Expect(err).To(MatchError(e)) 66 | _, err = NewAEADAESGCM12(keyBob, keyAlice, ivBob, ivAlice[1:]) 67 | Expect(err).To(MatchError(e)) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/aesgcm_aead.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "encoding/binary" 7 | "errors" 8 | 9 | "github.com/lucas-clemente/quic-go/internal/protocol" 10 | ) 11 | 12 | type aeadAESGCM struct { 13 | otherIV []byte 14 | myIV []byte 15 | encrypter cipher.AEAD 16 | decrypter cipher.AEAD 17 | } 18 | 19 | var _ AEAD = &aeadAESGCM{} 20 | 21 | const ivLen = 12 22 | 23 | // NewAEADAESGCM creates a AEAD using AES-GCM 24 | func NewAEADAESGCM(otherKey []byte, myKey []byte, otherIV []byte, myIV []byte) (AEAD, error) { 25 | // the IVs need to be at least 8 bytes long, otherwise we can't compute the nonce 26 | if len(otherIV) != ivLen || len(myIV) != ivLen { 27 | return nil, errors.New("AES-GCM: expected 12 byte IVs") 28 | } 29 | 30 | encrypterCipher, err := aes.NewCipher(myKey) 31 | if err != nil { 32 | return nil, err 33 | } 34 | encrypter, err := cipher.NewGCM(encrypterCipher) 35 | if err != nil { 36 | return nil, err 37 | } 38 | decrypterCipher, err := aes.NewCipher(otherKey) 39 | if err != nil { 40 | return nil, err 41 | } 42 | decrypter, err := cipher.NewGCM(decrypterCipher) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | return &aeadAESGCM{ 48 | otherIV: otherIV, 49 | myIV: myIV, 50 | encrypter: encrypter, 51 | decrypter: decrypter, 52 | }, nil 53 | } 54 | 55 | func (aead *aeadAESGCM) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) { 56 | return aead.decrypter.Open(dst, aead.makeNonce(aead.otherIV, packetNumber), src, associatedData) 57 | } 58 | 59 | func (aead *aeadAESGCM) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { 60 | return aead.encrypter.Seal(dst, aead.makeNonce(aead.myIV, packetNumber), src, associatedData) 61 | } 62 | 63 | func (aead *aeadAESGCM) makeNonce(iv []byte, packetNumber protocol.PacketNumber) []byte { 64 | nonce := make([]byte, ivLen) 65 | binary.BigEndian.PutUint64(nonce[ivLen-8:], uint64(packetNumber)) 66 | for i := 0; i < ivLen; i++ { 67 | nonce[i] ^= iv[i] 68 | } 69 | return nonce 70 | } 71 | 72 | func (aead *aeadAESGCM) Overhead() int { 73 | return aead.encrypter.Overhead() 74 | } 75 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/aesgcm_aead_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | var _ = Describe("AES-GCM", func() { 12 | var ( 13 | alice, bob AEAD 14 | keyAlice, keyBob, ivAlice, ivBob []byte 15 | ) 16 | 17 | BeforeEach(func() { 18 | ivAlice = make([]byte, 12) 19 | ivBob = make([]byte, 12) 20 | }) 21 | 22 | // 16 bytes for TLS_AES_128_GCM_SHA256 23 | // 32 bytes for TLS_AES_256_GCM_SHA384 24 | for _, ks := range []int{16, 32} { 25 | keySize := ks 26 | 27 | Context(fmt.Sprintf("with %d byte keys", keySize), func() { 28 | BeforeEach(func() { 29 | keyAlice = make([]byte, keySize) 30 | keyBob = make([]byte, keySize) 31 | rand.Reader.Read(keyAlice) 32 | rand.Reader.Read(keyBob) 33 | rand.Reader.Read(ivAlice) 34 | rand.Reader.Read(ivBob) 35 | var err error 36 | alice, err = NewAEADAESGCM(keyBob, keyAlice, ivBob, ivAlice) 37 | Expect(err).ToNot(HaveOccurred()) 38 | bob, err = NewAEADAESGCM(keyAlice, keyBob, ivAlice, ivBob) 39 | Expect(err).ToNot(HaveOccurred()) 40 | }) 41 | 42 | It("seals and opens", func() { 43 | b := alice.Seal(nil, []byte("foobar"), 42, []byte("aad")) 44 | text, err := bob.Open(nil, b, 42, []byte("aad")) 45 | Expect(err).ToNot(HaveOccurred()) 46 | Expect(text).To(Equal([]byte("foobar"))) 47 | }) 48 | 49 | It("seals and opens reverse", func() { 50 | b := bob.Seal(nil, []byte("foobar"), 42, []byte("aad")) 51 | text, err := alice.Open(nil, b, 42, []byte("aad")) 52 | Expect(err).ToNot(HaveOccurred()) 53 | Expect(text).To(Equal([]byte("foobar"))) 54 | }) 55 | 56 | It("has the proper length", func() { 57 | b := bob.Seal(nil, []byte("foobar"), 42, []byte("aad")) 58 | Expect(b).To(HaveLen(6 + bob.Overhead())) 59 | }) 60 | 61 | It("fails with wrong aad", func() { 62 | b := alice.Seal(nil, []byte("foobar"), 42, []byte("aad")) 63 | _, err := bob.Open(nil, b, 42, []byte("aad2")) 64 | Expect(err).To(HaveOccurred()) 65 | }) 66 | 67 | It("rejects wrong key and iv sizes", func() { 68 | e := "AES-GCM: expected 12 byte IVs" 69 | var err error 70 | _, err = NewAEADAESGCM(keyBob, keyAlice, ivBob[1:], ivAlice) 71 | Expect(err).To(MatchError(e)) 72 | _, err = NewAEADAESGCM(keyBob, keyAlice, ivBob, ivAlice[1:]) 73 | Expect(err).To(MatchError(e)) 74 | }) 75 | }) 76 | } 77 | 78 | It("errors when an invalid key size is used", func() { 79 | keyAlice = make([]byte, 17) 80 | keyBob = make([]byte, 17) 81 | _, err := NewAEADAESGCM(keyBob, keyAlice, ivBob, ivAlice) 82 | Expect(err).To(MatchError("crypto/aes: invalid key size 17")) 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/cert_cache.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "fmt" 5 | "hash/fnv" 6 | 7 | "github.com/hashicorp/golang-lru" 8 | "github.com/lucas-clemente/quic-go/internal/protocol" 9 | ) 10 | 11 | var ( 12 | compressedCertsCache *lru.Cache 13 | ) 14 | 15 | func getCompressedCert(chain [][]byte, pCommonSetHashes, pCachedHashes []byte) ([]byte, error) { 16 | // Hash all inputs 17 | hasher := fnv.New64a() 18 | for _, v := range chain { 19 | hasher.Write(v) 20 | } 21 | hasher.Write(pCommonSetHashes) 22 | hasher.Write(pCachedHashes) 23 | hash := hasher.Sum64() 24 | 25 | var result []byte 26 | 27 | resultI, isCached := compressedCertsCache.Get(hash) 28 | if isCached { 29 | result = resultI.([]byte) 30 | } else { 31 | var err error 32 | result, err = compressChain(chain, pCommonSetHashes, pCachedHashes) 33 | if err != nil { 34 | return nil, err 35 | } 36 | compressedCertsCache.Add(hash, result) 37 | } 38 | 39 | return result, nil 40 | } 41 | 42 | func init() { 43 | var err error 44 | compressedCertsCache, err = lru.New(protocol.NumCachedCertificates) 45 | if err != nil { 46 | panic(fmt.Sprintf("fatal error in quic-go: could not create lru cache: %s", err.Error())) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/cert_cache_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | lru "github.com/hashicorp/golang-lru" 5 | . "github.com/onsi/ginkgo" 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | var _ = Describe("Certificate cache", func() { 10 | BeforeEach(func() { 11 | var err error 12 | compressedCertsCache, err = lru.New(2) 13 | Expect(err).NotTo(HaveOccurred()) 14 | }) 15 | 16 | It("gives a compressed cert", func() { 17 | chain := [][]byte{{0xde, 0xca, 0xfb, 0xad}} 18 | expected, err := compressChain(chain, nil, nil) 19 | Expect(err).NotTo(HaveOccurred()) 20 | compressed, err := getCompressedCert(chain, nil, nil) 21 | Expect(err).ToNot(HaveOccurred()) 22 | Expect(compressed).To(Equal(expected)) 23 | }) 24 | 25 | It("gets the same result multiple times", func() { 26 | chain := [][]byte{{0xde, 0xca, 0xfb, 0xad}} 27 | compressed, err := getCompressedCert(chain, nil, nil) 28 | Expect(err).NotTo(HaveOccurred()) 29 | compressed2, err := getCompressedCert(chain, nil, nil) 30 | Expect(err).NotTo(HaveOccurred()) 31 | Expect(compressed).To(Equal(compressed2)) 32 | }) 33 | 34 | It("stores cached values", func() { 35 | chain := [][]byte{{0xde, 0xca, 0xfb, 0xad}} 36 | _, err := getCompressedCert(chain, nil, nil) 37 | Expect(err).NotTo(HaveOccurred()) 38 | Expect(compressedCertsCache.Len()).To(Equal(1)) 39 | Expect(compressedCertsCache.Contains(uint64(3838929964809501833))).To(BeTrue()) 40 | }) 41 | 42 | It("evicts old values", func() { 43 | _, err := getCompressedCert([][]byte{{0x00}}, nil, nil) 44 | Expect(err).NotTo(HaveOccurred()) 45 | _, err = getCompressedCert([][]byte{{0x01}}, nil, nil) 46 | Expect(err).NotTo(HaveOccurred()) 47 | _, err = getCompressedCert([][]byte{{0x02}}, nil, nil) 48 | Expect(err).NotTo(HaveOccurred()) 49 | Expect(compressedCertsCache.Len()).To(Equal(2)) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/cert_sets.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/lucas-clemente/quic-go-certificates" 7 | ) 8 | 9 | type certSet [][]byte 10 | 11 | var certSets = map[uint64]certSet{ 12 | certsets.CertSet2Hash: certsets.CertSet2, 13 | certsets.CertSet3Hash: certsets.CertSet3, 14 | } 15 | 16 | // findCertInSet searches for the cert in the set. Negative return value means not found. 17 | func (s *certSet) findCertInSet(cert []byte) int { 18 | for i, c := range *s { 19 | if bytes.Equal(c, cert) { 20 | return i 21 | } 22 | } 23 | return -1 24 | } 25 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/chacha20poly1305_aead.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package crypto 4 | 5 | import ( 6 | "crypto/cipher" 7 | "encoding/binary" 8 | "errors" 9 | 10 | "github.com/aead/chacha20" 11 | 12 | "github.com/lucas-clemente/quic-go/internal/protocol" 13 | ) 14 | 15 | type aeadChacha20Poly1305 struct { 16 | otherIV []byte 17 | myIV []byte 18 | encrypter cipher.AEAD 19 | decrypter cipher.AEAD 20 | } 21 | 22 | // NewAEADChacha20Poly1305 creates a AEAD using chacha20poly1305 23 | func NewAEADChacha20Poly1305(otherKey []byte, myKey []byte, otherIV []byte, myIV []byte) (AEAD, error) { 24 | if len(myKey) != 32 || len(otherKey) != 32 || len(myIV) != 4 || len(otherIV) != 4 { 25 | return nil, errors.New("chacha20poly1305: expected 32-byte keys and 4-byte IVs") 26 | } 27 | // copy because ChaCha20Poly1305 expects array pointers 28 | var MyKey, OtherKey [32]byte 29 | copy(MyKey[:], myKey) 30 | copy(OtherKey[:], otherKey) 31 | 32 | encrypter, err := chacha20.NewChaCha20Poly1305WithTagSize(&MyKey, 12) 33 | if err != nil { 34 | return nil, err 35 | } 36 | decrypter, err := chacha20.NewChaCha20Poly1305WithTagSize(&OtherKey, 12) 37 | if err != nil { 38 | return nil, err 39 | } 40 | return &aeadChacha20Poly1305{ 41 | otherIV: otherIV, 42 | myIV: myIV, 43 | encrypter: encrypter, 44 | decrypter: decrypter, 45 | }, nil 46 | } 47 | 48 | func (aead *aeadChacha20Poly1305) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) { 49 | return aead.decrypter.Open(dst, aead.makeNonce(aead.otherIV, packetNumber), src, associatedData) 50 | } 51 | 52 | func (aead *aeadChacha20Poly1305) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { 53 | return aead.encrypter.Seal(dst, aead.makeNonce(aead.myIV, packetNumber), src, associatedData) 54 | } 55 | 56 | func (aead *aeadChacha20Poly1305) makeNonce(iv []byte, packetNumber protocol.PacketNumber) []byte { 57 | res := make([]byte, 12) 58 | copy(res[0:4], iv) 59 | binary.LittleEndian.PutUint64(res[4:12], uint64(packetNumber)) 60 | return res 61 | } 62 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/chacha20poly1305_aead_test.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package crypto 4 | 5 | import ( 6 | "crypto/rand" 7 | 8 | . "github.com/onsi/ginkgo" 9 | . "github.com/onsi/gomega" 10 | ) 11 | 12 | var _ = Describe("Chacha20poly1305", func() { 13 | var ( 14 | alice, bob AEAD 15 | keyAlice, keyBob, ivAlice, ivBob []byte 16 | ) 17 | 18 | BeforeEach(func() { 19 | keyAlice = make([]byte, 32) 20 | keyBob = make([]byte, 32) 21 | ivAlice = make([]byte, 4) 22 | ivBob = make([]byte, 4) 23 | rand.Reader.Read(keyAlice) 24 | rand.Reader.Read(keyBob) 25 | rand.Reader.Read(ivAlice) 26 | rand.Reader.Read(ivBob) 27 | var err error 28 | alice, err = NewAEADChacha20Poly1305(keyBob, keyAlice, ivBob, ivAlice) 29 | Expect(err).ToNot(HaveOccurred()) 30 | bob, err = NewAEADChacha20Poly1305(keyAlice, keyBob, ivAlice, ivBob) 31 | Expect(err).ToNot(HaveOccurred()) 32 | }) 33 | 34 | It("seals and opens", func() { 35 | b := alice.Seal(nil, []byte("foobar"), 42, []byte("aad")) 36 | text, err := bob.Open(nil, b, 42, []byte("aad")) 37 | Expect(err).ToNot(HaveOccurred()) 38 | Expect(text).To(Equal([]byte("foobar"))) 39 | }) 40 | 41 | It("seals and opens reverse", func() { 42 | b := bob.Seal(nil, []byte("foobar"), 42, []byte("aad")) 43 | text, err := alice.Open(nil, b, 42, []byte("aad")) 44 | Expect(err).ToNot(HaveOccurred()) 45 | Expect(text).To(Equal([]byte("foobar"))) 46 | }) 47 | 48 | It("has the proper length", func() { 49 | b := bob.Seal(nil, []byte("foobar"), 42, []byte("aad")) 50 | Expect(b).To(HaveLen(6 + 12)) 51 | }) 52 | 53 | It("fails with wrong aad", func() { 54 | b := alice.Seal(nil, []byte("foobar"), 42, []byte("aad")) 55 | _, err := bob.Open(nil, b, 42, []byte("aad2")) 56 | Expect(err).To(HaveOccurred()) 57 | }) 58 | 59 | It("rejects wrong key and iv sizes", func() { 60 | var err error 61 | e := "chacha20poly1305: expected 32-byte keys and 4-byte IVs" 62 | _, err = NewAEADChacha20Poly1305(keyBob[1:], keyAlice, ivBob, ivAlice) 63 | Expect(err).To(MatchError(e)) 64 | _, err = NewAEADChacha20Poly1305(keyBob, keyAlice[1:], ivBob, ivAlice) 65 | Expect(err).To(MatchError(e)) 66 | _, err = NewAEADChacha20Poly1305(keyBob, keyAlice, ivBob[1:], ivAlice) 67 | Expect(err).To(MatchError(e)) 68 | _, err = NewAEADChacha20Poly1305(keyBob, keyAlice, ivBob, ivAlice[1:]) 69 | Expect(err).To(MatchError(e)) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/crypto_suite_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestCrypto(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Crypto Suite") 13 | } 14 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/curve_25519.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto/rand" 5 | "errors" 6 | 7 | "golang.org/x/crypto/curve25519" 8 | ) 9 | 10 | // KeyExchange manages the exchange of keys 11 | type curve25519KEX struct { 12 | secret [32]byte 13 | public [32]byte 14 | } 15 | 16 | var _ KeyExchange = &curve25519KEX{} 17 | 18 | // NewCurve25519KEX creates a new KeyExchange using Curve25519, see https://cr.yp.to/ecdh.html 19 | func NewCurve25519KEX() (KeyExchange, error) { 20 | c := &curve25519KEX{} 21 | if _, err := rand.Read(c.secret[:]); err != nil { 22 | return nil, errors.New("Curve25519: could not create private key") 23 | } 24 | // See https://cr.yp.to/ecdh.html 25 | c.secret[0] &= 248 26 | c.secret[31] &= 127 27 | c.secret[31] |= 64 28 | curve25519.ScalarBaseMult(&c.public, &c.secret) 29 | return c, nil 30 | } 31 | 32 | func (c *curve25519KEX) PublicKey() []byte { 33 | return c.public[:] 34 | } 35 | 36 | func (c *curve25519KEX) CalculateSharedKey(otherPublic []byte) ([]byte, error) { 37 | if len(otherPublic) != 32 { 38 | return nil, errors.New("Curve25519: expected public key of 32 byte") 39 | } 40 | var res [32]byte 41 | var otherPublicArray [32]byte 42 | copy(otherPublicArray[:], otherPublic) 43 | curve25519.ScalarMult(&res, &c.secret, &otherPublicArray) 44 | return res[:], nil 45 | } 46 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/curve_25519_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("ProofRsa", func() { 9 | It("works", func() { 10 | a, err := NewCurve25519KEX() 11 | Expect(err).ToNot(HaveOccurred()) 12 | b, err := NewCurve25519KEX() 13 | Expect(err).ToNot(HaveOccurred()) 14 | sA, err := a.CalculateSharedKey(b.PublicKey()) 15 | Expect(err).ToNot(HaveOccurred()) 16 | sB, err := b.CalculateSharedKey(a.PublicKey()) 17 | Expect(err).ToNot(HaveOccurred()) 18 | Expect(sA).To(Equal(sB)) 19 | }) 20 | 21 | It("rejects short public keys", func() { 22 | a, err := NewCurve25519KEX() 23 | Expect(err).ToNot(HaveOccurred()) 24 | _, err = a.CalculateSharedKey(nil) 25 | Expect(err).To(MatchError("Curve25519: expected public key of 32 byte")) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/key_derivation.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "github.com/bifurcation/mint" 5 | "github.com/lucas-clemente/quic-go/internal/protocol" 6 | ) 7 | 8 | const ( 9 | clientExporterLabel = "EXPORTER-QUIC client 1-RTT Secret" 10 | serverExporterLabel = "EXPORTER-QUIC server 1-RTT Secret" 11 | ) 12 | 13 | // MintController is an interface that bundles all methods needed to interact with mint 14 | type MintController interface { 15 | Handshake() mint.Alert 16 | GetCipherSuite() mint.CipherSuiteParams 17 | ComputeExporter(label string, context []byte, keyLength int) ([]byte, error) 18 | } 19 | 20 | // DeriveAESKeys derives the AES keys and creates a matching AES-GCM AEAD instance 21 | func DeriveAESKeys(mc MintController, pers protocol.Perspective) (AEAD, error) { 22 | var myLabel, otherLabel string 23 | if pers == protocol.PerspectiveClient { 24 | myLabel = clientExporterLabel 25 | otherLabel = serverExporterLabel 26 | } else { 27 | myLabel = serverExporterLabel 28 | otherLabel = clientExporterLabel 29 | } 30 | myKey, myIV, err := computeKeyAndIV(mc, myLabel) 31 | if err != nil { 32 | return nil, err 33 | } 34 | otherKey, otherIV, err := computeKeyAndIV(mc, otherLabel) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return NewAEADAESGCM(otherKey, myKey, otherIV, myIV) 39 | } 40 | 41 | func computeKeyAndIV(mc MintController, label string) (key, iv []byte, err error) { 42 | cs := mc.GetCipherSuite() 43 | secret, err := mc.ComputeExporter(label, nil, cs.Hash.Size()) 44 | if err != nil { 45 | return nil, nil, err 46 | } 47 | key = mint.HkdfExpandLabel(cs.Hash, secret, "key", nil, cs.KeyLen) 48 | iv = mint.HkdfExpandLabel(cs.Hash, secret, "iv", nil, cs.IvLen) 49 | return key, iv, nil 50 | } 51 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/key_derivation_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto" 5 | "errors" 6 | 7 | "github.com/bifurcation/mint" 8 | "github.com/lucas-clemente/quic-go/internal/protocol" 9 | . "github.com/onsi/ginkgo" 10 | . "github.com/onsi/gomega" 11 | ) 12 | 13 | type mockMintController struct { 14 | hash crypto.Hash 15 | computerError error 16 | } 17 | 18 | var _ MintController = &mockMintController{} 19 | 20 | func (c *mockMintController) Handshake() mint.Alert { panic("not implemented") } 21 | 22 | func (c *mockMintController) GetCipherSuite() mint.CipherSuiteParams { 23 | return mint.CipherSuiteParams{ 24 | Hash: c.hash, 25 | KeyLen: 32, 26 | IvLen: 12, 27 | } 28 | } 29 | 30 | func (c *mockMintController) ComputeExporter(label string, context []byte, keyLength int) ([]byte, error) { 31 | if c.computerError != nil { 32 | return nil, c.computerError 33 | } 34 | return append([]byte(label), context...), nil 35 | } 36 | 37 | var _ = Describe("Key Derivation", func() { 38 | It("derives keys", func() { 39 | clientAEAD, err := DeriveAESKeys(&mockMintController{hash: crypto.SHA256}, protocol.PerspectiveClient) 40 | Expect(err).ToNot(HaveOccurred()) 41 | serverAEAD, err := DeriveAESKeys(&mockMintController{hash: crypto.SHA256}, protocol.PerspectiveServer) 42 | Expect(err).ToNot(HaveOccurred()) 43 | ciphertext := clientAEAD.Seal(nil, []byte("foobar"), 0, []byte("aad")) 44 | data, err := serverAEAD.Open(nil, ciphertext, 0, []byte("aad")) 45 | Expect(err).ToNot(HaveOccurred()) 46 | Expect(data).To(Equal([]byte("foobar"))) 47 | }) 48 | 49 | It("fails when different hash functions are used", func() { 50 | clientAEAD, err := DeriveAESKeys(&mockMintController{hash: crypto.SHA256}, protocol.PerspectiveClient) 51 | Expect(err).ToNot(HaveOccurred()) 52 | serverAEAD, err := DeriveAESKeys(&mockMintController{hash: crypto.SHA512}, protocol.PerspectiveServer) 53 | Expect(err).ToNot(HaveOccurred()) 54 | ciphertext := clientAEAD.Seal(nil, []byte("foobar"), 0, []byte("aad")) 55 | _, err = serverAEAD.Open(nil, ciphertext, 0, []byte("aad")) 56 | Expect(err).To(MatchError("cipher: message authentication failed")) 57 | }) 58 | 59 | It("fails when computing the exporter fails", func() { 60 | testErr := errors.New("test error") 61 | _, err := DeriveAESKeys(&mockMintController{hash: crypto.SHA256, computerError: testErr}, protocol.PerspectiveClient) 62 | Expect(err).To(MatchError(testErr)) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/key_exchange.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | // KeyExchange manages the exchange of keys 4 | type KeyExchange interface { 5 | PublicKey() []byte 6 | CalculateSharedKey(otherPublic []byte) ([]byte, error) 7 | } 8 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/null_aead.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import "github.com/lucas-clemente/quic-go/internal/protocol" 4 | 5 | // NewNullAEAD creates a NullAEAD 6 | func NewNullAEAD(p protocol.Perspective, v protocol.VersionNumber) AEAD { 7 | if v.UsesTLS() { 8 | return &nullAEADFNV64a{} 9 | } 10 | return &nullAEADFNV128a{perspective: p} 11 | } 12 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/null_aead_fnv128a.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | 7 | "github.com/lucas-clemente/fnv128a" 8 | "github.com/lucas-clemente/quic-go/internal/protocol" 9 | ) 10 | 11 | // nullAEAD handles not-yet encrypted packets 12 | type nullAEADFNV128a struct { 13 | perspective protocol.Perspective 14 | } 15 | 16 | var _ AEAD = &nullAEADFNV128a{} 17 | 18 | // Open and verify the ciphertext 19 | func (n *nullAEADFNV128a) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) { 20 | if len(src) < 12 { 21 | return nil, errors.New("NullAEAD: ciphertext cannot be less than 12 bytes long") 22 | } 23 | 24 | hash := fnv128a.New() 25 | hash.Write(associatedData) 26 | hash.Write(src[12:]) 27 | if n.perspective == protocol.PerspectiveServer { 28 | hash.Write([]byte("Client")) 29 | } else { 30 | hash.Write([]byte("Server")) 31 | } 32 | testHigh, testLow := hash.Sum128() 33 | 34 | low := binary.LittleEndian.Uint64(src) 35 | high := binary.LittleEndian.Uint32(src[8:]) 36 | 37 | if uint32(testHigh&0xffffffff) != high || testLow != low { 38 | return nil, errors.New("NullAEAD: failed to authenticate received data") 39 | } 40 | return src[12:], nil 41 | } 42 | 43 | // Seal writes hash and ciphertext to the buffer 44 | func (n *nullAEADFNV128a) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { 45 | if cap(dst) < 12+len(src) { 46 | dst = make([]byte, 12+len(src)) 47 | } else { 48 | dst = dst[:12+len(src)] 49 | } 50 | 51 | hash := fnv128a.New() 52 | hash.Write(associatedData) 53 | hash.Write(src) 54 | 55 | if n.perspective == protocol.PerspectiveServer { 56 | hash.Write([]byte("Server")) 57 | } else { 58 | hash.Write([]byte("Client")) 59 | } 60 | 61 | high, low := hash.Sum128() 62 | 63 | copy(dst[12:], src) 64 | binary.LittleEndian.PutUint64(dst, low) 65 | binary.LittleEndian.PutUint32(dst[8:], uint32(high)) 66 | return dst 67 | } 68 | 69 | func (n *nullAEADFNV128a) Overhead() int { 70 | return 12 71 | } 72 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/null_aead_fnv128a_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "github.com/lucas-clemente/quic-go/internal/protocol" 5 | . "github.com/onsi/ginkgo" 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | var _ = Describe("NullAEAD using FNV128a", func() { 10 | aad := []byte("All human beings are born free and equal in dignity and rights.") 11 | plainText := []byte("They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood.") 12 | hash36 := []byte{0x98, 0x9b, 0x33, 0x3f, 0xe8, 0xde, 0x32, 0x5c, 0xa6, 0x7f, 0x9c, 0xf7} 13 | 14 | var aeadServer AEAD 15 | var aeadClient AEAD 16 | 17 | BeforeEach(func() { 18 | aeadServer = NewNullAEAD(protocol.PerspectiveServer, protocol.Version37) 19 | aeadClient = NewNullAEAD(protocol.PerspectiveClient, protocol.Version37) 20 | }) 21 | 22 | It("seals and opens, client => server", func() { 23 | cipherText := aeadClient.Seal(nil, plainText, 0, aad) 24 | res, err := aeadServer.Open(nil, cipherText, 0, aad) 25 | Expect(err).ToNot(HaveOccurred()) 26 | Expect(res).To(Equal([]byte("They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood."))) 27 | }) 28 | 29 | It("seals and opens, server => client", func() { 30 | cipherText := aeadServer.Seal(nil, plainText, 0, aad) 31 | res, err := aeadClient.Open(nil, cipherText, 0, aad) 32 | Expect(err).ToNot(HaveOccurred()) 33 | Expect(res).To(Equal([]byte("They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood."))) 34 | }) 35 | 36 | It("rejects short ciphertexts", func() { 37 | _, err := aeadServer.Open(nil, nil, 0, nil) 38 | Expect(err).To(MatchError("NullAEAD: ciphertext cannot be less than 12 bytes long")) 39 | }) 40 | 41 | It("seals in-place", func() { 42 | buf := make([]byte, 6, 12+6) 43 | copy(buf, []byte("foobar")) 44 | res := aeadServer.Seal(buf[0:0], buf, 0, nil) 45 | buf = buf[:12+6] 46 | Expect(buf[12:]).To(Equal([]byte("foobar"))) 47 | Expect(res[12:]).To(Equal([]byte("foobar"))) 48 | }) 49 | 50 | It("fails", func() { 51 | cipherText := append(append(hash36, plainText...), byte(0x42)) 52 | _, err := aeadClient.Open(nil, cipherText, 0, aad) 53 | Expect(err).To(HaveOccurred()) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/null_aead_fnv64a.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "hash/fnv" 7 | 8 | "github.com/lucas-clemente/quic-go/internal/protocol" 9 | ) 10 | 11 | type nullAEADFNV64a struct{} 12 | 13 | var _ AEAD = &nullAEADFNV64a{} 14 | 15 | // Open and verify the ciphertext 16 | func (n *nullAEADFNV64a) Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, error) { 17 | if len(src) < 8 { 18 | return nil, errors.New("NullAEAD: ciphertext cannot be less than 8 bytes long") 19 | } 20 | data := src[:len(src)-8] 21 | 22 | hash := fnv.New64a() 23 | hash.Write(associatedData) 24 | hash.Write(data) 25 | 26 | if hash.Sum64() != binary.BigEndian.Uint64(src[len(src)-8:]) { 27 | return nil, errors.New("NullAEAD: failed to authenticate received data") 28 | } 29 | return data, nil 30 | } 31 | 32 | // Seal writes hash and ciphertext to the buffer 33 | func (n *nullAEADFNV64a) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte { 34 | if cap(dst) < 8+len(src) { 35 | dst = make([]byte, 8+len(src)) 36 | } else { 37 | dst = dst[:8+len(src)] 38 | } 39 | 40 | hash := fnv.New64a() 41 | hash.Write(associatedData) 42 | hash.Write(src) 43 | copy(dst, src) 44 | binary.BigEndian.PutUint64(dst[len(src):], hash.Sum64()) 45 | return dst 46 | } 47 | 48 | func (n *nullAEADFNV64a) Overhead() int { 49 | return 8 50 | } 51 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/null_aead_fnv64a_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "encoding/binary" 5 | "hash/fnv" 6 | 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | var _ = Describe("NullAEAD using FNV128a", func() { 12 | var hash64 []byte 13 | var aead AEAD 14 | aad := []byte("All human beings are born free and equal in dignity and rights.") 15 | plainText := []byte("They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood.") 16 | 17 | BeforeEach(func() { 18 | aead = &nullAEADFNV64a{} 19 | hash := fnv.New64a() 20 | hash.Write(aad) 21 | hash.Write(plainText) 22 | hash64 = make([]byte, 8) 23 | binary.BigEndian.PutUint64(hash64, hash.Sum64()) 24 | }) 25 | 26 | It("opens", func() { 27 | data, err := aead.Open(nil, append(plainText, hash64...), 0, aad) 28 | Expect(err).ToNot(HaveOccurred()) 29 | Expect(data).To(Equal(plainText)) 30 | }) 31 | 32 | It("fails", func() { 33 | _, err := aead.Open(nil, append(plainText, hash64...), 0, append(aad, []byte{0x42}...)) 34 | Expect(err).To(MatchError("NullAEAD: failed to authenticate received data")) 35 | }) 36 | 37 | It("rejects short ciphertexts", func() { 38 | _, err := aead.Open(nil, []byte{1, 2, 3, 4, 5, 6, 7}, 0, []byte{}) 39 | Expect(err).To(MatchError("NullAEAD: ciphertext cannot be less than 8 bytes long")) 40 | }) 41 | 42 | It("opens empty messages", func() { 43 | hash := fnv.New64a() 44 | h := make([]byte, 8) 45 | binary.BigEndian.PutUint64(h, hash.Sum64()) 46 | data, err := aead.Open(nil, h, 0, []byte{}) 47 | Expect(err).ToNot(HaveOccurred()) 48 | Expect(data).To(BeEmpty()) 49 | }) 50 | 51 | It("seals", func() { 52 | sealed := aead.Seal(nil, plainText, 0, aad) 53 | Expect(sealed).To(Equal(append(plainText, hash64...))) 54 | Expect(sealed).To(HaveLen(len(plainText) + aead.Overhead())) 55 | }) 56 | 57 | It("seals in-place", func() { 58 | buf := make([]byte, 6, 6+8) 59 | copy(buf, []byte("foobar")) 60 | res := aead.Seal(buf[0:0], buf, 0, nil) 61 | // buf = buf[:8+6] 62 | Expect(buf[:6]).To(Equal([]byte("foobar"))) 63 | // Expect(res[:6]).To(Equal([]byte("foobar"))) 64 | Expect(buf[0 : 6+8]).To(Equal(res)) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/null_aead_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "github.com/lucas-clemente/quic-go/internal/protocol" 5 | . "github.com/onsi/ginkgo" 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | var _ = Describe("NullAEAD", func() { 10 | It("selects the right FVN variant", func() { 11 | Expect(NewNullAEAD(protocol.PerspectiveClient, protocol.Version39)).To(Equal(&nullAEADFNV128a{ 12 | perspective: protocol.PerspectiveClient, 13 | })) 14 | Expect(NewNullAEAD(protocol.PerspectiveClient, protocol.VersionTLS)).To(Equal(&nullAEADFNV64a{})) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/server_proof.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto" 5 | "crypto/ecdsa" 6 | "crypto/rand" 7 | "crypto/rsa" 8 | "crypto/sha256" 9 | "crypto/tls" 10 | "crypto/x509" 11 | "encoding/asn1" 12 | "errors" 13 | "math/big" 14 | ) 15 | 16 | type ecdsaSignature struct { 17 | R, S *big.Int 18 | } 19 | 20 | // signServerProof signs CHLO and server config for use in the server proof 21 | func signServerProof(cert *tls.Certificate, chlo []byte, serverConfigData []byte) ([]byte, error) { 22 | hash := sha256.New() 23 | hash.Write([]byte("QUIC CHLO and server config signature\x00")) 24 | chloHash := sha256.Sum256(chlo) 25 | hash.Write([]byte{32, 0, 0, 0}) 26 | hash.Write(chloHash[:]) 27 | hash.Write(serverConfigData) 28 | 29 | key, ok := cert.PrivateKey.(crypto.Signer) 30 | if !ok { 31 | return nil, errors.New("expected PrivateKey to implement crypto.Signer") 32 | } 33 | 34 | opts := crypto.SignerOpts(crypto.SHA256) 35 | 36 | if _, ok = key.(*rsa.PrivateKey); ok { 37 | opts = &rsa.PSSOptions{SaltLength: 32, Hash: crypto.SHA256} 38 | } 39 | 40 | return key.Sign(rand.Reader, hash.Sum(nil), opts) 41 | } 42 | 43 | // verifyServerProof verifies the server proof signature 44 | func verifyServerProof(proof []byte, cert *x509.Certificate, chlo []byte, serverConfigData []byte) bool { 45 | hash := sha256.New() 46 | hash.Write([]byte("QUIC CHLO and server config signature\x00")) 47 | chloHash := sha256.Sum256(chlo) 48 | hash.Write([]byte{32, 0, 0, 0}) 49 | hash.Write(chloHash[:]) 50 | hash.Write(serverConfigData) 51 | 52 | // RSA 53 | if cert.PublicKeyAlgorithm == x509.RSA { 54 | opts := &rsa.PSSOptions{SaltLength: 32, Hash: crypto.SHA256} 55 | err := rsa.VerifyPSS(cert.PublicKey.(*rsa.PublicKey), crypto.SHA256, hash.Sum(nil), proof, opts) 56 | return err == nil 57 | } 58 | 59 | // ECDSA 60 | signature := &ecdsaSignature{} 61 | rest, err := asn1.Unmarshal(proof, signature) 62 | if err != nil || len(rest) != 0 { 63 | return false 64 | } 65 | return ecdsa.Verify(cert.PublicKey.(*ecdsa.PublicKey), hash.Sum(nil), signature.R, signature.S) 66 | } 67 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/source_address_token.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | "crypto/sha256" 8 | "fmt" 9 | "io" 10 | 11 | "golang.org/x/crypto/hkdf" 12 | ) 13 | 14 | // StkSource is used to create and verify source address tokens 15 | type StkSource interface { 16 | // NewToken creates a new token 17 | NewToken([]byte) ([]byte, error) 18 | // DecodeToken decodes a token 19 | DecodeToken([]byte) ([]byte, error) 20 | } 21 | 22 | type stkSource struct { 23 | aead cipher.AEAD 24 | } 25 | 26 | const stkKeySize = 16 27 | 28 | // Chrome currently sets this to 12, but discusses changing it to 16. We start 29 | // at 16 :) 30 | const stkNonceSize = 16 31 | 32 | // NewStkSource creates a source for source address tokens 33 | func NewStkSource() (StkSource, error) { 34 | secret := make([]byte, 32) 35 | if _, err := rand.Read(secret); err != nil { 36 | return nil, err 37 | } 38 | key, err := deriveKey(secret) 39 | if err != nil { 40 | return nil, err 41 | } 42 | c, err := aes.NewCipher(key) 43 | if err != nil { 44 | return nil, err 45 | } 46 | aead, err := cipher.NewGCMWithNonceSize(c, stkNonceSize) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return &stkSource{aead: aead}, nil 51 | } 52 | 53 | func (s *stkSource) NewToken(data []byte) ([]byte, error) { 54 | nonce := make([]byte, stkNonceSize) 55 | if _, err := rand.Read(nonce); err != nil { 56 | return nil, err 57 | } 58 | return s.aead.Seal(nonce, nonce, data, nil), nil 59 | } 60 | 61 | func (s *stkSource) DecodeToken(p []byte) ([]byte, error) { 62 | if len(p) < stkNonceSize { 63 | return nil, fmt.Errorf("STK too short: %d", len(p)) 64 | } 65 | nonce := p[:stkNonceSize] 66 | return s.aead.Open(nil, nonce, p[stkNonceSize:], nil) 67 | } 68 | 69 | func deriveKey(secret []byte) ([]byte, error) { 70 | r := hkdf.New(sha256.New, secret, nil, []byte("QUIC source address token key")) 71 | key := make([]byte, stkKeySize) 72 | if _, err := io.ReadFull(r, key); err != nil { 73 | return nil, err 74 | } 75 | return key, nil 76 | } 77 | -------------------------------------------------------------------------------- /rlmp-quic/internal/crypto/source_address_token_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("Source Address Tokens", func() { 9 | It("should generate the encryption key", func() { 10 | Expect(deriveKey([]byte("TESTING"))).To(Equal([]byte{0xee, 0x71, 0x18, 0x9, 0xfd, 0xb8, 0x9a, 0x79, 0x19, 0xfc, 0x5e, 0x1a, 0x97, 0x20, 0xb2, 0x6})) 11 | }) 12 | 13 | Context("tokens", func() { 14 | var source *stkSource 15 | 16 | BeforeEach(func() { 17 | sourceI, err := NewStkSource() 18 | source = sourceI.(*stkSource) 19 | Expect(err).NotTo(HaveOccurred()) 20 | }) 21 | 22 | It("serializes", func() { 23 | token, err := source.NewToken([]byte("foobar")) 24 | Expect(err).ToNot(HaveOccurred()) 25 | data, err := source.DecodeToken(token) 26 | Expect(err).ToNot(HaveOccurred()) 27 | Expect(data).To(Equal([]byte("foobar"))) 28 | }) 29 | 30 | It("rejects invalid tokens", func() { 31 | _, err := source.DecodeToken([]byte("invalid source address token")) 32 | Expect(err).To(HaveOccurred()) 33 | Expect(err.Error()).To(ContainSubstring("message authentication failed")) 34 | }) 35 | 36 | It("rejects tokens of wrong size", func() { 37 | _, err := source.DecodeToken(nil) 38 | Expect(err).To(MatchError("STK too short: 0")) 39 | }) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /rlmp-quic/internal/flowcontrol/flowcontrol_suite_test.go: -------------------------------------------------------------------------------- 1 | package flowcontrol 2 | 3 | import ( 4 | "github.com/golang/mock/gomock" 5 | . "github.com/onsi/ginkgo" 6 | . "github.com/onsi/gomega" 7 | 8 | "testing" 9 | ) 10 | 11 | func TestCrypto(t *testing.T) { 12 | RegisterFailHandler(Fail) 13 | RunSpecs(t, "FlowControl Suite") 14 | } 15 | 16 | var mockCtrl *gomock.Controller 17 | 18 | var _ = BeforeEach(func() { 19 | mockCtrl = gomock.NewController(GinkgoT()) 20 | }) 21 | 22 | var _ = AfterEach(func() { 23 | mockCtrl.Finish() 24 | }) 25 | -------------------------------------------------------------------------------- /rlmp-quic/internal/flowcontrol/interface.go: -------------------------------------------------------------------------------- 1 | package flowcontrol 2 | 3 | import "github.com/lucas-clemente/quic-go/internal/protocol" 4 | 5 | // WindowUpdate provides the data for WindowUpdateFrames. 6 | type WindowUpdate struct { 7 | StreamID protocol.StreamID 8 | Offset protocol.ByteCount 9 | } 10 | 11 | // A FlowControlManager manages the flow control 12 | type FlowControlManager interface { 13 | NewStream(streamID protocol.StreamID, contributesToConnectionFlow bool) 14 | RemoveStream(streamID protocol.StreamID) 15 | // methods needed for receiving data 16 | ResetStream(streamID protocol.StreamID, byteOffset protocol.ByteCount) error 17 | UpdateHighestReceived(streamID protocol.StreamID, byteOffset protocol.ByteCount) error 18 | AddBytesRead(streamID protocol.StreamID, n protocol.ByteCount) error 19 | GetWindowUpdates(force bool) []WindowUpdate 20 | GetReceiveWindow(streamID protocol.StreamID) (protocol.ByteCount, error) 21 | // methods needed for sending data 22 | AddBytesSent(streamID protocol.StreamID, n protocol.ByteCount) error 23 | SendWindowSize(streamID protocol.StreamID) (protocol.ByteCount, error) 24 | RemainingConnectionWindowSize() protocol.ByteCount 25 | UpdateWindow(streamID protocol.StreamID, offset protocol.ByteCount) (bool, error) 26 | // methods useful to collect statistics 27 | GetBytesSent(streamID protocol.StreamID) (protocol.ByteCount, error) 28 | AddBytesRetrans(streamID protocol.StreamID, n protocol.ByteCount) error 29 | GetBytesRetrans(streamID protocol.StreamID) (protocol.ByteCount, error) 30 | } 31 | -------------------------------------------------------------------------------- /rlmp-quic/internal/handshake/ephermal_cache.go: -------------------------------------------------------------------------------- 1 | package handshake 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/lucas-clemente/quic-go/internal/crypto" 8 | "github.com/lucas-clemente/quic-go/internal/protocol" 9 | "github.com/lucas-clemente/quic-go/internal/utils" 10 | ) 11 | 12 | var ( 13 | kexLifetime = protocol.EphermalKeyLifetime 14 | kexCurrent crypto.KeyExchange 15 | kexCurrentTime time.Time 16 | kexMutex sync.RWMutex 17 | ) 18 | 19 | // getEphermalKEX returns the currently active KEX, which changes every protocol.EphermalKeyLifetime 20 | // See the explanation from the QUIC crypto doc: 21 | // 22 | // A single connection is the usual scope for forward security, but the security 23 | // difference between an ephemeral key used for a single connection, and one 24 | // used for all connections for 60 seconds is negligible. Thus we can amortise 25 | // the Diffie-Hellman key generation at the server over all the connections in a 26 | // small time span. 27 | func getEphermalKEX() (res crypto.KeyExchange) { 28 | kexMutex.RLock() 29 | res = kexCurrent 30 | t := kexCurrentTime 31 | kexMutex.RUnlock() 32 | if res != nil && time.Since(t) < kexLifetime { 33 | return res 34 | } 35 | 36 | kexMutex.Lock() 37 | defer kexMutex.Unlock() 38 | // Check if still unfulfilled 39 | if kexCurrent == nil || time.Since(kexCurrentTime) > kexLifetime { 40 | kex, err := crypto.NewCurve25519KEX() 41 | if err != nil { 42 | utils.Errorf("could not set KEX: %s", err.Error()) 43 | return kexCurrent 44 | } 45 | kexCurrent = kex 46 | kexCurrentTime = time.Now() 47 | return kexCurrent 48 | } 49 | return kexCurrent 50 | } 51 | -------------------------------------------------------------------------------- /rlmp-quic/internal/handshake/ephermal_cache_test.go: -------------------------------------------------------------------------------- 1 | package handshake 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/crypto" 7 | "github.com/lucas-clemente/quic-go/internal/protocol" 8 | . "github.com/onsi/ginkgo" 9 | . "github.com/onsi/gomega" 10 | ) 11 | 12 | var _ = Describe("Ephermal KEX", func() { 13 | It("has a consistent KEX", func() { 14 | kex1 := getEphermalKEX() 15 | Expect(kex1).ToNot(BeNil()) 16 | kex2 := getEphermalKEX() 17 | Expect(kex2).ToNot(BeNil()) 18 | Expect(kex1).To(Equal(kex2)) 19 | }) 20 | 21 | It("changes KEX", func() { 22 | kexLifetime = time.Millisecond 23 | defer func() { 24 | kexLifetime = protocol.EphermalKeyLifetime 25 | }() 26 | kex := getEphermalKEX() 27 | Expect(kex).ToNot(BeNil()) 28 | Eventually(func() crypto.KeyExchange { return getEphermalKEX() }).ShouldNot(Equal(kex)) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /rlmp-quic/internal/handshake/handshake_message_test.go: -------------------------------------------------------------------------------- 1 | package handshake 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/lucas-clemente/quic-go/qerr" 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | var _ = Describe("Handshake Message", func() { 12 | Context("when parsing", func() { 13 | It("parses sample CHLO message", func() { 14 | msg, err := ParseHandshakeMessage(bytes.NewReader(sampleCHLO)) 15 | Expect(err).ToNot(HaveOccurred()) 16 | Expect(msg.Tag).To(Equal(TagCHLO)) 17 | Expect(msg.Data).To(Equal(sampleCHLOMap)) 18 | }) 19 | 20 | It("rejects large numbers of pairs", func() { 21 | r := bytes.NewReader([]byte("CHLO\xff\xff\xff\xff")) 22 | _, err := ParseHandshakeMessage(r) 23 | Expect(err).To(MatchError(qerr.CryptoTooManyEntries)) 24 | }) 25 | 26 | It("rejects too long values", func() { 27 | r := bytes.NewReader([]byte{ 28 | 'C', 'H', 'L', 'O', 29 | 1, 0, 0, 0, 30 | 0, 0, 0, 0, 31 | 0xff, 0xff, 0xff, 0xff, 32 | }) 33 | _, err := ParseHandshakeMessage(r) 34 | Expect(err).To(MatchError(qerr.Error(qerr.CryptoInvalidValueLength, "value too long"))) 35 | }) 36 | }) 37 | 38 | Context("when writing", func() { 39 | It("writes sample message", func() { 40 | b := &bytes.Buffer{} 41 | HandshakeMessage{Tag: TagCHLO, Data: sampleCHLOMap}.Write(b) 42 | Expect(b.Bytes()).To(Equal(sampleCHLO)) 43 | }) 44 | }) 45 | 46 | Context("string representation", func() { 47 | It("has a string representation", func() { 48 | str := HandshakeMessage{ 49 | Tag: TagSHLO, 50 | Data: map[Tag][]byte{ 51 | TagAEAD: []byte("foobar"), 52 | TagEXPY: []byte("raboof"), 53 | }, 54 | }.String() 55 | Expect(str[:4]).To(Equal("SHLO")) 56 | Expect(str).To(ContainSubstring("AEAD: \"foobar\"")) 57 | Expect(str).To(ContainSubstring("EXPY: \"raboof\"")) 58 | }) 59 | 60 | It("lists padding separately", func() { 61 | str := HandshakeMessage{ 62 | Tag: TagSHLO, 63 | Data: map[Tag][]byte{ 64 | TagPAD: bytes.Repeat([]byte{0}, 1337), 65 | }, 66 | }.String() 67 | Expect(str).To(ContainSubstring("PAD")) 68 | Expect(str).To(ContainSubstring("1337 bytes")) 69 | }) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /rlmp-quic/internal/handshake/handshake_suite_test.go: -------------------------------------------------------------------------------- 1 | package handshake 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestQuicGo(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Handshake Suite") 13 | } 14 | -------------------------------------------------------------------------------- /rlmp-quic/internal/handshake/interface.go: -------------------------------------------------------------------------------- 1 | package handshake 2 | 3 | import "github.com/lucas-clemente/quic-go/internal/protocol" 4 | 5 | // Sealer seals a packet 6 | type Sealer interface { 7 | Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte 8 | Overhead() int 9 | } 10 | 11 | // CryptoSetup is a crypto setup 12 | type CryptoSetup interface { 13 | Open(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) ([]byte, protocol.EncryptionLevel, error) 14 | HandleCryptoStream() error 15 | // TODO: clean up this interface 16 | DiversificationNonce() []byte // only needed for cryptoSetupServer 17 | SetDiversificationNonce([]byte) // only needed for cryptoSetupClient 18 | 19 | GetSealer() (protocol.EncryptionLevel, Sealer) 20 | GetSealerWithEncryptionLevel(protocol.EncryptionLevel) (Sealer, error) 21 | GetSealerForCryptoStream() (protocol.EncryptionLevel, Sealer) 22 | } 23 | 24 | // TransportParameters are parameters sent to the peer during the handshake 25 | type TransportParameters struct { 26 | RequestConnectionIDTruncation bool 27 | CacheHandshake bool 28 | } 29 | -------------------------------------------------------------------------------- /rlmp-quic/internal/handshake/mint_utils.go: -------------------------------------------------------------------------------- 1 | package handshake 2 | 3 | import ( 4 | gocrypto "crypto" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "io" 8 | "net" 9 | "time" 10 | 11 | "github.com/bifurcation/mint" 12 | "github.com/lucas-clemente/quic-go/internal/crypto" 13 | "github.com/lucas-clemente/quic-go/internal/protocol" 14 | ) 15 | 16 | func tlsToMintConfig(tlsConf *tls.Config, pers protocol.Perspective) (*mint.Config, error) { 17 | mconf := &mint.Config{ 18 | NonBlocking: true, 19 | CipherSuites: []mint.CipherSuite{ 20 | mint.TLS_AES_128_GCM_SHA256, 21 | mint.TLS_AES_256_GCM_SHA384, 22 | }, 23 | } 24 | if tlsConf != nil { 25 | mconf.Certificates = make([]*mint.Certificate, len(tlsConf.Certificates)) 26 | for i, certChain := range tlsConf.Certificates { 27 | mconf.Certificates[i] = &mint.Certificate{ 28 | Chain: make([]*x509.Certificate, len(certChain.Certificate)), 29 | PrivateKey: certChain.PrivateKey.(gocrypto.Signer), 30 | } 31 | for j, cert := range certChain.Certificate { 32 | c, err := x509.ParseCertificate(cert) 33 | if err != nil { 34 | return nil, err 35 | } 36 | mconf.Certificates[i].Chain[j] = c 37 | } 38 | } 39 | } 40 | if err := mconf.Init(pers == protocol.PerspectiveClient); err != nil { 41 | return nil, err 42 | } 43 | return mconf, nil 44 | } 45 | 46 | type mintController struct { 47 | conn *mint.Conn 48 | } 49 | 50 | var _ crypto.MintController = &mintController{} 51 | 52 | func (mc *mintController) Handshake() mint.Alert { 53 | return mc.conn.Handshake() 54 | } 55 | 56 | func (mc *mintController) GetCipherSuite() mint.CipherSuiteParams { 57 | return mc.conn.State().CipherSuite 58 | } 59 | 60 | func (mc *mintController) ComputeExporter(label string, context []byte, keyLength int) ([]byte, error) { 61 | return mc.conn.ComputeExporter(label, context, keyLength) 62 | } 63 | 64 | // mint expects a net.Conn, but we're doing the handshake on a stream 65 | // so we wrap a stream such that implements a net.Conn 66 | type fakeConn struct { 67 | io.ReadWriter 68 | } 69 | 70 | var _ net.Conn = &fakeConn{} 71 | 72 | func (c *fakeConn) Close() error { return nil } 73 | func (c *fakeConn) LocalAddr() net.Addr { return nil } 74 | func (c *fakeConn) RemoteAddr() net.Addr { return nil } 75 | func (c *fakeConn) SetReadDeadline(time.Time) error { return nil } 76 | func (c *fakeConn) SetWriteDeadline(time.Time) error { return nil } 77 | func (c *fakeConn) SetDeadline(time.Time) error { return nil } 78 | -------------------------------------------------------------------------------- /rlmp-quic/internal/handshake/server_config.go: -------------------------------------------------------------------------------- 1 | package handshake 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | 7 | "github.com/lucas-clemente/quic-go/internal/crypto" 8 | ) 9 | 10 | // ServerConfig is a server config 11 | type ServerConfig struct { 12 | kex crypto.KeyExchange 13 | certChain crypto.CertChain 14 | ID []byte 15 | obit []byte 16 | } 17 | 18 | // NewServerConfig creates a new server config 19 | func NewServerConfig(kex crypto.KeyExchange, certChain crypto.CertChain) (*ServerConfig, error) { 20 | id := make([]byte, 16) 21 | _, err := rand.Read(id) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | obit := make([]byte, 8) 27 | if _, err = rand.Read(obit); err != nil { 28 | return nil, err 29 | } 30 | 31 | return &ServerConfig{ 32 | kex: kex, 33 | certChain: certChain, 34 | ID: id, 35 | obit: obit, 36 | }, nil 37 | } 38 | 39 | // Get the server config binary representation 40 | func (s *ServerConfig) Get() []byte { 41 | var serverConfig bytes.Buffer 42 | msg := HandshakeMessage{ 43 | Tag: TagSCFG, 44 | Data: map[Tag][]byte{ 45 | TagSCID: s.ID, 46 | TagKEXS: []byte("C255"), 47 | TagAEAD: []byte("AESG"), 48 | TagPUBS: append([]byte{0x20, 0x00, 0x00}, s.kex.PublicKey()...), 49 | TagOBIT: s.obit, 50 | TagEXPY: {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 51 | }, 52 | } 53 | msg.Write(&serverConfig) 54 | return serverConfig.Bytes() 55 | } 56 | 57 | // Sign the server config and CHLO with the server's keyData 58 | func (s *ServerConfig) Sign(sni string, chlo []byte) ([]byte, error) { 59 | return s.certChain.SignServerProof(sni, chlo, s.Get()) 60 | } 61 | 62 | // GetCertsCompressed returns the certificate data 63 | func (s *ServerConfig) GetCertsCompressed(sni string, commonSetHashes, compressedHashes []byte) ([]byte, error) { 64 | return s.certChain.GetCertsCompressed(sni, commonSetHashes, compressedHashes) 65 | } 66 | -------------------------------------------------------------------------------- /rlmp-quic/internal/handshake/server_config_test.go: -------------------------------------------------------------------------------- 1 | package handshake 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/crypto" 7 | 8 | . "github.com/onsi/ginkgo" 9 | . "github.com/onsi/gomega" 10 | ) 11 | 12 | var _ = Describe("ServerConfig", func() { 13 | var ( 14 | kex crypto.KeyExchange 15 | ) 16 | 17 | BeforeEach(func() { 18 | var err error 19 | kex, err = crypto.NewCurve25519KEX() 20 | Expect(err).NotTo(HaveOccurred()) 21 | }) 22 | 23 | It("generates a random ID and OBIT", func() { 24 | scfg1, err := NewServerConfig(kex, nil) 25 | Expect(err).ToNot(HaveOccurred()) 26 | scfg2, err := NewServerConfig(kex, nil) 27 | Expect(err).ToNot(HaveOccurred()) 28 | Expect(scfg1.ID).ToNot(Equal(scfg2.ID)) 29 | Expect(scfg1.obit).ToNot(Equal(scfg2.obit)) 30 | }) 31 | 32 | It("gets the proper binary representation", func() { 33 | scfg, err := NewServerConfig(kex, nil) 34 | Expect(err).NotTo(HaveOccurred()) 35 | expected := bytes.NewBuffer([]byte{0x53, 0x43, 0x46, 0x47, 0x6, 0x0, 0x0, 0x0, 0x41, 0x45, 0x41, 0x44, 0x4, 0x0, 0x0, 0x0, 0x53, 0x43, 0x49, 0x44, 0x14, 0x0, 0x0, 0x0, 0x50, 0x55, 0x42, 0x53, 0x37, 0x0, 0x0, 0x0, 0x4b, 0x45, 0x58, 0x53, 0x3b, 0x0, 0x0, 0x0, 0x4f, 0x42, 0x49, 0x54, 0x43, 0x0, 0x0, 0x0, 0x45, 0x58, 0x50, 0x59, 0x4b, 0x0, 0x0, 0x0, 0x41, 0x45, 0x53, 0x47}) 36 | expected.Write(scfg.ID) 37 | expected.Write([]byte{0x20, 0x0, 0x0}) 38 | expected.Write(kex.PublicKey()) 39 | expected.Write([]byte{0x43, 0x32, 0x35, 0x35}) 40 | expected.Write(scfg.obit) 41 | expected.Write([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}) 42 | Expect(scfg.Get()).To(Equal(expected.Bytes())) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /rlmp-quic/internal/mocks/gen.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | // mockgen source mode doesn't properly recognize structs defined in the same package 4 | // so we have to use sed to correct for that 5 | 6 | //go:generate sh -c "mockgen -package mocks_fc -source ../flowcontrol/interface.go | sed \"s/\\[\\]WindowUpdate/[]flowcontrol.WindowUpdate/g\" > mocks_fc/flow_control_manager.go" 7 | //go:generate sh -c "mockgen -package mocks -source ../handshake/connection_parameters_manager.go | sed \"s/\\[Tag\\]/[handshake.Tag]/g\" > cpm.go" 8 | //go:generate sh -c "goimports -w ." 9 | -------------------------------------------------------------------------------- /rlmp-quic/internal/protocol/encryption_level.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | // EncryptionLevel is the encryption level 4 | // Default value is Unencrypted 5 | type EncryptionLevel int 6 | 7 | const ( 8 | // EncryptionUnspecified is a not specified encryption level 9 | EncryptionUnspecified EncryptionLevel = iota 10 | // EncryptionUnencrypted is not encrypted 11 | EncryptionUnencrypted 12 | // EncryptionSecure is encrypted, but not forward secure 13 | EncryptionSecure 14 | // EncryptionForwardSecure is forward secure 15 | EncryptionForwardSecure 16 | ) 17 | 18 | func (e EncryptionLevel) String() string { 19 | switch e { 20 | case EncryptionUnencrypted: 21 | return "unencrypted" 22 | case EncryptionSecure: 23 | return "encrypted (not forward-secure)" 24 | case EncryptionForwardSecure: 25 | return "forward-secure" 26 | } 27 | return "unknown" 28 | } 29 | -------------------------------------------------------------------------------- /rlmp-quic/internal/protocol/encryption_level_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("Encryption Level", func() { 9 | It("has the correct string representation", func() { 10 | Expect(EncryptionUnspecified.String()).To(Equal("unknown")) 11 | Expect(EncryptionUnencrypted.String()).To(Equal("unencrypted")) 12 | Expect(EncryptionSecure.String()).To(Equal("encrypted (not forward-secure)")) 13 | Expect(EncryptionForwardSecure.String()).To(Equal("forward-secure")) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /rlmp-quic/internal/protocol/packet_number.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | // InferPacketNumber calculates the packet number based on the received packet number, its length and the last seen packet number 4 | func InferPacketNumber(packetNumberLength PacketNumberLen, lastPacketNumber PacketNumber, wirePacketNumber PacketNumber) PacketNumber { 5 | epochDelta := PacketNumber(1) << (uint8(packetNumberLength) * 8) 6 | epoch := lastPacketNumber & ^(epochDelta - 1) 7 | prevEpochBegin := epoch - epochDelta 8 | nextEpochBegin := epoch + epochDelta 9 | return closestTo( 10 | lastPacketNumber+1, 11 | epoch+wirePacketNumber, 12 | closestTo(lastPacketNumber+1, prevEpochBegin+wirePacketNumber, nextEpochBegin+wirePacketNumber), 13 | ) 14 | } 15 | 16 | func closestTo(target, a, b PacketNumber) PacketNumber { 17 | if delta(target, a) < delta(target, b) { 18 | return a 19 | } 20 | return b 21 | } 22 | 23 | func delta(a, b PacketNumber) PacketNumber { 24 | if a < b { 25 | return b - a 26 | } 27 | return a - b 28 | } 29 | 30 | // GetPacketNumberLengthForPublicHeader gets the length of the packet number for the public header 31 | // it never chooses a PacketNumberLen of 1 byte, since this is too short under certain circumstances 32 | func GetPacketNumberLengthForPublicHeader(packetNumber PacketNumber, leastUnacked PacketNumber) PacketNumberLen { 33 | diff := uint64(packetNumber - leastUnacked) 34 | if diff < (2 << (uint8(PacketNumberLen2)*8 - 2)) { 35 | return PacketNumberLen2 36 | } 37 | if diff < (2 << (uint8(PacketNumberLen4)*8 - 2)) { 38 | return PacketNumberLen4 39 | } 40 | // we do not check if there are less than 2^46 packets in flight, since flow control and congestion control will limit this number *a lot* sooner 41 | return PacketNumberLen6 42 | } 43 | 44 | // GetPacketNumberLength gets the minimum length needed to fully represent the packet number 45 | func GetPacketNumberLength(packetNumber PacketNumber) PacketNumberLen { 46 | if packetNumber < (1 << (uint8(PacketNumberLen1) * 8)) { 47 | return PacketNumberLen1 48 | } 49 | if packetNumber < (1 << (uint8(PacketNumberLen2) * 8)) { 50 | return PacketNumberLen2 51 | } 52 | if packetNumber < (1 << (uint8(PacketNumberLen4) * 8)) { 53 | return PacketNumberLen4 54 | } 55 | return PacketNumberLen6 56 | } 57 | -------------------------------------------------------------------------------- /rlmp-quic/internal/protocol/perspective.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | // Perspective determines if we're acting as a server or a client 4 | type Perspective int 5 | 6 | // the perspectives 7 | const ( 8 | PerspectiveServer Perspective = 1 9 | PerspectiveClient Perspective = 2 10 | ) 11 | -------------------------------------------------------------------------------- /rlmp-quic/internal/protocol/protocol_suite_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestProtocol(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Protocol Suite") 13 | } 14 | -------------------------------------------------------------------------------- /rlmp-quic/internal/protocol/version.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import "fmt" 4 | 5 | // VersionNumber is a version number as int 6 | type VersionNumber int 7 | 8 | // The version numbers, making grepping easier 9 | const ( 10 | Version37 VersionNumber = 37 + iota 11 | Version38 12 | Version39 13 | VersionTLS VersionNumber = 101 14 | VersionWhatever VersionNumber = 0 // for when the version doesn't matter 15 | VersionUnsupported VersionNumber = -1 16 | VersionUnknown VersionNumber = -2 17 | VersionMP VersionNumber = 512 18 | ) 19 | 20 | // SupportedVersions lists the versions that the server supports 21 | // must be in sorted descending order 22 | var SupportedVersions = []VersionNumber{ 23 | VersionMP, 24 | Version39, 25 | Version38, 26 | Version37, 27 | } 28 | 29 | // UsesTLS says if this QUIC version uses TLS 1.3 for the handshake 30 | func (vn VersionNumber) UsesTLS() bool { 31 | return vn == VersionTLS 32 | } 33 | 34 | func (vn VersionNumber) String() string { 35 | switch vn { 36 | case VersionWhatever: 37 | return "whatever" 38 | case VersionUnsupported: 39 | return "unsupported" 40 | case VersionUnknown: 41 | return "unknown" 42 | case VersionTLS: 43 | return "TLS dev version (WIP)" 44 | default: 45 | return fmt.Sprintf("%d", vn) 46 | } 47 | } 48 | 49 | // VersionNumberToTag maps version numbers ('32') to tags ('Q032') 50 | func VersionNumberToTag(vn VersionNumber) uint32 { 51 | v := uint32(vn) 52 | return 'Q' + ((v/100%10)+'0')<<8 + ((v/10%10)+'0')<<16 + ((v%10)+'0')<<24 53 | } 54 | 55 | // VersionTagToNumber is built from VersionNumberToTag in init() 56 | func VersionTagToNumber(v uint32) VersionNumber { 57 | return VersionNumber(((v>>8)&0xff-'0')*100 + ((v>>16)&0xff-'0')*10 + ((v>>24)&0xff - '0')) 58 | } 59 | 60 | // IsSupportedVersion returns true if the server supports this version 61 | func IsSupportedVersion(supported []VersionNumber, v VersionNumber) bool { 62 | for _, t := range supported { 63 | if t == v { 64 | return true 65 | } 66 | } 67 | return false 68 | } 69 | 70 | // ChooseSupportedVersion finds the best version in the overlap of ours and theirs 71 | // ours is a slice of versions that we support, sorted by our preference (descending) 72 | // theirs is a slice of versions offered by the peer. The order does not matter 73 | // if no suitable version is found, it returns VersionUnsupported 74 | func ChooseSupportedVersion(ours, theirs []VersionNumber) VersionNumber { 75 | for _, ourVer := range ours { 76 | for _, theirVer := range theirs { 77 | if ourVer == theirVer { 78 | return ourVer 79 | } 80 | } 81 | } 82 | return VersionUnsupported 83 | } 84 | -------------------------------------------------------------------------------- /rlmp-quic/internal/protocol/version_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("Version", func() { 9 | It("says if a version supports TLS", func() { 10 | Expect(Version37.UsesTLS()).To(BeFalse()) 11 | Expect(Version38.UsesTLS()).To(BeFalse()) 12 | Expect(Version39.UsesTLS()).To(BeFalse()) 13 | Expect(VersionTLS.UsesTLS()).To(BeTrue()) 14 | }) 15 | 16 | It("has the right string representation", func() { 17 | Expect(Version37.String()).To(Equal("37")) 18 | Expect(Version38.String()).To(Equal("38")) 19 | Expect(Version39.String()).To(Equal("39")) 20 | Expect(VersionTLS.String()).To(ContainSubstring("TLS")) 21 | Expect(VersionWhatever.String()).To(Equal("whatever")) 22 | Expect(VersionUnsupported.String()).To(Equal("unsupported")) 23 | Expect(VersionUnknown.String()).To(Equal("unknown")) 24 | }) 25 | 26 | It("converts tags to numbers", func() { 27 | Expect(VersionTagToNumber('Q' + '1'<<8 + '2'<<16 + '3'<<24)).To(Equal(VersionNumber(123))) 28 | }) 29 | 30 | It("converts number to tag", func() { 31 | Expect(VersionNumberToTag(VersionNumber(123))).To(Equal(uint32('Q' + '1'<<8 + '2'<<16 + '3'<<24))) 32 | }) 33 | 34 | It("recognizes supported versions", func() { 35 | Expect(IsSupportedVersion(SupportedVersions, 0)).To(BeFalse()) 36 | Expect(IsSupportedVersion(SupportedVersions, SupportedVersions[0])).To(BeTrue()) 37 | Expect(IsSupportedVersion(SupportedVersions, SupportedVersions[len(SupportedVersions)-1])).To(BeTrue()) 38 | }) 39 | 40 | It("has supported versions in sorted order", func() { 41 | for i := 0; i < len(SupportedVersions)-1; i++ { 42 | Expect(SupportedVersions[i]).To(BeNumerically(">", SupportedVersions[i+1])) 43 | } 44 | }) 45 | 46 | Context("highest supported version", func() { 47 | It("finds the supported version", func() { 48 | supportedVersions := []VersionNumber{1, 2, 3} 49 | other := []VersionNumber{6, 5, 4, 3} 50 | Expect(ChooseSupportedVersion(supportedVersions, other)).To(Equal(VersionNumber(3))) 51 | }) 52 | 53 | It("picks the preferred version", func() { 54 | supportedVersions := []VersionNumber{2, 1, 3} 55 | other := []VersionNumber{3, 6, 1, 8, 2, 10} 56 | Expect(ChooseSupportedVersion(supportedVersions, other)).To(Equal(VersionNumber(2))) 57 | }) 58 | 59 | It("handles empty inputs", func() { 60 | supportedVersions := []VersionNumber{102, 101} 61 | Expect(ChooseSupportedVersion(supportedVersions, nil)).To(Equal(VersionUnsupported)) 62 | Expect(ChooseSupportedVersion(supportedVersions, []VersionNumber{})).To(Equal(VersionUnsupported)) 63 | supportedVersions = []VersionNumber{} 64 | Expect(ChooseSupportedVersion(supportedVersions, []VersionNumber{1, 2})).To(Equal(VersionUnsupported)) 65 | Expect(ChooseSupportedVersion(supportedVersions, []VersionNumber{})).To(Equal(VersionUnsupported)) 66 | }) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /rlmp-quic/internal/testdata/cert.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | "crypto/tls" 5 | "path" 6 | "runtime" 7 | ) 8 | 9 | var certPath string 10 | 11 | func init() { 12 | _, filename, _, ok := runtime.Caller(0) 13 | if !ok { 14 | panic("Failed to get current frame") 15 | } 16 | 17 | certPath = path.Join(path.Dir(path.Dir(path.Dir(filename))), "example") 18 | } 19 | 20 | // GetCertificatePaths returns the paths to 'fullchain.pem' and 'privkey.pem' for the 21 | // quic.clemente.io cert. 22 | func GetCertificatePaths() (string, string) { 23 | return path.Join(certPath, "fullchain.pem"), path.Join(certPath, "privkey.pem") 24 | } 25 | 26 | // GetTLSConfig returns a tls config for quic.clemente.io 27 | func GetTLSConfig() *tls.Config { 28 | cert, err := tls.LoadX509KeyPair(GetCertificatePaths()) 29 | if err != nil { 30 | panic(err) 31 | } 32 | return &tls.Config{ 33 | Certificates: []tls.Certificate{cert}, 34 | } 35 | } 36 | 37 | // GetCertificate returns a certificate for quic.clemente.io 38 | func GetCertificate() tls.Certificate { 39 | cert, err := tls.LoadX509KeyPair(GetCertificatePaths()) 40 | if err != nil { 41 | panic(err) 42 | } 43 | return cert 44 | } 45 | -------------------------------------------------------------------------------- /rlmp-quic/internal/utils/_gen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "github.com/clipperhouse/linkedlist" 5 | _ "github.com/clipperhouse/slice" 6 | _ "github.com/clipperhouse/stringer" 7 | ) 8 | -------------------------------------------------------------------------------- /rlmp-quic/internal/utils/atomic_bool.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "sync/atomic" 4 | 5 | // An AtomicBool is an atomic bool 6 | type AtomicBool struct { 7 | v int32 8 | } 9 | 10 | // Set sets the value 11 | func (a *AtomicBool) Set(value bool) { 12 | var n int32 13 | if value { 14 | n = 1 15 | } 16 | atomic.StoreInt32(&a.v, n) 17 | } 18 | 19 | // Get gets the value 20 | func (a *AtomicBool) Get() bool { 21 | return atomic.LoadInt32(&a.v) != 0 22 | } 23 | -------------------------------------------------------------------------------- /rlmp-quic/internal/utils/atomic_bool_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("Atomic Bool", func() { 9 | var a *AtomicBool 10 | 11 | BeforeEach(func() { 12 | a = &AtomicBool{} 13 | }) 14 | 15 | It("has the right default value", func() { 16 | Expect(a.Get()).To(BeFalse()) 17 | }) 18 | 19 | It("sets the value to true", func() { 20 | a.Set(true) 21 | Expect(a.Get()).To(BeTrue()) 22 | }) 23 | 24 | It("sets the value to false", func() { 25 | a.Set(true) 26 | a.Set(false) 27 | Expect(a.Get()).To(BeFalse()) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /rlmp-quic/internal/utils/byteorder.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | 7 | "github.com/lucas-clemente/quic-go/internal/protocol" 8 | ) 9 | 10 | // A ByteOrder specifies how to convert byte sequences into 16-, 32-, or 64-bit unsigned integers. 11 | type ByteOrder interface { 12 | ReadUintN(b io.ByteReader, length uint8) (uint64, error) 13 | ReadUint64(io.ByteReader) (uint64, error) 14 | ReadUint32(io.ByteReader) (uint32, error) 15 | ReadUint16(io.ByteReader) (uint16, error) 16 | 17 | WriteUint64(*bytes.Buffer, uint64) 18 | WriteUint56(*bytes.Buffer, uint64) 19 | WriteUint48(*bytes.Buffer, uint64) 20 | WriteUint40(*bytes.Buffer, uint64) 21 | WriteUint32(*bytes.Buffer, uint32) 22 | WriteUint24(*bytes.Buffer, uint32) 23 | WriteUint16(*bytes.Buffer, uint16) 24 | 25 | ReadUfloat16(io.ByteReader) (uint64, error) 26 | WriteUfloat16(*bytes.Buffer, uint64) 27 | } 28 | 29 | // GetByteOrder gets the ByteOrder (little endian or big endian) used to represent values on the wire 30 | // from QUIC 39, values are encoded in big endian, before that in little endian 31 | func GetByteOrder(v protocol.VersionNumber) ByteOrder { 32 | if v < protocol.Version39 { 33 | return LittleEndian 34 | } 35 | return BigEndian 36 | } 37 | -------------------------------------------------------------------------------- /rlmp-quic/internal/utils/byteorder_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/lucas-clemente/quic-go/internal/protocol" 5 | . "github.com/onsi/ginkgo" 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | var _ = Describe("Byte Order", func() { 10 | It("says little Little Endian before QUIC 39", func() { 11 | Expect(GetByteOrder(protocol.Version37)).To(Equal(LittleEndian)) 12 | Expect(GetByteOrder(protocol.Version38)).To(Equal(LittleEndian)) 13 | }) 14 | 15 | It("says little Little Endian for QUIC 39", func() { 16 | Expect(GetByteOrder(protocol.Version39)).To(Equal(BigEndian)) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /rlmp-quic/internal/utils/connection_id.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/binary" 6 | 7 | "github.com/lucas-clemente/quic-go/internal/protocol" 8 | ) 9 | 10 | // GenerateConnectionID generates a connection ID using cryptographic random 11 | func GenerateConnectionID() (protocol.ConnectionID, error) { 12 | b := make([]byte, 8) 13 | _, err := rand.Read(b) 14 | if err != nil { 15 | return 0, err 16 | } 17 | return protocol.ConnectionID(binary.LittleEndian.Uint64(b)), nil 18 | } 19 | -------------------------------------------------------------------------------- /rlmp-quic/internal/utils/connection_id_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("Connection ID generation", func() { 9 | It("generates random connection IDs", func() { 10 | c1, err := GenerateConnectionID() 11 | Expect(err).ToNot(HaveOccurred()) 12 | Expect(c1).ToNot(BeZero()) 13 | c2, err := GenerateConnectionID() 14 | Expect(err).ToNot(HaveOccurred()) 15 | Expect(c1).ToNot(Equal(c2)) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /rlmp-quic/internal/utils/host.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net/url" 5 | "strings" 6 | ) 7 | 8 | // HostnameFromAddr determines the hostname in an address string 9 | func HostnameFromAddr(addr string) (string, error) { 10 | p, err := url.Parse(addr) 11 | if err != nil { 12 | return "", err 13 | } 14 | h := p.Host 15 | 16 | // copied from https://golang.org/src/net/http/transport.go 17 | if hasPort(h) { 18 | h = h[:strings.LastIndex(h, ":")] 19 | } 20 | 21 | return h, nil 22 | } 23 | 24 | // copied from https://golang.org/src/net/http/http.go 25 | func hasPort(s string) bool { 26 | return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") 27 | } 28 | -------------------------------------------------------------------------------- /rlmp-quic/internal/utils/host_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("Hostname", func() { 9 | It("gets the hostname from an URL", func() { 10 | h, err := HostnameFromAddr("https://quic.clemente.io/file.dat?param=true¶m2=false") 11 | Expect(err).ToNot(HaveOccurred()) 12 | Expect(h).To(Equal("quic.clemente.io")) 13 | }) 14 | 15 | It("gets the hostname from an URL with a port number", func() { 16 | h, err := HostnameFromAddr("https://quic.clemente.io:6121/file.dat") 17 | Expect(err).ToNot(HaveOccurred()) 18 | Expect(h).To(Equal("quic.clemente.io")) 19 | }) 20 | 21 | It("gets the hostname from an URL containing username and password", func() { 22 | h, err := HostnameFromAddr("https://user:password@quic.clemente.io:6121/file.dat") 23 | Expect(err).ToNot(HaveOccurred()) 24 | Expect(h).To(Equal("quic.clemente.io")) 25 | }) 26 | 27 | It("gets local hostnames", func() { 28 | h, err := HostnameFromAddr("https://localhost/file.dat") 29 | Expect(err).ToNot(HaveOccurred()) 30 | Expect(h).To(Equal("localhost")) 31 | }) 32 | 33 | It("gets the hostname for other protocols", func() { 34 | h, err := HostnameFromAddr("ftp://quic.clemente.io:6121/file.dat") 35 | Expect(err).ToNot(HaveOccurred()) 36 | Expect(h).To(Equal("quic.clemente.io")) 37 | }) 38 | 39 | It("gets an IP", func() { 40 | h, err := HostnameFromAddr("https://1.3.3.7:6121/file.dat") 41 | Expect(err).ToNot(HaveOccurred()) 42 | Expect(h).To(Equal("1.3.3.7")) 43 | }) 44 | 45 | It("errors on malformed URLs", func() { 46 | _, err := HostnameFromAddr("://quic.clemente.io:6121/file.dat") 47 | Expect(err).To(HaveOccurred()) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /rlmp-quic/internal/utils/log.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // LogLevel of quic-go 12 | type LogLevel uint8 13 | 14 | const logEnv = "QUIC_GO_LOG_LEVEL" 15 | 16 | const ( 17 | // LogLevelNothing disables 18 | LogLevelNothing LogLevel = iota 19 | // LogLevelError enables err logs 20 | LogLevelError 21 | // LogLevelInfo enables info logs (e.g. packets) 22 | LogLevelInfo 23 | // LogLevelDebug enables debug logs (e.g. packet contents) 24 | LogLevelDebug 25 | ) 26 | 27 | var ( 28 | logLevel = LogLevelNothing 29 | timeFormat = time.StampMicro 30 | ) 31 | 32 | // SetLogLevel sets the log level 33 | func SetLogLevel(level LogLevel) { 34 | logLevel = level 35 | } 36 | 37 | // SetLogTimeFormat sets the format of the timestamp 38 | // an empty string disables the logging of timestamps 39 | func SetLogTimeFormat(format string) { 40 | log.SetFlags(0) // disable timestamp logging done by the log package 41 | timeFormat = format 42 | } 43 | 44 | // Debugf logs something 45 | func Debugf(format string, args ...interface{}) { 46 | if logLevel == LogLevelDebug { 47 | logMessage(format, args...) 48 | } 49 | } 50 | 51 | // Infof logs something 52 | func Infof(format string, args ...interface{}) { 53 | if logLevel >= LogLevelInfo { 54 | logMessage(format, args...) 55 | } 56 | } 57 | 58 | // Errorf logs something 59 | func Errorf(format string, args ...interface{}) { 60 | if logLevel >= LogLevelError { 61 | logMessage(format, args...) 62 | } 63 | } 64 | 65 | func logMessage(format string, args ...interface{}) { 66 | if len(timeFormat) > 0 { 67 | log.Printf(time.Now().Format(timeFormat)+" "+format, args...) 68 | } else { 69 | log.Printf(format, args...) 70 | } 71 | } 72 | 73 | // Debug returns true if the log level is LogLevelDebug 74 | func Debug() bool { 75 | return logLevel == LogLevelDebug 76 | } 77 | 78 | func init() { 79 | readLoggingEnv() 80 | } 81 | 82 | func readLoggingEnv() { 83 | switch strings.ToLower(os.Getenv(logEnv)) { 84 | case "": 85 | return 86 | case "debug": 87 | logLevel = LogLevelDebug 88 | case "info": 89 | logLevel = LogLevelInfo 90 | case "error": 91 | logLevel = LogLevelError 92 | default: 93 | fmt.Fprintln(os.Stderr, "invalid quic-go log level, see https://github.com/lucas-clemente/quic-go/wiki/Logging") 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /rlmp-quic/internal/utils/packet_interval.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/lucas-clemente/quic-go/internal/protocol" 4 | 5 | // PacketInterval is an interval from one PacketNumber to the other 6 | // +gen linkedlist 7 | type PacketInterval struct { 8 | Start protocol.PacketNumber 9 | End protocol.PacketNumber 10 | } 11 | -------------------------------------------------------------------------------- /rlmp-quic/internal/utils/streamframe_interval.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/lucas-clemente/quic-go/internal/protocol" 4 | 5 | // ByteInterval is an interval from one ByteCount to the other 6 | // +gen linkedlist 7 | type ByteInterval struct { 8 | Start protocol.ByteCount 9 | End protocol.ByteCount 10 | } 11 | -------------------------------------------------------------------------------- /rlmp-quic/internal/utils/timer.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "time" 4 | 5 | // A Timer wrapper that behaves correctly when resetting 6 | type Timer struct { 7 | t *time.Timer 8 | read bool 9 | deadline time.Time 10 | } 11 | 12 | // NewTimer creates a new timer that is not set 13 | func NewTimer() *Timer { 14 | return &Timer{t: time.NewTimer(0)} 15 | } 16 | 17 | // Chan returns the channel of the wrapped timer 18 | func (t *Timer) Chan() <-chan time.Time { 19 | return t.t.C 20 | } 21 | 22 | // Reset the timer, no matter whether the value was read or not 23 | func (t *Timer) Reset(deadline time.Time) { 24 | if deadline.Equal(t.deadline) { 25 | // No need to reset the timer 26 | return 27 | } 28 | 29 | // We need to drain the timer if the value from its channel was not read yet. 30 | // See https://groups.google.com/forum/#!topic/golang-dev/c9UUfASVPoU 31 | if !t.t.Stop() && !t.read { 32 | <-t.t.C 33 | } 34 | t.t.Reset(deadline.Sub(time.Now())) 35 | 36 | t.read = false 37 | t.deadline = deadline 38 | } 39 | 40 | // SetRead should be called after the value from the chan was read 41 | func (t *Timer) SetRead() { 42 | t.read = true 43 | } 44 | -------------------------------------------------------------------------------- /rlmp-quic/internal/utils/timer_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "time" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("Timer", func() { 11 | const d = 10 * time.Millisecond 12 | 13 | It("works", func() { 14 | t := NewTimer() 15 | t.Reset(time.Now().Add(d)) 16 | Eventually(t.Chan()).Should(Receive()) 17 | }) 18 | 19 | It("works multiple times with reading", func() { 20 | t := NewTimer() 21 | for i := 0; i < 10; i++ { 22 | t.Reset(time.Now().Add(d)) 23 | Eventually(t.Chan()).Should(Receive()) 24 | t.SetRead() 25 | } 26 | }) 27 | 28 | It("works multiple times without reading", func() { 29 | t := NewTimer() 30 | for i := 0; i < 10; i++ { 31 | t.Reset(time.Now().Add(d)) 32 | time.Sleep(d * 2) 33 | } 34 | Eventually(t.Chan()).Should(Receive()) 35 | }) 36 | 37 | It("works when resetting without expiration", func() { 38 | t := NewTimer() 39 | for i := 0; i < 10; i++ { 40 | t.Reset(time.Now().Add(time.Hour)) 41 | } 42 | t.Reset(time.Now().Add(d)) 43 | Eventually(t.Chan()).Should(Receive()) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /rlmp-quic/internal/utils/utils_suite_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestCrypto(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Utils Suite") 13 | } 14 | -------------------------------------------------------------------------------- /rlmp-quic/internal/wire/ack_range.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import "github.com/lucas-clemente/quic-go/internal/protocol" 4 | 5 | // AckRange is an ACK range 6 | type AckRange struct { 7 | First protocol.PacketNumber 8 | Last protocol.PacketNumber 9 | } 10 | -------------------------------------------------------------------------------- /rlmp-quic/internal/wire/blocked_frame.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | "github.com/lucas-clemente/quic-go/internal/utils" 8 | ) 9 | 10 | // A BlockedFrame in QUIC 11 | type BlockedFrame struct { 12 | StreamID protocol.StreamID 13 | } 14 | 15 | //Write writes a BlockedFrame frame 16 | func (f *BlockedFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { 17 | b.WriteByte(0x05) 18 | utils.GetByteOrder(version).WriteUint32(b, uint32(f.StreamID)) 19 | return nil 20 | } 21 | 22 | // MinLength of a written frame 23 | func (f *BlockedFrame) MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) { 24 | return 1 + 4, nil 25 | } 26 | 27 | // ParseBlockedFrame parses a BLOCKED frame 28 | func ParseBlockedFrame(r *bytes.Reader, version protocol.VersionNumber) (*BlockedFrame, error) { 29 | frame := &BlockedFrame{} 30 | 31 | // read the TypeByte 32 | if _, err := r.ReadByte(); err != nil { 33 | return nil, err 34 | } 35 | sid, err := utils.GetByteOrder(version).ReadUint32(r) 36 | if err != nil { 37 | return nil, err 38 | } 39 | frame.StreamID = protocol.StreamID(sid) 40 | return frame, nil 41 | } 42 | -------------------------------------------------------------------------------- /rlmp-quic/internal/wire/blocked_frame_test.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | var _ = Describe("BlockedFrame", func() { 12 | Context("when parsing", func() { 13 | Context("in little endian", func() { 14 | It("accepts sample frame", func() { 15 | b := bytes.NewReader([]byte{0x5, 0xef, 0xbe, 0xad, 0xde}) 16 | frame, err := ParseBlockedFrame(b, versionLittleEndian) 17 | Expect(err).ToNot(HaveOccurred()) 18 | Expect(frame.StreamID).To(Equal(protocol.StreamID(0xdeadbeef))) 19 | }) 20 | }) 21 | 22 | Context("in big endian", func() { 23 | It("accepts sample frame", func() { 24 | b := bytes.NewReader([]byte{0x5, 0xde, 0xad, 0xbe, 0xef}) 25 | frame, err := ParseBlockedFrame(b, versionBigEndian) 26 | Expect(err).ToNot(HaveOccurred()) 27 | Expect(frame.StreamID).To(Equal(protocol.StreamID(0xdeadbeef))) 28 | }) 29 | }) 30 | 31 | It("errors on EOFs", func() { 32 | data := []byte{0x5, 0xef, 0xbe, 0xad, 0xde} 33 | _, err := ParseBlockedFrame(bytes.NewReader(data), protocol.VersionWhatever) 34 | Expect(err).NotTo(HaveOccurred()) 35 | for i := range data { 36 | _, err := ParseBlockedFrame(bytes.NewReader(data[0:i]), protocol.VersionWhatever) 37 | Expect(err).To(HaveOccurred()) 38 | } 39 | }) 40 | }) 41 | 42 | Context("when writing", func() { 43 | Context("in little endian", func() { 44 | It("writes a sample frame", func() { 45 | b := &bytes.Buffer{} 46 | frame := BlockedFrame{StreamID: 0x1337} 47 | frame.Write(b, versionLittleEndian) 48 | Expect(b.Bytes()).To(Equal([]byte{0x5, 0x37, 0x13, 0x0, 0x0})) 49 | }) 50 | }) 51 | 52 | Context("in big endian", func() { 53 | It("writes a sample frame", func() { 54 | b := &bytes.Buffer{} 55 | frame := BlockedFrame{StreamID: 0x1337} 56 | frame.Write(b, versionBigEndian) 57 | Expect(b.Bytes()).To(Equal([]byte{0x5, 0x0, 0x0, 0x13, 0x37})) 58 | }) 59 | }) 60 | 61 | It("writes a connection-level Blocked", func() { 62 | b := &bytes.Buffer{} 63 | frame := BlockedFrame{StreamID: 0} 64 | frame.Write(b, 0) 65 | Expect(b.Bytes()).To(Equal([]byte{0x5, 0, 0, 0, 0})) 66 | }) 67 | 68 | It("has the correct min length", func() { 69 | frame := BlockedFrame{StreamID: 3} 70 | Expect(frame.MinLength(0)).To(Equal(protocol.ByteCount(5))) 71 | }) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /rlmp-quic/internal/wire/connection_close_frame.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "math" 8 | 9 | "github.com/lucas-clemente/quic-go/internal/protocol" 10 | "github.com/lucas-clemente/quic-go/internal/utils" 11 | "github.com/lucas-clemente/quic-go/qerr" 12 | ) 13 | 14 | // A ConnectionCloseFrame in QUIC 15 | type ConnectionCloseFrame struct { 16 | ErrorCode qerr.ErrorCode 17 | ReasonPhrase string 18 | } 19 | 20 | // ParseConnectionCloseFrame reads a CONNECTION_CLOSE frame 21 | func ParseConnectionCloseFrame(r *bytes.Reader, version protocol.VersionNumber) (*ConnectionCloseFrame, error) { 22 | frame := &ConnectionCloseFrame{} 23 | 24 | // read the TypeByte 25 | _, err := r.ReadByte() 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | errorCode, err := utils.GetByteOrder(version).ReadUint32(r) 31 | if err != nil { 32 | return nil, err 33 | } 34 | frame.ErrorCode = qerr.ErrorCode(errorCode) 35 | 36 | reasonPhraseLen, err := utils.GetByteOrder(version).ReadUint16(r) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | if reasonPhraseLen > uint16(protocol.MaxPacketSize) { 42 | return nil, qerr.Error(qerr.InvalidConnectionCloseData, "reason phrase too long") 43 | } 44 | 45 | reasonPhrase := make([]byte, reasonPhraseLen) 46 | if _, err := io.ReadFull(r, reasonPhrase); err != nil { 47 | return nil, err 48 | } 49 | frame.ReasonPhrase = string(reasonPhrase) 50 | 51 | return frame, nil 52 | } 53 | 54 | // MinLength of a written frame 55 | func (f *ConnectionCloseFrame) MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) { 56 | return 1 + 4 + 2 + protocol.ByteCount(len(f.ReasonPhrase)), nil 57 | } 58 | 59 | // Write writes an CONNECTION_CLOSE frame. 60 | func (f *ConnectionCloseFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { 61 | b.WriteByte(0x02) 62 | utils.GetByteOrder(version).WriteUint32(b, uint32(f.ErrorCode)) 63 | 64 | if len(f.ReasonPhrase) > math.MaxUint16 { 65 | return errors.New("ConnectionFrame: ReasonPhrase too long") 66 | } 67 | 68 | reasonPhraseLen := uint16(len(f.ReasonPhrase)) 69 | utils.GetByteOrder(version).WriteUint16(b, reasonPhraseLen) 70 | b.WriteString(f.ReasonPhrase) 71 | 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /rlmp-quic/internal/wire/frame.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | ) 8 | 9 | // A Frame in QUIC 10 | type Frame interface { 11 | Write(b *bytes.Buffer, version protocol.VersionNumber) error 12 | MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) 13 | } 14 | -------------------------------------------------------------------------------- /rlmp-quic/internal/wire/goaway_frame.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | 7 | "github.com/lucas-clemente/quic-go/internal/protocol" 8 | "github.com/lucas-clemente/quic-go/internal/utils" 9 | "github.com/lucas-clemente/quic-go/qerr" 10 | ) 11 | 12 | // A GoawayFrame is a GOAWAY frame 13 | type GoawayFrame struct { 14 | ErrorCode qerr.ErrorCode 15 | LastGoodStream protocol.StreamID 16 | ReasonPhrase string 17 | } 18 | 19 | // ParseGoawayFrame parses a GOAWAY frame 20 | func ParseGoawayFrame(r *bytes.Reader, version protocol.VersionNumber) (*GoawayFrame, error) { 21 | frame := &GoawayFrame{} 22 | 23 | if _, err := r.ReadByte(); err != nil { 24 | return nil, err 25 | } 26 | 27 | errorCode, err := utils.GetByteOrder(version).ReadUint32(r) 28 | if err != nil { 29 | return nil, err 30 | } 31 | frame.ErrorCode = qerr.ErrorCode(errorCode) 32 | 33 | lastGoodStream, err := utils.GetByteOrder(version).ReadUint32(r) 34 | if err != nil { 35 | return nil, err 36 | } 37 | frame.LastGoodStream = protocol.StreamID(lastGoodStream) 38 | 39 | reasonPhraseLen, err := utils.GetByteOrder(version).ReadUint16(r) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | if reasonPhraseLen > uint16(protocol.MaxPacketSize) { 45 | return nil, qerr.Error(qerr.InvalidGoawayData, "reason phrase too long") 46 | } 47 | 48 | reasonPhrase := make([]byte, reasonPhraseLen) 49 | if _, err := io.ReadFull(r, reasonPhrase); err != nil { 50 | return nil, err 51 | } 52 | frame.ReasonPhrase = string(reasonPhrase) 53 | return frame, nil 54 | } 55 | 56 | func (f *GoawayFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { 57 | b.WriteByte(0x03) 58 | utils.GetByteOrder(version).WriteUint32(b, uint32(f.ErrorCode)) 59 | utils.GetByteOrder(version).WriteUint32(b, uint32(f.LastGoodStream)) 60 | utils.GetByteOrder(version).WriteUint16(b, uint16(len(f.ReasonPhrase))) 61 | b.WriteString(f.ReasonPhrase) 62 | return nil 63 | } 64 | 65 | // MinLength of a written frame 66 | func (f *GoawayFrame) MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) { 67 | return protocol.ByteCount(1 + 4 + 4 + 2 + len(f.ReasonPhrase)), nil 68 | } 69 | -------------------------------------------------------------------------------- /rlmp-quic/internal/wire/log.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import "github.com/lucas-clemente/quic-go/internal/utils" 4 | 5 | // LogFrame logs a frame, either sent or received 6 | func LogFrame(frame Frame, sent bool) { 7 | if !utils.Debug() { 8 | return 9 | } 10 | dir := "<-" 11 | if sent { 12 | dir = "->" 13 | } 14 | switch f := frame.(type) { 15 | case *StreamFrame: 16 | utils.Debugf("\t%s &wire.StreamFrame{StreamID: %d, FinBit: %t, Offset: 0x%x, Data length: 0x%x, Offset + Data length: 0x%x}", dir, f.StreamID, f.FinBit, f.Offset, f.DataLen(), f.Offset+f.DataLen()) 17 | case *StopWaitingFrame: 18 | if sent { 19 | utils.Debugf("\t%s &wire.StopWaitingFrame{LeastUnacked: 0x%x, PacketNumberLen: 0x%x}", dir, f.LeastUnacked, f.PacketNumberLen) 20 | } else { 21 | utils.Debugf("\t%s &wire.StopWaitingFrame{LeastUnacked: 0x%x}", dir, f.LeastUnacked) 22 | } 23 | case *AckFrame: 24 | utils.Debugf("\t%s &wire.AckFrame{PathID: 0x%x, LargestAcked: 0x%x, LowestAcked: 0x%x, AckRanges: %#v, DelayTime: %s}", dir, f.PathID, f.LargestAcked, f.LowestAcked, f.AckRanges, f.DelayTime.String()) 25 | case *AddAddressFrame: 26 | utils.Debugf("\t%s &wire.AddAddressFrame{IPVersion: %d, Addr: %s}", dir, f.IPVersion, f.Addr.String()) 27 | case *ClosePathFrame: 28 | utils.Debugf("\t%s &wire.ClosePathFrame{PathID: 0x%x, LargestAcked: 0x%x, LowestAcked: 0x%x, AckRanges: %#v}", dir, f.PathID, f.LargestAcked, f.LowestAcked, f.AckRanges) 29 | default: 30 | utils.Debugf("\t%s %#v", dir, frame) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /rlmp-quic/internal/wire/ping_frame.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | ) 8 | 9 | // A PingFrame is a ping frame 10 | type PingFrame struct{} 11 | 12 | // ParsePingFrame parses a Ping frame 13 | func ParsePingFrame(r *bytes.Reader, version protocol.VersionNumber) (*PingFrame, error) { 14 | frame := &PingFrame{} 15 | 16 | _, err := r.ReadByte() 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | return frame, nil 22 | } 23 | 24 | func (f *PingFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { 25 | typeByte := uint8(0x07) 26 | b.WriteByte(typeByte) 27 | return nil 28 | } 29 | 30 | // MinLength of a written frame 31 | func (f *PingFrame) MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) { 32 | return 1, nil 33 | } 34 | -------------------------------------------------------------------------------- /rlmp-quic/internal/wire/ping_frame_test.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | var _ = Describe("PingFrame", func() { 12 | Context("when parsing", func() { 13 | It("accepts sample frame", func() { 14 | b := bytes.NewReader([]byte{0x07}) 15 | _, err := ParsePingFrame(b, protocol.VersionWhatever) 16 | Expect(err).ToNot(HaveOccurred()) 17 | Expect(b.Len()).To(BeZero()) 18 | }) 19 | 20 | It("errors on EOFs", func() { 21 | _, err := ParsePingFrame(bytes.NewReader(nil), protocol.VersionWhatever) 22 | Expect(err).To(HaveOccurred()) 23 | }) 24 | }) 25 | 26 | Context("when writing", func() { 27 | It("writes a sample frame", func() { 28 | b := &bytes.Buffer{} 29 | frame := PingFrame{} 30 | frame.Write(b, protocol.VersionWhatever) 31 | Expect(b.Bytes()).To(Equal([]byte{0x07})) 32 | }) 33 | 34 | It("has the correct min length", func() { 35 | frame := PingFrame{} 36 | Expect(frame.MinLength(0)).To(Equal(protocol.ByteCount(1))) 37 | }) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /rlmp-quic/internal/wire/public_reset.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | 8 | "github.com/lucas-clemente/quic-go/internal/handshake" 9 | "github.com/lucas-clemente/quic-go/internal/protocol" 10 | "github.com/lucas-clemente/quic-go/internal/utils" 11 | ) 12 | 13 | // A PublicReset is a PUBLIC_RESET 14 | type PublicReset struct { 15 | RejectedPacketNumber protocol.PacketNumber 16 | Nonce uint64 17 | } 18 | 19 | // WritePublicReset writes a Public Reset 20 | func WritePublicReset(connectionID protocol.ConnectionID, rejectedPacketNumber protocol.PacketNumber, nonceProof uint64) []byte { 21 | // TODO (QDC): a public reset should also contains the path ID 22 | b := &bytes.Buffer{} 23 | b.WriteByte(0x0a) 24 | utils.LittleEndian.WriteUint64(b, uint64(connectionID)) 25 | utils.LittleEndian.WriteUint32(b, uint32(handshake.TagPRST)) 26 | utils.LittleEndian.WriteUint32(b, 2) 27 | utils.LittleEndian.WriteUint32(b, uint32(handshake.TagRNON)) 28 | utils.LittleEndian.WriteUint32(b, 8) 29 | utils.LittleEndian.WriteUint32(b, uint32(handshake.TagRSEQ)) 30 | utils.LittleEndian.WriteUint32(b, 16) 31 | utils.LittleEndian.WriteUint64(b, nonceProof) 32 | utils.LittleEndian.WriteUint64(b, uint64(rejectedPacketNumber)) 33 | return b.Bytes() 34 | } 35 | 36 | // ParsePublicReset parses a Public Reset 37 | func ParsePublicReset(r *bytes.Reader) (*PublicReset, error) { 38 | // TODO (QDC): a public reset should also contains the path ID 39 | pr := PublicReset{} 40 | msg, err := handshake.ParseHandshakeMessage(r) 41 | if err != nil { 42 | return nil, err 43 | } 44 | if msg.Tag != handshake.TagPRST { 45 | return nil, errors.New("wrong public reset tag") 46 | } 47 | 48 | rseq, ok := msg.Data[handshake.TagRSEQ] 49 | if !ok { 50 | return nil, errors.New("RSEQ missing") 51 | } 52 | if len(rseq) != 8 { 53 | return nil, errors.New("invalid RSEQ tag") 54 | } 55 | pr.RejectedPacketNumber = protocol.PacketNumber(binary.LittleEndian.Uint64(rseq)) 56 | 57 | rnon, ok := msg.Data[handshake.TagRNON] 58 | if !ok { 59 | return nil, errors.New("RNON missing") 60 | } 61 | if len(rnon) != 8 { 62 | return nil, errors.New("invalid RNON tag") 63 | } 64 | pr.Nonce = binary.LittleEndian.Uint64(rnon) 65 | 66 | return &pr, nil 67 | } 68 | -------------------------------------------------------------------------------- /rlmp-quic/internal/wire/rst_stream_frame.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | "github.com/lucas-clemente/quic-go/internal/utils" 8 | ) 9 | 10 | // A RstStreamFrame in QUIC 11 | type RstStreamFrame struct { 12 | StreamID protocol.StreamID 13 | ErrorCode uint32 14 | ByteOffset protocol.ByteCount 15 | } 16 | 17 | //Write writes a RST_STREAM frame 18 | func (f *RstStreamFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { 19 | b.WriteByte(0x01) 20 | utils.GetByteOrder(version).WriteUint32(b, uint32(f.StreamID)) 21 | utils.GetByteOrder(version).WriteUint64(b, uint64(f.ByteOffset)) 22 | utils.GetByteOrder(version).WriteUint32(b, f.ErrorCode) 23 | return nil 24 | } 25 | 26 | // MinLength of a written frame 27 | func (f *RstStreamFrame) MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) { 28 | return 1 + 4 + 8 + 4, nil 29 | } 30 | 31 | // ParseRstStreamFrame parses a RST_STREAM frame 32 | func ParseRstStreamFrame(r *bytes.Reader, version protocol.VersionNumber) (*RstStreamFrame, error) { 33 | frame := &RstStreamFrame{} 34 | 35 | // read the TypeByte 36 | if _, err := r.ReadByte(); err != nil { 37 | return nil, err 38 | } 39 | 40 | sid, err := utils.GetByteOrder(version).ReadUint32(r) 41 | if err != nil { 42 | return nil, err 43 | } 44 | frame.StreamID = protocol.StreamID(sid) 45 | 46 | byteOffset, err := utils.GetByteOrder(version).ReadUint64(r) 47 | if err != nil { 48 | return nil, err 49 | } 50 | frame.ByteOffset = protocol.ByteCount(byteOffset) 51 | 52 | frame.ErrorCode, err = utils.GetByteOrder(version).ReadUint32(r) 53 | if err != nil { 54 | return nil, err 55 | } 56 | return frame, nil 57 | } 58 | -------------------------------------------------------------------------------- /rlmp-quic/internal/wire/version_negotiation.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | "github.com/lucas-clemente/quic-go/internal/utils" 8 | ) 9 | 10 | // ComposeVersionNegotiation composes a Version Negotiation Packet 11 | func ComposeVersionNegotiation(connectionID protocol.ConnectionID, versions []protocol.VersionNumber) []byte { 12 | fullReply := &bytes.Buffer{} 13 | responsePublicHeader := PublicHeader{ 14 | ConnectionID: connectionID, 15 | PacketNumber: 1, 16 | VersionFlag: true, 17 | } 18 | err := responsePublicHeader.Write(fullReply, protocol.VersionWhatever, protocol.PerspectiveServer) 19 | if err != nil { 20 | utils.Errorf("error composing version negotiation packet: %s", err.Error()) 21 | } 22 | for _, v := range versions { 23 | utils.LittleEndian.WriteUint32(fullReply, protocol.VersionNumberToTag(v)) 24 | } 25 | return fullReply.Bytes() 26 | } 27 | -------------------------------------------------------------------------------- /rlmp-quic/internal/wire/version_negotiation_test.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import ( 4 | "github.com/lucas-clemente/quic-go/internal/protocol" 5 | . "github.com/onsi/ginkgo" 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | var _ = Describe("Version Negotiation Packet", func() { 10 | It("composes version negotiation packets", func() { 11 | expected := append( 12 | []byte{0x01 | 0x08, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 13 | []byte{'Q', '0', '9', '9'}..., 14 | ) 15 | Expect(ComposeVersionNegotiation(1, []protocol.VersionNumber{99})).To(Equal(expected)) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /rlmp-quic/internal/wire/window_update_frame.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | "github.com/lucas-clemente/quic-go/internal/utils" 8 | ) 9 | 10 | // A WindowUpdateFrame in QUIC 11 | type WindowUpdateFrame struct { 12 | StreamID protocol.StreamID 13 | ByteOffset protocol.ByteCount 14 | } 15 | 16 | //Write writes a RST_STREAM frame 17 | func (f *WindowUpdateFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error { 18 | b.WriteByte(0x4) 19 | utils.GetByteOrder(version).WriteUint32(b, uint32(f.StreamID)) 20 | utils.GetByteOrder(version).WriteUint64(b, uint64(f.ByteOffset)) 21 | return nil 22 | } 23 | 24 | // MinLength of a written frame 25 | func (f *WindowUpdateFrame) MinLength(version protocol.VersionNumber) (protocol.ByteCount, error) { 26 | return 1 + 4 + 8, nil 27 | } 28 | 29 | // ParseWindowUpdateFrame parses a RST_STREAM frame 30 | func ParseWindowUpdateFrame(r *bytes.Reader, version protocol.VersionNumber) (*WindowUpdateFrame, error) { 31 | frame := &WindowUpdateFrame{} 32 | 33 | // read the TypeByte 34 | if _, err := r.ReadByte(); err != nil { 35 | return nil, err 36 | } 37 | 38 | sid, err := utils.GetByteOrder(version).ReadUint32(r) 39 | if err != nil { 40 | return nil, err 41 | } 42 | frame.StreamID = protocol.StreamID(sid) 43 | 44 | byteOffset, err := utils.GetByteOrder(version).ReadUint64(r) 45 | if err != nil { 46 | return nil, err 47 | } 48 | frame.ByteOffset = protocol.ByteCount(byteOffset) 49 | return frame, nil 50 | } 51 | -------------------------------------------------------------------------------- /rlmp-quic/internal/wire/wire_suite_test.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import ( 4 | "github.com/lucas-clemente/quic-go/internal/protocol" 5 | "github.com/lucas-clemente/quic-go/internal/utils" 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | 9 | "testing" 10 | ) 11 | 12 | func TestCrypto(t *testing.T) { 13 | RegisterFailHandler(Fail) 14 | RunSpecs(t, "Wire Suite") 15 | } 16 | 17 | const ( 18 | versionLittleEndian = protocol.Version37 // a QUIC version that uses little endian encoding 19 | versionBigEndian = protocol.Version39 // a QUIC version that uses big endian encoding 20 | ) 21 | 22 | var _ = BeforeSuite(func() { 23 | Expect(utils.GetByteOrder(versionLittleEndian)).To(Equal(utils.LittleEndian)) 24 | Expect(utils.GetByteOrder(versionBigEndian)).To(Equal(utils.BigEndian)) 25 | }) 26 | -------------------------------------------------------------------------------- /rlmp-quic/packet_number_generator.go: -------------------------------------------------------------------------------- 1 | package quic 2 | 3 | import ( 4 | "crypto/rand" 5 | "math" 6 | 7 | "github.com/lucas-clemente/quic-go/internal/protocol" 8 | ) 9 | 10 | // The packetNumberGenerator generates the packet number for the next packet 11 | // it randomly skips a packet number every averagePeriod packets (on average) 12 | // it is guarantued to never skip two consecutive packet numbers 13 | type packetNumberGenerator struct { 14 | averagePeriod protocol.PacketNumber 15 | 16 | next protocol.PacketNumber 17 | nextToSkip protocol.PacketNumber 18 | } 19 | 20 | func newPacketNumberGenerator(averagePeriod protocol.PacketNumber) *packetNumberGenerator { 21 | return &packetNumberGenerator{ 22 | next: 1, 23 | averagePeriod: averagePeriod, 24 | } 25 | } 26 | 27 | func (p *packetNumberGenerator) Peek() protocol.PacketNumber { 28 | return p.next 29 | } 30 | 31 | func (p *packetNumberGenerator) Pop() protocol.PacketNumber { 32 | next := p.next 33 | 34 | // generate a new packet number for the next packet 35 | p.next++ 36 | 37 | if p.next == p.nextToSkip { 38 | p.next++ 39 | p.generateNewSkip() 40 | } 41 | 42 | return next 43 | } 44 | 45 | func (p *packetNumberGenerator) generateNewSkip() error { 46 | num, err := p.getRandomNumber() 47 | if err != nil { 48 | return err 49 | } 50 | 51 | skip := protocol.PacketNumber(num) * (p.averagePeriod - 1) / (math.MaxUint16 / 2) 52 | // make sure that there are never two consecutive packet numbers that are skipped 53 | p.nextToSkip = p.next + 2 + skip 54 | 55 | return nil 56 | } 57 | 58 | // getRandomNumber() generates a cryptographically secure random number between 0 and MaxUint16 (= 65535) 59 | // The expectation value is 65535/2 60 | func (p *packetNumberGenerator) getRandomNumber() (uint16, error) { 61 | b := make([]byte, 2) 62 | _, err := rand.Read(b) 63 | if err != nil { 64 | return 0, err 65 | } 66 | 67 | num := uint16(b[0])<<8 + uint16(b[1]) 68 | return num, nil 69 | } 70 | -------------------------------------------------------------------------------- /rlmp-quic/packet_number_generator_test.go: -------------------------------------------------------------------------------- 1 | package quic 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/protocol" 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | var _ = Describe("Packet Number Generator", func() { 12 | var png packetNumberGenerator 13 | 14 | BeforeEach(func() { 15 | png = *newPacketNumberGenerator(100) 16 | }) 17 | 18 | It("gets 1 as the first packet number", func() { 19 | num := png.Pop() 20 | Expect(num).To(Equal(protocol.PacketNumber(1))) 21 | }) 22 | 23 | It("allows peeking", func() { 24 | png.nextToSkip = 1000 25 | Expect(png.Peek()).To(Equal(protocol.PacketNumber(1))) 26 | Expect(png.Peek()).To(Equal(protocol.PacketNumber(1))) 27 | num := png.Pop() 28 | Expect(num).To(Equal(protocol.PacketNumber(1))) 29 | Expect(png.Peek()).To(Equal(protocol.PacketNumber(2))) 30 | Expect(png.Peek()).To(Equal(protocol.PacketNumber(2))) 31 | }) 32 | 33 | It("skips a packet number", func() { 34 | png.nextToSkip = 2 35 | num := png.Pop() 36 | Expect(num).To(Equal(protocol.PacketNumber(1))) 37 | Expect(png.Peek()).To(Equal(protocol.PacketNumber(3))) 38 | num = png.Pop() 39 | Expect(num).To(Equal(protocol.PacketNumber(3))) 40 | }) 41 | 42 | It("generates a new packet number to skip", func() { 43 | png.next = 100 44 | png.averagePeriod = 100 45 | 46 | rep := 5000 47 | var sum protocol.PacketNumber 48 | 49 | for i := 0; i < rep; i++ { 50 | png.generateNewSkip() 51 | Expect(png.nextToSkip).ToNot(Equal(protocol.PacketNumber(101))) 52 | sum += png.nextToSkip 53 | } 54 | 55 | average := sum / protocol.PacketNumber(rep) 56 | Expect(average).To(BeNumerically("==", protocol.PacketNumber(200), 4)) 57 | }) 58 | 59 | It("uses random numbers", func() { 60 | var smallest uint16 = math.MaxUint16 61 | var largest uint16 62 | var sum uint64 63 | 64 | rep := 10000 65 | 66 | for i := 0; i < rep; i++ { 67 | num, err := png.getRandomNumber() 68 | Expect(err).ToNot(HaveOccurred()) 69 | sum += uint64(num) 70 | if num > largest { 71 | largest = num 72 | } 73 | if num < smallest { 74 | smallest = num 75 | } 76 | } 77 | 78 | Expect(smallest).To(BeNumerically("<", 300)) 79 | Expect(largest).To(BeNumerically(">", math.MaxUint16-300)) 80 | Expect(sum / uint64(rep)).To(BeNumerically("==", uint64(math.MaxUint16/2), 1000)) 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /rlmp-quic/qerr/errorcodes_test.go: -------------------------------------------------------------------------------- 1 | package qerr 2 | 3 | import ( 4 | "go/ast" 5 | "go/parser" 6 | "go/token" 7 | "path" 8 | "runtime" 9 | "strconv" 10 | 11 | . "github.com/onsi/ginkgo" 12 | . "github.com/onsi/gomega" 13 | ) 14 | 15 | var _ = Describe("error codes", func() { 16 | // If this test breaks, you should run `go generate ./...` 17 | It("has a string representation for every error code", func() { 18 | // We parse the error code file, extract all constants, and verify that 19 | // each of them has a string version. Go FTW! 20 | _, thisfile, _, ok := runtime.Caller(0) 21 | if !ok { 22 | panic("Failed to get current frame") 23 | } 24 | filename := path.Join(path.Dir(thisfile), "error_codes.go") 25 | fileAst, err := parser.ParseFile(token.NewFileSet(), filename, nil, 0) 26 | Expect(err).NotTo(HaveOccurred()) 27 | constSpecs := fileAst.Decls[0].(*ast.GenDecl).Specs 28 | Expect(len(constSpecs)).To(BeNumerically(">", 4)) // at time of writing 29 | for _, c := range constSpecs { 30 | name := c.(*ast.ValueSpec).Names[0].Name 31 | valString := c.(*ast.ValueSpec).Values[0].(*ast.BasicLit).Value 32 | val, err := strconv.Atoi(valString) 33 | Expect(err).NotTo(HaveOccurred()) 34 | Expect(ErrorCode(val).String()).To(Equal(name)) 35 | } 36 | Expect(ErrorCode(0).String()).To(Equal("ErrorCode(0)")) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /rlmp-quic/qerr/errors_suite_test.go: -------------------------------------------------------------------------------- 1 | package qerr 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestErrorcodes(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Errors Suite") 13 | } 14 | -------------------------------------------------------------------------------- /rlmp-quic/qerr/quic_error.go: -------------------------------------------------------------------------------- 1 | package qerr 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/lucas-clemente/quic-go/internal/utils" 7 | ) 8 | 9 | // ErrorCode can be used as a normal error without reason. 10 | type ErrorCode uint32 11 | 12 | func (e ErrorCode) Error() string { 13 | return e.String() 14 | } 15 | 16 | // A QuicError consists of an error code plus a error reason 17 | type QuicError struct { 18 | ErrorCode ErrorCode 19 | ErrorMessage string 20 | } 21 | 22 | // Error creates a new QuicError instance 23 | func Error(errorCode ErrorCode, errorMessage string) *QuicError { 24 | return &QuicError{ 25 | ErrorCode: errorCode, 26 | ErrorMessage: errorMessage, 27 | } 28 | } 29 | 30 | func (e *QuicError) Error() string { 31 | return fmt.Sprintf("%s: %s", e.ErrorCode.String(), e.ErrorMessage) 32 | } 33 | 34 | func (e *QuicError) Timeout() bool { 35 | switch e.ErrorCode { 36 | case NetworkIdleTimeout, 37 | HandshakeTimeout, 38 | TimeoutsWithOpenStreams: 39 | return true 40 | } 41 | return false 42 | } 43 | 44 | // ToQuicError converts an arbitrary error to a QuicError. It leaves QuicErrors 45 | // unchanged, and properly handles `ErrorCode`s. 46 | func ToQuicError(err error) *QuicError { 47 | switch e := err.(type) { 48 | case *QuicError: 49 | return e 50 | case ErrorCode: 51 | return Error(e, "") 52 | } 53 | utils.Errorf("Internal error: %v", err) 54 | return Error(InternalError, err.Error()) 55 | } 56 | -------------------------------------------------------------------------------- /rlmp-quic/qerr/quic_error_test.go: -------------------------------------------------------------------------------- 1 | package qerr 2 | 3 | import ( 4 | "io" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("Quic error", func() { 11 | Context("QuicError", func() { 12 | It("has a string representation", func() { 13 | err := Error(DecryptionFailure, "foobar") 14 | Expect(err.Error()).To(Equal("DecryptionFailure: foobar")) 15 | }) 16 | }) 17 | 18 | Context("ErrorCode", func() { 19 | It("works as error", func() { 20 | var err error = DecryptionFailure 21 | Expect(err).To(MatchError("DecryptionFailure")) 22 | }) 23 | }) 24 | 25 | Context("TimeoutError", func() { 26 | It("works as timeout error", func() { 27 | err := Error(HandshakeTimeout, "handshake timeout") 28 | Expect(err.Timeout()).Should(BeTrue()) 29 | }) 30 | }) 31 | 32 | Context("ToQuicError", func() { 33 | It("leaves QuicError unchanged", func() { 34 | err := Error(DecryptionFailure, "foo") 35 | Expect(ToQuicError(err)).To(Equal(err)) 36 | }) 37 | 38 | It("wraps ErrorCode properly", func() { 39 | var err error = DecryptionFailure 40 | Expect(ToQuicError(err)).To(Equal(Error(DecryptionFailure, ""))) 41 | }) 42 | 43 | It("changes default errors to InternalError", func() { 44 | Expect(ToQuicError(io.EOF)).To(Equal(Error(InternalError, "EOF"))) 45 | }) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /rlmp-quic/quic_suite_test.go: -------------------------------------------------------------------------------- 1 | package quic 2 | 3 | import ( 4 | "github.com/golang/mock/gomock" 5 | . "github.com/onsi/ginkgo" 6 | . "github.com/onsi/gomega" 7 | 8 | "testing" 9 | ) 10 | 11 | func TestQuicGo(t *testing.T) { 12 | RegisterFailHandler(Fail) 13 | RunSpecs(t, "QUIC Suite") 14 | } 15 | 16 | var mockCtrl *gomock.Controller 17 | 18 | var _ = BeforeEach(func() { 19 | mockCtrl = gomock.NewController(GinkgoT()) 20 | }) 21 | 22 | var _ = AfterEach(func() { 23 | mockCtrl.Finish() 24 | }) 25 | -------------------------------------------------------------------------------- /rlmp-quic/streamToPath_test.go: -------------------------------------------------------------------------------- 1 | package quic 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("StreamToPath", func() { 9 | var ( 10 | streamToPath StreamToPath 11 | ) 12 | Context("", func() { 13 | BeforeEach(func() { 14 | streamToPath = StreamToPath{} // maps are reference types itself 15 | streamToPath.Add(1, 1) 16 | streamToPath.Add(2, 1) 17 | streamToPath.Add(1, 2) 18 | 19 | }) 20 | 21 | Context("test func of StreamToPath", func() { 22 | It("add new item", func() { 23 | 24 | Expect(streamToPath).ToNot(BeNil()) 25 | 26 | values := streamToPath[1] 27 | Expect(len(values)).To(BeEquivalentTo(2)) 28 | 29 | }) 30 | It("get existing item", func() { 31 | values, err := streamToPath.Get(1) 32 | Expect(err).NotTo(HaveOccurred()) 33 | 34 | Expect(len(values)).To(BeEquivalentTo(2)) 35 | 36 | }) 37 | It("get not existed item", func() { 38 | values, err := streamToPath.Get(3) 39 | Expect(values).To(BeNil()) 40 | Expect(err).To(HaveOccurred()) 41 | 42 | }) 43 | It("delete existing item", func() { 44 | err := streamToPath.DeleteOne(1, 1) 45 | Expect(err).ToNot(HaveOccurred()) 46 | values := streamToPath[1] 47 | Expect(len(values)).To(BeEquivalentTo(1)) 48 | 49 | }) 50 | It("delete from nil map", func() { 51 | var st StreamToPath 52 | err := st.DeleteOne(1, 1) 53 | Expect(err).To(HaveOccurred()) 54 | 55 | }) 56 | 57 | It("delete not existed item", func() { 58 | err := streamToPath.DeleteOne(1, 4) 59 | Expect(err).To(HaveOccurred()) 60 | 61 | err = streamToPath.DeleteOne(4, 4) 62 | Expect(err).To(HaveOccurred()) 63 | 64 | err = streamToPath.DeleteOne(2, 4) 65 | Expect(err).To(HaveOccurred()) 66 | 67 | err = streamToPath.Delete(5) 68 | Expect(err).To(HaveOccurred()) 69 | 70 | }) 71 | }) 72 | }) 73 | 74 | }) 75 | -------------------------------------------------------------------------------- /rlmp-quic/transfer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | User="mininet" 4 | Host="192.168.122.157" 5 | SendDir="~/go/src/github.com/lucas-clemente/quic-go/" 6 | 7 | scp scheduler.go $User@$Host:$SendDir 8 | scp zclient.go $User@$Host:$SendDir 9 | scp zpublisher.go $User@$Host:$SendDir 10 | 11 | 12 | # scp example/client_browse_deptree/zpublisher.go $User@$Host:$SendDir/example/client_browse_deptree/ 13 | scp example/client_browse_deptree/main.go $User@$Host:$SendDir/example/client_browse_deptree/ 14 | # scp example/main.go $User@$Host:$SendDir/example/ 15 | 16 | 17 | # scp congestion/bdw_stats.go $User@$Host:$SendDir/congestion 18 | 19 | # Add PATH to stream 20 | scp session.go $User@$Host:$SendDir 21 | scp stream.go $User@$Host:$SendDir 22 | scp h2quic/server.go $User@$Host:$SendDir/h2quic/ --------------------------------------------------------------------------------