├── .gitignore ├── LICENSE ├── README.md ├── bin └── sn ├── config.json ├── example.py ├── install.sh ├── setup.py ├── starrynet ├── __init__.py ├── clean.py ├── cli.py ├── log.py ├── sn_observer.py ├── sn_orchestrater.py ├── sn_synchronizer.py └── sn_utils.py ├── tle ├── Dove.tle ├── Iridium.tle ├── OneWeb.tle ├── SkySat.tle ├── Starlink.tle └── Telesat.tle └── tools └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | .DS_Store 55 | .idea 56 | __pycache__ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 SpaceNetLab 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 | # StarryNet 2 | 3 | StarryNet for the emulation of satellite Internet constellations. 4 | 5 | ## What is StarryNet? 6 | 7 | StarryNet helps you to emulate your customized constellation and ground stations with run-time routing for a given duration. With StarryNet, you can test availability/bandwidth/loss within nodes, check routing states of a node and even damage certain links. 8 | 9 | ## What are the components? 10 | 11 | 1. A configuration file (`config.json`). 12 | 2. An API Library (`starrynet`). 13 | 3. An example leveraging APIs to run your trials (`example.py`). 14 | 4. A `setup.py` and `./bin/sn`. 15 | 16 | ## Preparation 17 | 18 | CentOS 7.9.2009 or above, Docker 20.10.17 or above and Python 3.6 or above. 19 | 20 | 1. Support for CentOS 7.9.2009 and Python 3 or Python 2. Also support Ubuntu 20.04 LTS (and 18.04). 21 | 2. Install Docker on the machine for emulation. 22 | 23 | ## Installation 24 | 25 | Then run `bash ./install.sh` to install CLI sn, which will also install packets like `python3 -m pip install setuptools xlrd copy argparse time numpy random requests math skyfield sgp4 datetime paramiko`. 26 | 27 | ## How to use it? 28 | 29 | 1. Prepare a data directory: 30 | 31 | Finish *remote_machine_IP, remote_machine_username and remote_machine_password* in config.json to specify the remote machine for running the emulation. 32 | 33 | Put `config.json`, `starrynet`, and `example.py` at the same working directory. 34 | 35 | 3. Start emulation: 36 | 37 | To speficy your own constellation, copy `config.json` and fill in the fields in it according to your satellite emulation environment, including the constellation name, orbit number, satellite number per orbit, ground station number, ground user number connected to each ground station and so on. You are only allowed to change `Name`, `Altitude (km)`, `Cycle (s)`, `Inclination`, `Phase shift`, `# of orbit`, `# of satellites`, `Duration(s)`, `update_time (s)`, `satellite link bandwidth ("X" Gbps)`, `sat-ground bandwidth ("X" Gbps)`, `satellite link loss ( 'X'% )`, `sat-ground loss ( 'X'% )`, `GS number`, `multi-machine('0' for no, '1' for yes)`, `antenna number`, `antenna_inclination_angle`, `remote_machine_IP`, `remote_machine_username`, `remote_machine_password` in `config.json`. 38 | 39 | Then use the APIs in `example.py` to start your trails. Remember to change the configuration_path of your `config.json`. 40 | 41 | 4. OSPF is the only intra-routing protocol. In `example.py` you need to set he hello-interval. (example in example.py): 42 | 43 | > HelloInterval = 1 44 | 45 | 5. In `example.py`, you need to specify the latitude and longitude of ground stations in sequence. Their node indexes will be named right after the satellite nodes. 46 | 47 | > GS_lat_long=[[50.110924,8.682127],[46.635700,14.311817]] # frankfurt and Austria 48 | 49 | 6. `ConfigurationFilePath` is where you put your config.json file, specified in `example.py`. 50 | > ConfigurationFilePath = "./config.json" 51 | 52 | ## What are the APIs? 53 | 54 | > sn.create_nodes() 55 | 56 | This API creates nodes for emulation, including satellite and GS nodes. 57 | 58 | > sn.create_links() 59 | 60 | This API creates initial network links for emulation. 61 | 62 | > sn.run_routing_deamon() 63 | 64 | This API initiates the OSPF routing for the network, otherwise the network has no routing protocol running. 65 | 66 | > sn.get_distance(node_index1, node_index2, time_index) 67 | 68 | This API returns distance between nodes at a certain time. 69 | 70 | > sn.get_neighbors(node_index1, time_index) 71 | 72 | This API returns neighbor node indexes of a node at a certain time. 73 | 74 | > sn.get_GSes(node_index1, time_index) 75 | 76 | This API returns GSes connected to the node at a certain time. 77 | 78 | > sn.get_position(node_index1, time_index) 79 | 80 | This API returns the LLA of a node at a certain time. 81 | 82 | > sn.get_utility(time_index) 83 | 84 | This API returns the current CPU utility and memory utility. 85 | 86 | > sn.get_IP(node_index1) 87 | 88 | This API returns a list of IPs of a node at a certain time. 89 | 90 | > sn.set_damage(ratio, time_index) 91 | 92 | This API sets a random damage for the network links of a given ratio at a certain time. 93 | 94 | > sn.set_recovery(time_index) 95 | 96 | This API will recover all the damaged links at a certain time. 97 | 98 | > sn.check_routing_table(node_index1, time_index) 99 | 100 | This API returns a routing table file of a node at a certain time. The output file could be found at the working directory. 101 | 102 | > sn.set_next_hop(sat, des, next_hop_sat, time_index) 103 | 104 | This API sets the next hop at a certain time. Sat, Des and NextHopSat are indexes and Sat and NextHopSat are neighbors. 105 | 106 | > sn.set_ping(node_index1, node_index2, time_index) 107 | 108 | This API will starts pinging msg of two nodes at a certain time. The output file could be found at the working directory. 109 | 110 | > sn.set_perf(node_index1, node_index2, time_index) 111 | 112 | This API will starts perfing msg of two nodes at a certain time. The output file could be found at the working directory. 113 | 114 | > sn.start_emulation() 115 | 116 | This API starts the entire emulation of the duration. 117 | 118 | > sn.stop_emulation() 119 | 120 | This API stops the eimulation and clears the environment. 121 | 122 | ## Example one: use APIs in python 123 | 124 | Run example.py to emulate the network. 125 | 126 | In this example, 5\*5 satellites from Starlink in 550km with an inclination of 53 degree and two ground stations in Frankfurt and Austria are emulated. The node index sequence is: 25 sattelites, 2 ground stations. 25 satellites and 2 ground stations are in one AS, where OSPF is running within it. Hello_interval(s) in OSPF is set as one second. AS specified in `config.json`, each GS has one antenna with an 25 degree inclination angle to connect the nearest satellite. Loss and throuput are alse set in `config.json`. Link delay updating granularity is one second. 127 | 128 | The emulation duration is set as 100 seconds in `config.json`. A 30 percent damage ratio is set in #5 second and the network will be recovered in #10 second. In #15 second, we'd like to see the routing table of node 27, which will be found in the working directory. In #20 second, we set the next hop of node 1 to node 2 in order to get to node 27. And we will have a ping information from node 26 to node 27 from #30 second to #80 second. After running, a new directory will be made in the current path, where the output information will be found. 129 | 130 | Other APIs help show the distance in km between node #1 and node #2 at #2 second, neighbor indexes of node #1 at the time, connected GS of node #7 at #2 second, LLA information of the node at the same time and all the IP of the node. Besides, get_utility will download a memory and CPU utility information at the time. 131 | 132 | ## Example two: use CLI in shell 133 | 134 | Finish *remote_machine_IP, remote_machine_username and remote_machine_password* in config.json to specify the remote machine for running the emulation. Other fields should also be filled as described above. 135 | 136 | > sn 137 | 138 | In the same path of `config.json`, run `sn` in shell, you will see the starrynet CLI. `sn` automatically starts a 5*5(satellites)+2(GS) scale network as described above if you only finish *remote_machine_IP, remote_machine_username and remote_machine_password* in config.json without changing other fields. See an example below. 139 | 140 | > sn -h 141 | 142 | > sn 143 | 144 | *This starts the CLI with the default 5*5+2 scale. You may also specify your customized scale in a config.json and run `sn -p "./config.json" -i 1 -n 27 -g 50.110924/8.682127/46.635700/14.311817` to start your own emulation. Here `-p` infers to the customized config.json path, `-i` infers to your customized hello packet intervall (10 by default), `-n` infers to the total node number and `-g` infers to the latitude and longitude of the GSes.* 145 | 146 | > starrynet> help 147 | 148 | > starrynet> create_nodes 149 | 150 | > starrynet> create_links 151 | 152 | > starrynet> run_routing_deamon 153 | 154 | > starrynet> get_distance 1 2 10 155 | 156 | *It means getting the distance of two node (#1 and #2) at #10 second.* 157 | 158 | > starrynet> get_neighbors 5 16 159 | 160 | *It means getting the neighbor node indexes of node #5 at #16 second.* 161 | 162 | > starrynet> get_GSes 7 20 163 | 164 | *It means getting the connected GS node indexes of node #6 at #20 second.* 165 | 166 | > starrynet> get_position 7 23 167 | 168 | *It means getting the LLA position of node #7 at #23 second.* 169 | 170 | > starrynet> get_IP 8 171 | 172 | *It means getting the IP addresses of node #8. "create_nodes" and "create_links" must be runned before this.* 173 | 174 | > starrynet> get_utility 27 175 | 176 | *It means getting the memory and CPU utility information at #27 second. The output file will be generated at the working directory once the emulation starts.* 177 | 178 | > starrynet> set_damage 0.3 30 179 | 180 | *It means setting a random damage of a given ratio of 0.3 at #30 second, which will be processed during emulation.* 181 | 182 | > starrynet> set_recovery 50 183 | 184 | *It means setting a recovery of the damages at #50 second, which will be processed during emulation.* 185 | 186 | > starrynet> check_routing_table 26 40 187 | 188 | *It means listing the routing table of node #26 at #40 second. The output file will be written at the working directory.* 189 | 190 | > starrynet> set_next_hop 1 26 2 45 191 | 192 | *It means setting the next hop to node #2 for node #1 for the destination of node #26 at #45 second. Sat, Des and NextHopSat are indexes and Sat and NextHopSat are neighbors, which will be processed during emulation.* 193 | 194 | > starrynet> set_ping 1 26 46 195 | 196 | *It means pinging from node #1 to node #26 at #46 second. The output file will be written at the working directory.* 197 | 198 | > starrynet> set_perf 1 26 46 199 | 200 | *It means perfing from node #1 to node #26 at #46 second. The perfing output file will be written at the working directory.* 201 | 202 | > starrynet> start_emulation 203 | 204 | *"create_nodes", "create_links" and "run_routing_deamon" must be runned before this. 205 | 206 | > starrynet> stop_emulation 207 | 208 | > starrynet> exit 209 | 210 | *After running the commands above, you will find a working directory at the Starrynet/starrynet directory, containing the output files.* -------------------------------------------------------------------------------- /bin/sn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Starrynet runner 4 | author: Yangtao Deng (dengyt21@mails.tsinghua.edu.cn) 5 | 6 | To see options: 7 | sudo sn -h 8 | 9 | """ 10 | 11 | from optparse import OptionParser 12 | import os 13 | import sys 14 | import time 15 | 16 | if 'PYTHONPATH' in os.environ: 17 | sys.path = os.environ['PYTHONPATH'].split(':') + sys.path 18 | from starrynet.sn_observer import * 19 | from starrynet.sn_orchestrater import * 20 | from starrynet.sn_synchronizer import * 21 | from starrynet.clean import cleanup 22 | import starrynet.cli 23 | from starrynet.log import lg, LEVELS, info, debug, warn, error, output 24 | 25 | 26 | class StarrynetRunner(object): 27 | "Build, setup, and run Starrynet." 28 | 29 | def __init__(self): 30 | "Init." 31 | self.options = None 32 | self.args = None 33 | self.validate = None 34 | self.AS = [] 35 | self.GS_lat_long = [] 36 | self.configuration_file_path = "./config.xls" 37 | self.hello_interval = None # hello_interval(s) in OSPF. 1-200 are supported. 38 | self.parseArgs() 39 | self.begin() 40 | 41 | def parseArgs(self): 42 | """Parse command-line args and return options object. 43 | returns: opts parse options dict""" 44 | 45 | desc = ("The %prog utility creates Starrynet network from the\n" 46 | "command line. It can create parametrized topologies,\n" 47 | "invoke the Starrynet CLI, and run tests.") 48 | 49 | usage = ('%prog [options]\n' 50 | '(type %prog -h for details)') 51 | 52 | opts = OptionParser(description=desc, usage=usage) 53 | opts.add_option( 54 | '--path', 55 | '-p', 56 | type='string', 57 | default="./config.xls", 58 | help=('path of the configuration file, e.g. ./config.xls')) 59 | opts.add_option( 60 | '--hello_interval', 61 | '-i', 62 | type='int', 63 | default=10, 64 | help=('interval to send hello packet, larger than 1, defaul 10')) 65 | opts.add_option( 66 | '--node_number', 67 | '-n', 68 | type='int', 69 | default=27, 70 | help=('number of network nodes, including satellites and GSes')) 71 | opts.add_option( 72 | '--GS', 73 | '-g', 74 | type='string', 75 | default="50.110924/8.682127/46.635700/14.311817", 76 | help= 77 | ('latitude and longitude of GSes sequentially, split by "/" like "la1/lo1/la2/lo2", e.g. 50.110924/8.682127/46.635700/14.311817' 78 | )) 79 | opts.add_option('--clean', 80 | '-c', 81 | action='store_true', 82 | default=False, 83 | help='clean and exit') 84 | 85 | self.options, self.args = opts.parse_args() 86 | 87 | # Extra arguments might be added in the future. 88 | if self.args: 89 | opts.print_help() 90 | exit() 91 | 92 | def begin(self): 93 | "Create and run starrynet." 94 | 95 | CLI = starrynet.cli.CLI 96 | 97 | opts = self.options 98 | self.AS.append([1, opts.node_number]) 99 | self.configuration_file_path = opts.path 100 | self.hello_interval = opts.hello_interval 101 | lat_long = opts.GS.split("/") 102 | for i in range(int(len(lat_long) / 2)): 103 | self.GS_lat_long.append( 104 | [float(lat_long[2 * i]), 105 | float(lat_long[2 * i + 1])]) 106 | 107 | if opts.clean: 108 | cleanup() 109 | exit() 110 | start = time.time() 111 | 112 | sn = StarryNet(self.configuration_file_path, self.GS_lat_long, 113 | self.hello_interval, self.AS) 114 | CLI(sn) 115 | cleanup() 116 | 117 | emulation_time = float(time.time() - start) 118 | info('completed in %0.3f seconds\n' % emulation_time) 119 | 120 | 121 | if __name__ == "__main__": 122 | try: 123 | StarrynetRunner() 124 | except KeyboardInterrupt: 125 | info("\n\nKeyboard Interrupt. Shutting down and cleaning up...\n\n") 126 | cleanup() 127 | except Exception: 128 | # Print exception 129 | type_, val_, trace_ = sys.exc_info() 130 | errorMsg = ("-" * 80 + "\n" + "Caught exception. Cleaning up...\n\n" + 131 | "%s: %s\n" % (type_.__name__, val_) + "-" * 80 + "\n") 132 | error(errorMsg) 133 | # Print stack trace to debug log 134 | import traceback 135 | stackTrace = traceback.format_exc() 136 | debug(stackTrace + "\n") 137 | cleanup() 138 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "starlink", 3 | "Altitude (km)": 550, 4 | "Cycle (s)": 5731, 5 | "Inclination": 53, 6 | "Phase shift": 1, 7 | "# of orbit": 5, 8 | "# of satellites": 5, 9 | "Duration (s)": 100, 10 | "update_time (s)": 10, 11 | "satellite link bandwidth (\"X\" Gbps)": 5, 12 | "sat-ground bandwidth (\"X\" Gbps)": 5, 13 | "satellite link loss (\"X\"% )": 1, 14 | "sat-ground loss (\"X\"% )": 1, 15 | "GS number": 2, 16 | "antenna number": 1, 17 | "antenna_inclination_angle": 25, 18 | "remote_machine_IP": "101.6.21.2", 19 | "remote_machine_username": "root", 20 | "remote_machine_password": "123456", 21 | "Satellite link": "grid", 22 | "IP version": "IPv4", 23 | "Intra-AS routing": "OSPF", 24 | "Inter-AS routing": "BGP", 25 | "Link policy": "LeastDelay", 26 | "Handover policy": "instant handover", 27 | "multi-machine (\"0\" for no, \"1\" for yes)": 0 28 | } -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | StarryNet: empowering researchers to evaluate futuristic integrated space and terrestrial networks. 5 | author: Zeqi Lai (zeqilai@tsinghua.edu.cn) and Yangtao Deng (dengyt21@mails.tsinghua.edu.cn) 6 | """ 7 | 8 | from starrynet.sn_observer import * 9 | from starrynet.sn_orchestrater import * 10 | from starrynet.sn_synchronizer import * 11 | 12 | if __name__ == "__main__": 13 | # Starlink 5*5: 25 satellite nodes, 2 ground stations. 14 | # The node index sequence is: 25 sattelites, 2 ground stations. 15 | # In this example, 25 satellites and 2 ground stations are one AS. 16 | 17 | AS = [[1, 27]] # Node #1 to Node #27 are within the same AS. 18 | GS_lat_long = [[50.110924, 8.682127], [46.635700, 14.311817] 19 | ] # latitude and longitude of frankfurt and Austria 20 | configuration_file_path = "./config.json" 21 | hello_interval = 1 # hello_interval(s) in OSPF. 1-200 are supported. 22 | 23 | print('Start StarryNet.') 24 | sn = StarryNet(configuration_file_path, GS_lat_long, hello_interval, AS) 25 | sn.create_nodes() 26 | sn.create_links() 27 | sn.run_routing_deamon() 28 | 29 | node_index1 = 1 30 | node_index2 = 2 31 | time_index = 2 32 | 33 | # distance between nodes at a certain time 34 | node_distance = sn.get_distance(node_index1, node_index2, time_index) 35 | print("node_distance (km): " + str(node_distance)) 36 | 37 | # neighbor node indexes of node at a certain time 38 | neighbors_index = sn.get_neighbors(node_index1, time_index) 39 | print("neighbors_index: " + str(neighbors_index)) 40 | 41 | # GS connected to the node at a certain time 42 | node_index1 = 7 43 | GSes = sn.get_GSes(node_index1, time_index) 44 | print("GSes are: " + str(GSes)) 45 | 46 | # LLA of a node at a certain time 47 | LLA = sn.get_position(node_index1, time_index) 48 | print("LLA: " + str(LLA)) 49 | 50 | sn.get_utility(time_index) # CPU and memory useage 51 | 52 | # IPList of a node 53 | IP_list = sn.get_IP(node_index1) 54 | print("IP: " + str(IP_list)) 55 | 56 | ratio = 0.3 57 | time_index = 5 58 | # random damage of a given ratio at a certain time 59 | sn.set_damage(ratio, time_index) 60 | 61 | time_index = 10 62 | sn.set_recovery(time_index) # recover the damages at a certain time 63 | 64 | node_index1 = 27 65 | time_index = 15 66 | # routing table of a node at a certain time. The output file will be written at the working directory. 67 | sn.check_routing_table(node_index1, time_index) 68 | 69 | sat = 1 70 | des = 27 71 | next_hop_sat = 2 72 | time_index = 20 73 | # set the next hop at a certain time. Sat, Des and NextHopSat are indexes and Sat and NextHopSat are neighbors. 74 | sn.set_next_hop(sat, des, next_hop_sat, time_index) 75 | 76 | node_index1 = 13 77 | node_index2 = 14 78 | time_index = 3 79 | # ping msg of two nodes at a certain time. The output file will be written at the working directory. 80 | sn.set_ping(node_index1, node_index2, time_index) 81 | for i in range(35, 80): 82 | node_index1 = 26 83 | node_index2 = 27 84 | time_index = i 85 | # ping msg of two nodes at a certain time. The output file will be written at the working directory. 86 | sn.set_ping(node_index1, node_index2, time_index) 87 | 88 | node_index1 = 13 89 | node_index2 = 14 90 | time_index = 4 91 | # perf msg of two nodes at a certain time. The output file will be written at the working directory. 92 | sn.set_perf(node_index1, node_index2, time_index) 93 | 94 | sn.start_emulation() 95 | sn.stop_emulation() 96 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # installation script for Ubuntu, Debian and CentOS 4 | # author: Yangtao Deng 5 | 6 | DIST=Unknown 7 | RELEASE=Unknown 8 | CODENAME=Unknown 9 | ARCH=`uname -m` 10 | if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; fi 11 | if [ "$ARCH" = "i686" ]; then ARCH="i386"; fi 12 | 13 | test -e /etc/debian_version && DIST="Debian" 14 | grep Ubuntu /etc/lsb-release &> /dev/null && DIST="Ubuntu" 15 | if [ "$DIST" = "Ubuntu" ] || [ "$DIST" = "Debian" ]; then 16 | # Truly non-interactive apt-get installation 17 | install='sudo DEBIAN_FRONTEND=noninteractive apt-get -y -q install' 18 | remove='sudo DEBIAN_FRONTEND=noninteractive apt-get -y -q remove' 19 | pkginst='sudo dpkg -i' 20 | update='sudo apt-get' 21 | # Prereqs for this script 22 | if ! which lsb_release &> /dev/null; then 23 | $install lsb-release 24 | fi 25 | fi 26 | test -e /etc/fedora-release && DIST="Fedora" 27 | test -e /etc/redhat-release && DIST="RedHatEnterpriseServer" 28 | if [ "$DIST" = "Fedora" -o "$DIST" = "RedHatEnterpriseServer" ] || [ "$DIST" = "CentOS" ]; then 29 | install='sudo yum -y install' 30 | remove='sudo yum -y erase' 31 | pkginst='sudo rpm -ivh' 32 | update='sudo yum' 33 | # Prereqs for this script 34 | if ! which lsb_release &> /dev/null; then 35 | $install redhat-lsb-core 36 | fi 37 | fi 38 | 39 | echo "Installing dependencies" 40 | $install python3 python-setuptools python3-pip 41 | $install docker-ce docker-ce-cli containerd.io 42 | sudo python3 -m pip install --upgrade pip 43 | sudo python3 -m pip install -r tools/requirements.txt 44 | sudo python3 setup.py install -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | "Setuptools params" 3 | 4 | from setuptools import setup, find_packages 5 | from os.path import join 6 | 7 | # Get version number from source tree 8 | import sys 9 | 10 | sys.path.append('.') 11 | 12 | scripts = [join('bin', filename) for filename in ['sn']] 13 | 14 | modname = distname = 'starrynet' 15 | 16 | setup( 17 | name=distname, 18 | version="1.0.0", 19 | description= 20 | 'StarryNet for the emulation of satellite Internet constellations.', 21 | author=' Yangtao Deng', 22 | author_email='dengyt21@mails.tsinghua.edu.cn', 23 | packages=['starrynet'], 24 | long_description=""" 25 | StarryNet is a network emulator for satellite Internet constellations. 26 | """, 27 | classifiers=[ 28 | "License :: OSI Approved :: BSD License", 29 | "Programming Language :: Python", 30 | "Development Status :: 1 - Production/Stable", 31 | "Intended Audience :: Developers", 32 | "Topic :: System :: Emulators", 33 | ], 34 | keywords='satellite Internet constellations emulator protocol', 35 | license='BSD', 36 | install_requires=['setuptools'], 37 | scripts=scripts, 38 | ) 39 | -------------------------------------------------------------------------------- /starrynet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpaceNetLab/StarryNet/91d87bd0d77c2c164b88db0adb0d28f3305a1b53/starrynet/__init__.py -------------------------------------------------------------------------------- /starrynet/clean.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | Starrynet Cleanup 5 | author: Yangtao Deng (dengyt21@mails.tsinghua.edu.cn) 6 | """ 7 | import os 8 | 9 | 10 | def cleanup(): 11 | print("Deleting all native bridges and containers...") 12 | #os.system("FOR /f \"tokens=*\" %i IN ('docker ps -q') DO docker stop %i") 13 | os.system("docker service rm constellation-test") 14 | with os.popen( 15 | "docker rm -f $(docker ps | grep \"lwsen/starlab_node:1.0\" | awk '{ print $1 }')" 16 | ) as f: 17 | f.readlines() 18 | with os.popen("docker network ls") as f: 19 | all_br_info = f.readlines() 20 | for line in all_br_info: 21 | if "La" in line or "Le" or "GS" in line: 22 | network_name = line.split()[1] 23 | print('docker network rm ' + network_name) 24 | os.system('docker network rm ' + network_name) 25 | 26 | 27 | if __name__ == "__main__": 28 | cleanup() -------------------------------------------------------------------------------- /starrynet/cli.py: -------------------------------------------------------------------------------- 1 | """ 2 | A simple command-line interface for Starrynet. 3 | 4 | The Starrynet CLI provides a simple control console which 5 | makes it easy to control the network and have access to nodes. For example, the command 6 | 7 | starrynet> create_nodes 8 | 9 | simply starts the network nodes. 10 | 11 | starrynet> get_neighbors 1, 5 12 | 13 | gets the neighbors of node#1 at 5 second. 14 | 15 | starrynet> set_ping 26 27 30 16 | 17 | should work correctly and allow node#26 to ping node#26 at 30 second. 18 | 19 | starrynet> set_perf 26 27 30 20 | 21 | should work correctly and allow node#26 to perf node#26 at 30 second. 22 | 23 | author: Yangtao Deng (dengyt21@mails.tsinghua.edu.cn) 24 | """ 25 | 26 | from subprocess import call 27 | from cmd import Cmd 28 | from os import isatty 29 | from select import poll, POLLIN 30 | import sys 31 | 32 | from starrynet.log import info, output, error 33 | # from starrynet.term import makeTerms, runX11 34 | # from starrynet.util import ( quietRun, dumpNodeConnections, 35 | # dumpPorts ) 36 | 37 | 38 | class CLI(Cmd): 39 | "Simple command-line interface to talk to nodes." 40 | 41 | prompt = 'starrynet> ' 42 | 43 | def __init__(self, starrynet, stdin=sys.stdin, *args, **kwargs): 44 | """Start and run interactive or batch mode CLI 45 | starrynet: Starrynet network object 46 | stdin: standard input for CLI 47 | script: script to run in batch mode""" 48 | self.sn = starrynet 49 | # Local variable bindings for py command 50 | self.locals = {'net': starrynet} 51 | # Attempt to handle input 52 | self.inPoller = poll() 53 | self.inPoller.register(stdin) 54 | Cmd.__init__(self, *args, stdin=stdin, **kwargs) 55 | info('*** Starting CLI:\n') 56 | 57 | self.run() 58 | 59 | def run(self): 60 | "Run our cmdloop(), catching KeyboardInterrupt" 61 | while True: 62 | try: 63 | self.cmdloop() 64 | break 65 | except KeyboardInterrupt: 66 | # Output a message - unless it's also interrupted 67 | # pylint: disable=broad-except 68 | try: 69 | output('\nInterrupt\n') 70 | except Exception: 71 | pass 72 | # pylint: enable=broad-except 73 | 74 | def emptyline(self): 75 | "Don't repeat last command when you hit return." 76 | pass 77 | 78 | def getLocals(self): 79 | "Local variable bindings for py command" 80 | self.locals.update(self.mn) 81 | return self.locals 82 | 83 | helpStr = ( 84 | 'Supported commands are as follows:\n' 85 | ' starrynet> help\n' 86 | ' starrynet> create_nodes\n' 87 | ' starrynet> create_links\n' 88 | ' starrynet> run_routing_deamon\n' 89 | ' starrynet> get_distance 1 2 10\n' 90 | ' // It means getting the distance of two node (#1 and #2) at #10 second.\n' 91 | ' starrynet> get_neighbors 5 16\n' 92 | ' // It means getting the neighbor node indexes of node #5 at #16 second.\n' 93 | ' starrynet> get_GSes 7 20\n' 94 | ' // It means getting the connected GS node indexes of node #6 at #20 second.\n' 95 | ' starrynet> get_position 7 23 \n' 96 | ' // It means getting the LLA position of node #7 at #23 second.\n' 97 | ' starrynet> get_IP 8 \n' 98 | ' // It means getting the IP addresses of node #8. "create_nodes" and "create_links" must be runned before this.\n' 99 | ' starrynet> get_utility 27\n' 100 | ' // It means getting the memory and CPU utility information at #27 second. The output file will be generated at the working directory once the emulation starts.\n' 101 | ' starrynet> set_damage 0.3 30\n' 102 | ' // It means setting a random damage of a given ratio of 0.3 at #30 second, which will be processed during emulation.\n' 103 | ' starrynet> set_recovery 50\n' 104 | ' // It means setting a recovery of the damages at #50 second, which will be processed during emulation.\n' 105 | ' starrynet> check_routing_table 26 40\n' 106 | ' // It means listing the routing table of node #26 at #40 second. The output file will be written at the working directory.\n' 107 | ' starrynet> set_next_hop 1 26 2 45\n' 108 | ' // It means setting the next hop to node #2 for node #1 for the destination of node #26 at #45 second. Sat, Des and NextHopSat are indexes and Sat and NextHopSat are neighbors, which will be processed during emulation.\n' 109 | ' starrynet> set_ping 1 26 46\n' 110 | ' // It means pinging msg of from node #1 to node #26 at #46 second. The output file will be written at the working directory.\n' 111 | ' starrynet> set_perf 1 26 46\n' 112 | ' // It means perfing from node #1 to node #26 at #46 second. The perfing output file will be written at the working directory.\n' 113 | ' starrynet> start_emulation\n' 114 | ' // "create_nodes", "create_links" and "run_routing_deamon" must be runned before this.' 115 | ' starrynet> stop_emulation\n' 116 | ' starrynet> exit\n' 117 | ' starrynet> quit\n' 118 | ' starrynet> EOF\n' 119 | ' You may use the commands multiple times.\n') 120 | 121 | def do_help(self, line): 122 | "Describe available CLI commands." 123 | Cmd.do_help(self, line) 124 | if line == '': 125 | output(self.helpStr) 126 | 127 | def do_create_nodes(self, _line): 128 | "initialize the entire network nodes" 129 | self.sn.create_nodes() 130 | 131 | def do_create_links(self, _line): 132 | "initialize the entire network links" 133 | self.sn.create_links() 134 | 135 | def do_run_routing_deamon(self, _line): 136 | "run routing deamon for each node" 137 | self.sn.run_routing_deamon() 138 | 139 | def do_get_distance(self, line): 140 | "calculate the distance of two node at a certain time" 141 | arg, args, line = self.parseline(line) 142 | rest = line.split(' ') 143 | node_distance = self.sn.get_distance(int(rest[0]), int(rest[1]), 144 | int(rest[2])) 145 | output("The distance between node#%d and node#%d is %.2fkm.\n" % 146 | (int(rest[0]), int(rest[1]), node_distance)) 147 | 148 | def do_get_neighbors(self, line): 149 | "list the neighbor node indexes of node at a certain time" 150 | arg, args, line = self.parseline(line) 151 | rest = line.split(' ') 152 | neighbors_index = self.sn.get_neighbors(int(rest[0]), int(rest[1])) 153 | output("The neighbors are: " + str(neighbors_index) + ".\n") 154 | 155 | def do_get_GSes(self, line): 156 | "list the GS connected to the node at a certain time" 157 | arg, args, line = self.parseline(line) 158 | rest = line.split(' ') 159 | GSes = self.sn.get_GSes(int(rest[0]), int(rest[1])) 160 | output("The connected GS(es) is(are): " + str(GSes) + ".\n") 161 | 162 | def do_get_position(self, line): 163 | "list the LLA of a node at a certain time" 164 | arg, args, line = self.parseline(line) 165 | rest = line.split(' ') 166 | LLA = self.sn.get_position(int(rest[0]), int(rest[1])) 167 | output("The LLA is: " + str(LLA)) 168 | 169 | def do_get_IP(self, line): 170 | "list the IP of a node" 171 | arg, args, line = self.parseline(line) 172 | IP = self.sn.get_IP(int(arg)) 173 | output("The IP list of the node is(are): " + str(IP) + ".\n") 174 | 175 | def do_get_utility(self, line): 176 | "list the CPU and memory useage at a certain time" 177 | "The output file will be generated once the emulation starts" 178 | arg, args, line = self.parseline(line) 179 | self.sn.get_utility(int(arg)) 180 | 181 | def do_set_damage(self, line): 182 | "set a random damage of a given ratio at a certain time, which will be processed during emulation" 183 | arg, args, line = self.parseline(line) 184 | rest = line.split(' ') 185 | self.sn.set_damage(float(rest[0]), int(rest[1])) 186 | 187 | def do_set_recovery(self, line): 188 | "set a recovery of the damages at a certain time, which will be processed during emulation" 189 | arg, args, line = self.parseline(line) 190 | rest = line.split(' ') 191 | self.sn.set_recovery(int(rest[0])) 192 | 193 | def do_check_routing_table(self, line): 194 | "list the routing table of a node at a certain time." 195 | "The output file will be written at the working directory." 196 | arg, args, line = self.parseline(line) 197 | rest = line.split(' ') 198 | self.sn.check_routing_table(int(rest[0]), int(rest[1])) 199 | 200 | def do_set_next_hop(self, line): 201 | "set the nhelpext hop at a certain time" 202 | "Sat, Des and NextHopSat are indexes and Sat and NextHopSat are neighbors, which will be processed during emulation" 203 | arg, args, line = self.parseline(line) 204 | rest = line.split(' ') 205 | self.sn.set_next_hop(int(rest[0]), int(rest[1]), int(rest[2]), 206 | int(rest[3])) 207 | 208 | def do_path(self, line): 209 | "get the working directory" 210 | output(self.sn.configuration_file_path + "\n") 211 | 212 | def do_set_ping(self, line): 213 | "ping msg of two nodes at a certain time" 214 | "The output file will be written at the working directory" 215 | arg, args, line = self.parseline(line) 216 | rest = line.split(' ') 217 | self.sn.set_ping(int(rest[0]), int(rest[1]), int(rest[2])) 218 | 219 | def do_set_perf(self, line): 220 | "perf msg of two nodes at a certain time" 221 | "The output file will be written at the working directory" 222 | arg, args, line = self.parseline(line) 223 | rest = line.split(' ') 224 | self.sn.set_perf(int(rest[0]), int(rest[1]), int(rest[2])) 225 | 226 | def do_start_emulation(self, _line): 227 | "start the emulation" 228 | self.sn.start_emulation() 229 | 230 | def do_stop_emulation(self, _line): 231 | "stop the emulation" 232 | self.sn.stop_emulation() 233 | return 'exited by user command' 234 | 235 | def do_exit(self, _line): 236 | "stop the emulation" 237 | self.sn.stop_emulation() 238 | return 'exited by user command' 239 | 240 | def do_quit(self, line): 241 | "Exit" 242 | return self.do_exit(line) 243 | 244 | def do_EOF(self, line): 245 | "Exit" 246 | output('\n') 247 | return self.do_exit(line) 248 | 249 | def default(self, line): 250 | "Exit" 251 | error('*** Unknown command: %s\n' % line) 252 | return 253 | -------------------------------------------------------------------------------- /starrynet/log.py: -------------------------------------------------------------------------------- 1 | "Logging functions for Starrynet." 2 | 3 | import logging 4 | from logging import Logger 5 | import types 6 | 7 | # Create a new loglevel, 'CLI info', which enables a Starrynet user to see only 8 | # the output of the commands they execute, plus any errors or warnings. This 9 | # level is in between info and warning. CLI info-level commands should not be 10 | # printed during regression tests. 11 | OUTPUT = 25 12 | 13 | LEVELS = {'debug': logging.DEBUG, 14 | 'info': logging.INFO, 15 | 'output': OUTPUT, 16 | 'warning': logging.WARNING, 17 | 'error': logging.ERROR, 18 | 'critical': logging.CRITICAL} 19 | 20 | # change this to logging.INFO to get printouts when running unit tests 21 | LOGLEVELDEFAULT = OUTPUT 22 | 23 | #default: '%(asctime)s - %(name)s - %(levelname)s - %(message)s' 24 | LOGMSGFORMAT = '%(message)s' 25 | 26 | 27 | # Modified from python2.5/__init__.py 28 | class StreamHandlerNoNewline(logging.StreamHandler): 29 | """StreamHandler that doesn't print newlines by default. 30 | Since StreamHandler automatically adds newlines, define a mod to more 31 | easily support interactive mode when we want it, or errors-only logging 32 | for running unit tests.""" 33 | 34 | def emit(self, record): 35 | """Emit a record. 36 | If a formatter is specified, it is used to format the record. 37 | The record is then written to the stream with a trailing newline 38 | [ N.B. this may be removed depending on feedback ]. If exception 39 | information is present, it is formatted using 40 | traceback.printException and appended to the stream.""" 41 | try: 42 | msg = self.format(record) 43 | fs = '%s' # was '%s\n' 44 | if not hasattr(types, 'UnicodeType'): # if no unicode support... 45 | self.stream.write(fs % msg) 46 | else: 47 | try: 48 | self.stream.write(fs % msg) 49 | except UnicodeError: 50 | self.stream.write(fs % msg.encode('UTF-8')) 51 | self.flush() 52 | except (KeyboardInterrupt, SystemExit): 53 | raise 54 | except: 55 | self.handleError(record) 56 | 57 | 58 | class Singleton(type): 59 | """Singleton pattern from Wikipedia 60 | See http://en.wikipedia.org/wiki/Singleton_Pattern 61 | 62 | Intended to be used as a __metaclass_ param, as shown for the class 63 | below.""" 64 | 65 | def __init__(cls, name, bases, dict_): 66 | super(Singleton, cls).__init__(name, bases, dict_) 67 | cls.instance = None 68 | 69 | def __call__(cls, *args, **kw): 70 | if cls.instance is None: 71 | cls.instance = super(Singleton, cls).__call__(*args, **kw) 72 | return cls.instance 73 | 74 | 75 | class StarrynetLogger(Logger, object): 76 | """Starrynet-specific logger 77 | Enable each starrynet .py file to with one import: 78 | 79 | from starrynet.log import [lg, info, error] 80 | 81 | ...get a default logger that doesn't require one newline per logging 82 | call. 83 | 84 | Inherit from object to ensure that we have at least one new-style base 85 | class, and can then use the __metaclass__ directive, to prevent this 86 | error: 87 | 88 | TypeError: Error when calling the metaclass bases 89 | a new-style class can't have only classic bases 90 | 91 | If Python2.5/logging/__init__.py defined Filterer as a new-style class, 92 | via Filterer( object ): rather than Filterer, we wouldn't need this. 93 | 94 | Use singleton pattern to ensure only one logger is ever created.""" 95 | 96 | __metaclass__ = Singleton 97 | 98 | def __init__(self): 99 | 100 | Logger.__init__(self, "starrynet") 101 | 102 | # create console handler 103 | ch = StreamHandlerNoNewline() 104 | # create formatter 105 | formatter = logging.Formatter(LOGMSGFORMAT) 106 | # add formatter to ch 107 | ch.setFormatter(formatter) 108 | # add ch to lg 109 | self.addHandler(ch) 110 | 111 | self.setLogLevel() 112 | 113 | def setLogLevel(self, levelname=None): 114 | """Setup loglevel. 115 | Convenience function to support lowercase names. 116 | levelName: level name from LEVELS""" 117 | level = LOGLEVELDEFAULT 118 | if levelname is not None: 119 | if levelname not in LEVELS: 120 | raise Exception('unknown levelname seen in setLogLevel') 121 | else: 122 | level = LEVELS.get(levelname, level) 123 | 124 | self.setLevel(level) 125 | self.handlers[0].setLevel(level) 126 | 127 | # pylint: disable=method-hidden 128 | # "An attribute inherited from starrynet.log hide this method" (sic) 129 | # Not sure why this is occurring - this function definitely gets called. 130 | 131 | # See /usr/lib/python2.5/logging/__init__.py; modified from warning() 132 | def output(self, msg, *args, **kwargs): 133 | """Log 'msg % args' with severity 'OUTPUT'. 134 | 135 | To pass exception information, use the keyword argument exc_info 136 | with a true value, e.g. 137 | 138 | logger.warning("Houston, we have a %s", "cli output", exc_info=1) 139 | """ 140 | if self.manager.disable >= OUTPUT: 141 | return 142 | if self.isEnabledFor(OUTPUT): 143 | self._log(OUTPUT, msg, args, kwargs) 144 | 145 | # pylint: enable=method-hidden 146 | 147 | 148 | lg = StarrynetLogger() 149 | 150 | # Make things a bit more convenient by adding aliases 151 | # (info, warn, error, debug) and allowing info( 'this', 'is', 'OK' ) 152 | # In the future we may wish to make things more efficient by only 153 | # doing the join (and calling the function) unless the logging level 154 | # is high enough. 155 | 156 | 157 | def makeListCompatible(fn): 158 | """Return a new function allowing fn( 'a 1 b' ) to be called as 159 | newfn( 'a', 1, 'b' )""" 160 | 161 | def newfn(*args): 162 | "Generated function. Closure-ish." 163 | if len(args) == 1: 164 | return fn(*args) 165 | args = ' '.join(str(arg) for arg in args) 166 | return fn(args) 167 | 168 | # Fix newfn's name and docstring 169 | setattr(newfn, '__name__', fn.__name__) 170 | setattr(newfn, '__doc__', fn.__doc__) 171 | return newfn 172 | 173 | 174 | _loggers = lg.info, lg.output, lg.warn, lg.error, lg.debug 175 | _loggers = tuple(makeListCompatible(logger) 176 | for logger in _loggers) 177 | lg.info, lg.output, lg.warn, lg.error, lg.debug = _loggers 178 | info, output, warn, error, debug = _loggers 179 | 180 | setLogLevel = lg.setLogLevel 181 | -------------------------------------------------------------------------------- /starrynet/sn_observer.py: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | import math 3 | from sgp4.api import Satrec, WGS84 4 | from skyfield.api import load, wgs84, EarthSatellite 5 | from datetime import datetime 6 | import numpy as np 7 | import os 8 | 9 | from starrynet.sn_utils import * 10 | 11 | _ = inf = 999999 # inf 12 | 13 | # To calculate the connection between satellites and GSes in time_in 14 | # fac_num: number of GSes 15 | 16 | 17 | class Observer(): 18 | 19 | def __init__(self, file_path, configuration_file_path, inclination, 20 | satellite_altitude, orbit_number, sat_number, duration, 21 | antenna_number, GS_lat_long, antenna_inclination, 22 | intra_routing, hello_interval, AS): 23 | self.file_path = file_path 24 | self.configuration_file_path = configuration_file_path 25 | self.inclination = inclination 26 | self.satellite_altitude = satellite_altitude 27 | self.orbit_number = orbit_number 28 | self.sat_number = sat_number 29 | self.duration = duration 30 | self.antenna_number = antenna_number 31 | self.GS_lat_long = GS_lat_long 32 | self.antenna_inclination = antenna_inclination 33 | self.intra_routing = intra_routing 34 | self.hello_interval = hello_interval 35 | self.AS = AS 36 | 37 | def access_P_L_shortest(self, sat_cbf, fac_cbf, fac_num, sat_num, 38 | num_orbits, num_sats_per_orbit, duration, fac_ll, 39 | sat_lla, bound_dis, alpha, antenna_num, path): 40 | delay_matrix = np.zeros((fac_num + sat_num, fac_num + sat_num)) 41 | for cur_time in range(duration): 42 | for i in range(0, fac_num): 43 | access_list = {} 44 | fac_lat = float(fac_ll[i][0]) # latitude 45 | up_lat = fac_lat + alpha # bound 46 | down_lat = fac_lat - alpha 47 | x2 = fac_cbf[i][0] 48 | y2 = fac_cbf[i][1] 49 | z2 = fac_cbf[i][2] 50 | for j in range(0, sat_num): 51 | if sat_lla[cur_time][j][0] >= down_lat and sat_lla[ 52 | cur_time][j][0] <= up_lat: 53 | x1 = sat_cbf[cur_time][j][0] # in km 54 | y1 = sat_cbf[cur_time][j][1] 55 | z1 = sat_cbf[cur_time][j][2] 56 | dist = math.sqrt( 57 | np.square(x1 - x2) + np.square(y1 - y2) + 58 | np.square(z1 - z2)) 59 | if dist < bound_dis: 60 | # [satellite index,distance] 61 | access_list.update({j: dist}) 62 | if len(access_list) > antenna_num: 63 | sorted_access_list = dict( 64 | sorted(access_list.items(), key=lambda item: item[1])) 65 | cnt = 0 66 | for key, value in sorted_access_list.items(): 67 | cnt = cnt + 1 68 | if cnt > antenna_num: 69 | break 70 | delay_time = value / (17.31 / 29.5 * 71 | 299792.458) * 1000 # ms 72 | delay_matrix[sat_num + i][key] = delay_time 73 | delay_matrix[key][sat_num + i] = delay_time 74 | elif len(access_list) != 0: 75 | for key, value in access_list.items(): 76 | delay_time = value / (17.31 / 29.5 * 77 | 299792.458) * 1000 # ms 78 | delay_matrix[sat_num + i][key] = delay_time 79 | delay_matrix[key][sat_num + i] = delay_time 80 | for i in range(num_orbits): 81 | for j in range(num_sats_per_orbit): 82 | num_sat1 = i * num_sats_per_orbit + j 83 | x1 = sat_cbf[cur_time][num_sat1][0] # km 84 | y1 = sat_cbf[cur_time][num_sat1][1] 85 | z1 = sat_cbf[cur_time][num_sat1][2] 86 | num_sat2 = i * num_sats_per_orbit + ( 87 | j + 1) % num_sats_per_orbit 88 | x2 = sat_cbf[cur_time][num_sat2][0] # km 89 | y2 = sat_cbf[cur_time][num_sat2][1] 90 | z2 = sat_cbf[cur_time][num_sat2][2] 91 | num_sat3 = ((i + 1) % num_orbits) * num_sats_per_orbit + j 92 | x3 = sat_cbf[cur_time][num_sat3][0] # km 93 | y3 = sat_cbf[cur_time][num_sat3][1] 94 | z3 = sat_cbf[cur_time][num_sat3][2] 95 | delay1 = math.sqrt( 96 | np.square(x1 - x2) + np.square(y1 - y2) + 97 | np.square(z1 - z2)) / (17.31 / 29.5 * 98 | 299792.458) * 1000 # ms 99 | delay2 = math.sqrt( 100 | np.square(x1 - x3) + np.square(y1 - y3) + 101 | np.square(z1 - z3)) / (17.31 / 29.5 * 102 | 299792.458) * 1000 # ms 103 | delay_matrix[num_sat1][num_sat2] = delay1 104 | delay_matrix[num_sat2][num_sat1] = delay1 105 | delay_matrix[num_sat1][num_sat3] = delay2 106 | delay_matrix[num_sat3][num_sat1] = delay2 107 | np.savetxt(path + "/delay/" + str(cur_time + 1) + ".txt", 108 | delay_matrix, 109 | fmt='%.2f', 110 | delimiter=',') 111 | for i in range(len(delay_matrix)): 112 | delay_matrix[i, ...] = 0 113 | 114 | def to_cbf(self, lat_long, 115 | length): # the xyz coordinate system. length: number of nodes 116 | cbf = [] 117 | radius = 6371 118 | for num in range(0, length): 119 | cbf_in = [] 120 | R = radius 121 | if len(lat_long[num]) > 2: 122 | R += lat_long[num][2] 123 | z = R * math.sin(math.radians(float(lat_long[num][0]))) 124 | x = R * math.cos(math.radians(float( 125 | lat_long[num][0]))) * math.cos( 126 | math.radians(float(lat_long[num][1]))) 127 | y = R * math.cos(math.radians(float( 128 | lat_long[num][0]))) * math.sin( 129 | math.radians(float(lat_long[num][1]))) 130 | cbf_in.append(x) 131 | cbf_in.append(y) 132 | cbf_in.append(z) 133 | cbf.append(cbf_in) 134 | return cbf # xyz coordinates of all the satellites 135 | 136 | def calculate_bound(self, inclination_angle, height): 137 | bound_distance = 6371 * math.cos( 138 | (90 + inclination_angle) / 180 * math.pi) + math.sqrt( 139 | math.pow( 140 | 6371 * math.cos( 141 | (90 + inclination_angle) / 180 * math.pi), 2) + 142 | math.pow(height, 2) + 2 * height * 6371) 143 | return bound_distance 144 | 145 | def matrix_to_change(self, duration, orbit_number, sat_number, path, 146 | GS_lat_long): 147 | no_fac = len(GS_lat_long) 148 | no_geo = 0 149 | duration = duration - 1 150 | no_leo = orbit_number * sat_number 151 | 152 | topo_duration = [[[0 for i in range(no_leo + no_geo + no_fac)] 153 | for i in range(no_leo + no_geo + no_fac)] 154 | for k in range(duration)] 155 | for time in range(1, duration + 1): 156 | topo_path = path + '/delay/' + str(time) + ".txt" 157 | adjacency_matrix = sn_get_param(topo_path) 158 | for i in range(len(adjacency_matrix)): 159 | for j in range(len(adjacency_matrix[i])): 160 | if float(adjacency_matrix[i][j]) > 0: 161 | adjacency_matrix[i][j] = 1 162 | else: 163 | adjacency_matrix[i][j] = 0 164 | topo_duration[time - 1] = adjacency_matrix 165 | 166 | changetime = [] 167 | Duration = [] 168 | for i in range(duration - 1): 169 | l1 = topo_duration[i] 170 | l2 = topo_duration[i + 1] 171 | if l1 == l2: 172 | continue 173 | else: 174 | changetime.append(i) 175 | pretime = 0 176 | for item in changetime: 177 | Duration.append(item - pretime) 178 | pretime = item 179 | Duration.append(60) 180 | 181 | topo_leo_change_path = path + "/Topo_leo_change.txt" 182 | f = open(topo_leo_change_path, "w") 183 | cnt = 1 184 | for i in range(duration - 1): 185 | pre_lines = topo_duration[i] 186 | now_lines = topo_duration[i + 1] 187 | if pre_lines == now_lines: 188 | continue 189 | else: 190 | f.write("time " + str(i + 2) + ":\n") # time started from 1 191 | f.write('duration ' + str(Duration[cnt]) + ":\n") 192 | cnt += 1 193 | f.write("add:\n") 194 | for j in range(no_fac): 195 | prelines = pre_lines[no_geo + no_leo + j] 196 | nowlines = now_lines[no_geo + no_leo + j] 197 | for k in range(no_geo + no_leo + no_fac): 198 | if prelines[k] == 0 and nowlines[k] == 1: 199 | f.write( 200 | str(k + 1) + "-" + str(no_leo + j + 1) + 201 | "\n") # index 202 | f.write("del:\n") 203 | for j in range(no_fac): 204 | prelines = pre_lines[no_geo + no_leo + j] 205 | nowlines = now_lines[no_geo + no_leo + j] 206 | for k in range(no_geo + no_leo + no_fac): 207 | if prelines[k] == 1 and nowlines[k] == 0: 208 | f.write( 209 | str(k + 1) + "-" + str(no_leo + j + 1) + 210 | "\n") # index 211 | f.write("time " + str(self.duration) + ":\n") # 212 | f.write("end of the emulation! \n") # 213 | f.close() 214 | cnt = 1 215 | 216 | def calculate_delay(self): 217 | path = self.configuration_file_path + "/" + self.file_path 218 | sat_cbf = [ 219 | ] # first dimension: time. second dimension: node. third dimension: xyz 220 | sat_lla = [ 221 | ] # first dimension: time. second dimension: node. third dimension: lla 222 | fac_cbf = [] # first dimension: node. second dimension: xyz 223 | 224 | if os.path.exists(path + '/delay') == True: 225 | osstr = "rm -f " + path + "/delay/*" 226 | os.system(osstr) 227 | else: 228 | os.system("mkdir " + path) 229 | os.system("mkdir " + path + "/delay") 230 | if os.path.exists(path + '/position') == True: 231 | osstr = "rm -f " + path + "/position/*" 232 | os.system(osstr) 233 | else: 234 | os.system("mkdir " + path + "/position") 235 | 236 | ts = load.timescale() 237 | since = datetime(1949, 12, 31, 0, 0, 0) 238 | start = datetime(2020, 1, 1, 0, 0, 0) 239 | epoch = (start - since).days 240 | inclination = self.inclination * 2 * np.pi / 360 241 | GM = 3.9860044e14 242 | R = 6371393 243 | altitude = self.satellite_altitude * 1000 244 | mean_motion = np.sqrt(GM / (R + altitude)**3) * 60 245 | num_of_orbit = self.orbit_number 246 | sat_per_orbit = self.sat_number 247 | num_of_sat = num_of_orbit * sat_per_orbit 248 | F = 18 249 | bound_dis = self.calculate_bound( 250 | self.antenna_inclination, self.satellite_altitude) * 29.5 / 17.31 251 | 252 | duration = self.duration # second 253 | result = [[] for i in range(duration)] # LLA result 254 | lla_per_sec = [[] for i in range(duration)] # LLA result 255 | 256 | for i in range(num_of_orbit): # range(num_of_orbit) 257 | raan = i / num_of_orbit * 2 * np.pi 258 | for j in range(sat_per_orbit): # range(sat_per_orbit) 259 | mean_anomaly = (j * 360 / sat_per_orbit + i * 360 * F / 260 | num_of_sat) % 360 * 2 * np.pi / 360 261 | satrec = Satrec() 262 | satrec.sgp4init( 263 | WGS84, # gravity model 264 | 'i', # 'a' = old AFSPC mode, 'i' = improved mode 265 | i * sat_per_orbit + j, # satnum: Satellite number 266 | epoch, # epoch: days since 1949 December 31 00:00 UT 267 | 2.8098e-05, # bstar: drag coefficient (/earth radii) 268 | 6.969196665e-13, # ndot: ballistic coefficient (revs/day) 269 | 0.0, # nddot: second derivative of mean motion (revs/day^3) 270 | 0.001, # ecco: eccentricity 271 | 0.0, # argpo: argument of perigee (radians) 272 | inclination, # inclo: inclination (radians) 273 | mean_anomaly, # mo: mean anomaly (radians) 274 | mean_motion, # no_kozai: mean motion (radians/minute) 275 | raan, # nodeo: right ascension of ascending node (radians) 276 | ) 277 | sat = EarthSatellite.from_satrec(satrec, ts) 278 | cur = datetime(2022, 1, 1, 1, 0, 0) 279 | t_ts = ts.utc(*cur.timetuple()[:5], 280 | range(duration)) # [:4]:minute,[:5]:second 281 | geocentric = sat.at(t_ts) 282 | subpoint = wgs84.subpoint(geocentric) 283 | # list: [subpoint.latitude.degrees] [subpoint.longitude.degrees] [subpoint.elevation.km] 284 | for t in range(duration): 285 | lla = '%f,%f,%f\n' % (subpoint.latitude.degrees[t], 286 | subpoint.longitude.degrees[t], 287 | subpoint.elevation.km[t]) 288 | result[t].append(lla) 289 | lla = [] 290 | lla.append(subpoint.latitude.degrees[t]) 291 | lla.append(subpoint.longitude.degrees[t]) 292 | lla.append(subpoint.elevation.km[t]) 293 | lla_per_sec[t].append(lla) 294 | 295 | for t in range(duration): 296 | file = path + '/position/' + '%d.txt' % t 297 | with open(file, 'w') as fw: 298 | fw.writelines(result[t]) 299 | cbf_per_sec = self.to_cbf(lla_per_sec[t], num_of_sat) 300 | sat_cbf.append(cbf_per_sec) 301 | sat_lla.append(lla_per_sec[t]) 302 | 303 | if len(self.GS_lat_long) != 0: 304 | fac_cbf = self.to_cbf(self.GS_lat_long, len(self.GS_lat_long)) 305 | 306 | alpha = np.degrees( 307 | np.arccos(6371 / (6371 + self.satellite_altitude) * 308 | np.cos(np.radians(inclination)))) - inclination 309 | self.access_P_L_shortest(sat_cbf, fac_cbf, len(self.GS_lat_long), 310 | self.sat_number * self.orbit_number, 311 | self.orbit_number, self.sat_number, 312 | self.duration, self.GS_lat_long, sat_lla, 313 | bound_dis, alpha, self.antenna_number, path) 314 | self.matrix_to_change(self.duration, self.orbit_number, 315 | self.sat_number, path, self.GS_lat_long) 316 | 317 | def compute_conf(self, sat_node_number, interval, num1, num2, ID, Q, 318 | num_backbone, matrix): 319 | Q.append( 320 | "log \"/var/log/bird.log\" { debug, trace, info, remote, warning, error, auth, fatal, bug };" 321 | ) 322 | Q.append("debug protocols all;") 323 | Q.append("protocol device {") 324 | Q.append("}") 325 | Q.append(" protocol direct {") 326 | Q.append(" disabled; # Disable by default") 327 | Q.append(" ipv4; # Connect to default IPv4 table") 328 | Q.append(" ipv6; # ... and to default IPv6 table") 329 | Q.append("}") 330 | Q.append("protocol kernel {") 331 | Q.append(" ipv4 { # Connect protocol to IPv4 table by channel") 332 | Q.append( 333 | " export all; # Export to protocol. default is export none") 334 | Q.append(" };") 335 | Q.append("}") 336 | # Q.append("protocol kernel {") 337 | # Q.append(" ipv6 { export all; ") 338 | # Q.append(" };") 339 | # Q.append("}") 340 | Q.append("protocol static {") 341 | Q.append(" ipv4; # Again, IPv6 channel with default options") 342 | Q.append("}") 343 | Q.append("protocol ospf{") 344 | Q.append(" ipv4 {") 345 | Q.append(" import all;") 346 | Q.append(" };") 347 | Q.append(" area 0 {") 348 | Q.append(" interface \"B%d-eth0\" {" % ID) 349 | Q.append(" type broadcast; # Detected by default") 350 | Q.append(" cost 256;") 351 | Q.append(" hello " + str(interval) + 352 | "; # Default hello perid 10 is too long") 353 | Q.append(" };") 354 | Q.append(" interface \"inter_machine\" {") 355 | Q.append(" type broadcast; # Detected by default") 356 | Q.append(" cost 256;") 357 | Q.append(" hello " + str(interval) + 358 | "; # Default hello perid 10 is too long") 359 | Q.append(" };") 360 | if num1 <= sat_node_number and num2 <= num_backbone and ID <= sat_node_number: # satellite 361 | for peer in range(num1, num2 + 1): 362 | if (peer == ID) or (int(float(matrix[ID - 1][peer - 1])) == 0): 363 | continue 364 | Q.append(" interface \"B%d-eth%d\" {" % (ID, peer)) 365 | Q.append(" type broadcast; # Detected by default") 366 | Q.append(" cost 256;") 367 | Q.append(" hello " + str(interval) + 368 | "; # Default hello perid 10 is too long") 369 | Q.append(" };") 370 | if num2 > sat_node_number: 371 | for i in range(sat_node_number + 1, 372 | num_backbone + 1): # each ground station 373 | Q.append(" interface \"B%d-eth%d\" {" % (ID, i)) 374 | Q.append(" type broadcast; # Detected by default") 375 | Q.append(" cost 256;") 376 | Q.append(" hello " + str(interval) + 377 | "; # Default hello perid 10 is too long") 378 | Q.append(" };") 379 | elif num1 <= sat_node_number and num2 <= num_backbone and ID > sat_node_number: # ground station 380 | for peer in range(1, 381 | 1 + sat_node_number): # fac and each satellite 382 | Q.append(" interface \"B%d-eth%d\" {" % (ID, peer)) 383 | Q.append(" type broadcast; # Detected by default") 384 | Q.append(" cost 256;") 385 | Q.append(" hello " + str(interval) + 386 | "; # Default hello perid 10 is too long") 387 | Q.append(" };") 388 | Q.append(" interface \"B%d-default\" {" % (ID)) 389 | Q.append(" type broadcast; # Detected by default") 390 | Q.append(" cost 256;") 391 | Q.append(" hello " + str(interval) + 392 | "; # Default hello perid 10 is too long") 393 | Q.append(" };") 394 | elif num1 > num_backbone and num2 > num_backbone: # ground users 395 | if ID != num1 and ID != num2: 396 | Q.append(" interface \"B%d-eth%d\" {" % (ID, ID - 1)) 397 | Q.append(" type broadcast; # Detected by default") 398 | Q.append(" cost 256;") 399 | Q.append(" hello " + str(interval) + 400 | "; # Default hello perid 10 is too long") 401 | Q.append(" };") 402 | Q.append(" interface \"B%d-eth%d\" {" % (ID, ID + 1)) 403 | Q.append(" type broadcast; # Detected by default") 404 | Q.append(" cost 256;") 405 | Q.append(" hello " + str(interval) + 406 | "; # Default hello perid 10 is too long") 407 | Q.append(" };") 408 | elif ID == num1: 409 | Q.append(" interface \"B%d-eth%d\" {" % (ID, ID + 1)) 410 | Q.append(" type broadcast; # Detected by default") 411 | Q.append(" cost 256;") 412 | Q.append(" hello " + str(interval) + 413 | "; # Default hello perid 10 is too long") 414 | Q.append(" };") 415 | elif ID == num2: 416 | Q.append(" interface \"B%d-eth%d\" {" % (ID, ID - 1)) 417 | Q.append(" type broadcast; # Detected by default") 418 | Q.append(" cost 256;") 419 | Q.append(" hello " + str(interval) + 420 | "; # Default hello perid 10 is too long") 421 | Q.append(" };") 422 | else: 423 | return False 424 | Q.append(" };") 425 | Q.append(" }") 426 | return True 427 | 428 | def print_conf(self, sat_node_number, fac_node_number, ID, Q, remote_ftp): 429 | filename = self.file_path + "/conf/bird-" + \ 430 | str(sat_node_number) + "-" + str(fac_node_number) + "/B%d.conf" % ID 431 | fout = open(self.configuration_file_path + "/" + filename, 'w+') 432 | for item in Q: 433 | fout.write(str(item) + "\n") 434 | fout.close() 435 | remote_ftp.put(self.configuration_file_path + "/" + filename, filename) 436 | 437 | def generate_conf(self, remote_ssh, remote_ftp): 438 | if self.intra_routing != "OSPF" and self.intra_routing != "ospf": 439 | return False 440 | if os.path.exists(self.configuration_file_path + "/" + self.file_path + 441 | "/conf/bird-" + 442 | str(self.orbit_number * self.sat_number) + "-" + 443 | str(len(self.GS_lat_long))) == True: 444 | osstr = "rm -f " + self.configuration_file_path+"/"+self.file_path+"/conf/bird-" + \ 445 | str(self.orbit_number*self.sat_number) + "-" + str(len(self.GS_lat_long)) + "/*" 446 | os.system(osstr) 447 | sn_remote_cmd(remote_ssh, "mkdir ~/" + self.file_path + "/conf") 448 | sn_remote_cmd( 449 | remote_ssh, "mkdir ~/" + self.file_path + "/conf/bird-" + 450 | str(self.orbit_number * self.sat_number) + "-" + 451 | str(len(self.GS_lat_long))) 452 | else: 453 | os.makedirs(self.configuration_file_path + "/" + self.file_path + 454 | "/conf/bird-" + 455 | str(self.orbit_number * self.sat_number) + "-" + 456 | str(len(self.GS_lat_long))) 457 | sn_remote_cmd(remote_ssh, "mkdir ~/" + self.file_path + "/conf") 458 | sn_remote_cmd( 459 | remote_ssh, "mkdir ~/" + self.file_path + "/conf/bird-" + 460 | str(self.orbit_number * self.sat_number) + "-" + 461 | str(len(self.GS_lat_long))) 462 | path = self.configuration_file_path + "/" + self.file_path + "/delay/1.txt" 463 | matrix = sn_get_param(path) 464 | num_backbone = self.orbit_number * self.sat_number + len( 465 | self.GS_lat_long) 466 | error = True 467 | for i in range(len(self.AS)): 468 | if len(self.AS[i]) != 1: 469 | for ID in range(self.AS[i][0], self.AS[i][1] + 1): 470 | Q = [] 471 | error = self.compute_conf( 472 | self.orbit_number * self.sat_number, 473 | self.hello_interval, self.AS[i][0], self.AS[i][1], ID, 474 | Q, num_backbone, matrix) 475 | self.print_conf(self.orbit_number * self.sat_number, 476 | len(self.GS_lat_long), ID, Q, remote_ftp) 477 | else: # one node in one AS 478 | ID = self.AS[i][0] 479 | Q = [] 480 | Q.append( 481 | "log \"/var/log/bird.log\" { debug, trace, info, remote, warning, error, auth, fatal, bug };" 482 | ) 483 | Q.append("debug protocols all;") 484 | Q.append("protocol device {") 485 | Q.append("}") 486 | Q.append(" protocol direct {") 487 | Q.append(" disabled; # Disable by default") 488 | Q.append(" ipv4; # Connect to default IPv4 table") 489 | Q.append(" ipv6; # ... and to default IPv6 table") 490 | Q.append("}") 491 | Q.append("protocol kernel {") 492 | Q.append( 493 | " ipv4 { # Connect protocol to IPv4 table by channel") 494 | Q.append( 495 | " export all; # Export to protocol. default is export none" 496 | ) 497 | Q.append(" };") 498 | Q.append("}") 499 | Q.append("protocol static {") 500 | Q.append( 501 | " ipv4; # Again, IPv6 channel with default options") 502 | Q.append("}") 503 | Q.append("protocol ospf {") 504 | Q.append(" ipv4 {") 505 | Q.append(" import all;") 506 | Q.append(" };") 507 | Q.append(" area 0 {") 508 | Q.append(" interface \"B%d-eth0\" {" % ID) 509 | Q.append(" type broadcast; # Detected by default") 510 | Q.append(" cost 256;") 511 | Q.append(" hello " + str(self.hello_interval) + 512 | "; # Default hello perid 10 is too long") 513 | Q.append(" };") 514 | Q.append(" interface \"inter_machine\" {") 515 | Q.append(" type broadcast; # Detected by default") 516 | Q.append(" cost 256;") 517 | Q.append(" hello " + str(self.hello_interval) + 518 | "; # Default hello perid 10 is too long") 519 | Q.append(" };") 520 | Q.append(" };") 521 | Q.append(" }") 522 | self.print_conf(self.orbit_number * self.sat_number, 523 | len(self.GS_lat_long), ID, Q, remote_ftp) 524 | 525 | return error 526 | -------------------------------------------------------------------------------- /starrynet/sn_orchestrater.py: -------------------------------------------------------------------------------- 1 | import os 2 | import threading 3 | import sys 4 | from time import sleep 5 | import numpy 6 | """ 7 | Used in the remote machine for link updating, initializing links, damaging and recovering links and other functionalities。 8 | author: Yangtao Deng (dengyt21@mails.tsinghua.edu.cn) and Zeqi Lai (zeqilai@tsinghua.edu.cn) 9 | """ 10 | 11 | 12 | def sn_get_right_satellite(current_sat_id, current_orbit_id, orbit_num): 13 | if current_orbit_id == orbit_num - 1: 14 | return [current_sat_id, 0] 15 | else: 16 | return [current_sat_id, current_orbit_id + 1] 17 | 18 | 19 | def sn_get_down_satellite(current_sat_id, current_orbit_id, sat_num): 20 | if current_sat_id == sat_num - 1: 21 | return [0, current_orbit_id] 22 | else: 23 | return [current_sat_id + 1, current_orbit_id] 24 | 25 | 26 | def sn_ISL_establish(current_sat_id, current_orbit_id, container_id_list, 27 | orbit_num, sat_num, constellation_size, matrix, bw, loss): 28 | current_id = current_orbit_id * sat_num + current_sat_id 29 | isl_idx = current_id * 2 + 1 30 | # Establish intra-orbit ISLs 31 | # (Down): 32 | [down_sat_id, 33 | down_orbit_id] = sn_get_down_satellite(current_sat_id, current_orbit_id, 34 | sat_num) 35 | print("[" + str(isl_idx) + "/" + str(constellation_size * 2) + 36 | "] Establish intra-orbit ISL from: (" + str(current_sat_id) + "," + 37 | str(current_orbit_id) + ") to (" + str(down_sat_id) + "," + 38 | str(down_orbit_id) + ")") 39 | ISL_name = "Le_" + str(current_sat_id) + "-" + str(current_orbit_id) + \ 40 | "_" + str(down_sat_id) + "-" + str(down_orbit_id) 41 | address_16_23 = isl_idx >> 8 42 | address_8_15 = isl_idx & 0xff 43 | # Create internal network in docker. 44 | os.system('docker network create ' + ISL_name + " --subnet 10." + 45 | str(address_16_23) + "." + str(address_8_15) + ".0/24") 46 | print('[Create ISL:]' + 'docker network create ' + ISL_name + 47 | " --subnet 10." + str(address_16_23) + "." + str(address_8_15) + 48 | ".0/24") 49 | os.system('docker network connect ' + ISL_name + " " + 50 | str(container_id_list[current_orbit_id * sat_num + 51 | current_sat_id]) + " --ip 10." + 52 | str(address_16_23) + "." + str(address_8_15) + ".40") 53 | delay = matrix[current_orbit_id * sat_num + 54 | current_sat_id][down_orbit_id * sat_num + down_sat_id] 55 | with os.popen( 56 | "docker exec -it " + 57 | str(container_id_list[current_orbit_id * sat_num + 58 | current_sat_id]) + 59 | " ip addr | grep -B 2 10." + str(address_16_23) + "." + 60 | str(address_8_15) + 61 | ".40 | head -n 1 | awk -F: '{ print $2 }' | tr -d [:blank:]") as f: 62 | ifconfig_output = f.readline() 63 | target_interface = str(ifconfig_output).split("@")[0] 64 | os.system("docker exec -d " + 65 | str(container_id_list[current_orbit_id * sat_num + 66 | current_sat_id]) + 67 | " ip link set dev " + target_interface + " down") 68 | os.system("docker exec -d " + 69 | str(container_id_list[current_orbit_id * sat_num + 70 | current_sat_id]) + 71 | " ip link set dev " + target_interface + " name " + "B" + 72 | str(current_orbit_id * sat_num + current_sat_id + 1) + 73 | "-eth" + str(down_orbit_id * sat_num + down_sat_id + 1)) 74 | os.system("docker exec -d " + 75 | str(container_id_list[current_orbit_id * sat_num + 76 | current_sat_id]) + 77 | " ip link set dev B" + 78 | str(current_orbit_id * sat_num + current_sat_id + 1) + 79 | "-eth" + str(down_orbit_id * sat_num + down_sat_id + 1) + 80 | " up") 81 | os.system("docker exec -d " + 82 | str(container_id_list[current_orbit_id * sat_num + 83 | current_sat_id]) + 84 | " tc qdisc add dev B" + 85 | str(current_orbit_id * sat_num + current_sat_id + 1) + 86 | "-eth" + str(down_orbit_id * sat_num + down_sat_id + 1) + 87 | " root netem delay " + str(delay) + "ms loss " + str(loss) + "% rate " + str(bw) + "Gbit") 88 | print('[Add current node:]' + 'docker network connect ' + ISL_name + " " + 89 | str(container_id_list[current_orbit_id * sat_num + current_sat_id]) + 90 | " --ip 10." + str(address_16_23) + "." + str(address_8_15) + ".40") 91 | os.system('docker network connect ' + ISL_name + " " + 92 | str(container_id_list[down_orbit_id * sat_num + down_sat_id]) + 93 | " --ip 10." + str(address_16_23) + "." + str(address_8_15) + 94 | ".10") 95 | with os.popen( 96 | "docker exec -it " + 97 | str(container_id_list[down_orbit_id * sat_num + down_sat_id]) + 98 | " ip addr | grep -B 2 10." + str(address_16_23) + "." + 99 | str(address_8_15) + 100 | ".10 | head -n 1 | awk -F: '{ print $2 }' | tr -d [:blank:]") as f: 101 | ifconfig_output = f.readline() 102 | target_interface = str(ifconfig_output).split("@")[0] 103 | os.system("docker exec -d " + 104 | str(container_id_list[down_orbit_id * sat_num + 105 | down_sat_id]) + " ip link set dev " + 106 | target_interface + " down") 107 | os.system("docker exec -d " + 108 | str(container_id_list[down_orbit_id * sat_num + 109 | down_sat_id]) + " ip link set dev " + 110 | target_interface + " name " + "B" + 111 | str(down_orbit_id * sat_num + down_sat_id + 1) + "-eth" + 112 | str(current_orbit_id * sat_num + current_sat_id + 1)) 113 | os.system("docker exec -d " + 114 | str(container_id_list[down_orbit_id * sat_num + 115 | down_sat_id]) + " ip link set dev B" + 116 | str(down_orbit_id * sat_num + down_sat_id + 1) + "-eth" + 117 | str(current_orbit_id * sat_num + current_sat_id + 1) + " up") 118 | os.system("docker exec -d " + 119 | str(container_id_list[down_orbit_id * sat_num + 120 | down_sat_id]) + " tc qdisc add dev B" + 121 | str(down_orbit_id * sat_num + down_sat_id + 1) + "-eth" + 122 | str(current_orbit_id * sat_num + current_sat_id + 1) + 123 | " root netem delay " + str(delay) + "ms loss " + str(loss) + "% rate " + str(bw) + "Gbit") 124 | print('[Add down node:]' + 'docker network connect ' + ISL_name + " " + 125 | str(container_id_list[down_orbit_id * sat_num + down_sat_id]) + 126 | " --ip 10." + str(address_16_23) + "." + str(address_8_15) + ".10") 127 | 128 | print("Add 10." + str(address_16_23) + "." + str(address_8_15) + 129 | ".40/24 and 10." + str(address_16_23) + "." + str(address_8_15) + 130 | ".10/24 to (" + str(current_sat_id) + "," + str(current_orbit_id) + 131 | ") to (" + str(down_sat_id) + "," + str(down_orbit_id) + ")") 132 | isl_idx = isl_idx + 1 133 | 134 | # Establish inter-orbit ISLs 135 | # (Right): 136 | [right_sat_id, 137 | right_orbit_id] = sn_get_right_satellite(current_sat_id, current_orbit_id, 138 | orbit_num) 139 | print("[" + str(isl_idx) + "/" + str(constellation_size * 2) + 140 | "] Establish inter-orbit ISL from: (" + str(current_sat_id) + "," + 141 | str(current_orbit_id) + ") to (" + str(right_sat_id) + "," + 142 | str(right_orbit_id) + ")") 143 | ISL_name = "La_" + str(current_sat_id) + "-" + str(current_orbit_id) + \ 144 | "_" + str(right_sat_id) + "-" + str(right_orbit_id) 145 | address_16_23 = isl_idx >> 8 146 | address_8_15 = isl_idx & 0xff 147 | # Create internal network in docker. 148 | os.system('docker network create ' + ISL_name + " --subnet 10." + 149 | str(address_16_23) + "." + str(address_8_15) + ".0/24") 150 | print('[Create ISL:]' + 'docker network create ' + ISL_name + 151 | " --subnet 10." + str(address_16_23) + "." + str(address_8_15) + 152 | ".0/24") 153 | os.system('docker network connect ' + ISL_name + " " + 154 | str(container_id_list[current_orbit_id * sat_num + 155 | current_sat_id]) + " --ip 10." + 156 | str(address_16_23) + "." + str(address_8_15) + ".30") 157 | delay = matrix[current_orbit_id * sat_num + 158 | current_sat_id][right_orbit_id * sat_num + right_sat_id] 159 | with os.popen( 160 | "docker exec -it " + 161 | str(container_id_list[current_orbit_id * sat_num + 162 | current_sat_id]) + 163 | " ip addr | grep -B 2 10." + str(address_16_23) + "." + 164 | str(address_8_15) + 165 | ".30 | head -n 1 | awk -F: '{ print $2 }' | tr -d [:blank:]") as f: 166 | ifconfig_output = f.readline() 167 | target_interface = str(ifconfig_output).split("@")[0] 168 | os.system("docker exec -d " + 169 | str(container_id_list[current_orbit_id * sat_num + 170 | current_sat_id]) + 171 | " ip link set dev " + target_interface + " down") 172 | os.system("docker exec -d " + 173 | str(container_id_list[current_orbit_id * sat_num + 174 | current_sat_id]) + 175 | " ip link set dev " + target_interface + " name " + "B" + 176 | str(current_orbit_id * sat_num + current_sat_id + 1) + 177 | "-eth" + str(right_orbit_id * sat_num + right_sat_id + 1)) 178 | os.system("docker exec -d " + 179 | str(container_id_list[current_orbit_id * sat_num + 180 | current_sat_id]) + 181 | " ip link set dev B" + 182 | str(current_orbit_id * sat_num + current_sat_id + 1) + 183 | "-eth" + str(right_orbit_id * sat_num + right_sat_id + 1) + 184 | " up") 185 | os.system("docker exec -d " + 186 | str(container_id_list[current_orbit_id * sat_num + 187 | current_sat_id]) + 188 | " tc qdisc add dev B" + 189 | str(current_orbit_id * sat_num + current_sat_id + 1) + 190 | "-eth" + str(right_orbit_id * sat_num + right_sat_id + 1) + 191 | " root netem delay " + str(delay) + "ms loss " + str(loss) + "% rate " + str(bw) + "Gbit") 192 | print('[Add current node:]' + 'docker network connect ' + ISL_name + " " + 193 | str(container_id_list[current_orbit_id * sat_num + current_sat_id]) + 194 | " --ip 10." + str(address_16_23) + "." + str(address_8_15) + ".30") 195 | os.system('docker network connect ' + ISL_name + " " + 196 | str(container_id_list[right_orbit_id * sat_num + right_sat_id]) + 197 | " --ip 10." + str(address_16_23) + "." + str(address_8_15) + 198 | ".20") 199 | 200 | with os.popen( 201 | "docker exec -it " + 202 | str(container_id_list[right_orbit_id * sat_num + right_sat_id]) + 203 | " ip addr | grep -B 2 10." + str(address_16_23) + "." + 204 | str(address_8_15) + 205 | ".20 | head -n 1 | awk -F: '{ print $2 }' | tr -d [:blank:]") as f: 206 | ifconfig_output = f.readline() 207 | target_interface = str(ifconfig_output).split("@")[0] 208 | os.system("docker exec -d " + 209 | str(container_id_list[right_orbit_id * sat_num + 210 | right_sat_id]) + " ip link set dev " + 211 | target_interface + " down") 212 | os.system("docker exec -d " + 213 | str(container_id_list[right_orbit_id * sat_num + 214 | right_sat_id]) + " ip link set dev " + 215 | target_interface + " name " + "B" + 216 | str(right_orbit_id * sat_num + right_sat_id + 1) + "-eth" + 217 | str(current_orbit_id * sat_num + current_sat_id + 1)) 218 | os.system("docker exec -d " + 219 | str(container_id_list[right_orbit_id * sat_num + 220 | right_sat_id]) + " ip link set dev B" + 221 | str(right_orbit_id * sat_num + right_sat_id + 1) + "-eth" + 222 | str(current_orbit_id * sat_num + current_sat_id + 1) + " up") 223 | os.system("docker exec -d " + 224 | str(container_id_list[right_orbit_id * sat_num + 225 | right_sat_id]) + 226 | " tc qdisc add dev B" + 227 | str(right_orbit_id * sat_num + right_sat_id + 1) + "-eth" + 228 | str(current_orbit_id * sat_num + current_sat_id + 1) + 229 | " root netem delay " + str(delay) + "ms loss " + str(loss) + "% rate " + str(bw) + "Gbit") 230 | print('[Add right node:]' + 'docker network connect ' + ISL_name + " " + 231 | str(container_id_list[right_orbit_id * sat_num + right_sat_id]) + 232 | " --ip 10." + str(address_16_23) + "." + str(address_8_15) + ".20") 233 | 234 | print("Add 10." + str(address_16_23) + "." + str(address_8_15) + 235 | ".30/24 and 10." + str(address_16_23) + "." + str(address_8_15) + 236 | ".20/24 to (" + str(current_sat_id) + "," + str(current_orbit_id) + 237 | ") to (" + str(right_sat_id) + "," + str(right_orbit_id) + ")") 238 | 239 | 240 | def sn_establish_ISLs(container_id_list, matrix, orbit_num, sat_num, 241 | constellation_size, bw, loss): 242 | ISL_threads = [] 243 | for current_orbit_id in range(0, orbit_num): 244 | for current_sat_id in range(0, sat_num): 245 | ISL_thread = threading.Thread( 246 | target=sn_ISL_establish, 247 | args=(current_sat_id, current_orbit_id, container_id_list, 248 | orbit_num, sat_num, constellation_size, matrix, bw, 249 | loss)) 250 | ISL_threads.append(ISL_thread) 251 | for ISL_thread in ISL_threads: 252 | ISL_thread.start() 253 | for ISL_thread in ISL_threads: 254 | ISL_thread.join() 255 | 256 | 257 | def sn_get_param(file_): 258 | f = open(file_) 259 | ADJ = f.readlines() 260 | for i in range(len(ADJ)): 261 | ADJ[i] = ADJ[i].strip('\n') 262 | ADJ = [x.split(',') for x in ADJ] 263 | f.close() 264 | return ADJ 265 | 266 | 267 | def sn_get_container_info(): 268 | # Read all container information in all_container_info 269 | with os.popen("docker ps") as f: 270 | all_container_info = f.readlines() 271 | n_container = len(all_container_info) - 1 272 | 273 | container_id_list = [] 274 | for container_idx in range(1, n_container + 1): 275 | container_id_list.append(all_container_info[container_idx].split()[0]) 276 | 277 | return container_id_list 278 | 279 | 280 | def sn_establish_GSL(container_id_list, matrix, GS_num, constellation_size, bw, 281 | loss): 282 | # starting links among satellites and ground stations 283 | for i in range(1, constellation_size + 1): 284 | for j in range(constellation_size + 1, 285 | constellation_size + GS_num + 1): 286 | # matrix[i-1][j-1])==1 means a link between node i and node j 287 | if ((float(matrix[i - 1][j - 1])) <= 0.01): 288 | continue 289 | # IP address (there is a link between i and j) 290 | delay = str(matrix[i - 1][j - 1]) 291 | address_16_23 = (j - constellation_size) & 0xff 292 | address_8_15 = i & 0xff 293 | GSL_name = "GSL_" + str(i) + "-" + str(j) 294 | # Create internal network in docker. 295 | os.system('docker network create ' + GSL_name + " --subnet 9." + 296 | str(address_16_23) + "." + str(address_8_15) + ".0/24") 297 | print('[Create GSL:]' + 'docker network create ' + GSL_name + 298 | " --subnet 9." + str(address_16_23) + "." + 299 | str(address_8_15) + ".0/24") 300 | os.system('docker network connect ' + GSL_name + " " + 301 | str(container_id_list[i - 1]) + " --ip 9." + 302 | str(address_16_23) + "." + str(address_8_15) + ".50") 303 | with os.popen( 304 | "docker exec -it " + str(container_id_list[i - 1]) + 305 | " ip addr | grep -B 2 9." + str(address_16_23) + "." + 306 | str(address_8_15) + 307 | ".50 | head -n 1 | awk -F: '{ print $2 }' | tr -d [:blank:]" 308 | ) as f: 309 | ifconfig_output = f.readline() 310 | target_interface = str(ifconfig_output).split("@")[0] 311 | os.system("docker exec -d " + str(container_id_list[i - 1]) + 312 | " ip link set dev " + target_interface + " down") 313 | os.system("docker exec -d " + str(container_id_list[i - 1]) + 314 | " ip link set dev " + target_interface + " name " + 315 | "B" + str(i - 1 + 1) + "-eth" + str(j)) 316 | os.system("docker exec -d " + str(container_id_list[i - 1]) + 317 | " ip link set dev B" + str(i - 1 + 1) + "-eth" + 318 | str(j) + " up") 319 | os.system("docker exec -d " + str(container_id_list[i - 1]) + 320 | " tc qdisc add dev B" + str(i - 1 + 1) + "-eth" + 321 | str(j) + " root netem delay " + str(delay) + "ms loss " + str(loss) + "% rate " + str(bw) + "Gbit") 322 | print('[Add current node:]' + 'docker network connect ' + 323 | GSL_name + " " + str(container_id_list[i - 1]) + " --ip 9." + 324 | str(address_16_23) + "." + str(address_8_15) + ".50") 325 | 326 | os.system('docker network connect ' + GSL_name + " " + 327 | str(container_id_list[j - 1]) + " --ip 9." + 328 | str(address_16_23) + "." + str(address_8_15) + ".60") 329 | with os.popen( 330 | "docker exec -it " + str(container_id_list[j - 1]) + 331 | " ip addr | grep -B 2 9." + str(address_16_23) + "." + 332 | str(address_8_15) + 333 | ".60 | head -n 1 | awk -F: '{ print $2 }' | tr -d [:blank:]" 334 | ) as f: 335 | ifconfig_output = f.readline() 336 | target_interface = str(ifconfig_output).split("@")[0] 337 | os.system("docker exec -d " + str(container_id_list[j - 1]) + 338 | " ip link set dev " + target_interface + " down") 339 | os.system("docker exec -d " + str(container_id_list[j - 1]) + 340 | " ip link set dev " + target_interface + " name " + 341 | "B" + str(j) + "-eth" + str(i - 1 + 1)) 342 | os.system("docker exec -d " + str(container_id_list[j - 1]) + 343 | " ip link set dev B" + str(j) + "-eth" + 344 | str(i - 1 + 1) + " up") 345 | os.system("docker exec -d " + str(container_id_list[j - 1]) + 346 | " tc qdisc add dev B" + str(j) + "-eth" + 347 | str(i - 1 + 1) + " root netem delay " + str(delay) + 348 | "ms loss " + str(loss) + "% rate " + str(bw) + 349 | "Gbit") 350 | print('[Add right node:]' + 'docker network connect ' + GSL_name + 351 | " " + str(container_id_list[j - 1]) + " --ip 9." + 352 | str(address_16_23) + "." + str(address_8_15) + ".60") 353 | for j in range(constellation_size + 1, constellation_size + GS_num + 1): 354 | GS_name = "GS_" + str(j) 355 | # Create default network and interface for GS. 356 | os.system('docker network create ' + GS_name + " --subnet 9." + 357 | str(j) + "." + str(j) + ".0/24") 358 | print('[Create GS network:]' + 'docker network create ' + GS_name + 359 | " --subnet 9." + str(j) + "." + str(j) + ".10/24") 360 | os.system('docker network connect ' + GS_name + " " + 361 | str(container_id_list[j - 1]) + " --ip 9." + str(j) + "." + 362 | str(j) + ".10") 363 | with os.popen( 364 | "docker exec -it " + str(container_id_list[j - 1]) + 365 | " ip addr | grep -B 2 9." + str(j) + "." + str(j) + 366 | ".10 | head -n 1 | awk -F: '{ print $2 }' | tr -d [:blank:]" 367 | ) as f: 368 | ifconfig_output = f.readline() 369 | target_interface = str(ifconfig_output).split("@")[0] 370 | os.system("docker exec -d " + str(container_id_list[j - 1]) + 371 | " ip link set dev " + target_interface + " down") 372 | os.system("docker exec -d " + str(container_id_list[j - 1]) + 373 | " ip link set dev " + target_interface + " name " + "B" + 374 | str(j - 1 + 1) + "-default") 375 | os.system("docker exec -d " + str(container_id_list[j - 1]) + 376 | " ip link set dev B" + str(j - 1 + 1) + "-default" + 377 | " up") 378 | print('[Add current node:]' + 'docker network connect ' + GS_name + 379 | " " + str(container_id_list[j - 1]) + " --ip 9." + str(j) + "." + 380 | str(j) + ".10") 381 | 382 | 383 | def sn_copy_run_conf(container_idx, Path, current, total): 384 | os.system("docker cp " + Path + "/B" + str(current + 1) + ".conf " + 385 | str(container_idx) + ":/B" + str(current + 1) + ".conf") 386 | print("[" + str(current + 1) + "/" + str(total) + "]" + 387 | " docker cp bird.conf " + str(container_idx) + ":/bird.conf") 388 | os.system("docker exec -it " + str(container_idx) + " bird -c B" + 389 | str(current + 1) + ".conf") 390 | print("[" + str(current + 1) + "/" + str(total) + 391 | "] Bird routing process for container: " + str(container_idx) + 392 | " has started. ") 393 | 394 | 395 | def sn_copy_run_conf_to_each_container(container_id_list, sat_node_number, 396 | fac_node_number, path): 397 | print( 398 | "Copy bird configuration file to each container and run routing process." 399 | ) 400 | total = len(container_id_list) 401 | copy_threads = [] 402 | for current in range(0, total): 403 | copy_thread = threading.Thread( 404 | target=sn_copy_run_conf, 405 | args=(container_id_list[current], path + "/conf/bird-" + 406 | str(sat_node_number) + "-" + str(fac_node_number), current, 407 | total)) 408 | copy_threads.append(copy_thread) 409 | for copy_thread in copy_threads: 410 | copy_thread.start() 411 | for copy_thread in copy_threads: 412 | copy_thread.join() 413 | print("Initializing routing...") 414 | sleep(120) 415 | print("Routing initialized!") 416 | 417 | 418 | def sn_damage_link(sat_index, container_id_list): 419 | with os.popen( 420 | "docker exec -it " + str(container_id_list[sat_index]) + 421 | " ifconfig | sed 's/[ \t].*//;/^\(eth0\|\)\(lo\|\)$/d'") as f: 422 | ifconfig_output = f.readlines() 423 | for intreface in range(0, len(ifconfig_output), 2): 424 | os.system("docker exec -d " + str(container_id_list[sat_index]) + 425 | " tc qdisc change dev " + 426 | ifconfig_output[intreface][:-1] + 427 | " root netem loss 100%") 428 | print("docker exec -d " + str(container_id_list[sat_index]) + 429 | " tc qdisc change dev " + ifconfig_output[intreface][:-1] + 430 | " root netem loss 100%") 431 | 432 | 433 | def sn_damage(random_list, container_id_list): 434 | damage_threads = [] 435 | for random_satellite in random_list: 436 | damage_thread = threading.Thread(target=sn_damage_link, 437 | args=(int(random_satellite), 438 | container_id_list)) 439 | damage_threads.append(damage_thread) 440 | for damage_thread in damage_threads: 441 | damage_thread.start() 442 | for damage_thread in damage_threads: 443 | damage_thread.join() 444 | 445 | 446 | def sn_recover_link( 447 | damaged_satellite, 448 | container_id_list, 449 | sat_loss, 450 | ): 451 | with os.popen( 452 | "docker exec -it " + str(container_id_list[damaged_satellite]) + 453 | " ifconfig | sed 's/[ \t].*//;/^\(eth0\|\)\(lo\|\)$/d'") as f: 454 | ifconfig_output = f.readlines() 455 | for i in range(0, len(ifconfig_output), 2): 456 | os.system("docker exec -d " + 457 | str(container_id_list[damaged_satellite]) + 458 | " tc qdisc change dev " + ifconfig_output[i][:-1] + 459 | " root netem loss " + str(sat_loss) + "%") 460 | print("docker exec -d " + 461 | str(container_id_list[damaged_satellite]) + 462 | " tc qdisc change dev " + ifconfig_output[i][:-1] + 463 | " root netem loss " + str(sat_loss) + "%") 464 | 465 | 466 | def sn_del_network(network_name): 467 | os.system('docker network rm ' + network_name) 468 | 469 | 470 | def sn_stop_emulation(): 471 | os.system("docker service rm constellation-test") 472 | with os.popen("docker rm -f $(docker ps -a -q)") as f: 473 | f.readlines() 474 | with os.popen("docker network ls") as f: 475 | all_br_info = f.readlines() 476 | del_threads = [] 477 | for line in all_br_info: 478 | if "La" in line or "Le" or "GS" in line: 479 | network_name = line.split()[1] 480 | del_thread = threading.Thread(target=sn_del_network, 481 | args=(network_name, )) 482 | del_threads.append(del_thread) 483 | for del_thread in del_threads: 484 | del_thread.start() 485 | for del_thread in del_threads: 486 | del_thread.join() 487 | 488 | 489 | def sn_recover(damage_list, container_id_list, sat_loss): 490 | recover_threads = [] 491 | for damaged_satellite in damage_list: 492 | recover_thread = threading.Thread(target=sn_recover_link, 493 | args=(int(damaged_satellite), 494 | container_id_list, sat_loss)) 495 | recover_threads.append(recover_thread) 496 | for recover_thread in recover_threads: 497 | recover_thread.start() 498 | for recover_thread in recover_threads: 499 | recover_thread.join() 500 | 501 | 502 | def sn_update_delay(matrix, container_id_list, 503 | constellation_size): # updating delays 504 | delay_threads = [] 505 | for row in range(len(matrix)): 506 | for col in range(row, len(matrix[row])): 507 | if float(matrix[row][col]) > 0: 508 | if row < col: 509 | delay_thread = threading.Thread( 510 | target=sn_delay_change, 511 | args=(row, col, matrix[row][col], container_id_list, 512 | constellation_size)) 513 | delay_threads.append(delay_thread) 514 | else: 515 | delay_thread = threading.Thread( 516 | target=sn_delay_change, 517 | args=(col, row, matrix[col][row], container_id_list, 518 | constellation_size)) 519 | delay_threads.append(delay_thread) 520 | for delay_thread in delay_threads: 521 | delay_thread.start() 522 | for delay_thread in delay_threads: 523 | delay_thread.join() 524 | print("Delay updating done.\n") 525 | 526 | 527 | def sn_delay_change(link_x, link_y, delay, container_id_list, 528 | constellation_size): # multi-thread updating delays 529 | if link_y <= constellation_size: 530 | os.system("docker exec -d " + str(container_id_list[link_x]) + 531 | " tc qdisc change dev B" + str(link_x + 1) + "-eth" + 532 | str(link_y + 1) + " root netem delay " + str(delay) + "ms") 533 | os.system("docker exec -d " + str(container_id_list[link_y]) + 534 | " tc qdisc change dev B" + str(link_y + 1) + "-eth" + 535 | str(link_x + 1) + " root netem delay " + str(delay) + "ms") 536 | else: 537 | os.system("docker exec -d " + str(container_id_list[link_x]) + 538 | " tc qdisc change dev B" + str(link_x + 1) + "-eth" + 539 | str(link_y + 1) + " root netem delay " + str(delay) + "ms") 540 | os.system("docker exec -d " + str(container_id_list[link_y]) + 541 | " tc qdisc change dev B" + str(link_y + 1) + "-eth" + 542 | str(link_x + 1) + " root netem delay " + str(delay) + "ms") 543 | 544 | 545 | if __name__ == '__main__': 546 | if len(sys.argv) == 10: 547 | orbit_num = int(sys.argv[1]) 548 | sat_num = int(sys.argv[2]) 549 | constellation_size = int(sys.argv[3]) 550 | GS_num = int(sys.argv[4]) 551 | sat_bandwidth = float(sys.argv[5]) 552 | sat_loss = float(sys.argv[6]) 553 | sat_ground_bandwidth = float(sys.argv[7]) 554 | sat_ground_loss = float(sys.argv[8]) 555 | current_topo_path = sys.argv[9] 556 | matrix = sn_get_param(current_topo_path) 557 | container_id_list = sn_get_container_info() 558 | sn_establish_ISLs(container_id_list, matrix, orbit_num, sat_num, 559 | constellation_size, sat_bandwidth, sat_loss) 560 | sn_establish_GSL(container_id_list, matrix, GS_num, constellation_size, 561 | sat_ground_bandwidth, sat_ground_loss) 562 | elif len(sys.argv) == 4: 563 | if sys.argv[3] == "update": 564 | current_delay_path = sys.argv[1] 565 | constellation_size = int(sys.argv[2]) 566 | matrix = sn_get_param(current_delay_path) 567 | container_id_list = sn_get_container_info() 568 | sn_update_delay(matrix, container_id_list, constellation_size) 569 | else: 570 | constellation_size = int(sys.argv[1]) 571 | GS_num = int(sys.argv[2]) 572 | path = sys.argv[3] 573 | container_id_list = sn_get_container_info() 574 | sn_copy_run_conf_to_each_container(container_id_list, 575 | constellation_size, GS_num, 576 | path) 577 | elif len(sys.argv) == 2: 578 | path = sys.argv[1] 579 | random_list = numpy.loadtxt(path + "/damage_list.txt") 580 | container_id_list = sn_get_container_info() 581 | sn_damage(random_list, container_id_list) 582 | elif len(sys.argv) == 3: 583 | path = sys.argv[1] 584 | sat_loss = float(sys.argv[2]) 585 | damage_list = numpy.loadtxt(path + "/damage_list.txt") 586 | container_id_list = sn_get_container_info() 587 | sn_recover(damage_list, container_id_list, sat_loss) 588 | elif len(sys.argv) == 1: 589 | sn_stop_emulation() 590 | -------------------------------------------------------------------------------- /starrynet/sn_synchronizer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | StarryNet: empowering researchers to evaluate futuristic integrated space and terrestrial networks. 5 | author: Zeqi Lai (zeqilai@tsinghua.edu.cn) and Yangtao Deng (dengyt21@mails.tsinghua.edu.cn) 6 | """ 7 | from starrynet.sn_observer import * 8 | from starrynet.sn_utils import * 9 | 10 | 11 | class StarryNet(): 12 | 13 | def __init__(self, 14 | configuration_file_path, 15 | GS_lat_long, 16 | hello_interval=10, 17 | AS=[]): 18 | # Initialize constellation information. 19 | sn_args = sn_load_file(configuration_file_path, GS_lat_long) 20 | self.name = sn_args.cons_name 21 | self.satellite_altitude = sn_args.satellite_altitude 22 | self.inclination = sn_args.inclination 23 | self.orbit_number = sn_args.orbit_number 24 | self.sat_number = sn_args.sat_number 25 | self.fac_num = sn_args.fac_num 26 | self.constellation_size = self.orbit_number * self.sat_number 27 | self.node_size = self.orbit_number * self.sat_number + sn_args.fac_num 28 | self.link_style = sn_args.link_style 29 | self.IP_version = sn_args.IP_version 30 | self.link_policy = sn_args.link_policy 31 | self.update_interval = sn_args.update_interval 32 | self.duration = sn_args.duration 33 | self.inter_routing = sn_args.inter_routing 34 | self.intra_routing = sn_args.intra_routing 35 | self.cycle = sn_args.cycle 36 | self.time_slot = sn_args.time_slot 37 | self.sat_bandwidth = sn_args.sat_bandwidth 38 | self.sat_ground_bandwidth = sn_args.sat_ground_bandwidth 39 | self.sat_loss = sn_args.sat_loss 40 | self.sat_ground_loss = sn_args.sat_ground_loss 41 | self.ground_num = sn_args.ground_num 42 | self.multi_machine = sn_args.multi_machine 43 | self.antenna_number = sn_args.antenna_number 44 | self.antenna_inclination = sn_args.antenna_inclination 45 | self.container_global_idx = 1 46 | self.hello_interval = hello_interval 47 | self.AS = AS 48 | self.configuration_file_path = os.path.dirname( 49 | os.path.abspath(configuration_file_path)) 50 | self.file_path = './' + sn_args.cons_name + '-' + str( 51 | sn_args.orbit_number) + '-' + str(sn_args.sat_number) + '-' + str( 52 | sn_args.satellite_altitude) + '-' + str( 53 | sn_args.inclination 54 | ) + '-' + sn_args.link_style + '-' + sn_args.link_policy 55 | self.observer = Observer(self.file_path, self.configuration_file_path, 56 | self.inclination, self.satellite_altitude, 57 | self.orbit_number, self.sat_number, 58 | self.duration, self.antenna_number, 59 | GS_lat_long, self.antenna_inclination, 60 | self.intra_routing, self.hello_interval, 61 | self.AS) 62 | self.docker_service_name = 'constellation-test' 63 | self.isl_idx = 0 64 | self.ISL_hub = 'ISL_hub' 65 | self.container_id_list = [] 66 | self.n_container = 0 67 | # Get ssh handler. 68 | self.remote_ssh, self.transport = sn_init_remote_machine( 69 | sn_args.remote_machine_IP, sn_args.remote_machine_username, 70 | sn_args.remote_machine_password) 71 | if self.remote_ssh is None: 72 | print('Remote SSH login failure.') 73 | return 74 | if self.transport is None: 75 | print('Remote transport login failure.') 76 | return 77 | self.remote_ftp = sn_init_remote_ftp(self.transport) 78 | if self.remote_ftp is None: 79 | print('Remote ftp login failure.') 80 | return 81 | self.utility_checking_time = [] 82 | self.ping_src = [] 83 | self.ping_des = [] 84 | self.ping_time = [] 85 | self.perf_src = [] 86 | self.perf_des = [] 87 | self.perf_time = [] 88 | self.sr_src = [] 89 | self.sr_des = [] 90 | self.sr_target = [] 91 | self.sr_time = [] 92 | self.damage_ratio = [] 93 | self.damage_time = [] 94 | self.damage_list = [] 95 | self.recovery_time = [] 96 | self.route_src = [] 97 | self.route_time = [] 98 | 99 | # Initiate a working directory 100 | sn_thread = sn_init_directory_thread(self.file_path, 101 | self.configuration_file_path, 102 | self.remote_ssh) 103 | sn_thread.start() 104 | sn_thread.join() 105 | # Initiate a necessary delay and position data for emulation 106 | self.observer.calculate_delay() 107 | # Generate configuration file for routing 108 | self.observer.generate_conf(self.remote_ssh, self.remote_ftp) 109 | 110 | def create_nodes(self): 111 | # Initialize each machine in multiple threads. 112 | sn_thread = sn_Node_Init_Thread(self.remote_ssh, 113 | self.docker_service_name, 114 | self.node_size, self.container_id_list, 115 | self.container_global_idx) 116 | sn_thread.start() 117 | sn_thread.join() 118 | self.container_id_list = sn_get_container_info(self.remote_ssh) 119 | print("Constellation initialization done. " + 120 | str(len(self.container_id_list)) + " have been created.") 121 | 122 | def create_links(self): 123 | print("Create Links.") 124 | isl_thread = sn_Link_Init_Thread( 125 | self.remote_ssh, self.remote_ftp, self.orbit_number, 126 | self.sat_number, self.constellation_size, self.fac_num, 127 | self.file_path, self.configuration_file_path, self.sat_bandwidth, 128 | self.sat_ground_bandwidth, self.sat_loss, self.sat_ground_loss) 129 | isl_thread.start() 130 | isl_thread.join() 131 | print("Link initialization done.") 132 | 133 | def run_routing_deamon(self): 134 | routing_thread = sn_Routing_Init_Thread( 135 | self.remote_ssh, self.remote_ftp, self.orbit_number, 136 | self.sat_number, self.constellation_size, self.fac_num, 137 | self.file_path, self.sat_bandwidth, self.sat_ground_bandwidth, 138 | self.sat_loss, self.sat_ground_loss) 139 | routing_thread.start() 140 | routing_thread.join() 141 | print("Bird routing in all containers are running.") 142 | 143 | def get_distance(self, sat1_index, sat2_index, time_index): 144 | delaypath = self.configuration_file_path + "/" + self.file_path + '/delay/' + str( 145 | time_index) + '.txt' 146 | adjacency_matrix = sn_get_param(delaypath) 147 | delay = float(adjacency_matrix[sat1_index - 1][sat2_index - 1]) 148 | dis = delay * (17.31 / 29.5 * 299792.458) / 1000 # km 149 | return dis 150 | 151 | def get_neighbors(self, sat_index, time_index): 152 | neighbors = [] 153 | delaypath = self.configuration_file_path + "/" + self.file_path + '/delay/' + str( 154 | time_index) + '.txt' 155 | adjacency_matrix = sn_get_param(delaypath) 156 | sats = self.orbit_number * self.sat_number 157 | for i in range(sats): 158 | if (float(adjacency_matrix[i][sat_index - 1]) > 0.01): 159 | neighbors.append(i + 1) 160 | return neighbors 161 | 162 | def get_GSes(self, sat_index, time_index): 163 | GSes = [] 164 | delaypath = self.configuration_file_path + "/" + self.file_path + '/delay/' + str( 165 | time_index) + '.txt' 166 | adjacency_matrix = sn_get_param(delaypath) 167 | sats = self.orbit_number * self.sat_number 168 | for i in range(sats, len(adjacency_matrix)): 169 | if (float(adjacency_matrix[i][sat_index - 1]) > 0.01): 170 | GSes.append(i + 1) 171 | return GSes 172 | 173 | def get_utility(self, time_index): 174 | self.utility_checking_time.append(time_index) 175 | 176 | def get_position(self, sat_index, time_index): 177 | path = self.configuration_file_path + "/" + self.file_path + '/position/' + '/%d.txt' % time_index 178 | f = open(path) 179 | ADJ = f.readlines() 180 | return ADJ[sat_index - 1] 181 | 182 | def get_IP(self, sat_index): 183 | IP_info = sn_remote_cmd( 184 | self.remote_ssh, "docker inspect" + 185 | " --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}\n{{end}}'" 186 | + " ovs_container_" + str(sat_index)) 187 | ip_list = [] 188 | for i in range(len(IP_info) - 2): 189 | ip_list.append(IP_info[i].split()[0]) 190 | return ip_list 191 | 192 | def set_damage(self, damaging_ratio, time_index): 193 | self.damage_ratio.append(damaging_ratio) 194 | self.damage_time.append(time_index) 195 | 196 | def set_recovery(self, time_index): 197 | self.recovery_time.append(time_index) 198 | 199 | def check_routing_table(self, sat_index, time_index): 200 | self.route_src.append(sat_index) 201 | self.route_time.append(time_index) 202 | 203 | def set_next_hop(self, sat_index, des, next_hop_sat, time_index): 204 | self.sr_src.append(sat_index) 205 | self.sr_des.append(des) 206 | self.sr_target.append(next_hop_sat) 207 | self.sr_time.append(time_index) 208 | 209 | def set_ping(self, sat1_index, sat2_index, time_index): 210 | self.ping_src.append(sat1_index) 211 | self.ping_des.append(sat2_index) 212 | self.ping_time.append(time_index) 213 | 214 | def set_perf(self, sat1_index, sat2_index, time_index): 215 | self.perf_src.append(sat1_index) 216 | self.perf_des.append(sat2_index) 217 | self.perf_time.append(time_index) 218 | 219 | def start_emulation(self): 220 | # Start emulation in a new thread. 221 | sn_thread = sn_Emulation_Start_Thread( 222 | self.remote_ssh, self.remote_ftp, self.sat_loss, 223 | self.sat_ground_bandwidth, self.sat_ground_loss, 224 | self.container_id_list, self.file_path, 225 | self.configuration_file_path, self.update_interval, 226 | self.constellation_size, self.ping_src, self.ping_des, 227 | self.ping_time, self.sr_src, self.sr_des, self.sr_target, 228 | self.sr_time, self.damage_ratio, self.damage_time, 229 | self.damage_list, self.recovery_time, self.route_src, 230 | self.route_time, self.duration, self.utility_checking_time, 231 | self.perf_src, self.perf_des, self.perf_time) 232 | sn_thread.start() 233 | sn_thread.join() 234 | 235 | def stop_emulation(self): 236 | # Stop emulation in a new thread. 237 | sn_thread = sn_Emulation_Stop_Thread(self.remote_ssh, self.remote_ftp, 238 | self.file_path) 239 | sn_thread.start() 240 | sn_thread.join() 241 | -------------------------------------------------------------------------------- /starrynet/sn_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import threading 3 | import json 4 | import copy 5 | import argparse 6 | import os 7 | from time import sleep 8 | import time 9 | import numpy 10 | import random 11 | """ 12 | Starrynet utils that are used in sn_synchronizer 13 | author: Yangtao Deng (dengyt21@mails.tsinghua.edu.cn) and Zeqi Lai (zeqilai@tsinghua.edu.cn) 14 | """ 15 | try: 16 | import threading 17 | except ImportError: 18 | os.system("pip3 install threading") 19 | import threading 20 | 21 | try: 22 | import paramiko 23 | except ImportError: 24 | os.system("pip3 install paramiko") 25 | import paramiko 26 | 27 | try: 28 | import requests 29 | except ImportError: 30 | os.system("pip3 install requests") 31 | import requests 32 | 33 | 34 | def get_right_satellite(current_sat_id, current_orbit_id, orbit_num): 35 | if current_orbit_id == orbit_num - 1: 36 | return [current_sat_id, 0] 37 | else: 38 | return [current_sat_id, current_orbit_id + 1] 39 | 40 | 41 | def get_down_satellite(current_sat_id, current_orbit_id, sat_num): 42 | if current_sat_id == sat_num - 1: 43 | return [0, current_orbit_id] 44 | else: 45 | return [current_sat_id + 1, current_orbit_id] 46 | 47 | 48 | def sn_load_file(path, GS_lat_long): 49 | f = open("./config.json", "r", encoding='utf8') 50 | table = json.load(f) 51 | data = {} 52 | data['cons_name'] = table["Name"] 53 | data['altitude'] = table["Altitude (km)"] 54 | data['cycle'] = table["Cycle (s)"] 55 | data['inclination'] = table["Inclination"] 56 | data['phase_shift'] = table["Phase shift"] 57 | data['orbit'] = table["# of orbit"] 58 | data['sat'] = table["# of satellites"] 59 | data['link'] = table["Satellite link"] 60 | data['duration'] = table["Duration (s)"] 61 | data['ip'] = table["IP version"] 62 | data['intra_as_routing'] = table["Intra-AS routing"] 63 | data['inter_as_routing'] = table["Inter-AS routing"] 64 | data['link_policy'] = table["Link policy"] 65 | data['handover_policy'] = table["Handover policy"] 66 | data['update_time'] = table["update_time (s)"] 67 | data['sat_bw'] = table["satellite link bandwidth (\"X\" Gbps)"] 68 | data['sat_ground_bw'] = table["sat-ground bandwidth (\"X\" Gbps)"] 69 | data['sat_loss'] = table["satellite link loss (\"X\"% )"] 70 | data['sat_ground_loss'] = table["sat-ground loss (\"X\"% )"] 71 | data['ground_num'] = table["GS number"] 72 | data['multi_machine'] = table[ 73 | "multi-machine (\"0\" for no, \"1\" for yes)"] 74 | data['antenna_number'] = table["antenna number"] 75 | data['antenna_inclination'] = table["antenna_inclination_angle"] 76 | data['remote_machine_IP'] = table["remote_machine_IP"] 77 | data['remote_machine_username'] = table["remote_machine_username"] 78 | data['remote_machine_password'] = table["remote_machine_password"] 79 | 80 | parser = argparse.ArgumentParser(description='manual to this script') 81 | parser.add_argument('--cons_name', type=str, default=data['cons_name']) 82 | parser.add_argument('--satellite_altitude', 83 | type=int, 84 | default=data['altitude']) 85 | parser.add_argument('--inclination', type=int, default=data['inclination']) 86 | parser.add_argument('--orbit_number', type=int, default=data['orbit']) 87 | parser.add_argument('--sat_number', type=int, default=data['sat']) 88 | parser.add_argument('--fac_num', type=int, default=len(GS_lat_long)) 89 | parser.add_argument('--link_style', type=str, default=data['link']) 90 | parser.add_argument('--IP_version', type=str, default=data['ip']) 91 | parser.add_argument('--link_policy', type=str, default=data['link_policy']) 92 | # link delay updating granularity 93 | parser.add_argument('--update_interval', 94 | type=int, 95 | default=data['update_time']) 96 | parser.add_argument('--duration', type=int, default=data['duration']) 97 | parser.add_argument('--inter_routing', 98 | type=str, 99 | default=data['inter_as_routing']) 100 | parser.add_argument('--intra_routing', 101 | type=str, 102 | default=data['intra_as_routing']) 103 | parser.add_argument('--cycle', type=int, default=data['cycle']) 104 | parser.add_argument('--time_slot', type=int, default=100) 105 | parser.add_argument('--sat_bandwidth', type=int, default=data['sat_bw']) 106 | parser.add_argument('--sat_ground_bandwidth', 107 | type=int, 108 | default=data['sat_ground_bw']) 109 | parser.add_argument('--sat_loss', type=int, default=data['sat_loss']) 110 | parser.add_argument('--sat_ground_loss', 111 | type=int, 112 | default=data['sat_ground_loss']) 113 | parser.add_argument('--ground_num', type=int, default=data['ground_num']) 114 | parser.add_argument('--multi_machine', 115 | type=int, 116 | default=data['multi_machine']) 117 | parser.add_argument('--antenna_number', 118 | type=int, 119 | default=data['antenna_number']) 120 | parser.add_argument('--antenna_inclination', 121 | type=int, 122 | default=data['antenna_inclination']) 123 | parser.add_argument('--user_num', type=int, default=0) 124 | parser.add_argument('--remote_machine_IP', 125 | type=str, 126 | default=data['remote_machine_IP']) 127 | parser.add_argument('--remote_machine_username', 128 | type=str, 129 | default=data['remote_machine_username']) 130 | parser.add_argument('--remote_machine_password', 131 | type=str, 132 | default=data['remote_machine_password']) 133 | 134 | parser.add_argument('--path', 135 | '-p', 136 | type=str, 137 | default="starrynet/config.xls") 138 | parser.add_argument('--hello_interval', '-i', type=int, default=10) 139 | parser.add_argument('--node_number', '-n', type=int, default=27) 140 | parser.add_argument('--GS', 141 | '-g', 142 | type=str, 143 | default="50.110924/8.682127/46.635700/14.311817") 144 | 145 | sn_args = parser.parse_args() 146 | return sn_args 147 | 148 | 149 | def sn_get_param(file_): 150 | f = open(file_) 151 | ADJ = f.readlines() 152 | for i in range(len(ADJ)): 153 | ADJ[i] = ADJ[i].strip('\n') 154 | ADJ = [x.split(',') for x in ADJ] 155 | f.close() 156 | return ADJ 157 | 158 | 159 | def sn_init_remote_machine(host, username, password): 160 | # transport = paramiko.Transport((host, 22)) 161 | # transport.connect(username=username, password=password) 162 | remote_machine_ssh = paramiko.SSHClient() 163 | # remote_machine_ssh._transport = transport 164 | remote_machine_ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 165 | 166 | remote_machine_ssh.connect(hostname=host, 167 | port=22, 168 | username=username, 169 | password=password) 170 | transport = paramiko.Transport((host, 22)) 171 | transport.connect(username=username, password=password) 172 | return remote_machine_ssh, transport 173 | # transport.close() 174 | 175 | 176 | def sn_init_remote_ftp(transport): 177 | ftp_client = paramiko.SFTPClient.from_transport(transport) ## ftp client 178 | return ftp_client 179 | 180 | 181 | def sn_remote_cmd(remote_ssh, cmd): 182 | stdin, stdout, stderr = remote_ssh.exec_command(cmd, get_pty=True) 183 | lines = stdout.readlines() 184 | return lines 185 | 186 | 187 | # A thread designed for initializing working directory. 188 | class sn_init_directory_thread(threading.Thread): 189 | 190 | def __init__(self, file_path, configuration_file_path, remote_ssh): 191 | threading.Thread.__init__(self) 192 | self.file_path = file_path 193 | self.remote_ssh = remote_ssh 194 | self.configuration_file_path = configuration_file_path 195 | 196 | def run(self): 197 | # Reset docker environment. 198 | os.system("rm " + self.configuration_file_path + "/" + self.file_path + 199 | "/*.txt") 200 | if os.path.exists(self.file_path + "/mid_files") == False: 201 | os.system("mkdir " + self.configuration_file_path + "/" + 202 | self.file_path) 203 | os.system("mkdir " + self.configuration_file_path + "/" + 204 | self.file_path + "/delay") 205 | os.system("mkdir " + self.configuration_file_path + "/" + 206 | self.file_path + "/mid_files") 207 | sn_remote_cmd(self.remote_ssh, "mkdir ~/" + self.file_path) 208 | sn_remote_cmd(self.remote_ssh, "mkdir ~/" + self.file_path + "/delay") 209 | 210 | 211 | # A thread designed for initializing constellation nodes. 212 | class sn_Node_Init_Thread(threading.Thread): 213 | 214 | def __init__(self, remote_ssh, docker_service_name, node_size, 215 | container_id_list, container_global_idx): 216 | threading.Thread.__init__(self) 217 | self.remote_ssh = remote_ssh 218 | self.docker_service_name = docker_service_name 219 | self.node_size = node_size 220 | self.container_global_idx = container_global_idx 221 | self.container_id_list = copy.deepcopy(container_id_list) 222 | 223 | def run(self): 224 | 225 | # Reset docker environment. 226 | sn_reset_docker_env(self.remote_ssh, self.docker_service_name, 227 | self.node_size) 228 | # Get container list in each machine. 229 | self.container_id_list = sn_get_container_info(self.remote_ssh) 230 | # Rename all containers with the global idx 231 | sn_rename_all_container(self.remote_ssh, self.container_id_list, 232 | self.container_global_idx) 233 | 234 | 235 | def sn_get_container_info(remote_machine_ssh): 236 | # Read all container information in all_container_info 237 | all_container_info = sn_remote_cmd(remote_machine_ssh, "docker ps") 238 | n_container = len(all_container_info) - 1 239 | container_id_list = [] 240 | for container_idx in range(1, n_container + 1): 241 | container_id_list.append(all_container_info[container_idx].split()[0]) 242 | 243 | return container_id_list 244 | 245 | 246 | def sn_delete_remote_network_bridge(remote_ssh): 247 | all_br_info = sn_remote_cmd(remote_ssh, "docker network ls") 248 | for line in all_br_info: 249 | if "La" in line or "Le" in line or "GS" in line: 250 | network_name = line.split()[1] 251 | print('docker network rm ' + network_name) 252 | sn_remote_cmd(remote_ssh, 'docker network rm ' + network_name) 253 | 254 | 255 | def sn_reset_docker_env(remote_ssh, docker_service_name, node_size): 256 | print("Reset docker environment for constellation emulation ...") 257 | print("Remove legacy containers.") 258 | print(sn_remote_cmd(remote_ssh, 259 | "docker service rm " + docker_service_name)) 260 | print(sn_remote_cmd(remote_ssh, "docker rm -f $(docker ps -a -q)")) 261 | print("Remove legacy emulated ISLs.") 262 | sn_delete_remote_network_bridge(remote_ssh) 263 | print("Creating new containers...") 264 | sn_remote_cmd( 265 | remote_ssh, "docker service create --replicas " + str(node_size) + 266 | " --name " + str(docker_service_name) + 267 | " --cap-add ALL lwsen/starlab_node:1.0 ping www.baidu.com") 268 | 269 | 270 | def sn_rename_all_container(remote_ssh, container_id_list, new_idx): 271 | print("Rename all containers ...") 272 | new_idx = 1 273 | for container_id in container_id_list: 274 | sn_remote_cmd( 275 | remote_ssh, "docker rename " + str(container_id) + 276 | " ovs_container_" + str(new_idx)) 277 | new_idx = new_idx + 1 278 | 279 | 280 | # A thread designed for initializing constellation links. 281 | class sn_Link_Init_Thread(threading.Thread): 282 | 283 | def __init__(self, remote_ssh, remote_ftp, orbit_num, sat_num, 284 | constellation_size, fac_num, file_path, 285 | configuration_file_path, sat_bandwidth, sat_ground_bandwidth, 286 | sat_loss, sat_ground_loss): 287 | threading.Thread.__init__(self) 288 | self.remote_ssh = remote_ssh 289 | self.constellation_size = constellation_size 290 | self.fac_num = fac_num 291 | self.orbit_num = orbit_num 292 | self.sat_num = sat_num 293 | self.file_path = file_path 294 | self.configuration_file_path = configuration_file_path 295 | self.sat_bandwidth = sat_bandwidth 296 | self.sat_ground_bandwidth = sat_ground_bandwidth 297 | self.sat_loss = sat_loss 298 | self.sat_ground_loss = sat_ground_loss 299 | self.remote_ftp = remote_ftp 300 | 301 | def run(self): 302 | print('Run in link init thread.') 303 | self.remote_ftp.put( 304 | os.path.join(os.getcwd(), "starrynet/sn_orchestrater.py"), 305 | self.file_path + "/sn_orchestrater.py") 306 | self.remote_ftp.put( 307 | self.configuration_file_path + "/" + self.file_path + 308 | '/delay/1.txt', self.file_path + "/1.txt") 309 | print('Initializing links ...') 310 | sn_remote_cmd( 311 | self.remote_ssh, "python3 " + self.file_path + 312 | "/sn_orchestrater.py" + " " + str(self.orbit_num) + " " + 313 | str(self.sat_num) + " " + str(self.constellation_size) + " " + 314 | str(self.fac_num) + " " + str(self.sat_bandwidth) + " " + 315 | str(self.sat_loss) + " " + str(self.sat_ground_bandwidth) + " " + 316 | str(self.sat_ground_loss) + " " + self.file_path + "/1.txt") 317 | 318 | 319 | # A thread designed for initializing bird routing. 320 | class sn_Routing_Init_Thread(threading.Thread): 321 | 322 | def __init__(self, remote_ssh, remote_ftp, orbit_num, sat_num, 323 | constellation_size, fac_num, file_path, sat_bandwidth, 324 | sat_ground_bandwidth, sat_loss, sat_ground_loss): 325 | threading.Thread.__init__(self) 326 | self.remote_ssh = remote_ssh 327 | self.constellation_size = constellation_size 328 | self.fac_num = fac_num 329 | self.orbit_num = orbit_num 330 | self.sat_num = sat_num 331 | self.file_path = file_path 332 | self.sat_bandwidth = sat_bandwidth 333 | self.sat_ground_bandwidth = sat_ground_bandwidth 334 | self.sat_loss = sat_loss 335 | self.sat_ground_loss = sat_ground_loss 336 | self.remote_ftp = remote_ftp 337 | 338 | def run(self): 339 | print( 340 | "Copy bird configuration file to each container and run routing process." 341 | ) 342 | self.remote_ftp.put( 343 | os.path.join(os.getcwd(), "starrynet/sn_orchestrater.py"), 344 | self.file_path + "/sn_orchestrater.py") 345 | print('Initializing routing ...') 346 | sn_remote_cmd( 347 | self.remote_ssh, "python3 " + self.file_path + 348 | "/sn_orchestrater.py" + " " + str(self.constellation_size) + " " + 349 | str(self.fac_num) + " " + self.file_path) 350 | print("Routing initialized!") 351 | 352 | 353 | # A thread designed for emulation. 354 | class sn_Emulation_Start_Thread(threading.Thread): 355 | 356 | def __init__(self, remote_ssh, remote_ftp, sat_loss, sat_ground_bw, 357 | sat_ground_loss, container_id_list, file_path, 358 | configuration_file_path, update_interval, constellation_size, 359 | ping_src, ping_des, ping_time, sr_src, sr_des, sr_target, 360 | sr_time, damage_ratio, damage_time, damage_list, 361 | recovery_time, route_src, route_time, duration, 362 | utility_checking_time, perf_src, perf_des, perf_time): 363 | threading.Thread.__init__(self) 364 | self.remote_ssh = remote_ssh 365 | self.remote_ftp = remote_ftp 366 | self.sat_loss = sat_loss 367 | self.sat_ground_bw = sat_ground_bw 368 | self.sat_ground_loss = sat_ground_loss 369 | self.container_id_list = copy.deepcopy(container_id_list) 370 | self.file_path = file_path 371 | self.configuration_file_path = configuration_file_path 372 | self.update_interval = update_interval 373 | self.constellation_size = constellation_size 374 | self.ping_src = ping_src 375 | self.ping_des = ping_des 376 | self.ping_time = ping_time 377 | self.perf_src = perf_src 378 | self.perf_des = perf_des 379 | self.perf_time = perf_time 380 | self.sr_src = sr_src 381 | self.sr_des = sr_des 382 | self.sr_target = sr_target 383 | self.sr_time = sr_time 384 | self.damage_ratio = damage_ratio 385 | self.damage_time = damage_time 386 | self.damage_list = damage_list 387 | self.recovery_time = recovery_time 388 | self.route_src = route_src 389 | self.route_time = route_time 390 | self.duration = duration 391 | self.utility_checking_time = utility_checking_time 392 | if self.container_id_list == []: 393 | self.container_id_list = sn_get_container_info(self.remote_ssh) 394 | 395 | def run(self): 396 | ping_threads = [] 397 | perf_threads = [] 398 | timeptr = 2 # current emulating time 399 | topo_change_file_path = self.configuration_file_path + "/" + self.file_path + '/Topo_leo_change.txt' 400 | fi = open(topo_change_file_path, 'r') 401 | line = fi.readline() 402 | while line: # starting reading change information and emulating 403 | words = line.split() 404 | if words[0] == 'time': 405 | print('Emulation in No.' + str(timeptr) + ' second.') 406 | # the time when the new change occurrs 407 | current_time = str(int(words[1][:-1])) 408 | while int(current_time) > timeptr: 409 | start_time = time.time() 410 | if timeptr in self.utility_checking_time: 411 | sn_check_utility( 412 | timeptr, self.remote_ssh, 413 | self.configuration_file_path + "/" + 414 | self.file_path) 415 | if timeptr % self.update_interval == 0: 416 | # updating link delays after link changes 417 | sn_update_delay(self.file_path, 418 | self.configuration_file_path, timeptr, 419 | self.constellation_size, 420 | self.remote_ssh, self.remote_ftp) 421 | if timeptr in self.damage_time: 422 | sn_damage( 423 | self.damage_ratio[self.damage_time.index(timeptr)], 424 | self.damage_list, self.constellation_size, 425 | self.remote_ssh, self.remote_ftp, self.file_path, 426 | self.configuration_file_path) 427 | if timeptr in self.recovery_time: 428 | sn_recover(self.damage_list, self.sat_loss, 429 | self.remote_ssh, self.remote_ftp, 430 | self.file_path, 431 | self.configuration_file_path) 432 | if timeptr in self.sr_time: 433 | index = [ 434 | i for i, val in enumerate(self.sr_time) 435 | if val == timeptr 436 | ] 437 | for index_num in index: 438 | sn_sr(self.sr_src[index_num], 439 | self.sr_des[index_num], 440 | self.sr_target[index_num], 441 | self.container_id_list, self.remote_ssh) 442 | if timeptr in self.ping_time: 443 | if timeptr in self.ping_time: 444 | index = [ 445 | i for i, val in enumerate(self.ping_time) 446 | if val == timeptr 447 | ] 448 | for index_num in index: 449 | ping_thread = threading.Thread( 450 | target=sn_ping, 451 | args=(self.ping_src[index_num], 452 | self.ping_des[index_num], 453 | self.ping_time[index_num], 454 | self.constellation_size, 455 | self.container_id_list, 456 | self.file_path, 457 | self.configuration_file_path, 458 | self.remote_ssh)) 459 | ping_thread.start() 460 | ping_threads.append(ping_thread) 461 | if timeptr in self.perf_time: 462 | if timeptr in self.perf_time: 463 | index = [ 464 | i for i, val in enumerate(self.perf_time) 465 | if val == timeptr 466 | ] 467 | for index_num in index: 468 | perf_thread = threading.Thread( 469 | target=sn_perf, 470 | args=(self.perf_src[index_num], 471 | self.perf_des[index_num], 472 | self.perf_time[index_num], 473 | self.constellation_size, 474 | self.container_id_list, 475 | self.file_path, 476 | self.configuration_file_path, 477 | self.remote_ssh)) 478 | perf_thread.start() 479 | perf_threads.append(perf_thread) 480 | if timeptr in self.route_time: 481 | index = [ 482 | i for i, val in enumerate(self.route_time) 483 | if val == timeptr 484 | ] 485 | for index_num in index: 486 | sn_route(self.route_src[index_num], 487 | self.route_time[index_num], 488 | self.file_path, 489 | self.configuration_file_path, 490 | self.container_id_list, self.remote_ssh) 491 | timeptr += 1 492 | end_time = time.time() 493 | passed_time = ( 494 | end_time - 495 | start_time) if (end_time - start_time) < 1 else 1 496 | sleep(1 - passed_time) 497 | if timeptr >= self.duration: 498 | return 499 | print('Emulation in No.' + str(timeptr) + ' second.') 500 | print("A change in time " + current_time + ':') 501 | line = fi.readline() 502 | words = line.split() 503 | line = fi.readline() 504 | line = fi.readline() 505 | words = line.split() 506 | while words[0] != 'del:': # addlink 507 | word = words[0].split('-') 508 | s = int(word[0]) 509 | f = int(word[1]) 510 | if s > f: 511 | tmp = s 512 | s = f 513 | f = tmp 514 | print("add link", s, f) 515 | current_topo_path = self.configuration_file_path + "/" + self.file_path + '/delay/' + str( 516 | current_time) + '.txt' 517 | matrix = sn_get_param(current_topo_path) 518 | sn_establish_new_GSL(self.container_id_list, matrix, 519 | self.constellation_size, 520 | self.sat_ground_bw, 521 | self.sat_ground_loss, s, f, 522 | self.remote_ssh) 523 | line = fi.readline() 524 | words = line.split() 525 | line = fi.readline() 526 | words = line.split() 527 | if len(words) == 0: 528 | return 529 | while words[0] != 'time': # delete link 530 | word = words[0].split('-') 531 | s = int(word[0]) 532 | f = int(word[1]) 533 | if s > f: 534 | tmp = s 535 | s = f 536 | f = tmp 537 | print("del link " + str(s) + "-" + str(f) + "\n") 538 | sn_del_link(s, f, self.container_id_list, self.remote_ssh) 539 | line = fi.readline() 540 | words = line.split() 541 | if len(words) == 0: 542 | return 543 | if timeptr in self.utility_checking_time: 544 | sn_check_utility( 545 | timeptr, self.remote_ssh, 546 | self.configuration_file_path + "/" + self.file_path) 547 | if timeptr % self.update_interval == 0: 548 | # updating link delays after link changes 549 | sn_update_delay(self.file_path, 550 | self.configuration_file_path, timeptr, 551 | self.constellation_size, self.remote_ssh, 552 | self.remote_ftp) 553 | if timeptr in self.damage_time: 554 | sn_damage( 555 | self.damage_ratio[self.damage_time.index(timeptr)], 556 | self.damage_list, self.constellation_size, 557 | self.remote_ssh, self.remote_ftp, self.file_path, 558 | self.configuration_file_path) 559 | if timeptr in self.recovery_time: 560 | sn_recover(self.damage_list, self.sat_loss, 561 | self.remote_ssh, self.remote_ftp, 562 | self.file_path, self.configuration_file_path) 563 | if timeptr in self.sr_time: 564 | index = [ 565 | i for i, val in enumerate(self.sr_time) 566 | if val == timeptr 567 | ] 568 | for index_num in index: 569 | sn_sr(self.sr_src[index_num], self.sr_des[index_num], 570 | self.sr_target[index_num], 571 | self.container_id_list, self.remote_ssh) 572 | if timeptr in self.ping_time: 573 | if timeptr in self.ping_time: 574 | index = [ 575 | i for i, val in enumerate(self.ping_time) 576 | if val == timeptr 577 | ] 578 | for index_num in index: 579 | ping_thread = threading.Thread( 580 | target=sn_ping, 581 | args=(self.ping_src[index_num], 582 | self.ping_des[index_num], 583 | self.ping_time[index_num], 584 | self.constellation_size, 585 | self.container_id_list, self.file_path, 586 | self.configuration_file_path, 587 | self.remote_ssh)) 588 | ping_thread.start() 589 | ping_threads.append(ping_thread) 590 | if timeptr in self.perf_time: 591 | if timeptr in self.perf_time: 592 | index = [ 593 | i for i, val in enumerate(self.perf_time) 594 | if val == timeptr 595 | ] 596 | for index_num in index: 597 | perf_thread = threading.Thread( 598 | target=sn_perf, 599 | args=(self.perf_src[index_num], 600 | self.perf_des[index_num], 601 | self.perf_time[index_num], 602 | self.constellation_size, 603 | self.container_id_list, self.file_path, 604 | self.configuration_file_path, 605 | self.remote_ssh)) 606 | perf_thread.start() 607 | perf_threads.append(perf_thread) 608 | if timeptr in self.route_time: 609 | index = [ 610 | i for i, val in enumerate(self.route_time) 611 | if val == timeptr 612 | ] 613 | for index_num in index: 614 | sn_route(self.route_src[index_num], 615 | self.route_time[index_num], self.file_path, 616 | self.configuration_file_path, 617 | self.container_id_list, self.remote_ssh) 618 | timeptr += 1 # current emulating time 619 | if timeptr >= self.duration: 620 | return 621 | fi.close() 622 | for ping_thread in ping_threads: 623 | ping_thread.join() 624 | for perf_thread in perf_threads: 625 | perf_thread.join() 626 | 627 | 628 | def sn_check_utility(time_index, remote_ssh, file_path): 629 | result = sn_remote_cmd(remote_ssh, "vmstat") 630 | f = open(file_path + "/utility-info" + "_" + str(time_index) + ".txt", "w") 631 | f.writelines(result) 632 | f.close() 633 | 634 | 635 | def sn_update_delay(file_path, configuration_file_path, timeptr, 636 | constellation_size, remote_ssh, 637 | remote_ftp): # updating delays 638 | remote_ftp.put(os.path.join(os.getcwd(), "starrynet/sn_orchestrater.py"), 639 | file_path + "/sn_orchestrater.py") 640 | remote_ftp.put( 641 | configuration_file_path + "/" + file_path + '/delay/' + str(timeptr) + 642 | '.txt', file_path + '/' + str(timeptr) + '.txt') 643 | sn_remote_cmd( 644 | remote_ssh, 645 | "python3 " + file_path + "/sn_orchestrater.py " + file_path + '/' + 646 | str(timeptr) + '.txt ' + str(constellation_size) + " update") 647 | print("Delay updating done.\n") 648 | 649 | 650 | def sn_damage(ratio, damage_list, constellation_size, remote_ssh, remote_ftp, 651 | file_path, configuration_file_path): 652 | print("Randomly setting damaged links...\n") 653 | random_list = [] 654 | cumulated_damage_list = damage_list 655 | while len(random_list) < (int(constellation_size * ratio)): 656 | target = int(random.uniform(0, constellation_size - 1)) 657 | random_list.append(target) 658 | cumulated_damage_list.append(target) 659 | numpy.savetxt( 660 | configuration_file_path + "/" + file_path + 661 | '/mid_files/damage_list.txt', random_list) 662 | remote_ftp.put(os.path.join(os.getcwd(), "starrynet/sn_orchestrater.py"), 663 | file_path + "/sn_orchestrater.py") 664 | remote_ftp.put( 665 | configuration_file_path + "/" + file_path + 666 | '/mid_files/damage_list.txt', file_path + "/damage_list.txt") 667 | sn_remote_cmd(remote_ssh, 668 | "python3 " + file_path + "/sn_orchestrater.py " + file_path) 669 | print("Damage done.\n") 670 | 671 | 672 | def sn_recover(damage_list, sat_loss, remote_ssh, remote_ftp, file_path, 673 | configuration_file_path): 674 | print("Recovering damaged links...\n") 675 | cumulated_damage_list = damage_list 676 | numpy.savetxt( 677 | configuration_file_path + "/" + file_path + 678 | '/mid_files/damage_list.txt', cumulated_damage_list) 679 | remote_ftp.put(os.path.join(os.getcwd(), "starrynet/sn_orchestrater.py"), 680 | file_path + "/sn_orchestrater.py") 681 | remote_ftp.put( 682 | configuration_file_path + "/" + file_path + 683 | '/mid_files/damage_list.txt', file_path + "/damage_list.txt") 684 | sn_remote_cmd( 685 | remote_ssh, "python3 " + file_path + "/sn_orchestrater.py " + 686 | file_path + " " + str(sat_loss)) 687 | cumulated_damage_list.clear() 688 | print("Link recover done.\n") 689 | 690 | 691 | def sn_sr(src, des, target, container_id_list, remote_ssh): 692 | ifconfig_output = sn_remote_cmd( 693 | remote_ssh, "docker exec -it " + str(container_id_list[des - 1]) + 694 | " ifconfig | sed 's/[ \t].*//;/^\(eth0\|\)\(lo\|\)$/d'") 695 | des_IP = sn_remote_cmd( 696 | remote_ssh, 697 | "docker exec -it " + str(container_id_list[des - 1]) + " ifconfig " + 698 | ifconfig_output[0][:-1] + "|awk -F '[ :]+' 'NR==2{print $4}'") 699 | target_IP = sn_remote_cmd( 700 | remote_ssh, "docker exec -it " + str(container_id_list[target - 1]) + 701 | " ifconfig B" + str(target) + "-eth" + str(src) + 702 | "|awk -F '[ :]+' 'NR==2{print $4}'") 703 | sn_remote_cmd( 704 | remote_ssh, "docker exec -d " + str(container_id_list[src - 1]) + 705 | " ip route del " + str(des_IP[0][:-3]) + "0/24") 706 | sn_remote_cmd( 707 | remote_ssh, "docker exec -d " + str(container_id_list[src - 1]) + 708 | " ip route add " + str(des_IP[0][:-3]) + "0/24 dev B%d-eth%d via " % 709 | (src, target) + target_IP[0]) 710 | print("docker exec -d " + str(container_id_list[src - 1]) + 711 | " ip route add " + str(des_IP[0][:-3]) + "0/24 dev B%d-eth%d via " % 712 | (src, target) + target_IP[0]) 713 | 714 | 715 | def sn_ping(src, des, time_index, constellation_size, container_id_list, 716 | file_path, configuration_file_path, remote_ssh): 717 | if des <= constellation_size: 718 | ifconfig_output = sn_remote_cmd( 719 | remote_ssh, "docker exec -it " + str(container_id_list[des - 1]) + 720 | " ifconfig | sed 's/[ \t].*//;/^\(eth0\|\)\(lo\|\)$/d'") 721 | des_IP = sn_remote_cmd( 722 | remote_ssh, "docker exec -it " + str(container_id_list[des - 1]) + 723 | " ifconfig " + ifconfig_output[0][:-1] + 724 | "|awk -F '[ :]+' 'NR==2{print $4}'") 725 | else: 726 | des_IP = sn_remote_cmd( 727 | remote_ssh, "docker exec -it " + str(container_id_list[des - 1]) + 728 | " ifconfig B" + str(des) + 729 | "-default |awk -F '[ :]+' 'NR==2{print $4}'") 730 | ping_result = sn_remote_cmd( 731 | remote_ssh, "docker exec -i " + str(container_id_list[src - 1]) + 732 | " ping " + str(des_IP[0][:-1]) + " -c 4 -i 0.01 ") 733 | f = open( 734 | configuration_file_path + "/" + file_path + "/ping-" + str(src) + "-" + 735 | str(des) + "_" + str(time_index) + ".txt", "w") 736 | f.writelines(ping_result) 737 | f.close() 738 | 739 | 740 | def sn_perf(src, des, time_index, constellation_size, container_id_list, 741 | file_path, configuration_file_path, remote_ssh): 742 | if des <= constellation_size: 743 | ifconfig_output = sn_remote_cmd( 744 | remote_ssh, "docker exec -it " + str(container_id_list[des - 1]) + 745 | " ifconfig | sed 's/[ \t].*//;/^\(eth0\|\)\(lo\|\)$/d'") 746 | des_IP = sn_remote_cmd( 747 | remote_ssh, "docker exec -it " + str(container_id_list[des - 1]) + 748 | " ifconfig " + ifconfig_output[0][:-1] + 749 | "|awk -F '[ :]+' 'NR==2{print $4}'") 750 | else: 751 | des_IP = sn_remote_cmd( 752 | remote_ssh, "docker exec -it " + str(container_id_list[des - 1]) + 753 | " ifconfig B" + str(des) + 754 | "-default |awk -F '[ :]+' 'NR==2{print $4}'") 755 | 756 | sn_remote_cmd( 757 | remote_ssh, 758 | "docker exec -id " + str(container_id_list[des - 1]) + " iperf3 -s ") 759 | print("iperf server success") 760 | perf_result = sn_remote_cmd( 761 | remote_ssh, "docker exec -i " + str(container_id_list[src - 1]) + 762 | " iperf3 -c " + str(des_IP[0][:-1]) + " -t 5 ") 763 | print("iperf client success") 764 | f = open( 765 | configuration_file_path + "/" + file_path + "/perf-" + str(src) + "-" + 766 | str(des) + "_" + str(time_index) + ".txt", "w") 767 | f.writelines(perf_result) 768 | f.close() 769 | 770 | 771 | def sn_route(src, time_index, file_path, configuration_file_path, 772 | container_id_list, remote_ssh): 773 | route_result = sn_remote_cmd( 774 | remote_ssh, 775 | "docker exec -it " + str(container_id_list[src - 1]) + " route ") 776 | f = open( 777 | configuration_file_path + "/" + file_path + "/route-" + str(src) + 778 | "_" + str(time_index) + ".txt", "w") 779 | f.writelines(route_result) 780 | f.close() 781 | 782 | 783 | def sn_establish_new_GSL(container_id_list, matrix, constellation_size, bw, 784 | loss, sat_index, GS_index, remote_ssh): 785 | i = sat_index 786 | j = GS_index 787 | # IP address (there is a link between i and j) 788 | delay = str(matrix[i - 1][j - 1]) 789 | address_16_23 = (j - constellation_size) & 0xff 790 | address_8_15 = i & 0xff 791 | GSL_name = "GSL_" + str(i) + "-" + str(j) 792 | # Create internal network in docker. 793 | sn_remote_cmd( 794 | remote_ssh, 'docker network create ' + GSL_name + " --subnet 9." + 795 | str(address_16_23) + "." + str(address_8_15) + ".0/24") 796 | print('[Create GSL:]' + 'docker network create ' + GSL_name + 797 | " --subnet 9." + str(address_16_23) + "." + str(address_8_15) + 798 | ".0/24") 799 | sn_remote_cmd( 800 | remote_ssh, 'docker network connect ' + GSL_name + " " + 801 | str(container_id_list[i - 1]) + " --ip 9." + str(address_16_23) + "." + 802 | str(address_8_15) + ".50") 803 | ifconfig_output = sn_remote_cmd( 804 | remote_ssh, "docker exec -it " + str(container_id_list[i - 1]) + 805 | " ip addr | grep -B 2 9." + str(address_16_23) + "." + 806 | str(address_8_15) + 807 | ".50 | head -n 1 | awk -F: '{ print $2 }' | tr -d [:blank:]") 808 | target_interface = str(ifconfig_output[0]).split("@")[0] 809 | sn_remote_cmd( 810 | remote_ssh, "docker exec -d " + str(container_id_list[i - 1]) + 811 | " ip link set dev " + target_interface + " down") 812 | sn_remote_cmd( 813 | remote_ssh, "docker exec -d " + str(container_id_list[i - 1]) + 814 | " ip link set dev " + target_interface + " name " + "B" + 815 | str(i - 1 + 1) + "-eth" + str(j)) 816 | sn_remote_cmd( 817 | remote_ssh, "docker exec -d " + str(container_id_list[i - 1]) + 818 | " ip link set dev B" + str(i - 1 + 1) + "-eth" + str(j) + " up") 819 | sn_remote_cmd( 820 | remote_ssh, "docker exec -d " + str(container_id_list[i - 1]) + 821 | " tc qdisc add dev B" + str(i - 1 + 1) + "-eth" + str(j) + 822 | " root netem delay " + str(delay) + "ms") 823 | sn_remote_cmd( 824 | remote_ssh, "docker exec -d " + str(container_id_list[i - 1]) + 825 | " tc qdisc add dev B" + str(i - 1 + 1) + "-eth" + str(j) + 826 | " root netem loss " + str(loss) + "%") 827 | sn_remote_cmd( 828 | remote_ssh, "docker exec -d " + str(container_id_list[i - 1]) + 829 | " tc qdisc add dev B" + str(i - 1 + 1) + "-eth" + str(j) + 830 | " root netem rate " + str(bw) + "Gbps") 831 | print('[Add current node:]' + 'docker network connect ' + GSL_name + " " + 832 | str(container_id_list[i - 1]) + " --ip 10." + str(address_16_23) + 833 | "." + str(address_8_15) + ".50") 834 | sn_remote_cmd( 835 | remote_ssh, 'docker network connect ' + GSL_name + " " + 836 | str(container_id_list[j - 1]) + " --ip 9." + str(address_16_23) + "." + 837 | str(address_8_15) + ".60") 838 | ifconfig_output = sn_remote_cmd( 839 | remote_ssh, "docker exec -it " + str(container_id_list[j - 1]) + 840 | " ip addr | grep -B 2 9." + str(address_16_23) + "." + 841 | str(address_8_15) + 842 | ".60 | head -n 1 | awk -F: '{ print $2 }' | tr -d [:blank:]") 843 | target_interface = str(ifconfig_output[0]).split("@")[0] 844 | sn_remote_cmd( 845 | remote_ssh, "docker exec -d " + str(container_id_list[j - 1]) + 846 | " ip link set dev " + target_interface + " down") 847 | sn_remote_cmd( 848 | remote_ssh, "docker exec -d " + str(container_id_list[j - 1]) + 849 | " ip link set dev " + target_interface + " name " + "B" + str(j) + 850 | "-eth" + str(i - 1 + 1)) 851 | sn_remote_cmd( 852 | remote_ssh, "docker exec -d " + str(container_id_list[j - 1]) + 853 | " ip link set dev B" + str(j) + "-eth" + str(i - 1 + 1) + " up") 854 | sn_remote_cmd( 855 | remote_ssh, "docker exec -d " + str(container_id_list[j - 1]) + 856 | " tc qdisc add dev B" + str(j) + "-eth" + str(i - 1 + 1) + 857 | " root netem delay " + str(delay) + "ms") 858 | sn_remote_cmd( 859 | remote_ssh, "docker exec -d " + str(container_id_list[j - 1]) + 860 | " tc qdisc add dev B" + str(j) + "-eth" + str(i - 1 + 1) + 861 | " root netem loss " + str(loss) + "%") 862 | sn_remote_cmd( 863 | remote_ssh, "docker exec -d " + str(container_id_list[j - 1]) + 864 | " tc qdisc add dev B" + str(j) + "-eth" + str(i - 1 + 1) + 865 | " root netem rate " + str(bw) + "Gbps") 866 | print('[Add right node:]' + 'docker network connect ' + GSL_name + " " + 867 | str(container_id_list[j - 1]) + " --ip 10." + str(address_16_23) + 868 | "." + str(address_8_15) + ".60") 869 | 870 | 871 | def sn_del_link(first_index, second_index, container_id_list, remote_ssh): 872 | sn_remote_cmd( 873 | remote_ssh, "docker exec -d " + 874 | str(container_id_list[second_index - 1]) + " ip link set dev B" + 875 | str(second_index) + "-eth" + str(first_index) + " down") 876 | sn_remote_cmd( 877 | remote_ssh, "docker exec -d " + 878 | str(container_id_list[first_index - 1]) + " ip link set dev B" + 879 | str(first_index) + "-eth" + str(second_index) + " down") 880 | GSL_name = "GSL_" + str(first_index) + "-" + str(second_index) 881 | sn_remote_cmd( 882 | remote_ssh, 'docker network disconnect ' + GSL_name + " " + 883 | str(container_id_list[first_index - 1])) 884 | sn_remote_cmd( 885 | remote_ssh, 'docker network disconnect ' + GSL_name + " " + 886 | str(container_id_list[second_index - 1])) 887 | sn_remote_cmd(remote_ssh, 'docker network rm ' + GSL_name) 888 | 889 | 890 | # A thread designed for stopping the emulation. 891 | class sn_Emulation_Stop_Thread(threading.Thread): 892 | 893 | def __init__(self, remote_ssh, remote_ftp, file_path): 894 | threading.Thread.__init__(self) 895 | self.remote_ssh = remote_ssh 896 | self.remote_ftp = remote_ftp 897 | self.file_path = file_path 898 | 899 | def run(self): 900 | print("Deleting all native bridges and containers...") 901 | self.remote_ftp.put( 902 | os.path.join(os.getcwd(), "starrynet/sn_orchestrater.py"), 903 | self.file_path + "/sn_orchestrater.py") 904 | sn_remote_cmd(self.remote_ssh, 905 | "python3 " + self.file_path + "/sn_orchestrater.py") 906 | -------------------------------------------------------------------------------- /tle/Dove.tle: -------------------------------------------------------------------------------- 1 | FLOCK 2P-9 2 | 1 41610U 16040M 22113.39133221 .00035922 00000+0 11624-2 0 9992 3 | 2 41610 97.2370 172.7438 0007333 298.0873 61.9627 15.31936909324610 4 | FLOCK 2P-8 5 | 1 41613U 16040Q 22113.43359179 .00035379 00000+0 11488-2 0 9996 6 | 2 41613 97.2419 173.8132 0007256 308.8505 51.2088 15.31829022324784 7 | FLOCK 2P-3 8 | 1 41618U 16040V 22113.53190544 .00034346 00000+0 11127-2 0 9994 9 | 2 41618 97.2442 174.4024 0006913 308.2264 51.8355 15.31911530324809 10 | FLOCK 3P-24 11 | 1 41956U 17008J 22113.14096315 .00016840 00000+0 53620-3 0 9996 12 | 2 41956 97.2845 183.7074 0007803 55.9068 304.2913 15.32606417288840 13 | FLOCK 3P-4 14 | 1 41965U 17008T 22113.48757871 .00026157 00000+0 84656-3 0 9999 15 | 2 41965 97.2800 180.5252 0008027 68.0377 292.1717 15.31992720288583 16 | FLOCK 3P-2 17 | 1 41966U 17008U 22113.43397096 .00013860 00000+0 45429-3 0 9996 18 | 2 41966 97.2816 180.6617 0007924 68.6961 291.5126 15.31718717288496 19 | FLOCK 3P-3 20 | 1 41968U 17008W 22113.44797239 .00015284 00000+0 50516-3 0 9997 21 | 2 41968 97.2816 181.0062 0008087 65.8179 294.3907 15.31426434288601 22 | FLOCK 3P-12 23 | 1 41972U 17008AA 22113.14180611 .00014081 00000+0 45889-3 0 9990 24 | 2 41972 97.2817 180.4726 0007939 72.5854 287.6255 15.31900449288533 25 | FLOCK 3P-11 26 | 1 41975U 17008AD 22113.41807815 .00014471 00000+0 46925-3 0 9990 27 | 2 41975 97.2805 180.6035 0007842 70.0781 290.1305 15.32054691288460 28 | FLOCK 3P-58 29 | 1 41977U 17008AF 22113.14729257 .00040130 00000+0 12921-2 0 9994 30 | 2 41977 97.2818 180.4743 0007886 71.7517 288.4583 15.32068836288614 31 | FLOCK 3P-57 32 | 1 41978U 17008AG 22113.46510203 .00017200 00000+0 55837-3 0 9995 33 | 2 41978 97.2810 180.7361 0007820 69.3741 290.8339 15.31980368288502 34 | FLOCK 3P-34 35 | 1 41988U 17008AS 22113.59247755 .00009818 00000+0 32185-3 0 9996 36 | 2 41988 97.2838 181.0979 0007419 70.2128 289.9913 15.31805302288002 37 | FLOCK 3P-33 38 | 1 41990U 17008AU 22113.40380253 .00011752 00000+0 40940-3 0 9998 39 | 2 41990 97.2824 179.9031 0008054 75.2618 284.9515 15.29780116287914 40 | FLOCK 3P-49 41 | 1 42001U 17008BF 22113.14654693 .00028606 00000+0 91632-3 0 9995 42 | 2 42001 97.2817 180.8048 0008069 87.5176 272.6990 15.32305183288474 43 | FLOCK 3P-61 44 | 1 42009U 17008BP 22113.11721352 .00015910 00000+0 52002-3 0 9999 45 | 2 42009 97.2816 180.5138 0008286 89.0943 271.1248 15.31775685288587 46 | FLOCK 3P-54 47 | 1 42014U 17008BU 22113.42968210 .00016777 00000+0 54374-3 0 9991 48 | 2 42014 97.2828 180.8747 0007329 90.0282 270.1800 15.32043549288660 49 | FLOCK 3P-23 50 | 1 42018U 17008BY 22113.42394984 .00017314 00000+0 56237-3 0 9994 51 | 2 42018 97.2806 180.5012 0007975 66.3409 293.8669 15.31961545287428 52 | FLOCK 3P-76 53 | 1 42019U 17008BZ 22113.44149523 .00021257 00000+0 67723-3 0 9997 54 | 2 42019 97.2864 181.7076 0007746 71.7555 288.4530 15.32541278288497 55 | FLOCK 3P-32 56 | 1 42023U 17008CD 22113.44646735 .00016075 00000+0 52312-3 0 9999 57 | 2 42023 97.2812 180.7803 0007709 69.2345 290.9722 15.31918119288661 58 | FLOCK 3P-80 59 | 1 42026U 17008CG 22113.43980434 .00030371 00000+0 98746-3 0 9994 60 | 2 42026 97.2855 181.4654 0007567 74.5812 285.6266 15.31817643286643 61 | FLOCK 3P-66 62 | 1 42027U 17008CH 22113.14254317 .00024227 00000+0 79181-3 0 9992 63 | 2 42027 97.2821 180.5108 0008382 85.9846 274.2354 15.31689405288568 64 | FLOCK 3P-65 65 | 1 42028U 17008CJ 22113.45562549 .00027178 00000+0 87732-3 0 9995 66 | 2 42028 97.2827 180.9803 0007997 85.3923 274.8232 15.32068203288516 67 | FLOCK 3P-50 68 | 1 42029U 17008CK 22113.45153902 .00016293 00000+0 52933-3 0 9996 69 | 2 42029 97.2836 181.0898 0007656 85.4747 274.7369 15.31970106287837 70 | FLOCK 3P-46 71 | 1 42031U 17008CM 22113.17333393 .00015532 00000+0 50475-3 0 9996 72 | 2 42031 97.2808 180.4751 0008162 85.5721 274.6453 15.31966225288624 73 | FLOCK 3P-64 74 | 1 42034U 17008CQ 22113.44274689 .00027532 00000+0 89071-3 0 9998 75 | 2 42034 97.2807 180.7017 0007875 88.9428 271.2716 15.31995739288655 76 | FLOCK 3P-55 77 | 1 42041U 17008CX 22113.13111852 .00022835 00000+0 74097-3 0 9997 78 | 2 42041 97.2800 180.5003 0007449 93.3940 266.8154 15.31939528288622 79 | FLOCK 3P-74 80 | 1 42048U 17008DE 22113.16015558 .00011962 00000+0 39179-3 0 9994 81 | 2 42048 97.2865 181.2478 0008412 73.1030 287.1133 15.31773388287779 82 | FLOCK 2K-3 83 | 1 42850U 17042AB 22113.48337948 .00197957 00000+0 17124-2 0 9992 84 | 2 42850 96.8202 316.5096 0002645 19.2728 340.8635 15.68571770268477 85 | FLOCK 2K-21 86 | 1 42860U 17042AM 22113.44165942 .00227849 00000+0 26279-2 0 9997 87 | 2 42860 96.8246 316.0905 0005750 7.0928 353.0411 15.61171924268420 88 | FLOCK 2K-7 89 | 1 42862U 17042AP 22113.49377822 .00212353 00000+0 26112-2 0 9996 90 | 2 42862 96.7899 316.1726 0001990 311.1709 49.7578 15.59549345268443 91 | FLOCK 2K-40 92 | 1 42865U 17042AS 22113.32477785 .00055333 00000+0 52273-3 0 9991 93 | 2 42865 96.8203 316.2075 0003338 321.6185 38.4839 15.66847135268425 94 | FLOCK 2K-39 95 | 1 42866U 17042AT 22113.51056794 .00044539 00000+0 58956-3 0 9996 96 | 2 42866 96.8205 316.0875 0005896 13.5338 346.6076 15.58165766268434 97 | FLOCK 2K-37 98 | 1 42867U 17042AU 22113.50973809 .00294570 00000+0 28122-2 0 9994 99 | 2 42867 96.8157 316.1211 0005369 352.4563 7.6617 15.65814793268441 100 | FLOCK 2K-32 101 | 1 42870U 17042AX 22113.80256261 .00058480 00000+0 76154-3 0 9994 102 | 2 42870 96.8195 316.3189 0005102 33.2664 326.8913 15.58548832268483 103 | FLOCK 2K-29 104 | 1 42871U 17042AY 22113.48236695 .00051622 00000+0 67433-3 0 9999 105 | 2 42871 96.8191 316.0098 0004870 22.7807 337.3666 15.58495433268434 106 | FLOCK 2K-30 107 | 1 42872U 17042AZ 22113.83539519 .00044150 00000+0 60534-3 0 9993 108 | 2 42872 96.8242 316.4569 0005599 16.9452 343.1991 15.57224801268477 109 | FLOCK 2K-41 110 | 1 42875U 17042BC 22113.40954084 .00451111 00000+0 31761-2 0 9995 111 | 2 42875 96.8184 316.5204 0005867 312.1386 47.9380 15.72795066268459 112 | FLOCK 2K-35 113 | 1 42877U 17042BE 22113.85733095 .00050851 00000+0 65943-3 0 9999 114 | 2 42877 96.8240 316.5501 0005211 356.1484 3.9732 15.58691049268491 115 | FLOCK 2K-19 116 | 1 42886U 17042BP 22113.77488924 .00064551 00000+0 82507-3 0 9995 117 | 2 42886 96.8201 316.4481 0005823 358.2395 1.8841 15.59017267268489 118 | FLOCK 2K-18 119 | 1 42887U 17042BQ 22113.45868275 .00048888 00000+0 63743-3 0 9998 120 | 2 42887 96.8222 316.1749 0006376 6.2908 353.8428 15.58546240268434 121 | FLOCK 2K-15 122 | 1 42890U 17042BT 22113.35833389 .00215071 00000+0 25385-2 0 9998 123 | 2 42890 96.8185 316.2314 0006197 359.6016 0.5237 15.60605438268422 124 | FLOCK 2K-12 125 | 1 42893U 17042BW 22113.50716670 .00054214 00000+0 69570-3 0 9996 126 | 2 42893 96.8160 316.1962 0005898 15.1973 344.9461 15.58951838268440 127 | FLOCK 2K-10 128 | 1 42895U 17042BY 22113.32538807 .00050071 00000+0 64774-3 0 9990 129 | 2 42895 96.8204 315.9379 0006215 0.9940 359.1329 15.58751992268417 130 | FLOCK 3M-4 131 | 1 42997U 17068L 22113.58211671 .00008720 00000+0 31751-3 0 9999 132 | 2 42997 97.4546 227.7636 0011530 254.8842 105.1122 15.28396604248521 133 | FLOCK 3P'-3 134 | 1 43119U 18004J 22113.51208569 .00007219 00000+0 27970-3 0 9992 135 | 2 43119 97.3772 188.2219 0009436 187.7609 172.3486 15.26416260237833 136 | FLOCK 3P'-2 137 | 1 43120U 18004K 22113.63273762 .00008310 00000+0 32662-3 0 9991 138 | 2 43120 97.3778 187.9906 0009261 189.5115 170.5949 15.25890834237823 139 | FLOCK 3P'-4 140 | 1 43122U 18004M 22113.81647654 .00008149 00000+0 31980-3 0 9990 141 | 2 43122 97.3784 188.3337 0009237 190.4742 169.6305 15.25950146237856 142 | FLOCK 3R-9 143 | 1 43723U 18096E 22113.47853102 .00073552 00000+0 17599-2 0 9996 144 | 2 43723 97.3585 197.6038 0012187 235.6234 187.3058 15.41120565189781 145 | FLOCK 3R-12 146 | 1 43724U 18096F 22113.18927399 .00019864 00000+0 47884-3 0 9992 147 | 2 43724 97.3587 197.4036 0012542 235.8108 124.1950 15.41192077189740 148 | FLOCK 3R-11 149 | 1 43725U 18096G 22113.47845408 .00076814 00000+0 18875-2 0 9990 150 | 2 43725 97.3590 197.5946 0012737 234.0121 125.9944 15.40291775189788 151 | FLOCK 3R-4 152 | 1 43733U 18096Q 22113.86135771 .00067760 00000+0 16277-2 0 9991 153 | 2 43733 97.3616 198.3960 0014776 231.2314 198.2278 15.40990628189853 154 | FLOCK 3R-6 155 | 1 43741U 18096Y 22113.50603341 .00068524 00000+0 16603-2 0 9996 156 | 2 43741 97.3559 197.8283 0012769 237.4273 134.9793 15.40754749189808 157 | FLOCK 3R-7 158 | 1 43742U 18096Z 22113.49976956 .00052720 00000+0 12514-2 0 9998 159 | 2 43742 97.3635 197.8336 0011694 237.5044 134.7760 15.41461326189793 160 | FLOCK 3R-1 161 | 1 43747U 18096AE 22113.47612505 .00032255 00000+0 78337-3 0 9995 162 | 2 43747 97.3616 198.1403 0014801 238.9339 121.0455 15.40834938189816 163 | FLOCK 3S-1 164 | 1 43769U 18099M 22113.44308218 .00001785 00000+0 16406-3 0 9995 165 | 2 43769 97.6235 180.6388 0015468 98.8889 261.4085 14.95947072184783 166 | FLOCK 3K-4 167 | 1 43893U 18111T 22113.43243278 .00010136 00000+0 30073-3 0 9997 168 | 2 43893 97.2005 5.1952 0015872 269.1161 90.8264 15.34879927185124 169 | FLOCK 3K-2 170 | 1 43895U 18111V 22113.48691611 .00033917 00000+0 99491-3 0 9993 171 | 2 43895 97.2000 5.2031 0016465 265.1038 94.8326 15.34964269185136 172 | FLOCK 3K-5 173 | 1 43899U 18111Z 22113.60348988 .00024432 00000+0 70697-3 0 9996 174 | 2 43899 97.2005 5.4259 0014591 261.3796 98.5795 15.35488311185159 175 | FLOCK 3K-7 176 | 1 43902U 18111AC 22113.26702944 .00013246 00000+0 38554-3 0 9996 177 | 2 43902 97.2004 5.0918 0014318 262.0780 97.8839 15.35437586185103 178 | FLOCK 3K-10 179 | 1 43905U 18111AF 22113.44341397 .00035601 00000+0 10418-2 0 9997 180 | 2 43905 97.2004 5.3335 0014415 258.0626 101.9001 15.35061104185134 181 | FLOCK 3K-9 182 | 1 43906U 18111AG 22113.39147382 .00015150 00000+0 44800-3 0 9990 183 | 2 43906 97.1995 5.1724 0014832 257.9527 102.0055 15.34900559185129 184 | FLOCK 4A-2 185 | 1 44080U 19018C 22113.12383381 .00023787 00000+0 82517-3 0 9995 186 | 2 44080 97.3169 173.8204 0008665 305.3986 54.6445 15.29761458170214 187 | FLOCK 4A-17 188 | 1 44088U 19018L 22113.40461868 .00017950 00000+0 62314-3 0 9999 189 | 2 44088 97.3180 174.4913 0009651 313.2421 46.8013 15.29786581170271 190 | FLOCK 4A-19 191 | 1 44090U 19018N 22113.43523912 .00011921 00000+0 41753-3 0 9990 192 | 2 44090 97.3174 174.5747 0009963 313.8895 46.1522 15.29587140170289 193 | FLOCK 4A-6 194 | 1 44094U 19018S 22113.48534030 .00032618 00000+0 11373-2 0 9992 195 | 2 44094 97.3185 174.7522 0007551 323.1355 36.9366 15.29539033170296 196 | FLOCK 4A-5 197 | 1 44095U 19018T 22113.40589288 .00022712 00000+0 81890-3 0 9996 198 | 2 44095 97.3188 174.4025 0008254 321.4933 38.5717 15.28511160170264 199 | FLOCK 4A-11 200 | 1 44096U 19018U 22113.12875396 .00016656 00000+0 57666-3 0 9998 201 | 2 44096 97.3186 174.5041 0007601 328.9400 31.1390 15.29907105170230 202 | FLOCK 4A-9 203 | 1 44098U 19018W 22113.44423602 .00021601 00000+0 75024-3 0 9998 204 | 2 44098 97.3195 174.8415 0008440 325.4214 34.6476 15.29743736170294 205 | FLOCK 4P-10 206 | 1 44808U 19081E 22113.42378545 .00030208 00000+0 11912-2 0 9998 207 | 2 44808 97.4053 178.8353 0013037 61.8757 298.3797 15.25437386133508 208 | FLOCK 4P-11 209 | 1 44809U 19081F 22113.45850965 .00010621 00000+0 41773-3 0 9999 210 | 2 44809 97.4052 178.9171 0012669 62.0825 298.1694 15.25763124133513 211 | FLOCK 4P-4 212 | 1 44811U 19081H 22113.45875722 .00025904 00000+0 98579-3 0 9996 213 | 2 44811 97.4105 179.4186 0011107 52.2649 307.9594 15.26676529133520 214 | FLOCK 4P-3 215 | 1 44812U 19081J 22113.45277219 .00028188 00000+0 10671-2 0 9997 216 | 2 44812 97.4102 179.4174 0010831 53.4078 306.8156 15.26836043133528 217 | FLOCK 4P-1 218 | 1 44814U 19081L 22113.41531360 .00017157 00000+0 68816-3 0 9994 219 | 2 44814 97.4073 178.9576 0011149 56.0770 304.1526 15.25007237133502 220 | FLOCK 4P-7 221 | 1 44816U 19081N 22113.43907516 .00027494 00000+0 10897-2 0 9998 222 | 2 44816 97.4061 179.0476 0011194 56.3501 303.8804 15.25308378133513 223 | FLOCK 4P-5 224 | 1 44818U 19081Q 22113.40979643 .00031769 00000+0 12433-2 0 9995 225 | 2 44818 97.4064 179.1023 0010133 56.7553 303.4655 15.25712013133515 226 | FLOCK 4V-12 227 | 1 46283U 20061M 22113.77515325 .00006609 00000+0 37473-3 0 9993 228 | 2 46283 97.4524 185.2326 0005101 97.6031 262.5780 15.13294709 90220 229 | FLOCK 4V-11 230 | 1 46285U 20061P 22113.44148045 .00013096 00000+0 73981-3 0 9992 231 | 2 46285 97.4506 184.8428 0005251 94.4149 265.7682 15.13226750 90172 232 | FLOCK 4V-14 233 | 1 46286U 20061Q 22113.16595372 .00011533 00000+0 65439-3 0 9994 234 | 2 46286 97.4506 184.5976 0004902 93.9940 266.1851 15.13098869 90130 235 | FLOCK 4V-8 236 | 1 46288U 20061S 22113.42785766 .00010330 00000+0 58361-3 0 9993 237 | 2 46288 97.4496 184.8462 0004326 102.9576 257.2139 15.13284811 90087 238 | FLOCK 4V-6 239 | 1 46289U 20061T 22113.48179912 .00007511 00000+0 42509-3 0 9994 240 | 2 46289 97.4510 184.9471 0003846 109.0240 251.1408 15.13319381 90182 241 | FLOCK 4V-5 242 | 1 46290U 20061U 22113.62424544 .00004084 00000+0 23849-3 0 9994 243 | 2 46290 97.4507 185.0360 0004245 102.6822 257.4884 15.12460663 90207 244 | FLOCK 4V-7 245 | 1 46291U 20061V 22113.45948649 .00010063 00000+0 56776-3 0 9992 246 | 2 46291 97.4512 184.9730 0003975 99.6947 260.4733 15.13341434 90072 247 | FLOCK 4V-10 248 | 1 46296U 20061AA 22113.14188572 .00016354 00000+0 91877-3 0 9994 249 | 2 46296 97.4513 184.8188 0001927 83.7746 276.3705 15.13383485 90101 250 | FLOCK 4V-13 251 | 1 46297U 20061AB 22113.46715246 .00014893 00000+0 83847-3 0 9998 252 | 2 46297 97.4517 185.1460 0002225 83.0582 277.0902 15.13328168 90133 253 | FLOCK 4V-1 254 | 1 46313U 20061AT 22113.44933998 .00012145 00000+0 68711-3 0 9992 255 | 2 46313 97.4521 185.4760 0003683 44.5020 315.6506 15.13196096 90166 256 | FLOCK 4V-4 257 | 1 46314U 20061AU 22113.14841689 .00009547 00000+0 53920-3 0 9993 258 | 2 46314 97.4516 185.1195 0003611 34.6688 325.4778 15.13321710 90164 259 | FLOCK 4V-2 260 | 1 46321U 20061BB 22113.13975479 .00012076 00000+0 66226-3 0 9999 261 | 2 46321 97.4517 185.4388 0003391 42.3578 317.7915 15.14312357 90063 262 | FLOCK 4V-3 263 | 1 46322U 20061BC 22113.81840517 .00006874 00000+0 38997-3 0 9992 264 | 2 46322 97.4525 185.7676 0002961 43.1995 316.9468 15.13268791 90223 265 | FLOCK 4V-16 266 | 1 46527U 20061BD 22113.62116966 .00008052 00000+0 38471-3 0 9999 267 | 2 46527 97.4019 185.7671 0006472 321.1291 38.9477 15.19270217 86556 268 | FLOCK 4V-15 269 | 1 46528U 20061BE 22113.43823673 .00018062 00000+0 85793-3 0 9990 270 | 2 46528 97.3994 186.0546 0002637 21.5704 338.5641 15.19280150 86911 271 | FLOCK 4V-17 272 | 1 46529U 20061BF 22113.53014414 .00007080 00000+0 33763-3 0 9995 273 | 2 46529 97.3966 185.8670 0001943 0.0441 0.0793 15.19394844 86123 274 | FLOCK 4V-18 275 | 1 46597U 20061BG 22113.51031764 .00006798 00000+0 32506-3 0 9994 276 | 2 46597 97.4066 186.1995 0005770 328.9532 31.1361 15.19303937 85486 277 | FLOCK 4V-19 278 | 1 46609U 20061BH 22113.47702289 .00019008 00000+0 90183-3 0 9993 279 | 2 46609 97.3948 185.6756 0001920 322.2219 37.8881 15.19311035 85049 280 | FLOCK 4V-20 281 | 1 46612U 20061BJ 22113.53630479 .00007559 00000+0 36173-3 0 9992 282 | 2 46612 97.4052 186.0593 0005838 332.8728 27.2201 15.19238396 84550 283 | FLOCK 4V-22 284 | 1 46735U 20061BL 22113.42902155 .00010732 00000+0 51055-3 0 9992 285 | 2 46735 97.3925 185.1028 0004569 332.8889 27.2100 15.19344156 83374 286 | FLOCK 4V-23 287 | 1 46737U 20061BM 22113.46355692 .00015292 00000+0 72761-3 0 9999 288 | 2 46737 97.3963 185.6272 0002792 301.0557 59.0403 15.19256255 83315 289 | FLOCK 4V-24 290 | 1 46738U 20061BN 22113.45087032 .00012374 00000+0 59060-3 0 9994 291 | 2 46738 97.4054 186.1078 0005990 348.4379 11.7042 15.19185800 82880 292 | FLOCK 4V-25 293 | 1 46812U 20061BP 22113.48366404 .00006976 00000+0 33249-3 0 9995 294 | 2 46812 97.4034 185.7284 0004001 304.2992 55.7852 15.19411872 82538 295 | FLOCK 4E'-1 296 | 1 46813U 20077A 22113.58104743 .00026536 00000+0 12159-2 0 9990 297 | 2 46813 97.4252 177.0312 0010361 241.5876 118.4315 15.20400002 82139 298 | FLOCK 4E'-7 299 | 1 46814U 20077B 22113.79506253 .00009586 00000+0 43833-3 0 9996 300 | 2 46814 97.4254 177.3694 0010960 235.7448 124.2750 15.20699990 82171 301 | FLOCK 4E'-9 302 | 1 46815U 20077C 22113.82573447 .00004539 00000+0 21813-3 0 9997 303 | 2 46815 97.4226 177.2288 0010639 232.3292 127.6979 15.19274689 82185 304 | FLOCK 4E'-8 305 | 1 46816U 20077D 22113.82219043 .00020430 00000+0 93083-3 0 9993 306 | 2 46816 97.4275 177.7630 0008749 223.6734 136.3809 15.20663814 82193 307 | FLOCK 4E'-5 308 | 1 46817U 20077E 22113.70448709 .00015982 00000+0 73015-3 0 9993 309 | 2 46817 97.4278 177.9240 0012977 220.1647 139.8630 15.20580855 82192 310 | FLOCK 4E'-3 311 | 1 46819U 20077G 22113.80967565 .00007336 00000+0 33653-3 0 9990 312 | 2 46819 97.4252 177.9178 0013774 213.7631 146.2728 15.20646328 82214 313 | FLOCK 4E'-2 314 | 1 46820U 20077H 22113.84597306 .00014198 00000+0 65411-3 0 9999 315 | 2 46820 97.4311 178.3644 0013442 203.5705 156.4916 15.20313177 82222 316 | FLOCK 4E'-4 317 | 1 46821U 20077J 22113.80258359 .00008082 00000+0 37707-3 0 9990 318 | 2 46821 97.4269 178.1539 0013825 201.4766 158.5891 15.20029155 82185 319 | FLOCK 4E'-6 320 | 1 46822U 20077K 22113.58306478 .00006263 00000+0 29724-3 0 9991 321 | 2 46822 97.4232 177.3056 0011240 210.5952 149.4684 15.19556139 82171 322 | FLOCK 4V-26 323 | 1 46825U 20061BQ 22113.19634802 .00010582 00000+0 50168-3 0 9994 324 | 2 46825 97.4041 185.8230 0005169 356.2315 3.8881 15.19462457 82030 325 | FLOCK 4S-22 326 | 1 47452U 21006AR 22113.46642138 .00005984 00000+0 32358-3 0 9993 327 | 2 47452 97.4492 173.8373 0012705 106.8097 253.4530 15.14976366 68944 328 | FLOCK 4S-21 329 | 1 47456U 21006AV 22113.47010109 .00006489 00000+0 35095-3 0 9997 330 | 2 47456 97.4499 173.8804 0012606 106.2483 254.0137 15.14938620 68948 331 | FLOCK 4S-11 332 | 1 47462U 21006BB 22113.45412361 .00005979 00000+0 34142-3 0 9993 333 | 2 47462 97.4452 172.7527 0009754 125.3328 234.8816 15.13050035 68881 334 | FLOCK 4S-33 335 | 1 47469U 21006BJ 22113.16920146 .00007068 00000+0 38137-3 0 9992 336 | 2 47469 97.4444 172.9648 0008633 121.2654 238.9425 15.15023964 68870 337 | FLOCK 4S-12 338 | 1 47471U 21006BL 22113.42928626 .00006124 00000+0 32989-3 0 9996 339 | 2 47471 97.4485 173.4209 0009376 122.8678 237.3458 15.15129769 68919 340 | FLOCK 4S-13 341 | 1 47472U 21006BM 22113.41281353 .00006411 00000+0 34650-3 0 9991 342 | 2 47472 97.4511 173.5879 0009741 121.3884 238.8303 15.14991677 68912 343 | FLOCK 4S-35 344 | 1 47473U 21006BN 22113.41482289 .00016823 00000+0 89351-3 0 9999 345 | 2 47473 97.4499 173.5248 0009145 119.7800 240.4343 15.15344609 68916 346 | FLOCK 4S-8 347 | 1 47474U 21006BP 22113.58526715 .00005765 00000+0 32085-3 0 9996 348 | 2 47474 97.4432 173.1789 0009865 120.4472 239.7736 15.13985651 68938 349 | FLOCK 4S-31 350 | 1 47475U 21006BQ 22113.41044728 .00005600 00000+0 30331-3 0 9998 351 | 2 47475 97.4436 173.1439 0009089 118.7943 241.4203 15.14980287 68914 352 | FLOCK 4S-34 353 | 1 47477U 21006BS 22113.81593148 .00009875 00000+0 53259-3 0 9991 354 | 2 47477 97.4497 173.8871 0009003 117.7513 242.4634 15.14931299 68972 355 | FLOCK 4S-9 356 | 1 47478U 21006BT 22113.48588235 .00005549 00000+0 30050-3 0 9994 357 | 2 47478 97.4441 173.2188 0009610 122.3789 237.8375 15.14984320 68926 358 | FLOCK 4S-32 359 | 1 47479U 21006BU 22113.48569650 .00019382 00000+0 10295-2 0 9993 360 | 2 47479 97.4442 173.2410 0008763 122.2170 237.9914 15.15314599 68923 361 | FLOCK 4S-39 362 | 1 47490U 21006CF 22113.84509195 .00008603 00000+0 46353-3 0 9993 363 | 2 47490 97.4533 174.3423 0011743 108.7716 251.4792 15.14983616 68993 364 | FLOCK 4S-4 365 | 1 47491U 21006CG 22113.51136270 .00010599 00000+0 56979-3 0 9990 366 | 2 47491 97.4414 173.3313 0011652 108.1566 252.0936 15.15002514 68947 367 | FLOCK 4S-17 368 | 1 47492U 21006CH 22113.44165027 .00006255 00000+0 33827-3 0 9996 369 | 2 47492 97.4543 173.9893 0011730 110.7069 249.5422 15.14963131 68934 370 | FLOCK 4S-3 371 | 1 47494U 21006CK 22113.77630176 .00006758 00000+0 37444-3 0 9993 372 | 2 47494 97.4426 173.4983 0011795 106.1480 254.1050 15.14067030 68974 373 | FLOCK 4S-27 374 | 1 47495U 21006CL 22113.17459083 .00008369 00000+0 45018-3 0 9990 375 | 2 47495 97.4422 173.0620 0012124 109.7591 250.4950 15.15045653 68891 376 | FLOCK 4S-18 377 | 1 47496U 21006CM 22113.43656389 .00006278 00000+0 33926-3 0 9990 378 | 2 47496 97.4526 173.9343 0011924 109.5228 250.7293 15.14986072 68936 379 | FLOCK 4S-26 380 | 1 47497U 21006CN 22113.38046679 .00025998 00000+0 13288-2 0 9998 381 | 2 47497 97.4417 173.4107 0011401 110.7491 249.4965 15.16589537 68933 382 | FLOCK 4S-2 383 | 1 47498U 21006CP 22113.82634537 .00005781 00000+0 31104-3 0 9990 384 | 2 47498 97.4431 173.7505 0011610 105.1743 255.0774 15.15174985 68998 385 | FLOCK 4S-40 386 | 1 47499U 21006CQ 22113.43681093 .00012757 00000+0 68689-3 0 9996 387 | 2 47499 97.4527 173.9197 0012244 110.6333 249.6213 15.14894884 68931 388 | FLOCK 4S-19 389 | 1 47500U 21006CR 22113.16125087 .00014156 00000+0 75828-3 0 9993 390 | 2 47500 97.4525 173.6722 0012331 107.9480 252.3098 15.15052580 68899 391 | FLOCK 4S-28 392 | 1 47514U 21006DE 22113.58197820 .00007322 00000+0 39538-3 0 9993 393 | 2 47514 97.4412 173.2561 0009884 116.3209 243.9040 15.14975317 69905 394 | FLOCK 4S-5 395 | 1 47515U 21006DF 22113.44795684 .00012695 00000+0 68206-3 0 9994 396 | 2 47515 97.4406 173.0922 0010083 118.4027 241.8222 15.14993949 68923 397 | FLOCK 4S-16 398 | 1 47516U 21006DG 22113.59022807 .00007155 00000+0 38680-3 0 9999 399 | 2 47516 97.4518 173.9123 0010149 118.7262 241.4991 15.14938385 69908 400 | FLOCK 4S-6 401 | 1 47518U 21006DJ 22113.39365066 .00006811 00000+0 36823-3 0 9991 402 | 2 47518 97.4420 173.0796 0009925 120.0733 240.1485 15.14955322 69889 403 | FLOCK 4S-29 404 | 1 47520U 21006DL 22113.45444858 .00006432 00000+0 34771-3 0 9997 405 | 2 47520 97.4427 173.1882 0009376 118.8518 241.3657 15.14983859 68926 406 | FLOCK 4S-15 407 | 1 47521U 21006DM 22113.52911001 .00004597 00000+0 25436-3 0 9993 408 | 2 47521 97.4530 173.8015 0009863 119.8640 240.3573 15.14300596 69901 409 | FLOCK 4S-37 410 | 1 47522U 21006DN 22113.46293874 .00007035 00000+0 38034-3 0 9996 411 | 2 47522 97.4513 173.6968 0009589 119.3165 240.9026 15.14946421 69890 412 | FLOCK 4S-10 413 | 1 47523U 21006DP 22113.10589490 .00011500 00000+0 61901-3 0 9991 414 | 2 47523 97.4450 172.8666 0009581 124.7314 235.4822 15.14957524 68860 415 | FLOCK 4S-7 416 | 1 47527U 21006DT 22113.40018941 .00009432 00000+0 50842-3 0 9999 417 | 2 47527 97.4418 173.0784 0009722 120.9123 239.3067 15.14956749 69879 418 | FLOCK 4S-14 419 | 1 47528U 21006DU 22113.40496725 .00006242 00000+0 33788-3 0 9996 420 | 2 47528 97.4516 173.6371 0009809 120.3103 239.9101 15.14946844 69875 421 | FLOCK 4S-36 422 | 1 47530U 21006DW 22113.47225192 .00005610 00000+0 30376-3 0 9992 423 | 2 47530 97.4506 173.6447 0009265 119.0575 241.1586 15.14983622 69883 424 | FLOCK 4S-24 425 | 1 47539U 21006DZ 22113.47420126 .00005390 00000+0 29142-3 0 9999 426 | 2 47539 97.4457 173.6265 0012722 106.1072 254.1562 15.15025018 68947 427 | FLOCK 4S-20 428 | 1 47540U 21006EA 22113.47668029 .00005214 00000+0 28232-3 0 9991 429 | 2 47540 97.4511 173.9472 0012369 106.4290 253.8303 15.14989955 68010 430 | FLOCK 4S-25 431 | 1 47542U 21006EC 22113.48364143 .00005931 00000+0 32027-3 0 9997 432 | 2 47542 97.4438 173.4945 0012446 108.1862 252.0726 15.15027101 68017 433 | FLOCK 4S-1 434 | 1 47543U 21006ED 22113.41545661 .00006049 00000+0 32686-3 0 9999 435 | 2 47543 97.4436 173.4233 0012031 105.0775 255.1789 15.15003910 68009 436 | FLOCK 4S-30 437 | 1 47544U 21006EE 22113.39693014 .00006109 00000+0 33066-3 0 9993 438 | 2 47544 97.4431 173.1515 0009435 118.9337 241.2842 15.14964120 69876 439 | FLOCK 4S-23 440 | 1 47545U 21006EF 22113.12921500 .00014043 00000+0 75479-3 0 9999 441 | 2 47545 97.4466 173.3835 0012485 107.0517 253.2084 15.14930978 69864 442 | FLOCK 4S-42 443 | 1 47612U 21006EQ 22113.12453215 .00010287 00000+0 55366-3 0 9996 444 | 2 47612 97.4490 173.3916 0012469 119.0921 241.1562 15.14960660 68889 445 | FLOCK 4S-48 446 | 1 47617U 21006EJ 22113.43339131 .00015473 00000+0 82893-3 0 9992 447 | 2 47617 97.4479 173.8230 0013287 100.0391 260.2342 15.15017647 69917 448 | FLOCK 4S-46 449 | 1 47684U 21006EK 22113.11277906 .00006892 00000+0 37176-3 0 9991 450 | 2 47684 97.4480 173.4603 0009236 129.1914 231.0140 15.15041511 68890 451 | FLOCK 4S-47 452 | 1 47685U 21006EL 22113.57410876 .00011256 00000+0 60362-3 0 9990 453 | 2 47685 97.4460 173.3953 0008234 121.5279 238.6758 15.15104714 69909 454 | FLOCK 4S-44 455 | 1 47686U 21006EM 22113.42849846 .00010258 00000+0 55030-3 0 9995 456 | 2 47686 97.4504 173.7968 0013242 113.4785 246.7840 15.15071572 68935 457 | FLOCK 4S-43 458 | 1 47687U 21006EN 22113.54620032 .00005449 00000+0 29513-3 0 9996 459 | 2 47687 97.4473 173.5068 0008437 110.5701 249.6437 15.14994539 69904 460 | FLOCK 4S-45 461 | 1 47688U 21006EP 22113.42623267 .00006584 00000+0 35435-3 0 9998 462 | 2 47688 97.4487 173.2938 0012491 100.6870 259.5769 15.15107820 69879 463 | FLOCK 4S-41 464 | 1 47698U 21006ER 22113.45312306 .00006475 00000+0 34966-3 0 9998 465 | 2 47698 97.4536 173.8157 0011961 111.1081 249.1431 15.15000279 68922 466 | FLOCK 4X-8 467 | 1 50992U 22002J 22113.40678647 .00021187 00000+0 11667-2 0 9993 468 | 2 50992 97.4906 181.4656 0010560 258.4052 101.5994 15.13999933 15097 469 | FLOCK 4X-44 470 | 1 50994U 22002L 22113.40804747 .00007966 00000+0 44291-3 0 9996 471 | 2 50994 97.4931 181.4931 0010762 256.4265 103.5768 15.13900898 15094 472 | FLOCK 4X-2 473 | 1 50996U 22002N 22113.14365155 .00017191 00000+0 94920-3 0 9999 474 | 2 50996 97.4919 181.2201 0010733 258.1913 101.8115 15.13946757 15054 475 | FLOCK 4X-20 476 | 1 50997U 22002P 22113.34012133 .00006912 00000+0 38453-3 0 9995 477 | 2 50997 97.4920 181.4155 0010600 258.5022 101.5020 15.13930069 15089 478 | FLOCK 4X-30 479 | 1 51000U 22002S 22113.47156487 .00006694 00000+0 37252-3 0 9999 480 | 2 51000 97.4863 181.4699 0010774 247.9103 112.0985 15.13928751 15103 481 | FLOCK 4X-22 482 | 1 51003U 22002V 22113.86649003 .00006738 00000+0 37494-3 0 9993 483 | 2 51003 97.4904 181.9178 0010652 258.3044 101.6993 15.13931979 15169 484 | FLOCK 4X-5 485 | 1 51004U 22002W 22113.46904044 .00006660 00000+0 37044-3 0 9994 486 | 2 51004 97.4879 181.4890 0011106 250.3238 109.6795 15.13945423 15100 487 | FLOCK 4X-41 488 | 1 51005U 22002X 22113.13746208 .00007550 00000+0 41853-3 0 9997 489 | 2 51005 97.4854 181.1410 0010821 248.1857 111.8224 15.14023332 15057 490 | FLOCK 4X-3 491 | 1 51006U 22002Y 22113.46773902 .00011573 00000+0 63833-3 0 9997 492 | 2 51006 97.4922 181.5489 0011392 256.4235 103.5727 15.14071649 15106 493 | FLOCK 4X-18 494 | 1 51007U 22002Z 22113.13703956 .00017014 00000+0 93752-3 0 9993 495 | 2 51007 97.4881 181.1686 0011467 252.3236 107.6744 15.14014933 15056 496 | FLOCK 4X-37 497 | 1 51009U 22002AB 22113.46647195 .00013392 00000+0 73766-3 0 9991 498 | 2 51009 97.4926 181.5607 0011484 254.8190 105.1772 15.14082076 15103 499 | FLOCK 4X-15 500 | 1 51010U 22002AC 22113.19646746 .00011396 00000+0 62829-3 0 9994 501 | 2 51010 97.4937 181.3143 0011633 255.2310 104.7633 15.14088881 15061 502 | FLOCK 4X-29 503 | 1 51011U 22002AD 22113.46381020 .00017452 00000+0 96097-3 0 9999 504 | 2 51011 97.4870 181.4877 0011369 249.3735 110.6278 15.14035502 15102 505 | FLOCK 4X-27 506 | 1 51012U 22002AE 22113.46311768 .00018493 00000+0 10175-2 0 9990 507 | 2 51012 97.4914 181.5487 0011538 258.3207 101.6730 15.14049220 15107 508 | FLOCK 4X-12 509 | 1 51015U 22002AH 22113.79066658 .00011144 00000+0 61296-3 0 9991 510 | 2 51015 97.4918 181.8795 0011818 250.4184 109.5772 15.14175132 15157 511 | FLOCK 4X-25 512 | 1 51016U 22002AJ 22113.52880583 .00018591 00000+0 10218-2 0 9994 513 | 2 51016 97.4902 181.5985 0011292 260.1650 99.8307 15.14089306 15119 514 | FLOCK 4X-1 515 | 1 51017U 22002AK 22113.12883813 .00013189 00000+0 72549-3 0 9994 516 | 2 51017 97.4892 181.1827 0011766 253.0034 106.9908 15.14131345 15053 517 | FLOCK 4X-42 518 | 1 51018U 22002AL 22113.52676836 .00006902 00000+0 38165-3 0 9995 519 | 2 51018 97.4868 181.5507 0011360 245.8920 114.1123 15.14141457 15111 520 | FLOCK 4X-16 521 | 1 51020U 22002AN 22113.45785287 .00017628 00000+0 96760-3 0 9993 522 | 2 51020 97.4872 181.4917 0011545 247.5899 112.4110 15.14144648 15103 523 | FLOCK 4X-38 524 | 1 51023U 22002AR 22113.45717115 .00017928 00000+0 98439-3 0 9999 525 | 2 51023 97.4923 181.5646 0011791 254.4292 105.5638 15.14126398 15100 526 | FLOCK 4X-32 527 | 1 51024U 22002AS 22113.45565242 .00018118 00000+0 99408-3 0 9991 528 | 2 51024 97.4927 181.5703 0011704 250.6399 109.3567 15.14152456 15106 529 | FLOCK 4X-26 530 | 1 51026U 22002AU 22113.44861322 .00006462 00000+0 35777-3 0 9998 531 | 2 51026 97.4888 181.5228 0011604 251.7088 108.2881 15.14115861 15108 532 | FLOCK 4X-43 533 | 1 51027U 22002AV 22113.44912445 .00006087 00000+0 33752-3 0 9996 534 | 2 51027 97.4882 181.5169 0011587 249.0169 110.9823 15.14090024 15106 535 | FLOCK 4X-13 536 | 1 51028U 22002AW 22113.44718336 .00020249 00000+0 11097-2 0 9993 537 | 2 51028 97.4879 181.5177 0011596 247.4101 112.5904 15.14170493 15107 538 | FLOCK 4X-39 539 | 1 51029U 22002AX 22113.51714182 .00006699 00000+0 37000-3 0 9993 540 | 2 51029 97.4880 181.5787 0011730 245.7675 114.2331 15.14188676 15110 541 | FLOCK 4X-17 542 | 1 51035U 22002BD 22113.16855886 .00006036 00000+0 33689-3 0 9992 543 | 2 51035 97.4914 181.1780 0007586 255.8713 104.1676 15.13891367 15053 544 | FLOCK 4X-10 545 | 1 51037U 22002BF 22113.47585815 .00008725 00000+0 48437-3 0 9994 546 | 2 51037 97.4928 181.5513 0010748 254.6066 105.3979 15.13927519 15104 547 | FLOCK 4X-36 548 | 1 51039U 22002BH 22113.61825527 .00018406 00000+0 10148-2 0 9993 549 | 2 51039 97.4925 181.6681 0008904 256.9749 103.0488 15.14000830 15125 550 | FLOCK 4X-21 551 | 1 51040U 22002BJ 22113.49147633 .00005575 00000+0 31110-3 0 9992 552 | 2 51040 97.4902 181.4992 0007915 251.5067 108.5305 15.13929171 15103 553 | FLOCK 4X-31 554 | 1 51041U 22002BK 22113.42718697 .00005560 00000+0 31010-3 0 9993 555 | 2 51041 97.4870 181.3830 0008071 245.7849 114.2539 15.13947632 15098 556 | FLOCK 4X-34 557 | 1 51042U 22002BL 22113.36101513 .00006905 00000+0 38420-3 0 9996 558 | 2 51042 97.4934 181.3946 0008167 257.7529 102.2788 15.13945325 15081 559 | FLOCK 4X-6 560 | 1 51043U 22002BM 22113.16355573 .00016325 00000+0 90296-3 0 9998 561 | 2 51043 97.4906 181.1765 0007989 254.0559 105.9792 15.13917376 15059 562 | FLOCK 4X-28 563 | 1 51045U 22002BP 22113.49000499 .00005865 00000+0 32691-3 0 9992 564 | 2 51045 97.4880 181.4639 0008574 242.9203 117.1154 15.13940689 15104 565 | FLOCK 4X-19 566 | 1 51046U 22002BQ 22113.81955419 .00005847 00000+0 32577-3 0 9993 567 | 2 51046 97.4916 181.8444 0008207 254.1143 105.9185 15.13958889 15159 568 | FLOCK 4X-9 569 | 1 51047U 22002BR 22113.42119353 .00017029 00000+0 93944-3 0 9990 570 | 2 51047 97.4920 181.4647 0008724 258.0682 101.9571 15.13995978 15099 571 | FLOCK 4X-35 572 | 1 51048U 22002BS 22113.42306915 .00008960 00000+0 49667-3 0 9990 573 | 2 51048 97.4857 181.3739 0008277 249.0535 110.9811 15.13992401 15095 574 | FLOCK 4X-11 575 | 1 51049U 22002BT 22113.42943158 .00008780 00000+0 48733-3 0 9990 576 | 2 51049 97.4868 181.3798 0007968 253.8574 106.1781 15.13952519 15094 577 | FLOCK 4X-40 578 | 1 51052U 22002BW 22113.43337213 .00009152 00000+0 50802-3 0 9998 579 | 2 51052 97.4865 181.3721 0007566 251.9833 108.0574 15.13938115 15099 580 | FLOCK 4X-24 581 | 1 51056U 22002CA 22113.43455891 .00016930 00000+0 93555-3 0 9999 582 | 2 51056 97.4879 181.3821 0007642 247.4275 112.6148 15.13946109 15094 583 | FLOCK 4X-4 584 | 1 51064U 22002CJ 22113.41934524 .00017343 00000+0 95651-3 0 9999 585 | 2 51064 97.4873 181.3999 0008674 245.5384 114.4943 15.14002089 15093 586 | FLOCK 4X-7 587 | 1 51065U 22002CK 22113.48831978 .00007285 00000+0 40582-3 0 9993 588 | 2 51065 97.4907 181.5111 0008289 253.6232 106.4089 15.13883127 15107 589 | FLOCK 4X-14 590 | 1 51066U 22002CL 22113.81703340 .00006258 00000+0 34841-3 0 9992 591 | 2 51066 97.4939 181.8765 0008985 257.2434 102.7794 15.13954058 15157 -------------------------------------------------------------------------------- /tle/Iridium.tle: -------------------------------------------------------------------------------- 1 | IRIDIUM 7 [-] 2 | 1 24793U 97020B 22257.50747357 .00000297 00000+0 98432-4 0 9996 3 | 2 24793 86.3944 238.2679 0002181 91.8042 268.3404 14.34536528327510 4 | IRIDIUM 5 [-] 5 | 1 24795U 97020D 22257.29688535 .00002387 00000+0 25367-3 0 9994 6 | 2 24795 86.4034 181.9761 0137959 110.3440 251.2667 14.81621346335853 7 | IRIDIUM 4 [-] 8 | 1 24796U 97020E 22257.43232856 .00000311 00000+0 10131-3 0 9990 9 | 2 24796 86.3944 235.8465 0002024 85.5318 274.6110 14.35441007327984 10 | IRIDIUM 914 [-] 11 | 1 24836U 97030A 22257.69093228 .00000340 00000+0 10336-3 0 9991 12 | 2 24836 86.3899 247.2416 0001026 32.4041 327.7220 14.39164889324394 13 | IRIDIUM 16 [-] 14 | 1 24841U 97030F 22257.56636806 .00000295 00000+0 92532-4 0 9996 15 | 2 24841 86.3980 267.6091 0004349 111.5104 248.6557 14.37114757322362 16 | IRIDIUM 911 [-] 17 | 1 24842U 97030G 22257.52293214 .00000384 00000+0 10318-3 0 9995 18 | 2 24842 86.4407 267.2451 0013492 200.3194 159.7473 14.45454071329573 19 | IRIDIUM 17 [-] 20 | 1 24870U 97034B 22257.54767725 .00000270 00000+0 86026-4 0 9999 21 | 2 24870 86.3929 297.3964 0003936 81.1943 278.9699 14.36173472319031 22 | IRIDIUM 920 [-] 23 | 1 24871U 97034C 22257.54398105 .00000278 00000+0 81986-4 0 9990 24 | 2 24871 86.3919 278.9799 0012659 149.4972 210.6965 14.39885467321651 25 | IRIDIUM 921 [-] 26 | 1 24873U 97034E 22257.54447858 .00006588 00000+0 33787-3 0 9997 27 | 2 24873 86.3898 238.3245 0009318 129.5426 230.6640 15.16328586377390 28 | IRIDIUM 26 [-] 29 | 1 24903U 97043A 22257.26304137 .00000332 00000+0 10851-3 0 9990 30 | 2 24903 86.4034 172.8983 0002108 88.7057 271.4381 14.35618514312212 31 | IRIDIUM 22 [-] 32 | 1 24907U 97043E 22257.32043078 .00000317 00000+0 10545-3 0 9998 33 | 2 24907 86.4039 175.6086 0002089 82.4251 277.7183 14.34548349312363 34 | DUMMY MASS 1 [-] 35 | 1 24925U 97048A 22257.49497703 .00000360 00000+0 38469-4 0 9993 36 | 2 24925 86.3326 240.1418 0009282 105.3298 254.8952 14.85418384355623 37 | DUMMY MASS 2 [-] 38 | 1 24926U 97048B 22257.49635076 .00000354 00000+0 38077-4 0 9999 39 | 2 24926 86.3322 241.5737 0009938 113.7909 246.4358 14.85184078355424 40 | IRIDIUM 29 [-] 41 | 1 24944U 97051A 22257.38756351 .00000337 00000+0 11145-3 0 9998 42 | 2 24944 86.4012 205.6595 0001967 79.4044 280.7373 14.34985784308823 43 | IRIDIUM 33 [-] 44 | 1 24946U 97051C 22257.41851233 .00000248 00000+0 81854-4 0 9995 45 | 2 24946 86.3925 204.9937 0009807 114.6229 245.5989 14.33857358308430 46 | IRIDIUM 28 [-] 47 | 1 24948U 97051E 22257.38961199 .00000342 00000+0 11131-3 0 9991 48 | 2 24948 86.4018 203.7558 0002420 92.1410 268.0064 14.35918615309305 49 | IRIDIUM 36 [-] 50 | 1 24967U 97056C 22257.58816905 .00000344 00000+0 11184-3 0 9993 51 | 2 24967 86.3956 235.3059 0002362 82.4429 277.7036 14.35999607307696 52 | IRIDIUM 39 [-] 53 | 1 25042U 97069D 22257.54410476 .00000429 00000+0 11278-3 0 9999 54 | 2 25042 86.3868 281.7814 0020919 65.3491 294.9888 14.46716561303313 55 | IRIDIUM 38 [-] 56 | 1 25043U 97069E 22257.52381808 .00000257 00000+0 81062-4 0 9992 57 | 2 25043 86.3927 295.9939 0003234 116.2427 243.9103 14.36432797301374 58 | IRIDIUM 42 [-] 59 | 1 25077U 97077A 22257.52417187 .00000259 00000+0 84346-4 0 9998 60 | 2 25077 86.3940 300.9555 0002493 105.0086 255.1386 14.34802822296784 61 | IRIDIUM 44 [-] 62 | 1 25078U 97077B 22257.57121664 .00000284 00000+0 86480-4 0 9995 63 | 2 25078 86.3910 285.5417 0004182 92.6116 267.5561 14.38544327298915 64 | IRIDIUM 45 [-] 65 | 1 25104U 97082A 22257.82338833 .00008250 00000+0 60934-3 0 9993 66 | 2 25104 86.3848 159.1421 0203896 23.1984 337.8283 14.87870617301321 67 | IRIDIUM 24 [-] 68 | 1 25105U 97082B 22257.39650993 .00000384 00000+0 11483-3 0 9995 69 | 2 25105 86.3951 146.4215 0012275 150.2047 209.9853 14.40282799298221 70 | IRIDIUM 51 [-] 71 | 1 25262U 98018A 22257.25898019 .00009662 00000+0 63895-3 0 9999 72 | 2 25262 86.4035 170.7712 0191548 353.7203 6.1623 14.93339179295127 73 | IRIDIUM 57 [-] 74 | 1 25273U 98019B 22257.36167309 .00000310 00000+0 10275-3 0 9990 75 | 2 25273 86.4011 206.6606 0002162 84.3084 275.8358 14.34576936280382 76 | IRIDIUM 63 [-] 77 | 1 25286U 98021B 22257.39958122 .00000293 00000+0 96111-4 0 9995 78 | 2 25286 86.4018 143.6972 0001832 80.3925 279.7478 14.35040401279391 79 | IRIDIUM 69 [-] 80 | 1 25319U 98026A 22257.43650765 .00000369 00000+0 11302-3 0 9996 81 | 2 25319 86.4048 159.8668 0004243 90.8051 269.3634 14.39108594278195 82 | IRIDIUM 71 [-] 83 | 1 25320U 98026B 22257.24166774 .00000352 00000+0 10662-3 0 9999 84 | 2 25320 86.4025 154.9455 0003157 125.4529 234.6965 14.39480883278668 85 | IRIDIUM 73 [-] 86 | 1 25344U 98032C 22257.56926233 .00000341 00000+0 83767-4 0 9996 87 | 2 25344 86.4390 113.2810 0002491 99.1492 260.9994 14.49499176284654 88 | IRIDIUM 82 [-] 89 | 1 25467U 98051A 22257.86511545 .00010273 00000+0 71101-3 0 9995 90 | 2 25467 86.4000 224.4158 0169572 28.7898 332.2558 14.94509692272268 91 | IRIDIUM 2 [-] 92 | 1 25527U 98066A 22257.26188860 .00008939 00000+0 36847-3 0 9996 93 | 2 25527 85.5302 337.8273 0006250 215.0798 145.0035 15.23910979309022 -------------------------------------------------------------------------------- /tle/SkySat.tle: -------------------------------------------------------------------------------- 1 | SKYSAT-A 2 | 1 39418U 13066C 22113.46563840 .00001924 00000+0 16015-3 0 9997 3 | 2 39418 97.5541 186.9036 0021253 89.9298 270.4361 14.99651451460412 4 | SKYSAT-B 5 | 1 40072U 14037D 22113.46407846 .00000716 00000+0 98471-4 0 9992 6 | 2 40072 98.4176 339.2273 0007375 125.4428 234.7472 14.81636626421107 7 | SKYSAT-C1 8 | 1 41601U 16040C 22113.43793423 .00013369 00000+0 34076-3 0 9991 9 | 2 41601 97.1569 190.1908 0000614 171.3745 188.7512 15.39653121325545 10 | SKYSAT-C4 11 | 1 41771U 16058B 22113.41337520 .00014562 00000+0 33776-3 0 9990 12 | 2 41771 97.1237 180.2924 0001834 109.2280 250.9167 15.42493377312499 13 | SKYSAT-C5 14 | 1 41772U 16058C 22113.70303133 .00006304 00000+0 24776-3 0 9991 15 | 2 41772 97.2700 178.7407 0001856 90.2784 269.8667 15.26018754311426 16 | SKYSAT-C2 17 | 1 41773U 16058D 22113.44318909 .00012486 00000+0 31996-3 0 9990 18 | 2 41773 97.1246 180.7261 0002099 35.0330 325.1054 15.39501994312521 19 | SKYSAT-C3 20 | 1 41774U 16058E 22113.45417075 .00015435 00000+0 35413-3 0 9991 21 | 2 41774 97.1237 180.6665 0001835 107.4513 252.6935 15.42807245312516 22 | SKYSAT-C11 23 | 1 42987U 17068A 22113.56225163 .00013372 00000+0 36057-3 0 9990 24 | 2 42987 97.2826 232.8649 0002039 115.2364 244.9092 15.37923883249954 25 | SKYSAT-C10 26 | 1 42988U 17068B 22113.60342625 .00014188 00000+0 37923-3 0 9998 27 | 2 42988 97.2875 233.0881 0002971 56.4545 303.6984 15.38178224249980 28 | SKYSAT-C9 29 | 1 42989U 17068C 22113.58975546 .00013705 00000+0 36538-3 0 9995 30 | 2 42989 97.2815 232.2793 0000663 60.0438 300.0873 15.38269554250174 31 | SKYSAT-C8 32 | 1 42990U 17068D 22113.57063501 .00013090 00000+0 35371-3 0 9996 33 | 2 42990 97.2885 233.4971 0002890 256.7792 103.3130 15.37861112250004 34 | SKYSAT-C7 35 | 1 42991U 17068E 22113.55566965 .00013826 00000+0 37056-3 0 9996 36 | 2 42991 97.2888 233.5351 0001947 91.1383 269.0086 15.38102142250002 37 | SKYSAT-C6 38 | 1 42992U 17068F 22113.61298510 .00014084 00000+0 37656-3 0 9995 39 | 2 42992 97.2907 233.9556 0002062 81.4826 278.6653 15.38173264250020 40 | SKYSAT-C12 41 | 1 43797U 18099AR 22113.43141161 .00012886 00000+0 32814-3 0 9992 42 | 2 43797 97.1312 181.8359 0000565 15.0731 345.0532 15.39689060189022 43 | SKYSAT-C13 44 | 1 43802U 18099AW 22113.46228540 .00011303 00000+0 31527-3 0 9992 45 | 2 43802 97.1597 179.9806 0006042 119.1728 241.0122 15.36890958188517 46 | SKYSAT-C14 47 | 1 45788U 20038BL 22113.66468072 .00045465 00000+0 53908-3 0 9994 48 | 2 45788 52.9944 233.7993 0010188 13.4329 346.6952 15.60724403106108 49 | SKYSAT-C16 50 | 1 45789U 20038BM 22113.80847472 .00049600 00000+0 57089-3 0 9995 51 | 2 45789 52.9954 232.6052 0004197 59.5451 300.5975 15.61509655106121 52 | SKYSAT-C15 53 | 1 45790U 20038BN 22113.50512184 .00036864 00000+0 43366-3 0 9994 54 | 2 45790 52.9941 234.1890 0000652 322.9968 37.0999 15.61084192106074 55 | SKYSAT-C17 56 | 1 46179U 20057BQ 22113.19521717 .00037110 00000+0 45533-3 0 9997 57 | 2 46179 52.9954 328.6531 0004371 286.4699 73.5832 15.59970195 96622 58 | SKYSAT-C18 59 | 1 46180U 20057BR 22113.57798508 .00038249 00000+0 47960-3 0 9995 60 | 2 46180 52.9907 327.1812 0007458 22.1317 338.0015 15.59363882 95382 61 | SKYSAT-C19 62 | 1 46235U 20057BS 22113.71457777 .00036824 00000+0 47521-3 0 9992 63 | 2 46235 52.9914 324.5483 0005480 328.3163 31.7518 15.58633863 95438 -------------------------------------------------------------------------------- /tle/Telesat.tle: -------------------------------------------------------------------------------- 1 | AMSC 1 2 | 1 23553C 95019A 22257.25000000 .00000016 00000+0 00000+0 0 2579 3 | 2 23553 12.0277 25.6165 0005071 151.8800 158.2266 1.00272926 10 4 | ANIK F1 5 | 1 26624C 00076A 22257.00000000 .00000028 00000+0 00000+0 0 2576 6 | 2 26624 1.6548 91.5474 0009055 50.8634 101.3439 1.00271100 15 7 | NIMIQ 2 8 | 1 27632C 02062A 22257.25000000 .00000028 00000+0 00000+0 0 2570 9 | 2 27632 6.2102 69.8185 0007680 123.1500 141.0552 1.00271830 14 10 | ANIK F2 11 | 1 28378C 04027A 22256.16666667 .00000041 00000+0 00000+0 0 2561 12 | 2 28378 0.0439 102.7247 0003660 53.4744 144.8357 1.00272842 10 13 | XTAR-EUR 14 | 1 28542C 05005A 22257.25000000 .00000241 00000+0 00000+0 0 2578 15 | 2 28542 0.7975 93.6247 0003132 88.3713 290.2081 1.00275315 18 16 | ANIK F1R 17 | 1 28868C 05036A 22257.20833333 .00000016 00000+0 00000+0 0 2571 18 | 2 28868 0.7802 92.8719 0002298 51.3754 176.5999 1.00272654 17 19 | ANIK F3 20 | 1 31102C 07009A 22257.20833333 -.00002880 00000+0 00000+0 0 2570 21 | 2 31102 0.0003 183.9833 0001673 346.8785 138.5793 1.00271811 13 22 | NIMIQ 4 23 | 1 33373C 08044A 22257.20833333 -.00000149 00000+0 00000+0 0 2578 24 | 2 33373 0.0000 169.1524 0002048 6.9854 170.0314 1.00272790 10 25 | TELSTAR 11N 26 | 1 34111C 09009A 22253.83333333 -.00000169 00000+0 00000+0 0 2539 27 | 2 34111 0.0048 138.6035 0002445 18.2385 95.4203 1.00272222 14 28 | TERRESTAR-1 29 | 1 35496C 09035A 22257.25000000 .00000040 00000+0 00000+0 0 2578 30 | 2 35496 3.3227 40.2379 0002772 131.5386 160.4108 1.00272699 13 31 | NIMIQ 5 32 | 1 35873C 09050A 22257.20833333 -.00000207 00000+0 00000+0 0 2578 33 | 2 35873 0.0297 262.7036 0003312 290.8937 161.8725 1.00271726 16 34 | SKYTERRA 1 35 | 1 37218C 10061A 22257.25000000 -.00000023 00000+0 00000+0 0 2575 36 | 2 37218 2.5502 35.4152 0002989 195.1881 111.2972 1.00272402 10 37 | TELSTAR 14R 38 | 1 37602C 11021A 22257.12500000 -.00000186 00000+0 00000+0 0 2570 39 | 2 37602 0.0085 116.3539 0000386 359.2345 219.4879 1.00272200 14 40 | NIMIQ 6 41 | 1 38342C 12026A 22257.25000000 -.00003671 00000+0 00000+0 0 2579 42 | 2 38342 0.0358 289.0452 0002988 251.2922 171.7635 1.00272222 17 43 | ANIK G1 44 | 1 39127C 13014A 22257.20833333 -.00003175 00000+0 00000+0 0 2576 45 | 2 39127 0.0080 217.5170 0002016 357.1070 106.2336 1.00272708 16 46 | TELSTAR 12V 47 | 1 41036C 15068A 22257.25000000 -.00000027 00000+0 00000+0 0 2574 48 | 2 41036 0.0000 166.9108 0002140 10.1876 251.0994 1.00272359 17 49 | VIASAT-2 50 | 1 42740C 17029A 22257.25000000 -.00000172 00000+0 00000+0 0 2577 51 | 2 42740 0.0290 256.7824 0000385 345.0367 131.4744 1.00271599 17 52 | LEO VANTAGE 1 53 | 1 43113C 18004C 22257.00000000 -.00001875 00000+0 -14919-3 0 2579 54 | 2 43113 99.2432 346.4864 0399532 285.3248 146.5340 14.54100473 14 55 | TELSTAR 19V 56 | 1 43562C 18059A 22257.25000000 -.00000190 00000+0 00000+0 0 2574 57 | 2 43562 0.0152 195.7213 0003427 328.7462 215.7312 1.00272884 10 58 | TELSTAR 18V (APSTAR 5C) 59 | 1 43611C 18069A 22257.20833333 -.00000112 00000+0 00000+0 0 2572 60 | 2 43611 0.0014 193.1345 0002089 344.0196 29.0090 1.00271959 19 61 | AMOS-17 62 | 1 44479C 19050A 22257.20833333 .00000199 00000+0 00000+0 0 2570 63 | 2 44479 0.0037 142.2661 0002434 44.1689 258.7338 1.00272398 16 -------------------------------------------------------------------------------- /tools/requirements.txt: -------------------------------------------------------------------------------- 1 | setuptools 2 | xlrd 3 | argparse 4 | numpy 5 | requests 6 | skyfield 7 | sgp4 8 | paramiko --------------------------------------------------------------------------------