├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── analytics ├── Dockerfile ├── Makefile ├── README.md ├── data │ ├── importcsv_asn.sql │ └── importcsv_proxy.sql └── main.c ├── animation-zoom.gif ├── animation.gif ├── architecture.png ├── dpdk-latency ├── Makefile ├── README.md ├── bind-interfaces.sh ├── build.sh └── main.c └── frontend ├── .babelrc ├── Dockerfile ├── README.md ├── app.js ├── bin └── www ├── package.json ├── public ├── images │ └── flags │ │ ├── .DS_Store │ │ ├── ad_64.png │ │ ├── ae_64.png │ │ ├── af_64.png │ │ ├── ag_64.png │ │ ├── ai_64.png │ │ ├── al_64.png │ │ ├── am_64.png │ │ ├── ao_64.png │ │ ├── aq_64.png │ │ ├── ar_64.png │ │ ├── as_64.png │ │ ├── at_64.png │ │ ├── au_64.png │ │ ├── aw_64.png │ │ ├── ax_64.png │ │ ├── az_64.png │ │ ├── ba_64.png │ │ ├── bb_64.png │ │ ├── bd_64.png │ │ ├── be_64.png │ │ ├── bf_64.png │ │ ├── bg_64.png │ │ ├── bh_64.png │ │ ├── bi_64.png │ │ ├── bj_64.png │ │ ├── bl_64.png │ │ ├── bm_64.png │ │ ├── bn_64.png │ │ ├── bo_64.png │ │ ├── bq_64.png │ │ ├── br_64.png │ │ ├── bs_64.png │ │ ├── bt_64.png │ │ ├── bv_64.png │ │ ├── bw_64.png │ │ ├── by_64.png │ │ ├── bz_64.png │ │ ├── ca_64.png │ │ ├── cc_64.png │ │ ├── cd_64.png │ │ ├── cf_64.png │ │ ├── cg_64.png │ │ ├── ch_64.png │ │ ├── ci_64.png │ │ ├── ck_64.png │ │ ├── cl_64.png │ │ ├── cm_64.png │ │ ├── cn_64.png │ │ ├── co_64.png │ │ ├── cr_64.png │ │ ├── cu_64.png │ │ ├── cv_64.png │ │ ├── cw_64.png │ │ ├── cx_64.png │ │ ├── cy_64.png │ │ ├── cz_64.png │ │ ├── de_64.png │ │ ├── dj_64.png │ │ ├── dk_64.png │ │ ├── dm_64.png │ │ ├── do_64.png │ │ ├── dz_64.png │ │ ├── ec_64.png │ │ ├── ee_64.png │ │ ├── eg_64.png │ │ ├── eh_64.png │ │ ├── er_64.png │ │ ├── es_64.png │ │ ├── et_64.png │ │ ├── fi_64.png │ │ ├── fj_64.png │ │ ├── fk_64.png │ │ ├── fm_64.png │ │ ├── fo_64.png │ │ ├── fr_64.png │ │ ├── ga_64.png │ │ ├── gb_64.png │ │ ├── gd_64.png │ │ ├── ge_64.png │ │ ├── gf_64.png │ │ ├── gg_64.png │ │ ├── gh_64.png │ │ ├── gi_64.png │ │ ├── gl_64.png │ │ ├── gm_64.png │ │ ├── gn_64.png │ │ ├── gp_64.png │ │ ├── gq_64.png │ │ ├── gr_64.png │ │ ├── gs_64.png │ │ ├── gt_64.png │ │ ├── gu_64.png │ │ ├── gw_64.png │ │ ├── gy_64.png │ │ ├── hk_64.png │ │ ├── hm_64.png │ │ ├── hn_64.png │ │ ├── hr_64.png │ │ ├── ht_64.png │ │ ├── hu_64.png │ │ ├── id_64.png │ │ ├── ie_64.png │ │ ├── il_64.png │ │ ├── im_64.png │ │ ├── in_64.png │ │ ├── io_64.png │ │ ├── iq_64.png │ │ ├── ir_64.png │ │ ├── is_64.png │ │ ├── it_64.png │ │ ├── je_64.png │ │ ├── jm_64.png │ │ ├── jo_64.png │ │ ├── jp_64.png │ │ ├── ke_64.png │ │ ├── kg_64.png │ │ ├── kh_64.png │ │ ├── ki_64.png │ │ ├── km_64.png │ │ ├── kn_64.png │ │ ├── kp_64.png │ │ ├── kr_64.png │ │ ├── kw_64.png │ │ ├── ky_64.png │ │ ├── kz_64.png │ │ ├── la_64.png │ │ ├── lb_64.png │ │ ├── lc_64.png │ │ ├── li_64.png │ │ ├── lk_64.png │ │ ├── lr_64.png │ │ ├── ls_64.png │ │ ├── lt_64.png │ │ ├── lu_64.png │ │ ├── lv_64.png │ │ ├── ly_64.png │ │ ├── ma_64.png │ │ ├── mc_64.png │ │ ├── md_64.png │ │ ├── me_64.png │ │ ├── mf_64.png │ │ ├── mg_64.png │ │ ├── mh_64.png │ │ ├── mk_64.png │ │ ├── ml_64.png │ │ ├── mm_64.png │ │ ├── mn_64.png │ │ ├── mo_64.png │ │ ├── mp_64.png │ │ ├── mq_64.png │ │ ├── mr_64.png │ │ ├── ms_64.png │ │ ├── mt_64.png │ │ ├── mu_64.png │ │ ├── mv_64.png │ │ ├── mw_64.png │ │ ├── mx_64.png │ │ ├── my_64.png │ │ ├── mz_64.png │ │ ├── na_64.png │ │ ├── nc_64.png │ │ ├── ne_64.png │ │ ├── nf_64.png │ │ ├── ng_64.png │ │ ├── ni_64.png │ │ ├── nl_64.png │ │ ├── no_64.png │ │ ├── np_64.png │ │ ├── nr_64.png │ │ ├── nu_64.png │ │ ├── nz_64.png │ │ ├── om_64.png │ │ ├── pa_64.png │ │ ├── pe_64.png │ │ ├── pf_64.png │ │ ├── pg_64.png │ │ ├── ph_64.png │ │ ├── pk_64.png │ │ ├── pl_64.png │ │ ├── pm_64.png │ │ ├── pn_64.png │ │ ├── pr_64.png │ │ ├── ps_64.png │ │ ├── pt_64.png │ │ ├── pw_64.png │ │ ├── py_64.png │ │ ├── qa_64.png │ │ ├── re_64.png │ │ ├── ro_64.png │ │ ├── rs_64.png │ │ ├── ru_64.png │ │ ├── rw_64.png │ │ ├── sa_64.png │ │ ├── sb_64.png │ │ ├── sc_64.png │ │ ├── sd_64.png │ │ ├── se_64.png │ │ ├── sg_64.png │ │ ├── sh_64.png │ │ ├── si_64.png │ │ ├── sj_64.png │ │ ├── sk_64.png │ │ ├── sl_64.png │ │ ├── sm_64.png │ │ ├── sn_64.png │ │ ├── so_64.png │ │ ├── sr_64.png │ │ ├── ss_64.png │ │ ├── st_64.png │ │ ├── sv_64.png │ │ ├── sx_64.png │ │ ├── sy_64.png │ │ ├── sz_64.png │ │ ├── tc_64.png │ │ ├── td_64.png │ │ ├── tf_64.png │ │ ├── tg_64.png │ │ ├── th_64.png │ │ ├── tj_64.png │ │ ├── tk_64.png │ │ ├── tl_64.png │ │ ├── tm_64.png │ │ ├── tn_64.png │ │ ├── to_64.png │ │ ├── tr_64.png │ │ ├── tt_64.png │ │ ├── tv_64.png │ │ ├── tw_64.png │ │ ├── tz_64.png │ │ ├── ua_64.png │ │ ├── ug_64.png │ │ ├── um_64.png │ │ ├── us_64.png │ │ ├── uy_64.png │ │ ├── uz_64.png │ │ ├── va_64.png │ │ ├── vc_64.png │ │ ├── ve_64.png │ │ ├── vg_64.png │ │ ├── vi_64.png │ │ ├── vn_64.png │ │ ├── vu_64.png │ │ ├── wf_64.png │ │ ├── ws_64.png │ │ ├── ye_64.png │ │ ├── yt_64.png │ │ ├── za_64.png │ │ ├── zm_64.png │ │ └── zw_64.png ├── javascripts │ ├── stats.js │ └── viewer.js └── stylesheets │ └── style.css ├── routes ├── index.js └── stats.js ├── src ├── map-utils.js └── react.js └── views ├── error.jade ├── index.jade ├── layout.jade ├── logo.html └── stats.jade /.gitignore: -------------------------------------------------------------------------------- 1 | dpdk-latency/build/* 2 | node_modules/ 3 | npm-debug.log 4 | ip2location-asn.csv 5 | ip2location-proxy.csv 6 | ip2location-db5.bin 7 | analytics/analytics 8 | analytics_header/analytics_header 9 | .DS_Store 10 | *.swp 11 | asn.db 12 | proxy.db 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/dpdk"] 2 | path = dpdk-latency/deps/dpdk 3 | url = https://github.com/emmericp/dpdk.git 4 | [submodule "deps/IP2Location-C-Library"] 5 | path = analytics/deps/IP2Location-C-Library 6 | url = https://github.com/chrislim2888/IP2Location-C-Library.git 7 | [submodule "deps/json-c"] 8 | path = analytics/deps/json-c 9 | url = https://github.com/json-c/json-c.git 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2016, RESEARCH AND EDUCATION ADVANCED NETWORK NEW ZEALAND LIMITED 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ruru: real-time TCP latency monitoring 2 | 'Ruru' is a TCP latency monitoring application that helps understanding wide area TCP traffic in real time. 3 | It utilises Intel DPDK for high speed packet processing (up to 40Gbit/s) and a Node.JS web frontend to present results. 4 | 5 | ![Ruru live](/animation-zoom.gif) 6 | 7 | ## Publications, talks, podcasts 8 | 9 | Ruru at RIPE 75: https://ripe75.ripe.net/archives/video/92/ 10 | 11 | Ruru was featured in a PacketPushers podcast: 12 | http://packetpushers.net/podcast/podcasts/pq-142-tcp-latency-monitoring-ruru/ 13 | 14 | Ruru has been published in SIGCOMM 2017. Our paper is: 15 | 16 | [1] Cziva, R., Lorier, C. and Pezaros, D. P. (2017) Ruru: High-speed, Flow-level Latency Measurement and Visualization of Live Internet Traffic. In: SIGCOMM 2017, Los Angeles, CA, USA, 21-25 Aug 2017 17 | 18 | ## High-level architecture 19 | 20 | The system componses of three parts: 21 | - DPDK-latency backend (written in C / multi-threaded): This software measures the elapsed time between SYN, SYN-ACK and the first ACK TCP packets for all TCP streams. It sends the measurement information (source IP, destination IP, latency (in microsecond)) on a ZMQ sockets. 22 | - Analytics (written in C / multi-threaded): This component retrieves AS / geotag information for all IPs (using the IP2location.com databases) in the measurement data received from the DPDK backend and generates basic statistics. It pushes information in JSON format on ZMQ sockets. 23 | - Frontend: It is a Node.js built with React and Deck.Gl. It uses socket.io to communicate with the browser. 24 | 25 | Communication between components uses sockets (zmq and websockets). The high-level architecture is shown below. 26 | 27 |

28 | Architecture 29 |

30 | 31 | # Installation 32 | 33 | Installation consists of the following steps: 34 | 35 | 1. Install dpdk with by running ./setup.sh 36 | 2. Compile dpdk-latency with make 37 | 3. Set up analytics (more details in the README file of the analytics) 38 | 4. Set up the frontend (more details in the README file of the frontend) 39 | 40 | ## Frequently asked questions 41 | 42 | ### What does Ruru mean? 43 | 44 | Ruru is an owl from New Zealand. The bird has been selected to symbolise our software's 'intelligence' and 'clarity in the darkness'. In Māori tradition the ruru was seen as a watchful guardian. You can learn more [here](http://www.doc.govt.nz/nature/native-animals/birds/birds-a-z/morepork-ruru/). 45 | 46 | ### Can I deploy it? What are the license restrictions? 47 | 48 | Ruru is free to deploy and use. The software is provided using a BSD licence that you can find in the LICENSE file. 49 | 50 | ### What does Ruru measure? 51 | 52 | It measures TCP handshakes for each individual flow: the time it takes to set up a TCP connection. It looks at TCP flags (SYN, SYN-ACK, first ACK) of the TCP packets (and nothing else). 53 | 54 | ### How does Ruru figure out geographical location / ASN information for IP addresses? 55 | 56 | Ruru uses the IP2Location.com databases for IP->ASN, IP->geolocation and IP->proxy information mapping (these are 3 different databases from IP2Location). 57 | 58 | ### What processing performance does Ruru provide? 59 | 60 | The core of Ruru (that measures the latency for each flow) is based on DPDK, therefore it can cope with up to 40Gbit/s traffic. Geo-localising each source and destination IP address takes a lot of CPU cycles, therefore it mostly depends on how powerful your host machine is. We have deployed Ruru tapping a 10Gbit/s link. 61 | 62 | ### Which NICs do you support? 63 | 64 | All DPDK supported NICs can be used for Ruru. All supported NICs can be found here: http://dpdk.org/doc/nics. We used Intel X520 NICs at REANNZ. 65 | 66 | In case of any other questions, please contact Richard Cziva (richard@es.net) 67 | -------------------------------------------------------------------------------- /analytics/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | 3 | RUN mkdir -p /root 4 | WORKDIR /root 5 | 6 | COPY . /root 7 | 8 | RUN apt-get update 9 | RUN apt-get install -y libtool pkg-config build-essential autoconf automake sqlite3 libsqlite3-dev libcurl4-gnutls-dev libzmq3-dev 10 | 11 | WORKDIR /root/deps/IP2Location-C-Library 12 | RUN autoreconf -i -v --force 13 | RUN ./configure 14 | RUN sudo make install 15 | RUN sudo ldconfig 16 | 17 | WORKDIR /root/deps/json-c 18 | RUN ./autogen.sh 19 | RUN ./configure 20 | RUN sudo make install 21 | RUN sudo ldconfig 22 | 23 | WORKDIR /root/ 24 | RUN make 25 | 26 | CMD ./analytics 27 | 28 | -------------------------------------------------------------------------------- /analytics/Makefile: -------------------------------------------------------------------------------- 1 | #Simple Makefile to build the localiser 2 | 3 | 4 | CFLAGS += -g -O0 5 | CFLAGS += -Wall 6 | CFLAGS += -I/usr/local/include/json-c 7 | LDFLAGS+= -L/usr/local/lib 8 | LDLIBS+= -lzmq -lsqlite3 -lpthread -ljson-c -lIP2Location -lcurl 9 | 10 | CC = gcc 11 | 12 | all: 13 | ${CC} $(CFLAGS) -g main.c -o analytics $(LDFLAGS) $(LDLIBS) 14 | -------------------------------------------------------------------------------- /analytics/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Dependencies 3 | 4 | ## Data dependency - setting up sqlite database for ASN lookups 5 | 6 | Download an ASN database in a .csv format from this site (it is free): http://lite.ip2location.com/database/ip-asn (you will need to register). After downloading, you need to import this csv file to the asn.db database. I have prepared a script for you to do this - you can find it in importcsv_asn.sql 7 | 8 | cd data 9 | sqlite asn.db 10 | run the contents of importcsv_asn.sql here 11 | 12 | ## Data dependency - setting up sqlite database for proxied IPs 13 | 14 | Download the Proxy database in a .csv format from this site (it is free): http://lite.ip2location.com/database/px4-ip-proxytype-country-region-city-isp (you will need to register). After downloading, you need to import this csv file to the proxy.db database. I have prepared a script for you to do this - you can find it in importcsv_proxy.sql 15 | 16 | cd data 17 | sqlite proxy.db 18 | run the contents of importcsv_proxy.sql here 19 | 20 | 21 | ## Data dependency - getting the IP2Location database 22 | 23 | You can get a free IP2Location database from here: http://lite.ip2location.com/database/ip-country-region-city-latitude-longitude (I am using DB5). It is important that you need the .bin format (not .csv). Once downloaded, move it to the data folder and name it ip2location-db5.bin. 24 | 25 | scp /home/clouduser/IP2LOCATION-LITE-DB5.BIN data/ip2location-db5.bin 26 | 27 | ## Init submodules 28 | 29 | git submodule update --init --recursive 30 | 31 | 32 | ## Setting up InfluxDB 33 | 34 | Start a command similar to this (change path to an empty directory where data will be stored) 35 | 36 | sudo docker run -d -p 127.0.0.1:8086 --name influxcontainer -v /home/rcziva/ruru/influxdb:/var/lib/influxdb influxdb 37 | 38 | Set the retention policy for Influx 39 | 40 | sudo docker exec -ti influxcontainer influx 41 | > create database rtt 42 | > alter retention policy autogen on rtt duration 520w 43 | 44 | 45 | ## Building the analytics module and running 46 | 47 | Once you have all the dependencies installed and the data folder populated with the asn.db and ip2location-db5.bin files, you can build the analytics container.. 48 | 49 | sudo docker build -t analytics . 50 | 51 | To run it, you need to specify a few zmq sockets: sockets it uses to receive data and sockets it will set up to publish data on. In this command below we have IP addresses of two other containers: 172.17.0.2 is the frontend and 172.17.0.3 is the influx container. Change according to your IPs (that you can get with docker inspect). 52 | 53 | sudo docker run -d -p 5502:5502 -p 5503:5503 analytics ./analytics --bind tcp://0.0.0.0:5502 tcp://0.0.0.0:5503 --publish tcp://172.17.0.2:6080 --influx http://172.17.0.3:8086/write?db=rtt 54 | 55 | 56 | If you see this output (have a look with docker logs), the analytics is running: 57 | 58 | Publishing on: tcp://0.0.0.0:6000 59 | Subscribing on: tcp://127.0.0.1:5502 60 | Subscribing on: tcp://127.0.0.1:5503 61 | Trying to start listener thread on tcp://127.0.0.1:5502 62 | Thread 0 created successfully 63 | IP2Location API version: 8.0.3 (80003) 64 | Trying to start listener thread on tcp://127.0.0.1:5503 65 | Thread 1 created successfully 66 | IP2Location API version: 8.0.3 (80003) 67 | 68 | 69 | ## (Optional) Run grafana 70 | 71 | With Grafana, you can browse the Influx DB from a web browser. You can start it in a container with a similar command to have it listening on port 4000: 72 | 73 | docker run -d -p 4000:3000 -v /home/rcziva/ruru/grafana:/var/lib/grafana -e "GF_SECURITY_ADMIN_PASSWORD=banana" grafana/grafana 74 | 75 | -------------------------------------------------------------------------------- /analytics/data/importcsv_asn.sql: -------------------------------------------------------------------------------- 1 | -- This is how I set up the asn db with sqlite 2 | 3 | CREATE TABLE "ip2location_asn" ( 4 | "ip_from" integer UNSIGNED, 5 | "ip_to" integer UNSIGNED, 6 | "cidr" VARCHAR(18), 7 | "asn" VARCHAR(5), 8 | "as" VARCHAR(256) 9 | ); 10 | 11 | CREATE INDEX idx_ip_from on ip2location_asn (ip_from); 12 | CREATE INDEX idx_ip_to on ip2location_asn (ip_to); 13 | CREATE UNIQUE INDEX idx_ip_from_to on ip2location_asn (ip_from, ip_to); 14 | 15 | .separator "," 16 | .import asn.csv ip2location_asn 17 | -------------------------------------------------------------------------------- /analytics/data/importcsv_proxy.sql: -------------------------------------------------------------------------------- 1 | -- This is how I set up the asn db with sqlite 2 | 3 | CREATE TABLE "ip2proxy" ( 4 | "ip_from" integer UNSIGNED, 5 | "ip_to" integer UNSIGNED, 6 | "proxy_type" VARCHAR(3), 7 | "country_code" CHAR(2), 8 | "country_name" VARCHAR(64), 9 | "region_name" VARCHAR(128), 10 | "city_name" VARCHAR(128), 11 | "isp" VARCHAR(256) 12 | ); 13 | 14 | CREATE INDEX idx_ip_from on ip2proxy (ip_from); 15 | CREATE INDEX idx_ip_to on ip2proxy (ip_to); 16 | CREATE UNIQUE INDEX idx_ip_from_to on ip2proxy (ip_from, ip_to); 17 | 18 | .separator "," 19 | .import proxy.csv ip2proxy 20 | -------------------------------------------------------------------------------- /analytics/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | int debug = 0; 14 | 15 | static char * locationdb = "data/ip2location-db5.bin"; 16 | static char * proxydb = "data/proxy.db"; 17 | static char * asndb = "data/asn.db"; 18 | 19 | typedef struct { 20 | unsigned int asnumber; 21 | char * asname; 22 | } asinfo; 23 | 24 | struct thread_args { 25 | char *publish; 26 | char *influx; 27 | char *bind; 28 | }; 29 | 30 | /* Description: Convert the IP address (v4) into number */ 31 | static uint32_t 32 | ip2no(char* ipstring) 33 | { 34 | uint32_t ip = inet_addr(ipstring); 35 | uint8_t *ptr = (uint8_t *) &ip; 36 | uint32_t a = 0; 37 | 38 | if (ipstring != NULL) 39 | { 40 | a = (uint8_t)(ptr[3]); 41 | a += (uint8_t)(ptr[2]) * 256; 42 | a += (uint8_t)(ptr[1]) * 256 * 256; 43 | a += (uint8_t)(ptr[0]) * 256 * 256 * 256; 44 | } 45 | return a; 46 | } 47 | 48 | asinfo * 49 | get_asn(char* ip, sqlite3_stmt *stmt) 50 | { 51 | uint32_t ipnumber = ip2no(ip); 52 | asinfo * info; 53 | 54 | info = (asinfo *) malloc(sizeof (asinfo)); 55 | 56 | sqlite3_bind_int64(stmt, 1, ipnumber); 57 | sqlite3_step(stmt); 58 | info->asnumber = sqlite3_column_int(stmt, 0); 59 | // This returned pointed ir only valid for a set of time 60 | if (info->asnumber != 0){ 61 | //info->asname = (char *) sqlite3_column_text(stmt, 1); 62 | info->asname = malloc ( sizeof(char) * strlen((char *)sqlite3_column_text(stmt, 1))+1 ); 63 | memcpy(info->asname, (char *) sqlite3_column_text(stmt, 1), strlen((char *)sqlite3_column_text(stmt, 1))+1); 64 | } else { 65 | info->asname = malloc ( sizeof(char) * 2 ); 66 | memcpy(info->asname, "-", 2); 67 | } 68 | 69 | sqlite3_reset(stmt); 70 | //printf("AS results: AS%u is %s\n", info->asnumber, info->asname); 71 | return info; 72 | } 73 | 74 | char * 75 | get_proxy_type(char* ip, sqlite3_stmt *stmt) 76 | { 77 | uint32_t ipnumber = ip2no(ip); 78 | // Proxy type can be: VPN, TOR, PUB, WEB, DCH 79 | char *proxy_type; 80 | 81 | sqlite3_bind_int64(stmt, 1, ipnumber); 82 | sqlite3_step(stmt); 83 | if ( sqlite3_column_text(stmt, 0) ){ 84 | proxy_type = malloc ( sizeof(char) * strlen((char *)sqlite3_column_text(stmt, 0))+1 ); 85 | memcpy(proxy_type, (char *) sqlite3_column_text(stmt, 0), strlen((char *)sqlite3_column_text(stmt, 0))+1); 86 | //printf("Proxy results: %s\n", proxy_type); 87 | } else { 88 | proxy_type = malloc ( sizeof(char) * 2 ); 89 | memcpy(proxy_type, "-", 2); 90 | } 91 | 92 | sqlite3_reset(stmt); 93 | return proxy_type; 94 | } 95 | 96 | /* call the location api */ 97 | IP2LocationRecord * 98 | get_location(char ip[30], IP2Location *ip2location){ 99 | IP2LocationRecord *record = IP2Location_get_all(ip2location, ip); 100 | return record; 101 | } 102 | 103 | /* convert hex to decimal IP quad */ 104 | int 105 | ip_hex_to_dquad(const char *input, char *output, size_t outlen) 106 | { 107 | unsigned int a, b, c, d; 108 | 109 | if (sscanf(input, "%2x%2x%2x%2x", &a, &b, &c, &d) != 4){ 110 | return -1; 111 | } 112 | 113 | snprintf(output, outlen, "%u.%u.%u.%u", a, b, c, d); 114 | return 0; 115 | } 116 | 117 | int 118 | count_escapes(char* src) 119 | { 120 | int count = 0; 121 | char c; 122 | 123 | while ((c = *(src++))) { 124 | switch(c) { 125 | case ',': 126 | case ' ': 127 | case '=': 128 | count++; 129 | break; 130 | } 131 | } 132 | 133 | return count; 134 | } 135 | 136 | /* Escaping for influx... */ 137 | int 138 | expand_escapes(char* dest, const char* src) 139 | { 140 | char c; 141 | int count = 0; 142 | 143 | while ((c = *(src++))) { 144 | switch(c) { 145 | case ',': 146 | case ' ': 147 | case '=': 148 | *(dest++) = '\\'; 149 | *(dest++) = c; 150 | count++; 151 | break; 152 | default: 153 | *(dest++) = c; 154 | } 155 | } 156 | 157 | *dest = '\0'; /* Ensure nul terminator */ 158 | return count; 159 | } 160 | 161 | /* Parse incoming messages */ 162 | int 163 | parse_message(char message[256], IP2Location *ip2location, sqlite3_stmt *stmt, sqlite3_stmt *stmt_proxy, void *publisher, CURL *curl, char *influx_hostname) 164 | { 165 | char *source_ip_hex; 166 | char *destination_ip_hex; 167 | char *latency_external; 168 | char *latency_internal; 169 | char source_ip[30]; 170 | char destination_ip[30]; 171 | IP2LocationRecord *source_location; 172 | IP2LocationRecord *destination_location; 173 | int i; 174 | asinfo *destination_as; 175 | asinfo *source_as; 176 | json_object *json; 177 | zmq_msg_t msg; 178 | const char *jsonstring; 179 | CURLcode res; 180 | char *influxstring; 181 | int influxstring_len; 182 | char *source_country_escaped; 183 | char *source_city_escaped; 184 | char *source_asname_escaped; 185 | char *source_proxy_type; 186 | char *destination_country_escaped; 187 | char *destination_city_escaped; 188 | char *destination_asname_escaped; 189 | char *destination_proxy_type; 190 | int escaped = 0; 191 | int latency_int_internal; 192 | int latency_int_external; 193 | int latency_int_total; 194 | 195 | // posititions in the string 196 | int lastpos = 0; 197 | int retrieved = 0; 198 | 199 | for (i=0; i<256; i++){ 200 | if (message[i] == '-'){ 201 | if (lastpos == 0){ 202 | lastpos = i; 203 | continue; 204 | } 205 | switch (retrieved++){ 206 | case 0: 207 | source_ip_hex = malloc ( sizeof(char) * ( i-lastpos ) ); 208 | memcpy(source_ip_hex, &message[lastpos+1], (i-lastpos-1)); 209 | source_ip_hex[i-lastpos-1] = '\0'; 210 | break; 211 | case 1: 212 | destination_ip_hex = malloc ( sizeof(char) * ( i-lastpos ) ); 213 | memcpy(destination_ip_hex, &message[lastpos+1], (i-lastpos-1)); 214 | destination_ip_hex[i-lastpos-1] = '\0'; 215 | break; 216 | case 2: 217 | latency_external = malloc ( sizeof(char) * ( i-lastpos ) ); 218 | memcpy(latency_external, &message[lastpos+1], (i-lastpos-1)); 219 | latency_external[i-lastpos-1] = '\0'; 220 | break; 221 | case 3: 222 | latency_internal = malloc ( sizeof(char) * ( i-lastpos ) ); 223 | memcpy(latency_internal, &message[lastpos+1], (i-lastpos-1)); 224 | latency_internal[i-lastpos-1] = '\0'; 225 | break; 226 | } 227 | lastpos = i; 228 | } 229 | if (message[i] == '\0') break; 230 | } 231 | 232 | if (retrieved != 4){ 233 | printf("Message parsing failed (less or more than 4 token are retrieved), skipping this message"); 234 | if (latency_external) free(latency_external); 235 | if (latency_internal) free(latency_internal); 236 | if (destination_ip_hex) free(destination_ip_hex); 237 | if (source_ip_hex) free(source_ip_hex); 238 | return -1; 239 | } 240 | 241 | ip_hex_to_dquad(source_ip_hex, source_ip, sizeof(source_ip)); 242 | ip_hex_to_dquad(destination_ip_hex, destination_ip, sizeof(destination_ip)); 243 | 244 | // IP -> location 245 | source_location = get_location(source_ip, ip2location); 246 | destination_location = get_location(destination_ip, ip2location); 247 | 248 | // IP -> Proxy 249 | source_proxy_type = get_proxy_type(source_ip, stmt_proxy); 250 | destination_proxy_type = get_proxy_type(destination_ip, stmt_proxy); 251 | 252 | // IP -> ASN 253 | destination_as = get_asn(destination_ip, stmt); 254 | source_as = get_asn(source_ip, stmt); 255 | 256 | // Latency to int 257 | latency_int_internal = atoi (latency_internal); 258 | latency_int_internal = latency_int_internal / 1000; 259 | latency_int_external = atoi (latency_external); 260 | latency_int_external = latency_int_external / 1000; 261 | latency_int_total = latency_int_internal + latency_int_external; 262 | 263 | 264 | json = json_object_new_object(); 265 | json_object_object_add(json, "source_country", json_object_new_string(source_location->country_long)); 266 | json_object_object_add(json, "source_countrycode", json_object_new_string(source_location->country_short)); 267 | json_object_object_add(json, "source_city", json_object_new_string(source_location->city)); 268 | json_object_object_add(json, "source_lat", json_object_new_double(source_location->latitude)); 269 | json_object_object_add(json, "source_long", json_object_new_double(source_location->longitude)); 270 | json_object_object_add(json, "source_asn", json_object_new_int(source_as->asnumber)); 271 | json_object_object_add(json, "source_as", json_object_new_string(source_as->asname)); 272 | json_object_object_add(json, "source_proxy_type", json_object_new_string(source_proxy_type)); 273 | json_object_object_add(json, "destination_country", json_object_new_string(destination_location->country_long)); 274 | json_object_object_add(json, "destination_countrycode", json_object_new_string(destination_location->country_short)); 275 | json_object_object_add(json, "destination_city", json_object_new_string(destination_location->city)); 276 | json_object_object_add(json, "destination_lat", json_object_new_double(destination_location->latitude)); 277 | json_object_object_add(json, "destination_long", json_object_new_double(destination_location->longitude)); 278 | json_object_object_add(json, "destination_asn", json_object_new_int(destination_as->asnumber)); 279 | json_object_object_add(json, "destination_as", json_object_new_string(destination_as->asname)); 280 | json_object_object_add(json, "destination_proxy_type", json_object_new_string(destination_proxy_type)); 281 | json_object_object_add(json, "latency_internal", json_object_new_int(latency_int_internal)); 282 | json_object_object_add(json, "latency_external", json_object_new_int(latency_int_external)); 283 | json_object_object_add(json, "latency_total", json_object_new_int(latency_int_total)); 284 | 285 | 286 | jsonstring = json_object_to_json_string(json); 287 | 288 | zmq_msg_init_size (&msg, strlen(jsonstring)); 289 | memcpy(zmq_msg_data(&msg), jsonstring, strlen(jsonstring)); 290 | 291 | if (debug) printf("Parsed JSON: %s stlen %lu\n", jsonstring, strlen(jsonstring)); 292 | 293 | //Send it to socket 294 | zmq_msg_send(&msg, publisher, 0); 295 | 296 | //escaping string values 297 | source_country_escaped = malloc(1 + sizeof(char) * (strlen(source_location->country_long) + count_escapes(source_location->country_long))); 298 | escaped += expand_escapes(source_country_escaped, source_location->country_long); 299 | 300 | source_city_escaped = malloc(1 + sizeof(char) * (strlen(source_location->city) + count_escapes(source_location->city))); 301 | escaped += expand_escapes(source_city_escaped, source_location->city); 302 | 303 | source_asname_escaped = malloc(1 + sizeof(char) * (strlen(source_as->asname) + count_escapes(source_as->asname))); 304 | escaped += expand_escapes(source_asname_escaped, source_as->asname); 305 | 306 | destination_country_escaped = malloc(1 + sizeof(char) * (strlen(destination_location->country_long) + count_escapes(destination_location->country_long))); 307 | escaped += expand_escapes(destination_country_escaped, destination_location->country_long); 308 | 309 | destination_city_escaped = malloc(1 + sizeof(char) * (strlen(destination_location->city) + count_escapes(destination_location->city))); 310 | escaped += expand_escapes(destination_city_escaped, destination_location->city); 311 | 312 | destination_asname_escaped = malloc(1 + sizeof(char) * (strlen(destination_as->asname) + count_escapes(destination_as->asname))); 313 | escaped += expand_escapes(destination_asname_escaped, destination_as->asname); 314 | 315 | // this must be enough 316 | influxstring_len = strlen(jsonstring) + escaped + 200; 317 | //printf("Escaped: %d, total size: %u \n", escaped, influxstring_len); 318 | 319 | influxstring = (char *) malloc(sizeof(char) * influxstring_len + 1); 320 | snprintf(influxstring, 321 | influxstring_len, 322 | "latency," 323 | "source_country=%s," 324 | "source_countrycode=%s," 325 | "source_city=%s," 326 | "source_lat=%f," 327 | "source_long=%f," 328 | "source_asn=%u," 329 | "source_as=%s," 330 | "source_proxy_type=%s," 331 | "destination_country=%s," 332 | "destination_countrycode=%s," 333 | "destination_city=%s," 334 | "destination_lat=%f," 335 | "destination_long=%f," 336 | "destination_asn=%u," 337 | "destination_as=%s," 338 | "destination_proxy_type=%s " 339 | "internal=%d,external=%d,total=%d", 340 | source_country_escaped, 341 | source_location->country_short, 342 | source_city_escaped, 343 | source_location->latitude, 344 | source_location->longitude, 345 | source_as->asnumber, 346 | source_asname_escaped, 347 | source_proxy_type, 348 | destination_country_escaped, 349 | destination_location->country_short, 350 | destination_city_escaped, 351 | destination_location->latitude, 352 | destination_location->longitude, 353 | destination_as->asnumber, 354 | destination_asname_escaped, 355 | destination_proxy_type, 356 | latency_int_internal, 357 | latency_int_external, 358 | latency_int_total); 359 | 360 | //Send it to Influx 361 | if(curl) { 362 | /* First set the URL that is about to receive our POST. This URL can 363 | just as well be a https:// URL if that is what should receive the 364 | data. */ 365 | curl_easy_setopt(curl, CURLOPT_URL, influx_hostname); 366 | //printf("Curl request"); 367 | /* Now specify the POST data */ 368 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, influxstring); 369 | 370 | /* Perform the request, es will get the return code */ 371 | res = curl_easy_perform(curl); 372 | /* Check for errors */ 373 | if(res != CURLE_OK){ 374 | fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); 375 | } 376 | } 377 | 378 | //Free everything malloced 379 | zmq_msg_close (&msg); 380 | json_object_put(json); 381 | IP2Location_free_record(source_location); 382 | IP2Location_free_record(destination_location); 383 | free(source_ip_hex); 384 | free(destination_ip_hex); 385 | free(destination_as->asname); 386 | free(destination_as); 387 | free(source_as->asname); 388 | free(source_as); 389 | free(latency_external); 390 | free(latency_internal); 391 | free(influxstring); 392 | free(source_country_escaped); 393 | free(source_asname_escaped); 394 | free(source_city_escaped); 395 | free(source_proxy_type); 396 | free(destination_country_escaped); 397 | free(destination_asname_escaped); 398 | free(destination_city_escaped); 399 | free(destination_proxy_type); 400 | return 0; 401 | } 402 | 403 | 404 | /* This is the part that runs mult-threaded and each connects to a socket */ 405 | void * 406 | process_socket(void* argp){ 407 | // TODO: as we are only reading, one handle for sqlite and ip2location might be enough 408 | sqlite3 *ip2asn; 409 | sqlite3 *ip2proxy; 410 | sqlite3_stmt *stmt; 411 | sqlite3_stmt *stmt_proxy; 412 | IP2Location *ip2location; 413 | void *context = zmq_ctx_new (); 414 | void *requester = zmq_socket (context, ZMQ_SUB); 415 | void *publisher = zmq_socket (context, ZMQ_PUB); 416 | struct thread_args *args = argp; 417 | char *hostname = args->bind; 418 | char *publish_hostname = args->publish; 419 | char *influx_hostname = args->influx; 420 | CURL *curl; 421 | 422 | int rc = zmq_bind (requester, hostname); 423 | assert (rc == 0); 424 | 425 | rc = zmq_connect (publisher, publish_hostname); 426 | assert (rc == 0); 427 | //rc = zmq_connect (publisher, "tcp://127.0.0.1:6000"); 428 | 429 | char *filter = "LAT-"; 430 | rc = zmq_setsockopt (requester, ZMQ_SUBSCRIBE, filter, strlen (filter)); 431 | assert (rc == 0); 432 | 433 | rc = sqlite3_open(asndb, &ip2asn); 434 | assert (rc == 0); 435 | 436 | rc = sqlite3_open(proxydb, &ip2proxy); 437 | assert (rc == 0); 438 | 439 | // create reusable sqlite3 stmt 440 | sqlite3_prepare_v2(ip2asn, "select asn,\"as\" from ip2location_asn where ip_from <= ?1 and ip_to >= ?1 limit 1;", -1, &stmt, NULL); 441 | 442 | // create reusable sqlite3 stmt 443 | sqlite3_prepare_v2(ip2proxy, "select proxy_type from ip2proxy where ip_from <= ?1 and ip_to >= ?1 limit 1;", -1, &stmt_proxy, NULL); 444 | 445 | ip2location = IP2Location_open(locationdb); 446 | printf("IP2Location API version: %s (%lu)\n", IP2Location_api_version_string(), IP2Location_api_version_num()); 447 | 448 | curl = curl_easy_init(); 449 | 450 | while (1){ 451 | char buffer[256]; 452 | int size = zmq_recv (requester, buffer, sizeof(buffer), 0); 453 | //printf("buffer: %s\n", buffer); 454 | buffer[size] = '\0'; 455 | parse_message(buffer, ip2location, stmt, stmt_proxy, publisher, curl, influx_hostname); 456 | } 457 | 458 | curl_easy_cleanup(curl); 459 | sqlite3_close(ip2asn); 460 | sqlite3_close(ip2proxy); 461 | 462 | free(args); 463 | zmq_close (requester); 464 | zmq_ctx_destroy (context); 465 | return 0; 466 | } 467 | 468 | int 469 | main (int argc, char **argv) 470 | { 471 | // attach to all DPDK sockets 472 | int cores = 0; 473 | int i, err, mode; 474 | pthread_t thread_id[100]; 475 | char *publish_hostname; 476 | char *influx_hostname; 477 | char *strlist[argc]; 478 | 479 | struct thread_args *args = malloc(sizeof *args); 480 | 481 | curl_global_init(CURL_GLOBAL_ALL); 482 | 483 | //Command line argument parsing 484 | if (argc < 2){ 485 | printf("Run as: %s --bind tcp://127.0.0.1:5502 tcp://127.0.0.1:5503 --publish tcp://0.0.0.0:6000 --influx http://localhost:8086/write?db=rtt\n", 486 | argv[0]); 487 | return -1; 488 | } 489 | 490 | for ( i = 1; ibind = strlist[i]; 535 | args->publish = publish_hostname; 536 | args->influx = influx_hostname; 537 | printf("Trying to start listener thread on %s", args->bind); 538 | err = pthread_create(&thread_id[i], NULL, &process_socket, args); 539 | if (err != 0){ 540 | printf("\n Can't create thread :[%s]", strerror(err)); 541 | } else { 542 | printf("\n Thread %i created successfully\n", i); 543 | } 544 | sleep(2); 545 | } 546 | 547 | 548 | // wait until all threads finish 549 | for ( i = 0; i < cores; i++) { 550 | pthread_join(thread_id[i], NULL); 551 | } 552 | 553 | curl_global_cleanup(); 554 | 555 | if (publish_hostname) free(publish_hostname); 556 | for ( i =0; i < cores; i++){ 557 | if(strlist[cores]) free(strlist[cores]); 558 | } 559 | 560 | free (args); 561 | return 0; 562 | } 563 | 564 | 565 | -------------------------------------------------------------------------------- /animation-zoom.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/animation-zoom.gif -------------------------------------------------------------------------------- /animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/animation.gif -------------------------------------------------------------------------------- /architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/architecture.png -------------------------------------------------------------------------------- /dpdk-latency/Makefile: -------------------------------------------------------------------------------- 1 | # BSD LICENSE 2 | # 3 | # Copyright(c) 2010-2014 Intel Corporation. All rights reserved. 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in 14 | # the documentation and/or other materials provided with the 15 | # distribution. 16 | # * Neither the name of Intel Corporation nor the names of its 17 | # contributors may be used to endorse or promote products derived 18 | # from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | 33 | # Default target, can be overriden by command line or environment 34 | ifeq ($(RTE_SDK),) 35 | $(error "Please define RTE_SDK environment variable") 36 | endif 37 | 38 | RTE_TARGET=build 39 | 40 | include $(RTE_SDK)/mk/rte.vars.mk 41 | 42 | # binary name 43 | APP = dpdk-latency 44 | 45 | # all source are stored in SRCS-y 46 | SRCS-y := main.c 47 | 48 | CFLAGS += -O3 49 | CFLAGS += -Werror=unused-value -Werror=unused-parameter 50 | LDFLAGS += -lzmq 51 | 52 | include $(RTE_SDK)/mk/rte.extapp.mk 53 | -------------------------------------------------------------------------------- /dpdk-latency/README.md: -------------------------------------------------------------------------------- 1 | 2 | # DPDK backend 3 | 4 | This is a DPDK application that performs high-speed TCP RTT tracking. It publishes results on ZMQ sockets. 5 | 6 | The output format on the sockets is the following: 7 | 8 | ``` 9 | LAT-xxxxxxxx-yyyyyyyy-0000000011-0000000044- 10 | ``` 11 | 12 | where: 13 | 14 | - LAT is just a label to filter between multiple messages in the future 15 | - xxxxxxxxx is the source IPv4 address in hex 16 | - yyyyyyyyy is the destination IPv4 address in hex 17 | - 00000000011 is the latency (RTT) measured between the source host and our measurement tap 18 | - 00000000044 is the latency (RTT) measured between the measurement tap and the destination host 19 | 20 | ## Running it 21 | 22 | After you have complied this app, you will have a binary in your build folder. 23 | 24 | Before running the app, You will need to bind interfaces to DPDK (at least one). Use the dpdk-devbind.py utility provided with DPDK to do this. 25 | You should see something like this: 26 | 27 | ```sh 28 | rcziva@m5:~/dpdk-latency/dpdk$ ~/dpdk/tools/dpdk-devbind.py --status 29 | 30 | Network devices using DPDK-compatible driver 31 | ============================================ 32 | 0000:03:00.0 '82599ES 10-Gigabit SFI/SFP+ Network Connection' drv=igb_uio unused=uio_pci_generic 33 | 0000:03:00.1 '82599ES 10-Gigabit SFI/SFP+ Network Connection' drv=igb_uio unused=uio_pci_generic 34 | 35 | ``` 36 | 37 | You can now start the application with a command similar to this: 38 | 39 | ```sh 40 | $ sudo ./build/dpdk-latency -c ff -n 4 -- -p ff -T 60 --config "(0,0,1),(0,1,2),(0,2,3),(0,3,4),(0,4,5),(0,5,6),(1,0,1),(1,1,2),(1,2,3),(1 41 | ,3,4),(1,4,5),(1,5,6)" --forwarding 42 | ``` 43 | 44 | The --config option specifies the (port, queue, lcore) config. 45 | 46 | If you are using the --forwarding option, the app behaves like a small bridge - it forwards all packets from port 0 to port 1 and from port 1 to port 0. If you have more than ttwo ports, this will not work out of the box. Actually, this is options is designed for debugging and performance validation purposes. 47 | 48 | The app prints out port statistics periodically. You can specify the time between updates with -T. In production deployment, I recommend -T 60 at least (or larger). 49 | 50 | ## Installing dependencies and DPDK 51 | 52 | Install DPDK and compile it for your target platform. Get DPDK from here: http://dpdk.org. 53 | 54 | Modify the Makefile to point to your DPDK directory. 55 | 56 | Install ZMQ 57 | 58 | ```sh 59 | $ sudo apt-get install libzmq3-dev 60 | ``` 61 | 62 | To compile this application, you can use the Makefile provided: 63 | 64 | ```sh 65 | $ make 66 | ``` 67 | 68 | ## Help 69 | 70 | If you need help, don't hesitate to contact me on r.cziva.1@research.gla.ac.uk. 71 | -------------------------------------------------------------------------------- /dpdk-latency/bind-interfaces.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Script from Moongen repository (https://github.com/libmoon/libmoon) 4 | 5 | ( 6 | cd $(dirname "${BASH_SOURCE[0]}") 7 | cd deps/dpdk 8 | 9 | modprobe uio 10 | (lsmod | grep igb_uio > /dev/null) || insmod ./build/kmod/igb_uio.ko 11 | 12 | i=0 13 | for id in $(tools/dpdk-devbind.py --status | grep -v Active | grep unused=igb_uio | cut -f 1 -d " ") 14 | do 15 | echo "Binding interface $id to DPDK" 16 | tools/dpdk-devbind.py --bind=igb_uio $id 17 | i=$(($i+1)) 18 | done 19 | 20 | if [[ $i == 0 ]] 21 | then 22 | echo "Could not find any inactive interfaces to bind to DPDK. Note that this script does not bind interfaces that are in use by the OS." 23 | echo "Delete IP addresses from interfaces you would like to use with Ruru and run this script again." 24 | echo "You can also use the script dpdk-devbind.py in ${ERROR_MSG_SUBDIR}deps/dpdk/tools manually to manage interfaces used by Ruru and the OS." 25 | fi 26 | 27 | ) 28 | -------------------------------------------------------------------------------- /dpdk-latency/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ( 4 | cd $(dirname "${BASH_SOURCE[0]}") 5 | git submodule update --init --recursive 6 | 7 | ( 8 | cd deps/dpdk 9 | make config T=x86_64-native-linuxapp-gcc 10 | ) 11 | 12 | ( 13 | cd deps/dpdk 14 | make 15 | ) 16 | 17 | echo Trying to bind interfaces, this will fail if you are not root 18 | echo Try "sudo ./bind-interfaces.sh" if this step fails 19 | ./bind-interfaces.sh 20 | ) 21 | 22 | -------------------------------------------------------------------------------- /dpdk-latency/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | 48 | static volatile bool force_quit; 49 | 50 | /* Disabling forwarding */ 51 | static int forwarding = 0; 52 | 53 | /* Debug mode */ 54 | static int debug = 0; 55 | 56 | #define RTE_LOGTYPE_DPDKLATENCY RTE_LOGTYPE_USER1 57 | 58 | #define NB_MBUF 8192 59 | 60 | #define MAX_PKT_BURST 32 61 | #define BURST_TX_DRAIN_US 100 /* TX drain every ~100us */ 62 | #define MEMPOOL_CACHE_SIZE 256 63 | #define NB_SOCKETS 8 64 | 65 | /* 66 | * Configurable number of RX/TX ring descriptors 67 | */ 68 | #define RTE_TEST_RX_DESC_DEFAULT 128 69 | #define RTE_TEST_TX_DESC_DEFAULT 512 70 | static uint16_t nb_rxd = RTE_TEST_RX_DESC_DEFAULT; 71 | static uint16_t nb_txd = RTE_TEST_TX_DESC_DEFAULT; 72 | 73 | /* mask of enabled ports */ 74 | static uint32_t dpdklatency_enabled_port_mask = 0; 75 | 76 | struct mbuf_table { 77 | unsigned len; 78 | struct rte_mbuf *m_table[MAX_PKT_BURST]; 79 | }; 80 | 81 | #define MAX_RX_QUEUE_PER_LCORE 16 82 | #define MAX_TX_QUEUE_PER_PORT 16 83 | 84 | static struct rte_eth_dev_tx_buffer *tx_buffer[RTE_MAX_ETHPORTS]; 85 | 86 | /* Magic hash key for symmetric RSS */ 87 | #define RSS_HASH_KEY_LENGTH 40 88 | static uint8_t hash_key[RSS_HASH_KEY_LENGTH] = { 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 89 | 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 90 | 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 91 | 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, }; 92 | 93 | /* Port configuration structure */ 94 | static const struct rte_eth_conf port_conf = { 95 | .rxmode = { 96 | .mq_mode = ETH_MQ_RX_RSS, /**< RSS enables */ 97 | .split_hdr_size = 0, 98 | .header_split = 0, /**< Header Split disabled */ 99 | .hw_ip_checksum = 0, /**< IP checksum offload disabled */ 100 | .hw_vlan_filter = 0, /**< VLAN filtering disabled */ 101 | .jumbo_frame = 0, /**< Jumbo Frame Support disabled */ 102 | .hw_strip_crc = 0, /**< CRC stripped by hardware */ 103 | }, 104 | .rx_adv_conf = { 105 | .rss_conf = { 106 | .rss_key = hash_key, 107 | .rss_hf = ETH_RSS_PROTO_MASK, 108 | }, 109 | }, 110 | .txmode = { 111 | .mq_mode = ETH_MQ_TX_NONE, 112 | }, 113 | }; 114 | 115 | struct rte_mempool * dpdklatency_pktmbuf_pool = NULL; 116 | 117 | /* Per-lcore (essentially queue) statistics struct */ 118 | struct dpdklatency_lcore_statistics { 119 | uint64_t tx; 120 | uint64_t rx; 121 | uint64_t dropped; 122 | } __rte_cache_aligned; 123 | struct dpdklatency_lcore_statistics lcore_statistics[RTE_MAX_LCORE]; 124 | 125 | #define MAX_TIMER_PERIOD 86400 /* 1 day max */ 126 | /* A tsc-based timer responsible for triggering statistics printout */ 127 | static uint64_t timer_period = 10; /* default period is 10 seconds */ 128 | 129 | #define TIMESTAMP_HASH_ENTRIES 99999 130 | 131 | typedef struct rte_hash lookup_struct_t; 132 | static lookup_struct_t *ipv4_timestamp_lookup_struct[NB_SOCKETS]; 133 | 134 | 135 | #ifdef RTE_MACHINE_CPUFLAG_SSE4_2 136 | #include 137 | #define DEFAULT_HASH_FUNC rte_hash_crc 138 | #else 139 | #include 140 | #define DEFAULT_HASH_FUNC rte_jhash 141 | #endif 142 | 143 | #define CLOCK_PRECISION 1000000000L /* one billion */ 144 | 145 | struct lcore_rx_queue { 146 | uint8_t port_id; 147 | uint8_t queue_id; 148 | } __rte_cache_aligned; 149 | 150 | #define MAX_LCORE_PARAMS 1024 151 | struct lcore_params { 152 | uint8_t port_id; 153 | uint8_t queue_id; 154 | uint8_t lcore_id; 155 | } __rte_cache_aligned; 156 | 157 | // Configure port-queue-lcore assigment here 158 | static struct lcore_params lcore_params_array[MAX_LCORE_PARAMS]; 159 | static struct lcore_params lcore_params_array_default[] = { 160 | {0, 0, 1}, 161 | {0, 1, 2}, 162 | {0, 2, 3}, 163 | {0, 3, 4}, 164 | }; 165 | 166 | static struct lcore_params * lcore_params = lcore_params_array_default; 167 | static uint16_t nb_lcore_params = sizeof(lcore_params_array_default) / 168 | sizeof(lcore_params_array_default[0]); 169 | 170 | struct lcore_conf { 171 | uint16_t n_rx_queue; 172 | struct lcore_rx_queue rx_queue_list[MAX_RX_QUEUE_PER_LCORE]; 173 | uint16_t tx_queue_id[RTE_MAX_ETHPORTS]; 174 | struct mbuf_table tx_mbufs[RTE_MAX_ETHPORTS]; 175 | void * zmq_client; 176 | void * zmq_client_header; 177 | lookup_struct_t * ipv4_lookup_struct; 178 | } __rte_cache_aligned; 179 | 180 | static struct lcore_conf lcore_conf[RTE_MAX_LCORE] __rte_cache_aligned; 181 | 182 | static const char* publishto; 183 | 184 | static void 185 | send_header_zmq_ipv4(uint8_t * alldata, uint32_t length) 186 | { 187 | unsigned lcore_id = rte_lcore_id(); 188 | struct lcore_conf *qconf; 189 | qconf = &lcore_conf[lcore_id]; 190 | void *zmq_client = qconf->zmq_client_header; 191 | 192 | if (zmq_client != NULL){ 193 | zmq_send (zmq_client, alldata, length, 0); 194 | } 195 | } 196 | 197 | static void 198 | send_to_zmq_ipv4(uint32_t sourceip, uint32_t destip, unsigned long long int timestamp_ext, unsigned long long int timestamp_int) 199 | { 200 | unsigned lcore_id = rte_lcore_id(); 201 | struct lcore_conf *qconf; 202 | qconf = &lcore_conf[lcore_id]; 203 | void *zmq_client = qconf->zmq_client; 204 | //message length is 28 bytes! 205 | char message[3+1+8+1+8+1+10+1+10+2]; 206 | 207 | snprintf(message, sizeof(message), "LAT-%08x-%08x-%010llu-%010llu-", 208 | (unsigned) sourceip, (unsigned) destip, timestamp_ext, timestamp_int); 209 | 210 | if (debug){ 211 | printf("%s\n", message); 212 | fflush(stdout); 213 | } 214 | if (zmq_client != NULL){ 215 | zmq_send (zmq_client, message, sizeof(message), 0); 216 | } 217 | } 218 | 219 | 220 | static void 221 | track_latency_syn_v4(uint64_t key, uint64_t *ipv4_timestamp_syn) 222 | { 223 | int ret = 0; 224 | unsigned lcore_id; 225 | struct timespec timestamp; 226 | 227 | lcore_id = rte_lcore_id(); 228 | 229 | ret = rte_hash_add_key (ipv4_timestamp_lookup_struct[lcore_id], (void *) &key); 230 | //printf("SYN lcore %u, ret: %d \n", lcore_id, ret); 231 | if (ret < 0) { 232 | RTE_LOG(INFO, DPDKLATENCY, "Hash table full for lcore %u - clearing it\n", lcore_id); 233 | rte_hash_reset(ipv4_timestamp_lookup_struct[lcore_id]); 234 | ret = rte_hash_add_key (ipv4_timestamp_lookup_struct[lcore_id], (void *) &key); 235 | if (ret < 0) { 236 | rte_exit(EXIT_FAILURE, "Unable to add SYN timestamp to hash after cleaning it"); 237 | } 238 | } 239 | clock_gettime(CLOCK_MONOTONIC, ×tamp); 240 | ipv4_timestamp_syn[ret] = CLOCK_PRECISION * timestamp.tv_sec + timestamp.tv_nsec; 241 | } 242 | 243 | static void 244 | track_latency_synack_v4(uint64_t key, uint64_t *ipv4_timestamp_synack) 245 | { 246 | int ret = 0; 247 | unsigned lcore_id; 248 | struct timespec timestamp; 249 | 250 | lcore_id = rte_lcore_id(); 251 | 252 | ret = rte_hash_lookup(ipv4_timestamp_lookup_struct[lcore_id], (const void *) &key); 253 | //printf("SYNACK lcore %u, ret: %d \n", lcore_id, ret); 254 | if (ret >= 0 ) { 255 | clock_gettime(CLOCK_MONOTONIC, ×tamp); 256 | ipv4_timestamp_synack[ret] = CLOCK_PRECISION * timestamp.tv_sec + timestamp.tv_nsec; 257 | } 258 | } 259 | 260 | static void 261 | track_latency_ack_v4(uint64_t key, uint32_t sourceip, uint32_t destip, uint64_t *ipv4_timestamp_syn, uint64_t *ipv4_timestamp_synack) 262 | { 263 | unsigned lcore_id; 264 | struct timespec timestamp; 265 | double elapsed_internal; 266 | double elapsed_external; 267 | int ret = 0; 268 | 269 | lcore_id = rte_lcore_id(); 270 | 271 | ret = rte_hash_lookup(ipv4_timestamp_lookup_struct[lcore_id], (const void *) &key); 272 | if (ret >= 0) { 273 | clock_gettime(CLOCK_MONOTONIC, ×tamp); 274 | elapsed_external = ipv4_timestamp_synack[ret] - ipv4_timestamp_syn[ret]; 275 | elapsed_internal = (CLOCK_PRECISION * timestamp.tv_sec + timestamp.tv_nsec) - ipv4_timestamp_synack[ret]; 276 | //printf("SYN-ACK %d %llu microsec \n", ret, (unsigned long long int) elapsed_external / 1000); 277 | // If elapsed ms is more than 9999, we do not send it 278 | if ( ((elapsed_internal / 1000000) < 9999) && ((elapsed_external / 1000000) < 9999)){ 279 | send_to_zmq_ipv4(destip, sourceip, 280 | (unsigned long long int) elapsed_external / 1000, 281 | (unsigned long long int) elapsed_internal / 1000); 282 | } 283 | rte_hash_del_key (ipv4_timestamp_lookup_struct[lcore_id], (void *) &key); 284 | } 285 | } 286 | 287 | 288 | /* For vlan tagged packets, we need to find the offset in order to remove tagging */ 289 | static inline size_t 290 | get_vlan_offset(struct ether_hdr *eth_hdr, uint16_t *proto) 291 | { 292 | size_t vlan_offset = 0; 293 | if (rte_cpu_to_be_16(ETHER_TYPE_VLAN) == *proto) { 294 | struct vlan_hdr *vlan_hdr = (struct vlan_hdr *)(eth_hdr + 1); 295 | vlan_offset = sizeof(struct vlan_hdr); 296 | *proto = vlan_hdr->eth_proto; 297 | if (rte_cpu_to_be_16(ETHER_TYPE_VLAN) == *proto) { 298 | vlan_hdr = vlan_hdr + 1; 299 | *proto = vlan_hdr->eth_proto; 300 | vlan_offset += sizeof(struct vlan_hdr); 301 | } 302 | } 303 | return vlan_offset; 304 | } 305 | 306 | 307 | /* This function is streaming TCP headers (with options) on ZMQ sockets */ 308 | static int 309 | send_tcpoptions(struct tcp_hdr *tcp_hdr) 310 | { 311 | uint32_t length = (tcp_hdr->data_off & 0xf0) >> 2; 312 | uint8_t alldata[length]; 313 | 314 | memcpy(alldata, tcp_hdr, length); 315 | send_header_zmq_ipv4(alldata, length); 316 | 317 | //printf("len: %u\n", length); 318 | //fflush(stdout); 319 | return 0; 320 | } 321 | 322 | static void 323 | track_latency(struct rte_mbuf *m, uint64_t *ipv4_timestamp_syn, uint64_t *ipv4_timestamp_synack) 324 | { 325 | struct ether_hdr *eth_hdr; 326 | struct tcp_hdr *tcp_hdr = NULL; 327 | struct ipv4_hdr* ipv4_hdr; 328 | uint16_t offset = 0; 329 | enum { URG_FLAG = 0x20, ACK_FLAG = 0x10, PSH_FLAG = 0x08, RST_FLAG = 0x04, SYN_FLAG = 0x02, FIN_FLAG = 0x01 }; 330 | uint64_t key; 331 | 332 | eth_hdr = rte_pktmbuf_mtod(m, struct ether_hdr *); 333 | 334 | //VLAN tagged frame 335 | if (eth_hdr->ether_type == rte_cpu_to_be_16(ETHER_TYPE_VLAN)){ 336 | offset = get_vlan_offset(eth_hdr, ð_hdr->ether_type); 337 | } 338 | 339 | // IPv4 340 | ipv4_hdr = rte_pktmbuf_mtod_offset(m, struct ipv4_hdr *, sizeof(struct ether_hdr)+offset); 341 | if (ipv4_hdr->next_proto_id == IPPROTO_TCP){ 342 | tcp_hdr = rte_pktmbuf_mtod_offset(m, struct tcp_hdr *, 343 | sizeof(struct ipv4_hdr) + sizeof(struct ether_hdr) + offset); 344 | switch (tcp_hdr->tcp_flags){ 345 | case SYN_FLAG: 346 | key = (long long) m->hash.rss << 32 | rte_be_to_cpu_32(tcp_hdr->sent_seq); 347 | track_latency_syn_v4( key, ipv4_timestamp_syn); 348 | break; 349 | case SYN_FLAG | ACK_FLAG: 350 | key = (long long) m->hash.rss << 32 | (rte_be_to_cpu_32(tcp_hdr->recv_ack)- 1); 351 | track_latency_synack_v4( key, ipv4_timestamp_synack); 352 | break; 353 | case ACK_FLAG: 354 | key = (long long) m->hash.rss << 32 | (rte_be_to_cpu_32(tcp_hdr->sent_seq) - 1 ); 355 | track_latency_ack_v4( key, 356 | rte_be_to_cpu_32(ipv4_hdr->dst_addr), 357 | rte_be_to_cpu_32(ipv4_hdr->src_addr), 358 | ipv4_timestamp_syn, 359 | ipv4_timestamp_synack); 360 | } 361 | } 362 | } 363 | 364 | 365 | /* Send the burst of packets on an output interface */ 366 | static int 367 | dpdklatency_send_burst(struct lcore_conf *qconf, unsigned n, uint8_t port) 368 | { 369 | struct rte_mbuf **m_table; 370 | unsigned ret; 371 | unsigned queueid = 0; 372 | unsigned lcore_id = rte_lcore_id(); 373 | 374 | m_table = (struct rte_mbuf **)qconf->tx_mbufs[port].m_table; 375 | 376 | // TODO: change here is more than one TX queue per port is required 377 | ret = rte_eth_tx_burst(port, (uint16_t) queueid, m_table, (uint16_t) n); 378 | lcore_statistics[lcore_id].tx += ret; 379 | if (unlikely(ret < n)) { 380 | lcore_statistics[lcore_id].dropped += (n - ret); 381 | do { 382 | rte_pktmbuf_free(m_table[ret]); 383 | } while (++ret < n); 384 | } 385 | 386 | return 0; 387 | } 388 | 389 | /* Enqueue packets for TX and prepare them to be sent */ 390 | static int 391 | dpdklatency_send_packet(struct rte_mbuf *m, uint8_t port) 392 | { 393 | unsigned lcore_id, len; 394 | struct lcore_conf *qconf; 395 | 396 | lcore_id = rte_lcore_id(); 397 | 398 | qconf = &lcore_conf[lcore_id]; 399 | len = qconf->tx_mbufs[port].len; 400 | qconf->tx_mbufs[port].m_table[len] = m; 401 | len++; 402 | 403 | /* enough pkts to be sent */ 404 | if (unlikely(len == MAX_PKT_BURST)) { 405 | dpdklatency_send_burst(qconf, MAX_PKT_BURST, port); 406 | len = 0; 407 | } 408 | 409 | qconf->tx_mbufs[port].len = len; 410 | return 0; 411 | } 412 | 413 | /* Print out statistics on packets dropped */ 414 | static void 415 | print_stats(void) 416 | { 417 | uint64_t total_packets_dropped, total_packets_tx, total_packets_rx; 418 | unsigned lcore_id; 419 | 420 | // TODO: dopped TX packets are not counted properly 421 | total_packets_dropped = 0; 422 | total_packets_tx = 0; 423 | total_packets_rx = 0; 424 | 425 | const char clr[] = { 27, '[', '2', 'J', '\0' }; 426 | const char topLeft[] = { 27, '[', '1', ';', '1', 'H','\0' }; 427 | 428 | /* Clear screen and move to top left */ 429 | printf("%s%s", clr, topLeft); 430 | 431 | printf("\nLcore statistics ===================================="); 432 | 433 | RTE_LCORE_FOREACH_SLAVE(lcore_id) { 434 | printf("\nStatistics for lcore %u ------------------------------" 435 | "\nPackets sent: %24"PRIu64 436 | "\nPackets received: %20"PRIu64 437 | "\nPackets dropped: %21"PRIu64, 438 | lcore_id, 439 | lcore_statistics[lcore_id].tx, 440 | lcore_statistics[lcore_id].rx, 441 | lcore_statistics[lcore_id].dropped); 442 | 443 | total_packets_dropped += lcore_statistics[lcore_id].dropped; 444 | total_packets_tx += lcore_statistics[lcore_id].tx; 445 | total_packets_rx += lcore_statistics[lcore_id].rx; 446 | } 447 | printf("\nAggregate statistics ===============================" 448 | "\nTotal packets sent: %18"PRIu64 449 | "\nTotal packets received: %14"PRIu64 450 | "\nTotal packets dropped: %15"PRIu64, 451 | total_packets_tx, 452 | total_packets_rx, 453 | total_packets_dropped); 454 | printf("\n====================================================\n"); 455 | } 456 | 457 | static void 458 | init_zmq_for_lcore(unsigned lcore_id){ 459 | void *context = zmq_ctx_new (); 460 | void *requester = zmq_socket (context, ZMQ_PUB); 461 | void *requester_headers = zmq_socket (context, ZMQ_PUB); 462 | char hostname[28]; 463 | int rc; 464 | 465 | if (lcore_id > 99){ 466 | rte_exit(EXIT_FAILURE, "Lcore %u is out of range", lcore_id); 467 | } 468 | 469 | //Starting port: 5550, 5551, 5552, etc. 470 | if (publishto == NULL){ 471 | snprintf(hostname, 21, "tcp://127.0.0.1:55%.2d", lcore_id); 472 | printf("Setting up ZMQ from lcore %u on socket %s %lu \n", lcore_id, hostname, sizeof(hostname)); 473 | rc = zmq_bind (requester, hostname); 474 | } else { 475 | snprintf(hostname, 28, "tcp://%s:55%.2d", publishto, lcore_id); 476 | printf("Connecting ZMQ from lcore %u to publish to socket %s %lu \n", lcore_id, hostname, sizeof(hostname)); 477 | rc = zmq_connect (requester, hostname); 478 | } 479 | 480 | if (rc != 0 || requester == NULL) { 481 | rte_exit(EXIT_FAILURE, "Unable to create zmq connection on lcore %u . Issue: %s", lcore_id, zmq_strerror (errno)); 482 | } 483 | 484 | lcore_conf[lcore_id].zmq_client = requester; 485 | } 486 | 487 | 488 | 489 | /* stats loop */ 490 | static void 491 | dpdklatency_stats_loop(void) 492 | { 493 | unsigned lcore_id; 494 | uint64_t prev_tsc, diff_tsc, cur_tsc, timer_tsc; 495 | const uint64_t drain_tsc = (rte_get_tsc_hz() + US_PER_S - 1) / US_PER_S * BURST_TX_DRAIN_US; 496 | 497 | prev_tsc = 0; 498 | timer_tsc = 0; 499 | 500 | lcore_id = rte_lcore_id(); 501 | 502 | RTE_LOG(INFO, DPDKLATENCY, "entering stats loop on lcore %u\n", lcore_id); 503 | 504 | while (!force_quit) { 505 | cur_tsc = rte_rdtsc(); 506 | diff_tsc = cur_tsc - prev_tsc; 507 | if (unlikely(diff_tsc > drain_tsc)) { 508 | /* if timer is enabled */ 509 | if (timer_period > 0) { 510 | /* advance the timer */ 511 | timer_tsc += diff_tsc; 512 | /* if timer has reached its timeout */ 513 | if (unlikely(timer_tsc >= (uint64_t) timer_period)) { 514 | print_stats(); 515 | /* reset the timer */ 516 | timer_tsc = 0; 517 | } 518 | } 519 | prev_tsc = cur_tsc; 520 | } 521 | } 522 | } 523 | 524 | /* packet processing loop */ 525 | static void 526 | dpdklatency_processing_loop(void) 527 | { 528 | struct rte_mbuf *pkts_burst[MAX_PKT_BURST]; 529 | unsigned lcore_id; 530 | unsigned i, j, portid, queueid, nb_rx; 531 | struct lcore_conf *qconf; 532 | struct rte_mbuf *m; 533 | uint64_t prev_tsc, diff_tsc, cur_tsc; 534 | const uint64_t drain_tsc = (rte_get_tsc_hz() + US_PER_S - 1) / US_PER_S * BURST_TX_DRAIN_US; 535 | uint64_t ipv4_timestamp_syn[TIMESTAMP_HASH_ENTRIES] __rte_cache_aligned; 536 | uint64_t ipv4_timestamp_synack[TIMESTAMP_HASH_ENTRIES] __rte_cache_aligned; 537 | 538 | prev_tsc = 0; 539 | 540 | lcore_id = rte_lcore_id(); 541 | qconf = &lcore_conf[lcore_id]; 542 | 543 | /* Init ZMQ */ 544 | init_zmq_for_lcore(lcore_id); 545 | 546 | if (qconf->n_rx_queue == 0) { 547 | RTE_LOG(INFO, DPDKLATENCY, "lcore %u has nothing to do - no RX queue assigned\n", lcore_id); 548 | return; 549 | } 550 | 551 | for (i = 0; i < qconf->n_rx_queue; i++) { 552 | portid = qconf->rx_queue_list[i].port_id; 553 | queueid = qconf->rx_queue_list[i].queue_id; 554 | RTE_LOG(INFO, DPDKLATENCY, " -- lcoreid=%u portid=%hhu " 555 | "rxqueueid=%hhu\n", lcore_id, portid, queueid); 556 | } 557 | 558 | while (!force_quit) { 559 | cur_tsc = rte_rdtsc(); 560 | 561 | /* TX burst queue drain */ 562 | diff_tsc = cur_tsc - prev_tsc; 563 | if (unlikely(diff_tsc > drain_tsc)) { 564 | 565 | for (portid = 0; portid < RTE_MAX_ETHPORTS; portid++) { 566 | if (qconf->tx_mbufs[portid].len == 0) 567 | continue; 568 | dpdklatency_send_burst(&lcore_conf[lcore_id], 569 | qconf->tx_mbufs[portid].len, 570 | (uint8_t) portid); 571 | 572 | qconf->tx_mbufs[portid].len = 0; 573 | } 574 | prev_tsc = cur_tsc; 575 | } 576 | 577 | for (i = 0; i < qconf->n_rx_queue; i++) { 578 | portid = qconf->rx_queue_list[i].port_id; 579 | queueid = qconf->rx_queue_list[i].queue_id; 580 | 581 | /* Reading from RX queue */ 582 | nb_rx = rte_eth_rx_burst(portid, queueid, pkts_burst, MAX_PKT_BURST); 583 | //if (nb_rx > 0){ 584 | // printf("reading from portid %u, queueid %u\n", portid, queueid); 585 | //} 586 | lcore_statistics[lcore_id].rx += nb_rx; 587 | 588 | for (j = 0; j < nb_rx; j++) { 589 | m = pkts_burst[j]; 590 | rte_prefetch0(rte_pktmbuf_mtod(m, void *)); 591 | 592 | // Call the latency tracker function for every packet 593 | track_latency(m, ipv4_timestamp_syn, ipv4_timestamp_synack); 594 | 595 | /* Forward packets if forwarding is enabled */ 596 | if (forwarding){ 597 | dpdklatency_send_packet(m, (uint8_t) !portid); 598 | } else { 599 | // drop it like it's hot 600 | rte_pktmbuf_free(m); 601 | } 602 | } 603 | } 604 | } 605 | } 606 | 607 | /* display usage */ 608 | static void 609 | dpdklatency_usage(const char *prgname) 610 | { 611 | printf("%s [EAL options] -- -p PORTMASK [-q NQ] [-T PERIOD] [--config (port, queue, lcore)] [--publishto IP] [--debug] [--forwarding]\n" 612 | " -p PORTMASK: hexadecimal bitmask of ports to configure\n" 613 | " -q NQ: number of queue (=ports) per lcore (default is 1)\n" 614 | " -T PERIOD: statistics will be refreshed each PERIOD seconds (0 to disable, 10 default, 86400 maximum)\n" 615 | " --config (port,queue,lcore)[,(port,queue,lcore)]\n" 616 | " --publishto IP: publish to a specific IP (where analytics is running). If not specified, this program binds.\n" 617 | " --debug: shows captured flows\n" 618 | " --[no-]forwarding: Enable or disable forwarding (disabled by default)\n" 619 | " When enabled, the app forwards packets between port 0 and 1:\n", 620 | prgname); 621 | } 622 | 623 | 624 | static int 625 | dpdklatency_parse_ip(const char *q_arg) 626 | { 627 | int i; 628 | publishto = q_arg; 629 | // very simple checks: parameter is not null and it contains a . (e.g., 10.0.0.1) 630 | if (q_arg == NULL){ 631 | publishto = NULL; 632 | return -1; 633 | } 634 | for (i=0; q_arg[i]; q_arg[i]=='.' ? i++ : *q_arg++); 635 | if (i != 3){ 636 | publishto = NULL; 637 | return -1; 638 | } 639 | return 0; 640 | } 641 | 642 | static int 643 | dpdklatency_parse_config(const char *q_arg) 644 | { 645 | char s[256]; 646 | const char *p, *p0 = q_arg; 647 | char *end; 648 | enum fieldnames { 649 | FLD_PORT = 0, 650 | FLD_QUEUE, 651 | FLD_LCORE, 652 | _NUM_FLD 653 | }; 654 | unsigned long int_fld[_NUM_FLD]; 655 | char *str_fld[_NUM_FLD]; 656 | int i; 657 | unsigned size; 658 | 659 | nb_lcore_params = 0; 660 | 661 | while ((p = strchr(p0,'(')) != NULL) { 662 | ++p; 663 | if((p0 = strchr(p,')')) == NULL) 664 | return -1; 665 | 666 | size = p0 - p; 667 | if(size >= sizeof(s)) 668 | return -1; 669 | 670 | snprintf(s, sizeof(s), "%.*s", size, p); 671 | if (rte_strsplit(s, sizeof(s), str_fld, _NUM_FLD, ',') != 672 | _NUM_FLD) 673 | return -1; 674 | for (i = 0; i < _NUM_FLD; i++){ 675 | errno = 0; 676 | int_fld[i] = strtoul(str_fld[i], &end, 0); 677 | if (errno != 0 || end == str_fld[i] || int_fld[i] > 678 | 255) 679 | return -1; 680 | } 681 | if (nb_lcore_params >= MAX_LCORE_PARAMS) { 682 | printf("exceeded max number of lcore params: %hu\n", 683 | nb_lcore_params); 684 | return -1; 685 | } 686 | lcore_params_array[nb_lcore_params].port_id = 687 | (uint8_t)int_fld[FLD_PORT]; 688 | lcore_params_array[nb_lcore_params].queue_id = 689 | (uint8_t)int_fld[FLD_QUEUE]; 690 | lcore_params_array[nb_lcore_params].lcore_id = 691 | (uint8_t)int_fld[FLD_LCORE]; 692 | ++nb_lcore_params; 693 | } 694 | lcore_params = lcore_params_array; 695 | 696 | return 0; 697 | } 698 | 699 | static int 700 | dpdklatency_parse_portmask(const char *portmask) 701 | { 702 | char *end = NULL; 703 | unsigned long pm; 704 | 705 | /* parse hexadecimal string */ 706 | pm = strtoul(portmask, &end, 16); 707 | if ((portmask[0] == '\0') || (end == NULL) || (*end != '\0')) 708 | return -1; 709 | 710 | if (pm == 0) 711 | return -1; 712 | 713 | return pm; 714 | } 715 | 716 | static int 717 | dpdklatency_parse_timer_period(const char *q_arg) 718 | { 719 | char *end = NULL; 720 | int n; 721 | 722 | /* parse number string */ 723 | n = strtol(q_arg, &end, 10); 724 | if ((q_arg[0] == '\0') || (end == NULL) || (*end != '\0')) 725 | return -1; 726 | if (n >= MAX_TIMER_PERIOD) 727 | return -1; 728 | 729 | return n; 730 | } 731 | 732 | /* Parse the argument given in the command line of the application */ 733 | static int 734 | dpdklatency_parse_args(int argc, char **argv) 735 | { 736 | int opt, ret, timer_secs; 737 | char **argvopt; 738 | int option_index; 739 | char *prgname = argv[0]; 740 | static struct option lgopts[] = { 741 | { "config", 1, 0, 0}, 742 | { "publishto", 1, 0, 0}, 743 | { "debug", no_argument, &debug, 1}, 744 | { "forwarding", no_argument, &forwarding, 1}, 745 | { "no-forwarding", no_argument, &forwarding, 0}, 746 | {NULL, 0, 0, 0} 747 | }; 748 | 749 | argvopt = argv; 750 | 751 | while ((opt = getopt_long(argc, argvopt, "p:q:T:", 752 | lgopts, &option_index)) != EOF) { 753 | 754 | switch (opt) { 755 | /* portmask */ 756 | case 'p': 757 | dpdklatency_enabled_port_mask = dpdklatency_parse_portmask(optarg); 758 | if (dpdklatency_enabled_port_mask == 0) { 759 | printf("invalid portmask\n"); 760 | dpdklatency_usage(prgname); 761 | return -1; 762 | } 763 | break; 764 | 765 | /* timer period */ 766 | case 'T': 767 | timer_secs = dpdklatency_parse_timer_period(optarg); 768 | if (timer_secs < 0) { 769 | printf("invalid timer period\n"); 770 | dpdklatency_usage(prgname); 771 | return -1; 772 | } 773 | timer_period = timer_secs; 774 | break; 775 | 776 | /* long options */ 777 | case 0: 778 | if (!strncmp(lgopts[option_index].name, "config", 6)) { 779 | ret = dpdklatency_parse_config(optarg); 780 | if (ret) { 781 | printf("invalid config\n"); 782 | dpdklatency_usage(prgname); 783 | return -1; 784 | } 785 | } 786 | if (!strncmp(lgopts[option_index].name, "publishto", 9)) { 787 | ret = dpdklatency_parse_ip(optarg); 788 | if (ret) { 789 | printf("invalid publishto\n"); 790 | dpdklatency_usage(prgname); 791 | return -1; 792 | } 793 | } 794 | break; 795 | 796 | default: 797 | dpdklatency_usage(prgname); 798 | return -1; 799 | } 800 | } 801 | 802 | if (optind >= 0) 803 | argv[optind-1] = prgname; 804 | 805 | ret = optind-1; 806 | optind = 0; /* reset getopt lib */ 807 | return ret; 808 | } 809 | 810 | /* Check the link status of all ports in up to 9s, and print them finally */ 811 | static void 812 | check_all_ports_link_status(uint8_t port_num, uint32_t port_mask) 813 | { 814 | #define CHECK_INTERVAL 100 /* 100ms */ 815 | #define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */ 816 | uint8_t portid, count, all_ports_up, print_flag = 0; 817 | struct rte_eth_link link; 818 | 819 | printf("\nChecking link status"); 820 | fflush(stdout); 821 | for (count = 0; count <= MAX_CHECK_TIME; count++) { 822 | if (force_quit) 823 | return; 824 | all_ports_up = 1; 825 | for (portid = 0; portid < port_num; portid++) { 826 | if (force_quit) 827 | return; 828 | if ((port_mask & (1 << portid)) == 0) 829 | continue; 830 | memset(&link, 0, sizeof(link)); 831 | rte_eth_link_get_nowait(portid, &link); 832 | /* print link status if flag set */ 833 | if (print_flag == 1) { 834 | if (link.link_status) 835 | printf("Port %d Link Up - speed %u " 836 | "Mbps - %s\n", (uint8_t)portid, 837 | (unsigned)link.link_speed, 838 | (link.link_duplex == ETH_LINK_FULL_DUPLEX) ? 839 | ("full-duplex") : ("half-duplex\n")); 840 | else 841 | printf("Port %d Link Down\n", 842 | (uint8_t)portid); 843 | continue; 844 | } 845 | /* clear all_ports_up flag if any link down */ 846 | if (link.link_status == ETH_LINK_DOWN) { 847 | all_ports_up = 0; 848 | break; 849 | } 850 | } 851 | /* after finally printing all link status, get out */ 852 | if (print_flag == 1) 853 | break; 854 | 855 | if (all_ports_up == 0) { 856 | printf("."); 857 | fflush(stdout); 858 | rte_delay_ms(CHECK_INTERVAL); 859 | } 860 | 861 | /* set the print_flag if all ports up or timeout */ 862 | if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) { 863 | print_flag = 1; 864 | printf("done\n"); 865 | } 866 | } 867 | } 868 | 869 | static void 870 | signal_handler(int signum) 871 | { 872 | if (signum == SIGINT || signum == SIGTERM) { 873 | printf("\n\nSignal %d received, preparing to exit...\n", 874 | signum); 875 | force_quit = true; 876 | } 877 | } 878 | 879 | static void 880 | setup_hash(int lcoreid) 881 | { 882 | struct rte_hash_parameters ipv4_timestamp_hash_params = { 883 | .name = NULL, 884 | .entries = TIMESTAMP_HASH_ENTRIES, 885 | .key_len = sizeof(uint64_t), 886 | .hash_func = DEFAULT_HASH_FUNC, 887 | .hash_func_init_val = 0, 888 | }; 889 | 890 | char s[64]; 891 | 892 | /* create ipv4 hash */ 893 | snprintf(s, sizeof(s), "ipv4_timestamp_hash_%d", lcoreid); 894 | ipv4_timestamp_hash_params.name = s; 895 | ipv4_timestamp_hash_params.socket_id = 0; 896 | ipv4_timestamp_lookup_struct[lcoreid] = 897 | rte_hash_create(&ipv4_timestamp_hash_params); 898 | if (ipv4_timestamp_lookup_struct[lcoreid] == NULL) 899 | rte_exit(EXIT_FAILURE, "Unable to create the timestamp hash on " 900 | "socket %d\n", 0); 901 | } 902 | 903 | static int 904 | init_hash(void) 905 | { 906 | struct lcore_conf *qconf; 907 | int socketid = 0; 908 | unsigned lcore_id; 909 | 910 | RTE_LCORE_FOREACH_SLAVE(lcore_id) { 911 | if (rte_lcore_is_enabled(lcore_id) == 0) 912 | continue; 913 | 914 | if (socketid >= NB_SOCKETS) { 915 | rte_exit(EXIT_FAILURE, "Socket %d of lcore %u is " 916 | "out of range %d\n", socketid, 917 | lcore_id, NB_SOCKETS); 918 | } 919 | printf("Setting up hash table for lcore %u, on socket %u\n", lcore_id, socketid); 920 | setup_hash(lcore_id); 921 | 922 | qconf = &lcore_conf[lcore_id]; 923 | qconf->ipv4_lookup_struct = ipv4_timestamp_lookup_struct[lcore_id]; 924 | } 925 | return 0; 926 | } 927 | 928 | 929 | static uint8_t 930 | get_port_n_rx_queues(const uint8_t port) 931 | { 932 | int queue = -1; 933 | uint16_t i; 934 | 935 | for (i = 0; i < nb_lcore_params; ++i) { 936 | if (lcore_params[i].port_id == port && 937 | lcore_params[i].queue_id > queue) 938 | queue = lcore_params[i].queue_id; 939 | } 940 | return (uint8_t)(++queue); 941 | } 942 | 943 | static int 944 | init_lcore_rx_queues(void) 945 | { 946 | uint16_t i, nb_rx_queue; 947 | uint8_t lcore; 948 | 949 | for (i = 0; i < nb_lcore_params; ++i) { 950 | lcore = lcore_params[i].lcore_id; 951 | nb_rx_queue = lcore_conf[lcore].n_rx_queue; 952 | if (nb_rx_queue >= MAX_RX_QUEUE_PER_LCORE) { 953 | printf("error: too many queues (%u) for lcore: %u\n", 954 | (unsigned)nb_rx_queue + 1, (unsigned)lcore); 955 | return -1; 956 | } else { 957 | lcore_conf[lcore].rx_queue_list[nb_rx_queue].port_id = 958 | lcore_params[i].port_id; 959 | lcore_conf[lcore].rx_queue_list[nb_rx_queue].queue_id = 960 | lcore_params[i].queue_id; 961 | lcore_conf[lcore].n_rx_queue++; 962 | } 963 | } 964 | return 0; 965 | } 966 | 967 | int 968 | main(int argc, char **argv) 969 | { 970 | struct lcore_conf *qconf; 971 | int ret; 972 | uint8_t nb_ports, nb_rx_queue; 973 | uint8_t nb_ports_available; 974 | uint8_t portid, queueid, queue; 975 | char *publish_host; 976 | unsigned lcore_id; 977 | 978 | /* init EAL */ 979 | ret = rte_eal_init(argc, argv); 980 | if (ret < 0) 981 | rte_exit(EXIT_FAILURE, "Invalid EAL arguments\n"); 982 | argc -= ret; 983 | argv += ret; 984 | 985 | force_quit = false; 986 | signal(SIGINT, signal_handler); 987 | signal(SIGTERM, signal_handler); 988 | 989 | /* parse application arguments (after the EAL ones) */ 990 | ret = dpdklatency_parse_args(argc, argv); 991 | if (ret < 0) 992 | rte_exit(EXIT_FAILURE, "Invalid DPDKLATENCY arguments\n"); 993 | 994 | ret = init_lcore_rx_queues(); 995 | if (ret < 0) 996 | rte_exit(EXIT_FAILURE, "init_lcore_rx_queues failed\n"); 997 | 998 | 999 | /* convert to number of cycles */ 1000 | timer_period *= rte_get_timer_hz(); 1001 | 1002 | /* create the mbuf pool */ 1003 | dpdklatency_pktmbuf_pool = rte_pktmbuf_pool_create("mbuf_pool", NB_MBUF, 1004 | MEMPOOL_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, 1005 | rte_socket_id()); 1006 | if (dpdklatency_pktmbuf_pool == NULL) 1007 | rte_exit(EXIT_FAILURE, "Cannot init mbuf pool\n"); 1008 | 1009 | nb_ports = rte_eth_dev_count(); 1010 | if (nb_ports == 0) 1011 | rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n"); 1012 | 1013 | qconf = NULL; 1014 | 1015 | nb_ports_available = nb_ports; 1016 | 1017 | /* Initialise each port */ 1018 | for (portid = 0; portid < nb_ports; portid++) { 1019 | /* skip ports that are not enabled */ 1020 | if ((dpdklatency_enabled_port_mask & (1 << portid)) == 0) { 1021 | printf("Skipping disabled port %u\n", (unsigned) portid); 1022 | nb_ports_available--; 1023 | continue; 1024 | } 1025 | 1026 | /* init port */ 1027 | printf("Initializing port %d ... \n", portid ); 1028 | fflush(stdout); 1029 | 1030 | /* init port */ 1031 | nb_rx_queue = get_port_n_rx_queues(portid); 1032 | ret = rte_eth_dev_configure(portid, nb_rx_queue, 1, &port_conf); 1033 | if (ret < 0) 1034 | rte_exit(EXIT_FAILURE, "Cannot configure device: err=%d, port=%u\n", 1035 | ret, (unsigned) portid); 1036 | 1037 | /* init one TX queue (queue id is 0) on each port */ 1038 | ret = rte_eth_tx_queue_setup(portid, 0, nb_txd, 1039 | rte_eth_dev_socket_id(portid), 1040 | NULL); 1041 | if (ret < 0) 1042 | rte_exit(EXIT_FAILURE, "rte_eth_tx_queue_setup:err=%d, port=%u\n", 1043 | ret, (unsigned) portid); 1044 | 1045 | 1046 | /* Initialize TX buffers */ 1047 | tx_buffer[portid] = rte_zmalloc_socket("tx_buffer", 1048 | RTE_ETH_TX_BUFFER_SIZE(MAX_PKT_BURST), 0, 1049 | rte_eth_dev_socket_id(portid)); 1050 | if (tx_buffer[portid] == NULL) 1051 | rte_exit(EXIT_FAILURE, "Cannot allocate buffer for tx on port %u\n", 1052 | (unsigned) portid); 1053 | 1054 | rte_eth_tx_buffer_init(tx_buffer[portid], MAX_PKT_BURST); 1055 | 1056 | } 1057 | 1058 | // TODO: maybe use separate mbuf pool per lcore? 1059 | /* init hash */ 1060 | ret = init_hash(); 1061 | if (ret < 0) 1062 | rte_exit(EXIT_FAILURE, "init_hash failed\n"); 1063 | 1064 | /* Init RX queues */ 1065 | for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) { 1066 | if (rte_lcore_is_enabled(lcore_id) == 0) 1067 | continue; 1068 | qconf = &lcore_conf[lcore_id]; 1069 | for(queue = 0; queue < qconf->n_rx_queue; ++queue) { 1070 | portid = qconf->rx_queue_list[queue].port_id; 1071 | queueid = qconf->rx_queue_list[queue].queue_id; 1072 | 1073 | printf("setting up rx queue on port %u, queue %u\n", portid, queueid); 1074 | ret = rte_eth_rx_queue_setup(portid, queueid, nb_rxd, 1075 | rte_eth_dev_socket_id(portid), 1076 | NULL, 1077 | dpdklatency_pktmbuf_pool); 1078 | if (ret < 0) 1079 | rte_exit(EXIT_FAILURE, "rte_eth_rx_queue_setup:err=%d, port=%u\n", 1080 | ret, (unsigned) portid); 1081 | } 1082 | } 1083 | 1084 | for (portid = 0; portid < nb_ports; portid++) { 1085 | /* Start device */ 1086 | ret = rte_eth_dev_start(portid); 1087 | if (ret < 0) 1088 | rte_exit(EXIT_FAILURE, "rte_eth_dev_start:err=%d, port=%u\n", 1089 | ret, (unsigned) portid); 1090 | 1091 | printf("done: \n"); 1092 | 1093 | rte_eth_promiscuous_enable(portid); 1094 | 1095 | /* initialize port stats */ 1096 | memset(&lcore_statistics, 0, sizeof(lcore_statistics)); 1097 | } 1098 | 1099 | if (!nb_ports_available) { 1100 | rte_exit(EXIT_FAILURE, 1101 | "All available ports are disabled. Please set portmask.\n"); 1102 | } 1103 | 1104 | check_all_ports_link_status(nb_ports, dpdklatency_enabled_port_mask); 1105 | 1106 | ret = 0; 1107 | 1108 | /* launch stats on core 0 */ 1109 | rte_eal_remote_launch((lcore_function_t *) dpdklatency_stats_loop, NULL, CALL_MASTER); 1110 | RTE_LCORE_FOREACH_SLAVE(lcore_id) { 1111 | rte_eal_remote_launch((lcore_function_t *) dpdklatency_processing_loop, NULL, lcore_id); 1112 | } 1113 | 1114 | RTE_LCORE_FOREACH_SLAVE(lcore_id) { 1115 | if (rte_eal_wait_lcore(lcore_id) < 0) { 1116 | return -1; 1117 | } 1118 | //TODO: free zmq things 1119 | } 1120 | 1121 | for (portid = 0; portid < nb_ports; portid++) { 1122 | if ((dpdklatency_enabled_port_mask & (1 << portid)) == 0) 1123 | continue; 1124 | printf("Closing port %d...", portid); 1125 | rte_eth_dev_stop(portid); 1126 | rte_eth_dev_close(portid); 1127 | printf(" Done\n"); 1128 | } 1129 | 1130 | printf("Bye...\n"); 1131 | 1132 | return ret; 1133 | } 1134 | -------------------------------------------------------------------------------- /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2", "react"], 3 | "plugins": [ 4 | "glslify", 5 | "static-fs", 6 | "transform-class-properties", 7 | "transform-decorators-legacy" 8 | ], 9 | "compact": "false" 10 | } 11 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:argon 2 | 3 | RUN mkdir -p /usr/src/app 4 | WORKDIR /usr/src/app 5 | 6 | RUN apt-get update 7 | RUN apt-get install -y libtool pkg-config build-essential autoconf automake 8 | RUN apt-get install -y libzmq3-dev 9 | 10 | COPY package.json /usr/src/app/ 11 | RUN npm install 12 | 13 | # Bundle app source 14 | COPY . /usr/src/app 15 | 16 | RUN npm install -g envify browserify 17 | 18 | EXPOSE 3000 3001 6080 19 | 20 | CMD echo $MAPBOX_TOKEN && npm run build && npm start 21 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | This map is based on Mapbox and Web.GL. Most parts are written in React and compiled into Javascript. Files are served by Node.js. 4 | 5 | 6 | ## Requirements 7 | 8 | In order to run the map, you will have to aquire an API key from Mapbox. It is free: http://mapbox.com. You will have to provide this key to the container that runs the UI. 9 | 10 | ## Build me (takes around 15 minutes) 11 | 12 | sudo docker build -t frontend . 13 | 14 | ## Run me 15 | 16 | Don't forget to set your token and the IP of your Influx container here. 17 | 18 | sudo docker run -d -p 3001:3001 -p 3000:3000 -p 127.0.0.1:6080:6080 -e MAPBOX_TOKEN='token_here' -e INFLUX_HOST='ip_here' frontend 19 | -------------------------------------------------------------------------------- /frontend/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var index = require('./routes/index'); 9 | var stats = require('./routes/stats'); 10 | 11 | var app = express(); 12 | 13 | // view engine setup 14 | app.set('views', path.join(__dirname, 'views')); 15 | app.set('view engine', 'jade'); 16 | 17 | // uncomment after placing your favicon in /public 18 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 19 | app.use(logger('dev')); 20 | app.use(bodyParser.json()); 21 | app.use(bodyParser.urlencoded({ extended: false })); 22 | app.use(cookieParser()); 23 | app.use(express.static(path.join(__dirname, 'public'))); 24 | 25 | app.use('/', index); 26 | app.use('/stats', stats.router); 27 | 28 | // catch 404 and forward to error handler 29 | app.use(function(req, res, next) { 30 | var err = new Error('Not Found'); 31 | err.status = 404; 32 | next(err); 33 | }); 34 | 35 | // error handler 36 | app.use(function(err, req, res, next) { 37 | // set locals, only providing error in development 38 | res.locals.message = err.message; 39 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 40 | 41 | // render the error page 42 | res.status(err.status || 500); 43 | res.render('error'); 44 | }); 45 | 46 | module.exports = app; 47 | -------------------------------------------------------------------------------- /frontend/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('latency:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "latency", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "NODE_ENV=production browserify src/react.js -o public/javascripts/bundle.js -t babelify -t sassify -t glslify -t envify", 7 | "start": "node bin/www" 8 | }, 9 | "dependencies": { 10 | "autobind-decorator": "^1.3.3", 11 | "babel-polyfill": "^6.1.19", 12 | "body-parser": "~1.15.2", 13 | "cookie-parser": "~1.4.3", 14 | "debug": "~2.2.0", 15 | "deck.gl": "^3.0.7", 16 | "earcut": "^2.0.6", 17 | "express": "~4.14.0", 18 | "extrude-polyline": "^1.0.6", 19 | "geojson-normalize": "0.0.1", 20 | "get-pixels": "^3.3.0", 21 | "gl-matrix": "^2.3.2", 22 | "glslify": "^5.0.0", 23 | "influx": "^5.0.0-alpha.4", 24 | "jade": "~1.11.0", 25 | "lodash.flattendeep": "^3.0.2", 26 | "luma.gl": "^2.10.0", 27 | "morgan": "~1.7.0", 28 | "react-map-gl": "^1.7.2", 29 | "save-pixels": "^2.3.0", 30 | "serve-favicon": "~2.3.0", 31 | "socket.io": "~1.6.0", 32 | "viewport-mercator-project": "3.0.0-beta2", 33 | "webgl-obj-loader": "^0.1.1", 34 | "zmq": "~2.15.3" 35 | }, 36 | "devDependencies": { 37 | "babel-cli": "^6.3.15", 38 | "babel-core": "^6.7.7", 39 | "babel-eslint": "^6.0.0", 40 | "babel-plugin-glslify": "1.0.2", 41 | "babel-plugin-static-fs": "^1.1.0", 42 | "babel-plugin-transform-class-properties": "^6.18.0", 43 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 44 | "babel-preset-es2015": "^6.3.13", 45 | "babel-preset-react": "^6.3.13", 46 | "babel-preset-stage-2": "^6.3.13", 47 | "babel-runtime": "^6.11.6", 48 | "babelify": "^7.2.0", 49 | "brfs-babel": "^1.0.0", 50 | "browserify": "13.0.0", 51 | "budo": "^8.0.3", 52 | "d3-color": "^1.0.1", 53 | "d3-request": "^1.0.2", 54 | "d3-scale": "^1.0.3", 55 | "discify": "^1.2.0", 56 | "electron-prebuilt": "^0.37.2", 57 | "envify": "^3.4.0", 58 | "eslint": "^2.0.0", 59 | "eslint-config-uber-es2015": "^2.0.0", 60 | "eslint-config-uber-jsx": "^2.0.0", 61 | "eslint-plugin-react": "^5.0.0", 62 | "faucet": "0.0.1", 63 | "gl": "^4.0.1", 64 | "highlight.js": "^9.7.0", 65 | "immutable": "^3.7.5", 66 | "luma.gl": "^2.10.0", 67 | "marked": "^0.3.6", 68 | "react": "^15.3.2", 69 | "react-addons-pure-render-mixin": "^15.3.2", 70 | "react-addons-test-utils": "^15.0.2", 71 | "react-dom": "^15.3.2", 72 | "react-map-gl": "^1.7.2", 73 | "react-redux": "^4.4.5", 74 | "react-router": "^2.8.1", 75 | "redux": "^3.6.0", 76 | "redux-thunk": "^2.1.0", 77 | "sassify": "^2.0.0", 78 | "stats.js": "^0.17.0", 79 | "tap-browser-color": "^0.1.2", 80 | "tape": "^4.6.0", 81 | "tape-catch": "^1.0.4", 82 | "testron": "^1.2.0", 83 | "tween.js": "^16.3.5", 84 | "uglify-js": "^2.6.1" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /frontend/public/images/flags/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/.DS_Store -------------------------------------------------------------------------------- /frontend/public/images/flags/ad_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ad_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ae_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ae_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/af_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/af_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ag_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ag_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ai_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ai_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/al_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/al_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/am_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/am_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ao_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ao_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/aq_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/aq_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ar_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ar_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/as_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/as_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/at_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/at_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/au_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/au_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/aw_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/aw_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ax_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ax_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/az_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/az_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ba_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ba_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bb_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bb_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bd_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bd_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/be_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/be_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bf_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bf_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bg_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bg_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bh_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bh_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bi_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bi_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bj_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bj_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bl_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bl_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bm_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bm_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bn_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bn_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bo_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bo_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bq_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bq_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/br_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/br_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bs_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bs_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bt_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bt_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bv_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bv_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bw_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bw_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/by_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/by_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/bz_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/bz_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ca_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ca_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/cc_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/cc_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/cd_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/cd_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/cf_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/cf_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/cg_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/cg_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ch_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ch_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ci_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ci_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ck_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ck_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/cl_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/cl_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/cm_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/cm_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/cn_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/cn_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/co_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/co_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/cr_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/cr_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/cu_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/cu_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/cv_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/cv_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/cw_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/cw_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/cx_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/cx_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/cy_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/cy_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/cz_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/cz_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/de_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/de_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/dj_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/dj_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/dk_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/dk_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/dm_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/dm_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/do_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/do_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/dz_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/dz_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ec_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ec_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ee_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ee_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/eg_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/eg_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/eh_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/eh_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/er_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/er_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/es_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/es_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/et_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/et_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/fi_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/fi_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/fj_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/fj_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/fk_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/fk_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/fm_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/fm_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/fo_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/fo_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/fr_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/fr_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ga_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ga_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gb_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gb_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gd_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gd_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ge_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ge_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gf_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gf_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gg_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gg_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gh_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gh_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gi_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gi_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gl_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gl_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gm_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gm_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gn_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gn_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gp_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gp_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gq_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gq_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gr_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gr_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gs_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gs_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gt_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gt_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gu_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gu_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gw_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gw_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/gy_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/gy_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/hk_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/hk_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/hm_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/hm_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/hn_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/hn_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/hr_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/hr_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ht_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ht_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/hu_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/hu_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/id_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/id_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ie_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ie_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/il_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/il_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/im_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/im_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/in_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/in_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/io_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/io_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/iq_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/iq_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ir_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ir_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/is_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/is_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/it_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/it_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/je_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/je_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/jm_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/jm_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/jo_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/jo_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/jp_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/jp_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ke_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ke_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/kg_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/kg_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/kh_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/kh_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ki_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ki_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/km_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/km_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/kn_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/kn_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/kp_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/kp_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/kr_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/kr_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/kw_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/kw_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ky_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ky_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/kz_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/kz_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/la_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/la_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/lb_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/lb_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/lc_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/lc_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/li_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/li_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/lk_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/lk_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/lr_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/lr_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ls_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ls_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/lt_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/lt_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/lu_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/lu_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/lv_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/lv_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ly_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ly_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ma_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ma_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mc_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mc_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/md_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/md_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/me_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/me_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mf_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mf_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mg_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mg_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mh_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mh_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mk_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mk_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ml_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ml_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mm_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mm_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mn_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mn_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mo_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mo_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mp_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mp_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mq_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mq_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mr_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mr_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ms_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ms_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mt_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mt_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mu_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mu_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mv_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mv_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mw_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mw_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mx_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mx_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/my_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/my_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/mz_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/mz_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/na_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/na_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/nc_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/nc_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ne_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ne_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/nf_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/nf_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ng_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ng_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ni_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ni_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/nl_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/nl_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/no_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/no_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/np_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/np_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/nr_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/nr_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/nu_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/nu_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/nz_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/nz_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/om_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/om_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/pa_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/pa_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/pe_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/pe_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/pf_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/pf_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/pg_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/pg_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ph_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ph_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/pk_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/pk_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/pl_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/pl_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/pm_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/pm_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/pn_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/pn_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/pr_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/pr_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ps_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ps_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/pt_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/pt_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/pw_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/pw_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/py_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/py_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/qa_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/qa_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/re_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/re_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ro_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ro_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/rs_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/rs_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ru_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ru_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/rw_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/rw_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sa_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sa_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sb_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sb_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sc_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sc_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sd_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sd_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/se_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/se_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sg_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sg_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sh_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sh_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/si_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/si_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sj_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sj_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sk_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sk_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sl_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sl_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sm_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sm_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sn_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sn_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/so_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/so_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sr_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sr_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ss_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ss_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/st_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/st_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sv_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sv_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sx_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sx_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sy_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sy_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/sz_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/sz_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/tc_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/tc_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/td_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/td_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/tf_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/tf_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/tg_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/tg_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/th_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/th_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/tj_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/tj_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/tk_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/tk_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/tl_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/tl_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/tm_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/tm_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/tn_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/tn_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/to_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/to_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/tr_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/tr_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/tt_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/tt_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/tv_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/tv_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/tw_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/tw_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/tz_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/tz_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ua_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ua_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ug_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ug_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/um_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/um_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/us_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/us_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/uy_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/uy_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/uz_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/uz_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/va_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/va_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/vc_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/vc_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ve_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ve_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/vg_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/vg_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/vi_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/vi_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/vn_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/vn_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/vu_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/vu_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/wf_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/wf_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ws_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ws_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/ye_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/ye_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/yt_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/yt_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/za_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/za_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/zm_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/zm_64.png -------------------------------------------------------------------------------- /frontend/public/images/flags/zw_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czivar/ruru/7164074d04df49a3e193b10a93db548ede3709ef/frontend/public/images/flags/zw_64.png -------------------------------------------------------------------------------- /frontend/public/javascripts/stats.js: -------------------------------------------------------------------------------- 1 | //Search 2 | var $rows = $('#statistics tbody tr'); 3 | $('#search').keyup(function() { 4 | var val = $.trim($(this).val()).replace(/ +/g, ' ').toLowerCase(); 5 | 6 | $rows.show().filter(function() { 7 | var text = $(this).text().replace(/\s+/g, ' ').toLowerCase(); 8 | return !~text.indexOf(val); 9 | }).hide(); 10 | }); -------------------------------------------------------------------------------- /frontend/public/javascripts/viewer.js: -------------------------------------------------------------------------------- 1 | var destinations = []; 2 | 3 | socket.on('counter', function(data){ 4 | var sum = data['sum']; 5 | $('.connectionnumber').text(sum); 6 | }); 7 | 8 | // Map stats (at the bottom of the page) 9 | socket.on('mapstats', function(data){ 10 | data.forEach(function(stats, index){ 11 | source_html = "" 12 | stats.forEach(function(elem){ 13 | source_html += ""); 31 | $('.sourcecity').text(data['source_city']); 32 | $('.destinationcountry').html(data['destination_country']+" "); 33 | $('.destinationcity').text(data['destination_city']); 34 | $('.latency').text(data['latency_total']+' ms'); 35 | }); 36 | 37 | function hello(){ 38 | console.log('whaetver'); 39 | } 40 | -------------------------------------------------------------------------------- /frontend/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 14px; 3 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 4 | height: 100%; 5 | min-height: 75rem; 6 | width: 100%; 7 | background-color: black; 8 | } 9 | 10 | .navbar { 11 | margin-bottom: 0px; 12 | } 13 | 14 | div#reannz-logo { 15 | float: left; 16 | margin-left: 20px; 17 | margin-top: 12px; 18 | } 19 | 20 | table#entries { 21 | display: block; 22 | height: 900px; 23 | overflow-y: scroll; 24 | } 25 | 26 | li.search { 27 | float: right; 28 | padding-top: 5px; 29 | } 30 | 31 | div#randomconnectionbar { 32 | padding: 10px; 33 | color: white; 34 | position: fixed; 35 | bottom: 335px; 36 | width: 250px; 37 | left: 10px; 38 | z-index: 99; 39 | font-size: 12px; 40 | background-color:rgba(66, 191, 244, 0.5); 41 | } 42 | 43 | div#animationbuttons { 44 | clear: both; 45 | padding-bottom: 3px; 46 | } 47 | 48 | div#randomdata { 49 | font-size: 16px; 50 | } 51 | 52 | p.randomtext { 53 | margin-top: 8px; 54 | margin-bottom: 0px; 55 | } 56 | 57 | div#statsbar { 58 | padding-right: 5px; 59 | padding-left: 5px; 60 | padding-top: 10px; 61 | color: white; 62 | position: absolute; 63 | top: 65px; 64 | right: 10px; 65 | z-index: 99; 66 | font-size: 12px; 67 | background-color:rgba(66, 191, 244, 0.5); 68 | } 69 | 70 | div#top5bar { 71 | color: white; 72 | position: fixed; 73 | bottom: 15px; 74 | z-index: 99; 75 | font-size: 12px; 76 | background-color:rgba(66, 191, 244, 0.5); 77 | } 78 | 79 | div.tables { 80 | width: 100%; 81 | display: table; 82 | table-layout: fixed; 83 | } 84 | 85 | div.child { 86 | display: table-cell; 87 | padding-bottom: 0px; 88 | } 89 | 90 | .top5_table { 91 | margin-bottom: 0px; 92 | } 93 | 94 | .top5_table>thead>tr>th { 95 | border: 0px; 96 | } 97 | 98 | .table-condensed>thead>tr>th, .table-condensed>tbody>tr>th, .table-condensed>tfoot>tr>th, .table-condensed>thead>tr>td, .table-condensed>tbody>tr>td, .table-condensed>tfoot>tr>td{ 99 | padding: 2px; 100 | text-align: center; 101 | border: 0px; 102 | } 103 | 104 | .table-striped > tbody > tr:nth-child(2n+1) > td, .table-striped > tbody > tr:nth-child(2n+1) > th { 105 | background-color: rgba(0, 81, 114, 0.6); 106 | border: 0px; 107 | } 108 | 109 | div.statspage { 110 | padding: 30px; 111 | } 112 | 113 | div#scale { 114 | text-align: center; 115 | padding: 5px; 116 | padding-right: 8px; 117 | padding-left: 8px; 118 | } 119 | 120 | div#gradient { 121 | width: 100%; 122 | height: 10px; 123 | background: red; /* For browsers that do not support gradients */ 124 | background: -webkit-linear-gradient(-90deg, red, yellow, green); /* For Safari 5.1 to 6.0 */ 125 | background: -o-linear-gradient(-90deg, red, yellow, green); /* For Opera 11.1 to 12.0 */ 126 | background: -moz-linear-gradient(-90deg, red, yellow, green); /* For Firefox 3.6 to 15 */ 127 | background: linear-gradient(-90deg, red, yellow, green); /* Standard syntax */ 128 | } 129 | 130 | div#left { 131 | float: left; 132 | display: inline; 133 | } 134 | 135 | div#right { 136 | float: right; 137 | display: inline; 138 | } 139 | 140 | div.information { 141 | text-align: center; 142 | } 143 | 144 | div.connectionnumber { 145 | font-size: 22px; 146 | text-align: center; 147 | } 148 | 149 | div#map { 150 | /*background-color: black;*/ 151 | position: relative; 152 | height: 100%; 153 | width: 100%; 154 | z-index: 0; 155 | cursor: crosshair; 156 | } 157 | 158 | div#stats { 159 | height: 450px; 160 | width: 100%; 161 | } 162 | 163 | div#tooltip { 164 | position: absolute; 165 | padding: 4px; 166 | background-color:rgba(106, 141, 250, 0.8); 167 | color: white; 168 | max-width: 400px; 169 | font-size: 14px; 170 | z-index: 99; 171 | pointer-events: none; 172 | } 173 | 174 | -------------------------------------------------------------------------------- /frontend/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var zmq = require('zmq'), client = zmq.socket('sub'); 4 | var stats = require('./stats'); 5 | var app = express(); 6 | 7 | var server = require('http').Server(app); 8 | server.listen(3001); 9 | var io = require('socket.io').listen(server); 10 | 11 | //Sub on ZMQ sock 12 | client.bindSync('tcp://*:6080'); 13 | console.log('Subscribed bound to port 6080'); 14 | 15 | /* GET home page. */ 16 | router.get('/', function(req, res, next) { 17 | res.render('index', { 18 | title: 'Latency viewer' 19 | }); 20 | }); 21 | 22 | client.on('error', function (err) { 23 | console.log('Error ' + err) 24 | }) 25 | 26 | // Subsribe on the latency channel 27 | client.subscribe(''); 28 | 29 | // Connection counter (5 sec resolution) 30 | var counters = []; 31 | var counter = 0; 32 | var tick = 0; 33 | 34 | // Send stats to map page every 5 seconds 35 | setInterval(function() { 36 | counters.push(counter); 37 | counter = 0; 38 | if (counters.length == 12) { 39 | counters.shift(); 40 | } 41 | var sum = counters.reduce(function(a, b) { return a + b; }, 0); 42 | io.emit('counter', {sum: sum}); 43 | io.emit('mapstats', stats.getTop5Stats()); 44 | }, 5000) 45 | 46 | 47 | var previous_source_long; 48 | var previous_destination_long; 49 | 50 | function sendRandomData(m) { 51 | io.emit('randomdata', { 52 | source_country: m['source_country'], 53 | source_countrycode: m['source_countrycode'], 54 | source_city: m['source_city'], 55 | destination_country: m['destination_country'], 56 | destination_countrycode: m['destination_countrycode'], 57 | destination_city: m['destination_city'], 58 | latency_total: m['latency_total'] 59 | }); 60 | } 61 | 62 | var lastTime = 0 63 | // This is the subscriber part 64 | client.on('message', function(message){ 65 | var m = JSON.parse(message); 66 | //console.log("Message: " + m['destination_lat']); 67 | 68 | var source_location_string = m['source_city'] + ' (' + m['source_country'] +')'; 69 | var destination_location_string = m['destination_city'] + ' (' + m['destination_country'] +')'; 70 | 71 | // Drop records that we did not get geolocation for 72 | if (m['source_country'] == '-' || m['destination_country'] == '-') return; 73 | 74 | // Filter out repeated measurements from display 75 | if (m['source_long'] != previous_source_long || m['destination_long'] != previous_destination_long){ 76 | 77 | // Send measurement for the map 78 | io.emit('latency', { 79 | destination_lat: m['destination_lat'], 80 | destination_long: m['destination_long'], 81 | destination_country: m['destination_country'], 82 | destination_city: m['destination_city'], 83 | destination_proxy_type: m['destination_proxy_type'], 84 | destination_as: m['destination_as'], 85 | destination_asn: m['destination_asn'], 86 | source_lat: m['source_lat'], 87 | source_long: m['source_long'], 88 | source_country: m['source_country'], 89 | source_city: m['source_city'], 90 | source_proxy_type: m['source_proxy_type'], 91 | source_as: m['source_as'], 92 | source_asn: m['source_asn'], 93 | latency: m['latency_total'] 94 | }); 95 | 96 | // If more than 5 seconds passed, we catch one measurement and send it to the random data 97 | var timeDiff = (new Date() - lastTime)/1000; 98 | if (lastTime == 0 || (Math.round(timeDiff%60) > 5)){ 99 | sendRandomData(m); 100 | lastTime = new Date(); 101 | } 102 | previous_source_long = m['source_long']; 103 | previous_destination_long = m['destination_long']; 104 | } 105 | 106 | counter++; 107 | }); 108 | 109 | module.exports = router; 110 | -------------------------------------------------------------------------------- /frontend/routes/stats.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | const Influx = require('influx'); 4 | 5 | const influx = new Influx.InfluxDB({ 6 | host: process.env.INFLUX_HOST, 7 | database: 'rtt', 8 | schema: [ 9 | { 10 | measurement: 'latency', 11 | fields: { 12 | label: Influx.FieldType.STRING, 13 | total: Influx.FieldType.INTEGER 14 | }, 15 | tags: [ 16 | 'destination_as', 17 | 'destination_asn', 18 | 'destination_city', 19 | 'destination_country', 20 | 'destination_countrycode', 21 | 'destination_lat', 22 | 'destination_long', 23 | 'source_as', 24 | 'source_asn', 25 | 'source_city', 26 | 'source_country', 27 | 'source_countrycode', 28 | 'source_lat', 29 | 'source_long', 30 | ] 31 | } 32 | ] 33 | }) 34 | 35 | var stats_destination_countries = {}; 36 | var stats_destination_cities = {}; 37 | var stats_source_cities = {}; 38 | var stats_destination_asn = {}; 39 | var stats_source_countries = {}; 40 | var stats_source_asn = {}; 41 | 42 | // This here repeats and queries stats every 20 second 43 | setInterval(function(){ 44 | 45 | influx.query("select min(total), mean(total) as mean, max(total) \ 46 | from latency \ 47 | where destination_country != '-' \ 48 | AND time >= now() - 10m \ 49 | group by destination_country, destination_countrycode") 50 | .then(results => { 51 | stats_destination_countries = {}; 52 | results.forEach(function(row) { 53 | stats_destination_countries[row['destination_country']] = row; 54 | stats_destination_countries[row['destination_country']]['countrycode'] = row['destination_countrycode']; 55 | }); 56 | }); 57 | 58 | influx.query("select min(total), mean(total) as mean, max(total) \ 59 | from latency \ 60 | where source_country != '-' \ 61 | AND time >= now() - 10m \ 62 | group by source_country, source_countrycode") 63 | .then(results => { 64 | stats_source_countries = {}; 65 | results.forEach(function(row) { 66 | stats_source_countries[row['source_country']] = row; 67 | stats_source_countries[row['source_country']]['countrycode'] = row['source_countrycode']; 68 | }); 69 | }); 70 | 71 | influx.query("select min(total), mean(total) as mean, max(total) \ 72 | from latency \ 73 | where source_city != '-' \ 74 | AND time >= now() - 10m \ 75 | group by source_city, source_countrycode") 76 | .then(results => { 77 | stats_source_cities = {}; 78 | results.forEach(function(row) { 79 | stats_source_cities[row['source_city']] = row; 80 | stats_source_cities[row['source_city']]['countrycode'] = row['source_countrycode']; 81 | }); 82 | }); 83 | 84 | influx.query("select min(total), mean(total) as mean, max(total) \ 85 | from latency \ 86 | where destination_city != '-' \ 87 | AND time >= now() - 10m \ 88 | group by destination_city, destination_countrycode") 89 | .then(results => { 90 | stats_destination_cities = {}; 91 | results.forEach(function(row) { 92 | stats_destination_cities[row['destination_city']] = row; 93 | stats_destination_cities[row['destination_city']]['countrycode'] = row['destination_countrycode']; 94 | }); 95 | }); 96 | 97 | influx.query("select min(total), mean(total) as mean, max(total) \ 98 | from latency \ 99 | where destination_as != '-' \ 100 | AND time >= now() - 10m \ 101 | group by destination_asn, destination_as") 102 | .then(results => { 103 | stats_destination_asn = {}; 104 | results.forEach(function(row) { 105 | stats_destination_asn[row['destination_asn']] = row; 106 | stats_destination_asn[row['destination_asn']]['name'] = row['destination_as']; 107 | }); 108 | }); 109 | 110 | influx.query("select min(total), mean(total) as mean, max(total) \ 111 | from latency \ 112 | where source_as != '-' \ 113 | AND time >= now() - 10m \ 114 | group by source_asn, source_as") 115 | .then(results => { 116 | stats_source_asn = {}; 117 | results.forEach(function(row) { 118 | stats_source_asn[row['source_asn']] = row; 119 | stats_source_asn[row['source_asn']]['name'] = row['source_as']; 120 | }); 121 | }); 122 | 123 | //console.log(stats_destination_countries); 124 | 125 | }, 20000); 126 | 127 | 128 | function getTop5Stats () { 129 | 130 | var top5_source = Object.keys(stats_source_cities).map(function(key) { 131 | return [key, stats_source_cities[key]]; 132 | }); 133 | 134 | var top5_dest = Object.keys(stats_destination_countries).map(function(key) { 135 | return [key, stats_destination_countries[key]]; 136 | }); 137 | 138 | var top5_source_sorted = top5_source.sort(function(a, b) { return a[1]['mean'] > b[1]['mean'] ? 1 : -1; }).slice(0, 5); 139 | var top5_destination_slow = top5_dest.sort(function(a, b) { return a[1]['mean'] < b[1]['mean'] ? 1 : -1; }).slice(0, 5); 140 | var top5_destination_fast = top5_dest.sort(function(a, b) { return a[1]['mean'] > b[1]['mean'] ? 1 : -1; }).slice(0, 5); 141 | 142 | return [top5_source_sorted, top5_destination_fast, top5_destination_slow]; 143 | } 144 | 145 | router.get('/', function(req, res, next) { 146 | res.render('stats', { 147 | title: 'Stats', 148 | stats_destination_cities: stats_destination_cities, 149 | stats_destination_countries: stats_destination_countries, 150 | stats_destination_asn: stats_destination_asn, 151 | stats_source_cities: stats_source_cities, 152 | stats_source_countries: stats_source_countries, 153 | stats_source_asn: stats_source_asn 154 | }); 155 | }); 156 | 157 | module.exports = { 158 | router:router, 159 | getTop5Stats:getTop5Stats 160 | } 161 | 162 | -------------------------------------------------------------------------------- /frontend/src/map-utils.js: -------------------------------------------------------------------------------- 1 | import TWEEN from 'tween.js'; 2 | 3 | let viewportTween; 4 | 5 | function animate() { 6 | TWEEN.update(); 7 | requestAnimationFrame(animate); 8 | } 9 | 10 | // get a linear tween 11 | function ease(fromState, toState, duration) { 12 | return new TWEEN.Tween(fromState).to(toState, duration); 13 | } 14 | 15 | // fly to new viewport 16 | function fly(fromViewport, toViewport, duration, onUpdate) { 17 | const fromState = {}; 18 | const nanState = {}; 19 | const toState = {}; 20 | 21 | Object.keys(toViewport).forEach(key => { 22 | const v0 = fromViewport[key]; 23 | const v1 = toViewport[key]; 24 | if (isNaN(v0) || isNaN(v1)) { 25 | nanState[key] = v1; 26 | } else { 27 | fromState[key] = v0; 28 | toState[key] = v1; 29 | } 30 | }); 31 | 32 | if (viewportTween) { 33 | TWEEN.remove(viewportTween); 34 | } 35 | 36 | viewportTween = new TWEEN.Tween(fromState) 37 | .to(toState, duration) 38 | .onUpdate(function() { 39 | onUpdate({...this, ...nanState}); 40 | }); 41 | 42 | return viewportTween; 43 | } 44 | 45 | export default { 46 | init: animate, 47 | Easing: TWEEN.Easing, 48 | ease, 49 | fly 50 | }; -------------------------------------------------------------------------------- /frontend/src/react.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable func-style */ 2 | /* eslint-disable no-console */ 3 | /* global console, process */ 4 | /* global document, window */ 5 | import 'babel-polyfill'; 6 | 7 | import React from 'react'; 8 | import ReactDOM from 'react-dom'; 9 | import {createStore} from 'redux'; 10 | import {Provider, connect} from 'react-redux'; 11 | import autobind from 'autobind-decorator'; 12 | 13 | import MapboxGLMap from 'react-map-gl'; 14 | 15 | import * as request from 'd3-request'; 16 | import DeckGL from 'deck.gl/react'; 17 | import {ArcLayer} from 'deck.gl'; 18 | import ViewportAnimation from './map-utils'; 19 | 20 | // ---- Default Settings ---- // 21 | /* eslint-disable no-process-env */ 22 | const MAPBOX_ACCESS_TOKEN = process.env.MAPBOX_TOKEN; 23 | 24 | const INITIAL_STATE = { 25 | mapViewState: { 26 | latitude: -1.32000000000005, 27 | longitude: 45.486055552412332, 28 | zoom: 1.7195968941767825, 29 | pitch: 26.57984356290917, 30 | bearing: -1.32000000000005 31 | }, 32 | points: null, 33 | arcs: null, 34 | arcStrokeWidth: 3 35 | }; 36 | 37 | const GOTO_NZ = { 38 | latitude: -43.97889477772071, 39 | longitude: 171.3568897450038, 40 | zoom: 1.7195968941767825, 41 | pitch: 26.57984356290917, 42 | bearing: -1.32000000000005 43 | }; 44 | 45 | const LOOKAT_NZ = { 46 | latitude: -43.97889477772071, 47 | longitude: 171.3568897450038, 48 | zoom: 4.7195968941767825, 49 | pitch: 43.87968924799148, 50 | bearing: -9.688794326241123 51 | }; 52 | 53 | const GOTO_EUROPE = { 54 | latitude: 49.97889477772071, 55 | longitude: 12.3568897450038, 56 | zoom: 2.7195968941767825, 57 | pitch: 2.57984356290917, 58 | bearing: -3.32000000000005 59 | }; 60 | 61 | const GOTO_US = { 62 | latitude: 40.97889477772071, 63 | longitude: -98.3568897450038, 64 | zoom: 2.7195968941767825, 65 | pitch: 12.57984356290917, 66 | bearing: -15.32000000000005 67 | }; 68 | 69 | 70 | 71 | // ---- Action ---- // 72 | function addArc(points) { 73 | return {type: 'ADD_NEW_ARC', points}; 74 | } 75 | 76 | function removeArcs() { 77 | return {type: 'REMOVE_ARCS'}; 78 | } 79 | 80 | function updateMap(mapViewState) { 81 | //console.log(mapViewState); 82 | return {type: 'UPDATE_MAP', mapViewState}; 83 | } 84 | 85 | function loadPoints(points) { 86 | return {type: 'LOAD_POINTS', points}; 87 | } 88 | 89 | // Swaps data props when clicked to trigger WebGLBuffer updates 90 | function swapData() { 91 | return {type: 'SWAP_DATA'}; 92 | } 93 | 94 | // ---- Reducer ---- // 95 | function reducer(state = INITIAL_STATE, action) { 96 | switch (action.type) { 97 | case 'ADD_NEW_ARC': { 98 | const arcs = action.points; 99 | const points = []; 100 | arcs.forEach(function(elem){ 101 | points.push({position: [elem['targetPosition'][0], elem['targetPosition'][1]]}); 102 | }) 103 | return {...state, arcs: arcs, points: points}; 104 | } 105 | case 'REMOVE_ARCS': 106 | return {...state, arcs: []}; 107 | case 'UPDATE_MAP': 108 | return {...state, mapViewState: action.mapViewState}; 109 | default: 110 | return state; 111 | } 112 | } 113 | 114 | // redux states -> react props 115 | function mapStateToProps(state) { 116 | return { 117 | mapViewState: state.mapViewState, 118 | points: state.points, 119 | arcs: state.arcs, 120 | arcStrokeWidth: 4 121 | }; 122 | } 123 | 124 | const MainLayer = props => 125 | new ArcLayer({ 126 | ...props, 127 | id: props.id || 'arcLayer', 128 | data: props.arcs, 129 | strokeWidth: props.arcStrokeWidth, 130 | pickable: true, 131 | onHover: props.onHover 132 | }); 133 | 134 | // ---- View ---- // 135 | class MapApp extends React.Component { 136 | constructor(props) { 137 | super(props); 138 | this.state = { 139 | hoveredItem: null 140 | }; 141 | } 142 | 143 | componentWillMount() { 144 | this._handleResize(); 145 | window.addEventListener('resize', this._handleResize); 146 | } 147 | 148 | componentWillUnmount() { 149 | window.removeEventListener('resize', this._handleResize); 150 | } 151 | 152 | @autobind _handleResize() { 153 | this.setState({width: window.innerWidth, height: window.innerHeight}); 154 | } 155 | 156 | @autobind _onHover(info) { 157 | console.log('hover', info); 158 | this.setState({hoveredItem: info}); 159 | } 160 | 161 | @autobind _handleViewportChanged(mapViewState) { 162 | if (mapViewState.pitch > 60) { 163 | mapViewState.pitch = 60; 164 | } 165 | this.props.dispatch(updateMap(mapViewState)); 166 | } 167 | 168 | @autobind _onWebGLInitialized(gl) { 169 | gl.enable(gl.DEPTH_TEST); 170 | gl.depthFunc(gl.LEQUAL); 171 | } 172 | 173 | _renderLayers() { 174 | const props = { 175 | ...this.props, 176 | ...this.state, 177 | onHover: this._onHover 178 | }; 179 | const layers = []; 180 | layers.push(MainLayer(props)); 181 | 182 | return layers; 183 | } 184 | 185 | _onToggleLayer(layerName, layer) { 186 | const activeLayers = {...this.state.activeLayers}; 187 | activeLayers[layerName] = !activeLayers[layerName]; 188 | this.setState({activeLayers}); 189 | } 190 | 191 | _renderOverlay() { 192 | const {arcs, points, mapViewState} = this.props; 193 | const {width, height} = this.state; 194 | 195 | // wait until data is ready before rendering 196 | if (!arcs) { 197 | return []; 198 | } 199 | 200 | return ( 201 | 210 | ); 211 | } 212 | 213 | _renderTooltip() { 214 | const {hoveredItem} = this.state; 215 | if (hoveredItem && hoveredItem.index >= 0) { 216 | const info = hoveredItem.object; 217 | return info && ( 218 |
220 | From: {info.source_city} ({info.source_country})
221 | To: {info.destination_city} ({info.destination_country})
222 | Source AS: AS{info.source_asn} ({info.source_as})
223 | Destination AS: AS{info.destination_asn} ({info.destination_as})
224 | End-to-end latency: {info.latency} ms
225 | Source proxy: {info.source_proxy}. Destination proxy: {info.destination_proxy}. 226 |
227 | ); 228 | } 229 | return null; 230 | } 231 | 232 | render() { 233 | const {mapViewState} = this.props; 234 | const {width, height, hoveredItem, clickedItem} = this.state; 235 | return ( 236 |
237 | 245 | { this._renderOverlay() } 246 | 247 | { this._renderTooltip() } 248 |
249 | ); 250 | } 251 | } 252 | 253 | const store = createStore(reducer); 254 | const container = document.getElementById('map'); 255 | if (container !== null) { 256 | // ---- Main ---- // 257 | const App = connect(mapStateToProps)(MapApp); 258 | document.body.appendChild(container); 259 | ReactDOM.render( 260 | 261 | 262 | , 263 | container 264 | ); 265 | } 266 | 267 | // Generating red to green colors 268 | function hslToRgb(h, s, l){ 269 | var r, g, b; 270 | 271 | if(s == 0){ 272 | r = g = b = l; // achromatic 273 | }else{ 274 | function hue2rgb(p, q, t){ 275 | if(t < 0) t += 1; 276 | if(t > 1) t -= 1; 277 | if(t < 1/6) return p + (q - p) * 6 * t; 278 | if(t < 1/2) return q; 279 | if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; 280 | return p; 281 | } 282 | 283 | var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 284 | var p = 2 * l - q; 285 | r = hue2rgb(p, q, h + 1/3); 286 | g = hue2rgb(p, q, h); 287 | b = hue2rgb(p, q, h - 1/3); 288 | } 289 | 290 | return [Math.floor(r * 255), Math.floor(g * 255), Math.floor(b * 255)]; 291 | } 292 | 293 | function numberToColorHsl(i) { 294 | var hue = i * 1.2 / 360; 295 | var rgb = hslToRgb(hue, 1, .5); 296 | return [ rgb[1], rgb[0], rgb[2], 255 ]; 297 | } 298 | 299 | // Array storing the arcs 300 | const arcs = []; 301 | 302 | function addNewArc(data, callback){ 303 | if (arcs.length > 400){ 304 | arcs.shift(); 305 | } 306 | var color = Math.floor((parseInt(data['latency']) / 500) * 100); 307 | if (color > 100){ 308 | color = 100; 309 | } 310 | 311 | arcs.push({ 312 | sourcePosition: [ 313 | data['source_long'], 314 | data['source_lat'], 315 | 0.0 316 | ], 317 | targetPosition: [ 318 | data['destination_long'], 319 | data['destination_lat'], 320 | 0.0 321 | ], 322 | source_country: data['source_country'], 323 | source_city: data['source_city'], 324 | source_proxy: data['source_proxy_type'], 325 | source_as: data['source_as'], 326 | source_asn: data['source_asn'], 327 | destination_country: data['destination_country'], 328 | destination_city: data['destination_city'], 329 | destination_as: data['destination_as'], 330 | destination_asn: data['destination_asn'], 331 | destination_proxy: data['destination_proxy_type'], 332 | latency: data['latency'], 333 | color: numberToColorHsl(color), 334 | }); 335 | 336 | } 337 | 338 | // Listen on the socket.io socket for new data 339 | socket.on('latency', addNewArc); 340 | 341 | ViewportAnimation.init(); 342 | 343 | function cameraUpdate(mapViewState){ 344 | store.dispatch(updateMap(mapViewState)); 345 | } 346 | 347 | var t0 = ViewportAnimation.fly( 348 | INITIAL_STATE['mapViewState'], 349 | GOTO_NZ, 350 | 9000, 351 | cameraUpdate 352 | ) 353 | .onComplete( function () { 354 | Object.keys(INITIAL_STATE['mapViewState']).forEach(key => { 355 | this[key] = INITIAL_STATE['mapViewState'][key]; 356 | }); 357 | }) 358 | .easing(ViewportAnimation.Easing.Cubic.InOut); 359 | 360 | var t1 = ViewportAnimation.fly( 361 | GOTO_NZ, 362 | LOOKAT_NZ, 363 | 9000, 364 | cameraUpdate 365 | ) 366 | .onComplete( function () { 367 | Object.keys(GOTO_NZ).forEach(key => { 368 | this[key] = GOTO_NZ[key]; 369 | }); 370 | }) 371 | .easing(ViewportAnimation.Easing.Cubic.InOut); 372 | 373 | var t2 = ViewportAnimation.fly( 374 | LOOKAT_NZ, 375 | GOTO_NZ, 376 | 10000, 377 | cameraUpdate 378 | ) 379 | .onComplete( function () { 380 | Object.keys(LOOKAT_NZ).forEach(key => { 381 | this[key] = LOOKAT_NZ[key]; 382 | }); 383 | }) 384 | .easing(ViewportAnimation.Easing.Cubic.InOut); 385 | 386 | var t3 = ViewportAnimation.fly( 387 | GOTO_NZ, 388 | INITIAL_STATE['mapViewState'], 389 | 9000, 390 | cameraUpdate 391 | ) 392 | .onComplete( function () { 393 | Object.keys(GOTO_NZ).forEach(key => { 394 | this[key] = GOTO_NZ[key]; 395 | }); 396 | }) 397 | .easing(ViewportAnimation.Easing.Cubic.InOut); 398 | 399 | var t4 = ViewportAnimation.fly( 400 | INITIAL_STATE['mapViewState'], 401 | GOTO_EUROPE, 402 | 14000, 403 | cameraUpdate 404 | ) 405 | .onComplete( function () { 406 | Object.keys(INITIAL_STATE['mapViewState']).forEach(key => { 407 | this[key] = INITIAL_STATE['mapViewState'][key]; 408 | }); 409 | }) 410 | .easing(ViewportAnimation.Easing.Cubic.InOut); 411 | 412 | var t5 = ViewportAnimation.fly( 413 | GOTO_EUROPE, 414 | INITIAL_STATE['mapViewState'], 415 | 9000, 416 | cameraUpdate 417 | ) 418 | .onComplete( function () { 419 | Object.keys(GOTO_EUROPE).forEach(key => { 420 | this[key] = GOTO_EUROPE[key]; 421 | }); 422 | }) 423 | .easing(ViewportAnimation.Easing.Cubic.InOut); 424 | 425 | var t6 = ViewportAnimation.fly( 426 | INITIAL_STATE['mapViewState'], 427 | GOTO_US, 428 | 9000, 429 | cameraUpdate 430 | ) 431 | .onComplete( function () { 432 | Object.keys(INITIAL_STATE['mapViewState']).forEach(key => { 433 | this[key] = INITIAL_STATE['mapViewState'][key]; 434 | }); 435 | }) 436 | .easing(ViewportAnimation.Easing.Cubic.InOut); 437 | 438 | var t7 = ViewportAnimation.fly( 439 | GOTO_US, 440 | INITIAL_STATE['mapViewState'], 441 | 10000, 442 | cameraUpdate 443 | ) 444 | .onComplete( function () { 445 | Object.keys(GOTO_US).forEach(key => { 446 | this[key] = GOTO_US[key]; 447 | }); 448 | }) 449 | .easing(ViewportAnimation.Easing.Cubic.InOut); 450 | 451 | t0.chain(t1); 452 | t1.chain(t2); 453 | t2.chain(t3); 454 | t3.chain(t4); 455 | t4.chain(t5); 456 | t5.chain(t6); 457 | t6.chain(t7); 458 | t7.chain(t0); 459 | 460 | window.startAnimation = function(){ 461 | t0.start(); 462 | } 463 | 464 | window.stopAnimation = function(){ 465 | t0.stop(); 466 | } 467 | 468 | // Call drawing every one second 469 | setInterval(function(){ 470 | // console.log('redraw map'); 471 | store.dispatch(addArc(arcs.slice())); 472 | }, 1000); 473 | /* eslint-enable func-style */ 474 | /* eslint-enable no-console */ 475 | -------------------------------------------------------------------------------- /frontend/views/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /frontend/views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | div#statsbar 5 | div.information Connections in the last 1 minute: 6 | div.connectionnumber - 7 | div#scale Meaning of colors: 8 | div#gradient 9 | div#legend 10 | div#left 0 ms 11 | div#right 500 ms 12 | div#animationbuttons 13 | button(class="btn btn-primary btn-xs", onclick="startAnimation()") Start Animation 14 | button(class="btn btn-primary btn-xs", onclick="stopAnimation()") Stop Animation 15 | div#randomconnectionbar 16 | div.information Randomly chosen connection 17 | p.randomtext From 18 | div#randomdata.sourcecity - 19 | div#randomdata.sourcecountry - 20 | p.randomtext To 21 | div#randomdata.destinationcity - 22 | div#randomdata.destinationcountry - 23 | p.randomtext Latency 24 | div#randomdata.latency - 25 | div#top5bar 26 | div.tables 27 | div.child 28 | table#top5_table0(class='table table-condensed table-striped top5_table') 29 | thead 30 | tr 31 | th.theader(colspan='3') 32 | Top 5 closest source cities 33 | tr 34 | th Flag 35 | th City 36 | th Latency 37 | tbody 38 | tr 39 | td - 40 | td - 41 | td - 42 | div.child 43 | table#top5_table1(class='table table-condensed table-striped top5_table') 44 | thead 45 | tr 46 | th.theader(colspan='3') 47 | Top 5 closest destination countries 48 | tr 49 | th Flag 50 | th Country 51 | th Latency 52 | tbody 53 | tr 54 | td - 55 | td - 56 | td - 57 | div.child 58 | table#top5_table2(class='table table-condensed table-striped top5_table') 59 | thead 60 | tr 61 | th.theader(colspan='3') 62 | Top 5 furthest destination countries 63 | tr 64 | th Flag 65 | th Country 66 | th Latency 67 | tbody 68 | tr 69 | td - 70 | td - 71 | td - 72 | div#map 73 | script(type='text/javascript', src='/javascripts/bundle.js') 74 | script(type='text/javascript', src='/javascripts/viewer.js') 75 | -------------------------------------------------------------------------------- /frontend/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='//fonts.googleapis.com/css?family=Roboto') 6 | script(src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.7.3/socket.io.js") 7 | script. 8 | var socket = io.connect(':3001'); 9 | script(src='//ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js') 10 | script(src='//cdnjs.cloudflare.com/ajax/libs/tether/1.3.7/js/tether.min.js') 11 | link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css') 12 | link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css') 13 | link(rel='stylesheet', href='/stylesheets/style.css') 14 | body 15 | nav(class='navbar navbar-fixed-top navbar-inverse') 16 | div#container 17 | div#navbar-header 18 | div#reannz-logo 19 | include logo.html 20 | ul(class='nav navbar-nav') 21 | li#nav-item 22 | a#nav-link(href='/') Real-time Connection Map 23 | li#nav-item 24 | a#nav-link(href='/stats') Statistics 25 | div#container 26 | block content 27 | script(src='//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js') 28 | 29 | -------------------------------------------------------------------------------- /frontend/views/logo.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/views/stats.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | div.statspage 5 | div(class='panel with-nav-tabs panel-default') 6 | div(class='panel-heading') 7 | ul(class='nav nav-tabs') 8 | li(class='active') 9 | a(href='#tab1default' data-toggle='tab') Destination City 10 | li 11 | a(href='#tab2default' data-toggle='tab') Destination AS 12 | li 13 | a(href='#tab3default' data-toggle='tab') Destination Country 14 | li 15 | a(href='#tab4default' data-toggle='tab') Source City 16 | li 17 | a(href='#tab5default' data-toggle='tab') Source AS 18 | li 19 | a(href='#tab6default' data-toggle='tab') Source Country 20 | li.search 21 | input#search(type="text" placeholder="Type to search") 22 | div(class='panel-body') 23 | div(class='tab-content') 24 | div(class='tab-pane fade in active' id='tab1default') 25 | table#statistics(class='table') 26 | thead 27 | tr 28 | th City 29 | th Min (ms) 30 | th Avg (ms) 31 | th Max (ms) 32 | tbody 33 | each stat, val in stats_destination_cities 34 | tr 35 | td #{val} 36 | td #{stat['min']} ms 37 | td #{stat['avg']} ms 38 | td #{stat['max']} ms 39 | div(class='tab-pane fade' id='tab2default') 40 | table#statistics(class='table') 41 | thead 42 | tr 43 | th ASN 44 | th AS name 45 | th Min (ms) 46 | th Avg (ms) 47 | th Max (ms) 48 | tbody 49 | each stat, val in stats_destination_asn 50 | tr 51 | td AS#{val} 52 | td #{stat['name']} 53 | td #{stat['min']} ms 54 | td #{stat['avg']} ms 55 | td #{stat['max']} ms 56 | div(class='tab-pane fade' id='tab3default') 57 | table#statistics(class='table') 58 | thead 59 | tr 60 | th Country 61 | th Min (ms) 62 | th Avg (ms) 63 | th Max (ms) 64 | tbody 65 | each stat, val in stats_destination_countries 66 | tr 67 | td #{val} 68 | td #{stat['min']} ms 69 | td #{stat['avg']} ms 70 | td #{stat['max']} ms 71 | div(class='tab-pane fade' id='tab4default') 72 | table#statistics(class='table') 73 | thead 74 | tr 75 | th City 76 | th Min (ms) 77 | th Average (ms) 78 | th Max (ms) 79 | tbody 80 | each stat, val in stats_source_cities 81 | tr 82 | td #{val} 83 | td #{stat['min']} ms 84 | td #{stat['avg']} ms 85 | td #{stat['max']} ms 86 | div(class='tab-pane fade' id='tab5default') 87 | table#statistics(class='table') 88 | thead 89 | tr 90 | th ASN 91 | th AS name 92 | th Min (ms) 93 | th Avg (ms) 94 | th Max (ms) 95 | tbody 96 | each stat, val in stats_source_asn 97 | tr 98 | td AS#{val} 99 | td #{stat['name']} 100 | td #{stat['min']} ms 101 | td #{stat['avg']} ms 102 | td #{stat['max']} ms 103 | div(class='tab-pane fade' id='tab6default') 104 | table#statistics(class='table') 105 | thead 106 | tr 107 | th Country 108 | th Min (ms) 109 | th Average (ms) 110 | th Max (ms) 111 | tbody 112 | each stat, val in stats_source_countries 113 | tr 114 | td #{val} 115 | td #{stat['min']} ms 116 | td #{stat['avg']} ms 117 | td #{stat['max']} ms 118 | script(type='text/javascript', src='/javascripts/stats.js') --------------------------------------------------------------------------------