├── .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 | 
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 |
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 += " | "
16 | source_html += elem[0]
17 | source_html += " | "
18 | source_html += Math.round(elem[1]['mean'])
19 | source_html += " ms |
"
20 | });
21 | source_html += ""
22 | $('#top5_table'+ index)
23 | .find('tbody')
24 | .replaceWith(source_html);
25 | });
26 | });
27 |
28 | // Refresh random data
29 | socket.on('randomdata', function(data){
30 | $('.sourcecountry').html(data['source_country']+"
");
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')
--------------------------------------------------------------------------------