Skip to content

Commit c007a34

Browse files
Dynmon_injector tool + example dataplanes
1 parent 1b9dafc commit c007a34

File tree

7 files changed

+346
-0
lines changed

7 files changed

+346
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Monitoring examples
2+
3+
This folder contains a set of monitoring dataplanes, which can be used as examples for the Dynamic Monitoring service.
4+
5+
- [packet_counter.json](packet_counter.json):
6+
- the `packets_total` metric represents the number of packets that have traversed the attached network interface
7+
8+
- [ntp_packets_counter.json](ntp_packets_counter.json):
9+
- the `ntp_packets_total` metric represents the number of NTP packets that have traversed the attached network interface
10+
11+
- [ntp_packets_ntp_mode_private_counters.json](ntp_packets_ntp_mode_private_counters.json):
12+
- the `ntp_packets_total` metric represents the number of NTP packets that have traversed the attached network interface;
13+
- the `ntp_mode_private_packets_total` metric represents the number of NTP packets with NTP_MODE = 7 (MODE_PRIVATE) that have traversed the attached network interface
14+
15+
All counters are *incremental*, hence their values are monotonically increasing.
16+
17+
18+
Unfortunately the dataplane code (eBPF restricted C) contained in the above JSONs is not easy to read for a human, due to the formatting limitations of the JSON format. A more human friendly version can be produced by unescaping the code by using [this online free tool](https://www.freeformatter.com/json-escape.html).
19+
20+
The same tool can be used also to escape any multiline code strings in order to create new valid injectable dataplanes.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "NTP Amplification BUA probe",
3+
"code": "\r\n#include <uapi/linux/ip.h>\r\n#include <uapi/linux/udp.h>\r\n\r\n#define IP_PROTO_UDP 17\r\n#define NTP_PORT 123\r\n\r\nstruct eth_hdr {\r\n __be64 dst : 48;\r\n __be64 src : 48;\r\n __be16 proto;\r\n} __attribute__((packed));\r\n\r\nBPF_ARRAY(NTP_PACKETS_COUNTER, uint64_t,1);\r\n\r\nstatic __always_inline\r\nint handle_rx(struct CTXTYPE *ctx, struct pkt_metadata *md) {\r\n /*Parsing L2*/\r\n void *data = (void *) (long) ctx->data;\r\n void *data_end = (void *) (long) ctx->data_end;\r\n struct eth_hdr *ethernet = data;\r\n if (data + sizeof(*ethernet) > data_end)\r\n return RX_OK;\r\n\r\n if (ethernet->proto != bpf_htons(ETH_P_IP))\r\n return RX_OK;\r\n\r\n /*Parsing L3*/\r\n struct iphdr *ip = data + sizeof(struct eth_hdr);\r\n if (data + sizeof(struct eth_hdr) + sizeof(*ip) > data_end)\r\n return RX_OK;\r\n if ((int) ip->version != 4)\r\n return RX_OK;\r\n\r\n if (ip->protocol != IP_PROTO_UDP)\r\n return RX_OK;\r\n\r\n /*Parsing L4*/\r\n uint8_t ip_header_len = 4 * ip->ihl;\r\n struct udphdr *udp = data + sizeof(*ethernet) + ip_header_len;\r\n if (data + sizeof(*ethernet) + ip_header_len + sizeof(*udp) > data_end)\r\n return RX_OK;\r\n\r\n if (udp->source == bpf_htons(NTP_PORT) || udp->dest == bpf_htons(NTP_PORT)) {\r\n pcn_log(ctx, LOG_TRACE, \"%I:%P\\t-> %I:%P\", ip->saddr,udp->source,ip->daddr,udp->dest);\r\n unsigned int key = 0;\r\n uint64_t * ntp_pckts_counter = NTP_PACKETS_COUNTER.lookup(&key);\r\n if (!ntp_pckts_counter)\r\n pcn_log(ctx, LOG_ERR, \"[NTP_AMP_BUA] Unable to find NTP_PACKETS_COUNTER map\");\r\n else\r\n *ntp_pckts_counter+=1;\r\n }\r\n\r\n return RX_OK;\r\n}",
4+
"metrics": [
5+
{
6+
"name": "ntp_packets_total",
7+
"map-name": "NTP_PACKETS_COUNTER",
8+
"open-metrics-metadata": {
9+
"help": "This metric represents the number of NTP packets that has traveled through this probe.",
10+
"type": "counter",
11+
"labels": [
12+
{
13+
"name": "IP_PROTO",
14+
"value": "UDP"
15+
},
16+
{
17+
"name": "L4",
18+
"value": "NTP"
19+
}
20+
]
21+
}
22+
}
23+
]
24+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "NTP Amplification WARNING probe",
3+
"code": "\r\n#include <uapi/linux/ip.h>\r\n#include <uapi/linux/udp.h>\r\n\r\n#define IP_PROTO_UDP 17\r\n#define NTP_PORT 123\r\n#define NTP_MODE_PRIVATE 7\r\n\r\n#define MODE(li_vn_mode) (uint8_t) ((li_vn_mode & 0x07) >> 0)\r\n\r\nstruct eth_hdr {\r\n __be64 dst : 48;\r\n __be64 src : 48;\r\n __be16 proto;\r\n} __attribute__((packed));\r\n\r\nstruct ntp_packet {\r\n uint8_t li_vn_mode;\r\n uint8_t stratum;\r\n uint8_t poll;\r\n uint8_t precision;\r\n uint32_t rootDelay;\r\n uint32_t rootDispersion;\r\n uint32_t refId;\r\n uint32_t refTm_s;\r\n uint32_t refTm_f;\r\n uint32_t origTm_s;\r\n uint32_t origTm_f;\r\n uint32_t rxTm_s;\r\n uint32_t rxTm_f;\r\n uint32_t txTm_s;\r\n uint32_t txTm_f;\r\n} __attribute__((packed));\r\n\r\nBPF_ARRAY(NTP_PACKETS_COUNTER, uint64_t, 1);\r\nBPF_ARRAY(NTP_MODE_PRIVATE_PACKETS_COUNTER, uint64_t, 1);\r\n\r\nstatic __always_inline\r\nint handle_rx(struct CTXTYPE *ctx, struct pkt_metadata *md) {\r\n /*Parsing L2*/\r\n void *data = (void *) (long) ctx->data;\r\n void *data_end = (void *) (long) ctx->data_end;\r\n struct eth_hdr *ethernet = data;\r\n if (data + sizeof(*ethernet) > data_end)\r\n return RX_OK;\r\n\r\n if (ethernet->proto != bpf_htons(ETH_P_IP))\r\n return RX_OK;\r\n\r\n /*Parsing L3*/\r\n struct iphdr *ip = data + sizeof(struct eth_hdr);\r\n if (data + sizeof(struct eth_hdr) + sizeof(*ip) > data_end)\r\n return RX_OK;\r\n if ((int) ip->version != 4)\r\n return RX_OK;\r\n\r\n if (ip->protocol != IP_PROTO_UDP)\r\n return RX_OK;\r\n\r\n /*Parsing L4*/\r\n uint8_t ip_header_len = 4 * ip->ihl;\r\n struct udphdr *udp = data + sizeof(*ethernet) + ip_header_len;\r\n if (data + sizeof(*ethernet) + ip_header_len + sizeof(*udp) > data_end)\r\n return RX_OK;\r\n\r\n if (udp->source == bpf_htons(NTP_PORT) || udp->dest == bpf_htons(NTP_PORT)) {\r\n pcn_log(ctx, LOG_TRACE, \"%I:%P\\t-> %I:%P\", ip->saddr,udp->source,ip->daddr,udp->dest);\r\n unsigned int key = 0;\r\n uint64_t * ntp_pckts_counter = NTP_PACKETS_COUNTER.lookup(&key);\r\n if (!ntp_pckts_counter)\r\n pcn_log(ctx, LOG_ERR, \"[NTP_AMP_WARNING] Unable to find NTP_PACKETS_COUNTER map\");\r\n else\r\n *ntp_pckts_counter+=1;\r\n\r\n /*Parsing NTP*/\r\n struct ntp_packet *ntp = data + sizeof(*ethernet) + ip_header_len + sizeof(struct udphdr);\r\n if (data + sizeof(*ethernet) + ip_header_len + sizeof(struct udphdr) + sizeof(*ntp) > data_end)\r\n return RX_OK;\r\n\r\n uint8_t mode = MODE(ntp->li_vn_mode);\r\n pcn_log(ctx, LOG_TRACE, \"NTP mode: %hhu\", mode);\r\n if (mode == NTP_MODE_PRIVATE) {\r\n pcn_log(ctx, LOG_TRACE, \"RECEIVED NTP MODE 7\");\r\n key = 0;\r\n uint64_t * ntp_mode_private_counter = NTP_MODE_PRIVATE_PACKETS_COUNTER.lookup(&key);\r\n if (!ntp_mode_private_counter)\r\n pcn_log(ctx, LOG_ERR, \"[NTP_AMP_WARNING] Unable to find NTP_MODE_PRIVATE_PACKETS_COUNTER map\");\r\n else\r\n *ntp_mode_private_counter+=1;\r\n }\r\n }\r\n\r\n return RX_OK;\r\n}",
4+
"metrics": [
5+
{
6+
"name": "ntp_packets_total",
7+
"map-name": "NTP_PACKETS_COUNTER",
8+
"open-metrics-metadata": {
9+
"help": "This metric represents the number of NTP packets that has traveled through this probe.",
10+
"type": "counter",
11+
"labels": [
12+
{
13+
"name": "IP_PROTO",
14+
"value": "UDP"
15+
},
16+
{
17+
"name": "L4",
18+
"value": "NTP"
19+
}
20+
]
21+
}
22+
},
23+
{
24+
"name": "ntp_mode_private_packets_total",
25+
"map-name": "NTP_MODE_PRIVATE_PACKETS_COUNTER",
26+
"open-metrics-metadata": {
27+
"help": "This metric represents the number of NTP packets with MODE = 7 (MODE_PRIVATE) that has traveled through this probe.",
28+
"type": "counter",
29+
"labels": [
30+
{
31+
"name": "IP_PROTO",
32+
"value": "UDP"
33+
},
34+
{
35+
"name": "L4",
36+
"value": "NTP"
37+
}
38+
]
39+
}
40+
}
41+
]
42+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "Packets counter probe",
3+
"code": "\r\n BPF_ARRAY(PKT_COUNTER, uint64_t, 1);\r\n static __always_inline int handle_rx(struct CTXTYPE *ctx, struct pkt_metadata *md) {\r\n unsigned int key = 0;\r\n uint64_t *pkt_counter = PKT_COUNTER.lookup(&key);\r\n if (!pkt_counter){\r\n /*counter map not found !? */\r\n return RX_OK;\r\n }\r\n *pkt_counter+=1;\r\n pcn_log(ctx, LOG_TRACE, \"counter: %d\", *pkt_counter);\r\n return RX_OK;\r\n }",
4+
"metrics": [
5+
{
6+
"name": "packets_total",
7+
"map-name": "PKT_COUNTER",
8+
"open-metrics-metadata": {
9+
"help": "This metric represents the number of packets that has traveled trough this probe.",
10+
"type": "counter",
11+
"labels": []
12+
}
13+
}
14+
]
15+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Dynmon Injector
2+
3+
This tool allows the creation and the manipulation of a `dynmon` cube without using the standard `polycubectl` CLI.
4+
5+
## Install
6+
Some dependencies are required for this tool to run:
7+
```
8+
pip install -r requirements.txt
9+
```
10+
11+
## Run
12+
13+
Running the tool:
14+
15+
```
16+
Usage: `dynmon_injector.py [-h] [-a ADDRESS] [-p PORT] [-v] cube_name peer_interface path_to_dataplane`
17+
18+
positional arguments:
19+
cube_name indicates the name of the cube
20+
peer_interface indicates the network interface to connect the cube to
21+
path_to_dataplane indicates the path to the json file which contains the new dataplane configuration
22+
which contains the new dataplane code and the metadata associated to the exported metrics
23+
24+
optional arguments:
25+
-h, --help show this help message and exit
26+
-a ADDRESS, --address ADDRESS set the polycube daemon ip address (default: localhost)
27+
-p PORT, --port PORT set the polycube daemon port (default: 9000)
28+
-v, --version show program's version number and exit
29+
```
30+
31+
Usage examples:
32+
33+
```
34+
basic usage:
35+
./dynmon_injector.py monitor_0 eno1 ../examples/packet_counter.json
36+
37+
setting custom ip address and port to contact the polycube daemon:
38+
./dynmon_injector.py -a 10.0.0.1 -p 5840 monitor_0 eno1 ../examples/packet_counter.json
39+
40+
```
41+
42+
This tool creates a new `dynmon` cube with the given configuration and attaches it to the selected interface.
43+
44+
If the monitor already exists, the tool checks if the attached interface is the same used previously; if not, it detaches the cube from the previous interface and attaches it to the new one; then, the selected dataplane is injected.
45+
46+
To inspect the current configuration of the cube or to read extracted monitoring data, use the [`polycubectl`](https://polycube-network.readthedocs.io/en/latest/polycubectl/polycubectl.html) command line interface.
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
#!/usr/bin/python3
2+
# coding: utf-8
3+
4+
import argparse
5+
import socket
6+
import requests
7+
import json
8+
import os.path
9+
from os import path
10+
11+
VERSION = '1.0'
12+
POLYCUBED_ADDR = 'localhost'
13+
POLYCUBED_PORT = 9000
14+
REQUESTS_TIMEOUT = 5 #seconds
15+
16+
polycubed_endpoint = 'http://{}:{}/polycube/v1'
17+
18+
19+
def main():
20+
global polycubed_endpoint
21+
args = parseArguments()
22+
23+
addr = args['address']
24+
port = args['port']
25+
26+
cube_name = args['cube_name']
27+
interface_name = args['peer_interface']
28+
path_to_dataplane = args['path_to_configuration']
29+
30+
dataplane = None
31+
32+
if not path.isfile(path_to_dataplane):
33+
print(f'File {path_to_dataplane} does not exist.')
34+
exit(1)
35+
else:
36+
with open(path_to_dataplane) as json_file:
37+
dataplane = json.load(json_file)
38+
39+
polycubed_endpoint = polycubed_endpoint.format(addr, port)
40+
41+
checkConnectionToDaemon()
42+
43+
already_exists, cube = checkIfServiceExists(cube_name)
44+
45+
if already_exists:
46+
print(f'Dynmon {cube_name} already exist')
47+
attached_interface = cube['parent']
48+
if attached_interface:
49+
if attached_interface != interface_name:
50+
detach_from_interface(cube_name, attached_interface)
51+
attach_to_interface(cube_name, interface_name)
52+
else:
53+
attach_to_interface(cube_name, interface_name)
54+
55+
injectNewDataplane(cube_name, dataplane)
56+
57+
else:
58+
createInstance(cube_name, dataplane)
59+
attach_to_interface(cube_name, interface_name)
60+
61+
62+
def checkConnectionToDaemon():
63+
try:
64+
requests.get(polycubed_endpoint, timeout=REQUESTS_TIMEOUT)
65+
except requests.exceptions.ConnectionError:
66+
print('Connection error: unable to connect to polycube daemon.')
67+
exit(1)
68+
except requests.exceptions.Timeout:
69+
print('Timeout error: unable to connect to polycube daemon.')
70+
exit(1)
71+
except requests.exceptions.RequestException:
72+
print('Error: unable to connect to polycube daemon.')
73+
exit(1)
74+
75+
76+
def checkIfServiceExists(cube_name):
77+
try:
78+
response = requests.get(f'{polycubed_endpoint}/dynmon/{cube_name}', timeout=REQUESTS_TIMEOUT)
79+
response.raise_for_status()
80+
return True, json.loads(response.content)
81+
except requests.exceptions.HTTPError:
82+
return False, None
83+
except requests.exceptions.ConnectionError:
84+
print('Connection error: unable to connect to polycube daemon.')
85+
exit(1)
86+
except requests.exceptions.Timeout:
87+
print('Timeout error: unable to connect to polycube daemon.')
88+
exit(1)
89+
except requests.exceptions.RequestException:
90+
print('Error: unable to connect to polycube daemon.')
91+
exit(1)
92+
93+
94+
def injectNewDataplane(cube_name, dataplane):
95+
try:
96+
print('Injecting the new dataplane')
97+
response = requests.put(f'{polycubed_endpoint}/dynmon/{cube_name}/dataplane',
98+
json.dumps(dataplane),
99+
timeout=REQUESTS_TIMEOUT)
100+
response.raise_for_status()
101+
except requests.exceptions.HTTPError:
102+
print(f'Error: {response.content.decode("UTF-8")}')
103+
exit(1)
104+
except requests.exceptions.ConnectionError:
105+
print('Connection error: unable to connect to polycube daemon.')
106+
exit(1)
107+
except requests.exceptions.Timeout:
108+
print('Timeout error: unable to connect to polycube daemon.')
109+
exit(1)
110+
except requests.exceptions.RequestException:
111+
print('Error: unable to connect to polycube daemon.')
112+
exit(1)
113+
114+
115+
def createInstance(cube_name, dataplane):
116+
try:
117+
print(f'Creating new dynmon instance named {cube_name}')
118+
response = requests.put(f'{polycubed_endpoint}/dynmon/{cube_name}',
119+
json.dumps({'dataplane': dataplane}),
120+
timeout=REQUESTS_TIMEOUT)
121+
response.raise_for_status()
122+
except requests.exceptions.HTTPError:
123+
print(f'Error: {response.content.decode("UTF-8")}')
124+
exit(1)
125+
except requests.exceptions.ConnectionError:
126+
print('Connection error: unable to connect to polycube daemon.')
127+
exit(1)
128+
except requests.exceptions.Timeout:
129+
print('Timeout error: unable to connect to polycube daemon.')
130+
exit(1)
131+
except requests.exceptions.RequestException:
132+
print('Error: unable to connect to polycube daemon.')
133+
exit(1)
134+
135+
136+
def detach_from_interface(cube_name, interface):
137+
try:
138+
print(f'Detaching {cube_name} from {interface}')
139+
response = requests.post(f'{polycubed_endpoint}/detach',
140+
json.dumps({'cube': cube_name, 'port': interface}),
141+
timeout=REQUESTS_TIMEOUT)
142+
response.raise_for_status()
143+
except requests.exceptions.HTTPError:
144+
print(f'Error: {response.content.decode("UTF-8")}')
145+
exit(1)
146+
except requests.exceptions.ConnectionError:
147+
print('Connection error: unable to connect to polycube daemon.')
148+
exit(1)
149+
except requests.exceptions.Timeout:
150+
print('Timeout error: unable to connect to polycube daemon.')
151+
exit(1)
152+
except requests.exceptions.RequestException:
153+
print('Error: unable to connect to polycube daemon.')
154+
exit(1)
155+
156+
157+
def attach_to_interface(cube_name, interface):
158+
try:
159+
print(f'Attaching {cube_name} to {interface}')
160+
response = requests.post(f'{polycubed_endpoint}/attach',
161+
json.dumps({'cube': cube_name, 'port': interface}),
162+
timeout=REQUESTS_TIMEOUT)
163+
response.raise_for_status()
164+
except requests.exceptions.HTTPError:
165+
print(f'Error: {response.content.decode("UTF-8")}')
166+
exit(1)
167+
except requests.exceptions.ConnectionError:
168+
print('Connection error: unable to connect to polycube daemon.')
169+
exit(1)
170+
except requests.exceptions.Timeout:
171+
print('Timeout error: unable to connect to polycube daemon.')
172+
exit(1)
173+
except requests.exceptions.RequestException:
174+
print('Error: unable to connect to polycube daemon.')
175+
exit(1)
176+
177+
178+
def parseArguments():
179+
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
180+
parser.add_argument('cube_name', help='indicates the name of the cube', type=str)
181+
parser.add_argument('peer_interface', help='indicates the network interface to connect the cube to', type=str)
182+
parser.add_argument('path_to_configuration',
183+
help='''indicates the path to the json file which contains the new dataplane configuration
184+
which contains the new dataplane code and the metadata associated to the exported metrics''',
185+
type=str)
186+
parser.add_argument('-a', '--address', help='set the polycube daemon ip address', type=str, default=POLYCUBED_ADDR)
187+
parser.add_argument('-p', '--port', help='set the polycube daemon port', type=int, default=POLYCUBED_PORT)
188+
parser.add_argument('-v', '--version', action='version', version=showVersion())
189+
return parser.parse_args().__dict__
190+
191+
192+
def showVersion():
193+
return '%(prog)s - Version ' + VERSION
194+
195+
196+
if __name__ == '__main__':
197+
main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
requests==2.23.0
2+
argparse==1.4.0

0 commit comments

Comments
 (0)