]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (c) 2008, 2009, 2010 Nicira Networks. | |
3 | * | |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at: | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | ||
17 | #include <config.h> | |
18 | #include "netflow.h" | |
19 | #include <arpa/inet.h> | |
20 | #include <errno.h> | |
21 | #include <stdlib.h> | |
22 | #include <unistd.h> | |
23 | #include "byte-order.h" | |
24 | #include "collectors.h" | |
25 | #include "flow.h" | |
26 | #include "netflow.h" | |
27 | #include "ofpbuf.h" | |
28 | #include "ofproto.h" | |
29 | #include "packets.h" | |
30 | #include "socket-util.h" | |
31 | #include "svec.h" | |
32 | #include "timeval.h" | |
33 | #include "util.h" | |
34 | #include "vlog.h" | |
35 | ||
36 | VLOG_DEFINE_THIS_MODULE(netflow); | |
37 | ||
38 | #define NETFLOW_V5_VERSION 5 | |
39 | ||
40 | /* Every NetFlow v5 message contains the header that follows. This is | |
41 | * followed by up to thirty records that describe a terminating flow. | |
42 | * We only send a single record per NetFlow message. | |
43 | */ | |
44 | struct netflow_v5_header { | |
45 | uint16_t version; /* NetFlow version is 5. */ | |
46 | uint16_t count; /* Number of records in this message. */ | |
47 | uint32_t sysuptime; /* System uptime in milliseconds. */ | |
48 | uint32_t unix_secs; /* Number of seconds since Unix epoch. */ | |
49 | uint32_t unix_nsecs; /* Number of residual nanoseconds | |
50 | after epoch seconds. */ | |
51 | uint32_t flow_seq; /* Number of flows since sending | |
52 | messages began. */ | |
53 | uint8_t engine_type; /* Engine type. */ | |
54 | uint8_t engine_id; /* Engine id. */ | |
55 | uint16_t sampling_interval; /* Set to zero. */ | |
56 | }; | |
57 | BUILD_ASSERT_DECL(sizeof(struct netflow_v5_header) == 24); | |
58 | ||
59 | /* A NetFlow v5 description of a terminating flow. It is preceded by a | |
60 | * NetFlow v5 header. | |
61 | */ | |
62 | struct netflow_v5_record { | |
63 | uint32_t src_addr; /* Source IP address. */ | |
64 | uint32_t dst_addr; /* Destination IP address. */ | |
65 | uint32_t nexthop; /* IP address of next hop. Set to 0. */ | |
66 | uint16_t input; /* Input interface index. */ | |
67 | uint16_t output; /* Output interface index. */ | |
68 | uint32_t packet_count; /* Number of packets. */ | |
69 | uint32_t byte_count; /* Number of bytes. */ | |
70 | uint32_t init_time; /* Value of sysuptime on first packet. */ | |
71 | uint32_t used_time; /* Value of sysuptime on last packet. */ | |
72 | ||
73 | /* The 'src_port' and 'dst_port' identify the source and destination | |
74 | * port, respectively, for TCP and UDP. For ICMP, the high-order | |
75 | * byte identifies the type and low-order byte identifies the code | |
76 | * in the 'dst_port' field. */ | |
77 | uint16_t src_port; | |
78 | uint16_t dst_port; | |
79 | ||
80 | uint8_t pad1; | |
81 | uint8_t tcp_flags; /* Union of seen TCP flags. */ | |
82 | uint8_t ip_proto; /* IP protocol. */ | |
83 | uint8_t ip_tos; /* IP TOS value. */ | |
84 | uint16_t src_as; /* Source AS ID. Set to 0. */ | |
85 | uint16_t dst_as; /* Destination AS ID. Set to 0. */ | |
86 | uint8_t src_mask; /* Source mask bits. Set to 0. */ | |
87 | uint8_t dst_mask; /* Destination mask bits. Set to 0. */ | |
88 | uint8_t pad[2]; | |
89 | }; | |
90 | BUILD_ASSERT_DECL(sizeof(struct netflow_v5_record) == 48); | |
91 | ||
92 | struct netflow { | |
93 | uint8_t engine_type; /* Value of engine_type to use. */ | |
94 | uint8_t engine_id; /* Value of engine_id to use. */ | |
95 | long long int boot_time; /* Time when netflow_create() was called. */ | |
96 | struct collectors *collectors; /* NetFlow collectors. */ | |
97 | bool add_id_to_iface; /* Put the 7 least signficiant bits of | |
98 | * 'engine_id' into the most signficant | |
99 | * bits of the interface fields. */ | |
100 | uint32_t netflow_cnt; /* Flow sequence number for NetFlow. */ | |
101 | struct ofpbuf packet; /* NetFlow packet being accumulated. */ | |
102 | long long int active_timeout; /* Timeout for flows that are still active. */ | |
103 | long long int reconfig_time; /* When we reconfigured the timeouts. */ | |
104 | }; | |
105 | ||
106 | static void | |
107 | gen_netflow_rec(struct netflow *nf, struct netflow_flow *nf_flow, | |
108 | struct ofexpired *expired, | |
109 | uint32_t packet_count, uint32_t byte_count) | |
110 | { | |
111 | struct netflow_v5_header *nf_hdr; | |
112 | struct netflow_v5_record *nf_rec; | |
113 | ||
114 | if (!nf->packet.size) { | |
115 | struct timespec now; | |
116 | ||
117 | time_wall_timespec(&now); | |
118 | ||
119 | nf_hdr = ofpbuf_put_zeros(&nf->packet, sizeof *nf_hdr); | |
120 | nf_hdr->version = htons(NETFLOW_V5_VERSION); | |
121 | nf_hdr->count = htons(0); | |
122 | nf_hdr->sysuptime = htonl(time_msec() - nf->boot_time); | |
123 | nf_hdr->unix_secs = htonl(now.tv_sec); | |
124 | nf_hdr->unix_nsecs = htonl(now.tv_nsec); | |
125 | nf_hdr->flow_seq = htonl(nf->netflow_cnt++); | |
126 | nf_hdr->engine_type = nf->engine_type; | |
127 | nf_hdr->engine_id = nf->engine_id; | |
128 | nf_hdr->sampling_interval = htons(0); | |
129 | } | |
130 | ||
131 | nf_hdr = nf->packet.data; | |
132 | nf_hdr->count = htons(ntohs(nf_hdr->count) + 1); | |
133 | ||
134 | nf_rec = ofpbuf_put_zeros(&nf->packet, sizeof *nf_rec); | |
135 | nf_rec->src_addr = expired->flow.nw_src; | |
136 | nf_rec->dst_addr = expired->flow.nw_dst; | |
137 | nf_rec->nexthop = htons(0); | |
138 | if (nf->add_id_to_iface) { | |
139 | uint16_t iface = (nf->engine_id & 0x7f) << 9; | |
140 | nf_rec->input = htons(iface | (expired->flow.in_port & 0x1ff)); | |
141 | nf_rec->output = htons(iface | (nf_flow->output_iface & 0x1ff)); | |
142 | } else { | |
143 | nf_rec->input = htons(expired->flow.in_port); | |
144 | nf_rec->output = htons(nf_flow->output_iface); | |
145 | } | |
146 | nf_rec->packet_count = htonl(packet_count); | |
147 | nf_rec->byte_count = htonl(byte_count); | |
148 | nf_rec->init_time = htonl(nf_flow->created - nf->boot_time); | |
149 | nf_rec->used_time = htonl(MAX(nf_flow->created, expired->used) | |
150 | - nf->boot_time); | |
151 | if (expired->flow.nw_proto == IP_TYPE_ICMP) { | |
152 | /* In NetFlow, the ICMP type and code are concatenated and | |
153 | * placed in the 'dst_port' field. */ | |
154 | uint8_t type = ntohs(expired->flow.tp_src); | |
155 | uint8_t code = ntohs(expired->flow.tp_dst); | |
156 | nf_rec->src_port = htons(0); | |
157 | nf_rec->dst_port = htons((type << 8) | code); | |
158 | } else { | |
159 | nf_rec->src_port = expired->flow.tp_src; | |
160 | nf_rec->dst_port = expired->flow.tp_dst; | |
161 | } | |
162 | nf_rec->tcp_flags = nf_flow->tcp_flags; | |
163 | nf_rec->ip_proto = expired->flow.nw_proto; | |
164 | nf_rec->ip_tos = expired->flow.nw_tos; | |
165 | ||
166 | /* NetFlow messages are limited to 30 records. */ | |
167 | if (ntohs(nf_hdr->count) >= 30) { | |
168 | netflow_run(nf); | |
169 | } | |
170 | } | |
171 | ||
172 | void | |
173 | netflow_expire(struct netflow *nf, struct netflow_flow *nf_flow, | |
174 | struct ofexpired *expired) | |
175 | { | |
176 | uint64_t pkt_delta = expired->packet_count - nf_flow->packet_count_off; | |
177 | uint64_t byte_delta = expired->byte_count - nf_flow->byte_count_off; | |
178 | ||
179 | nf_flow->last_expired += nf->active_timeout; | |
180 | ||
181 | /* NetFlow only reports on IP packets and we should only report flows | |
182 | * that actually have traffic. */ | |
183 | if (expired->flow.dl_type != htons(ETH_TYPE_IP) || pkt_delta == 0) { | |
184 | return; | |
185 | } | |
186 | ||
187 | if ((byte_delta >> 32) <= 175) { | |
188 | /* NetFlow v5 records are limited to 32-bit counters. If we've wrapped | |
189 | * a counter, send as multiple records so we don't lose track of any | |
190 | * traffic. We try to evenly distribute the packet and byte counters, | |
191 | * so that the bytes-per-packet lengths don't look wonky across the | |
192 | * records. */ | |
193 | while (byte_delta) { | |
194 | int n_recs = (byte_delta + UINT32_MAX - 1) / UINT32_MAX; | |
195 | uint32_t pkt_count = pkt_delta / n_recs; | |
196 | uint32_t byte_count = byte_delta / n_recs; | |
197 | ||
198 | gen_netflow_rec(nf, nf_flow, expired, pkt_count, byte_count); | |
199 | ||
200 | pkt_delta -= pkt_count; | |
201 | byte_delta -= byte_count; | |
202 | } | |
203 | } else { | |
204 | /* In 600 seconds, a 10GbE link can theoretically transmit 75 * 10**10 | |
205 | * == 175 * 2**32 bytes. The byte counter is bigger than that, so it's | |
206 | * probably a bug--for example, the netdev code uses UINT64_MAX to | |
207 | * report "unknown value", and perhaps that has leaked through to here. | |
208 | * | |
209 | * We wouldn't want to hit the loop above in this case, because it | |
210 | * would try to send up to UINT32_MAX netflow records, which would take | |
211 | * a long time. | |
212 | */ | |
213 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); | |
214 | ||
215 | VLOG_WARN_RL(&rl, "impossible byte counter %"PRIu64, byte_delta); | |
216 | } | |
217 | ||
218 | /* Update flow tracking data. */ | |
219 | nf_flow->created = 0; | |
220 | nf_flow->packet_count_off = expired->packet_count; | |
221 | nf_flow->byte_count_off = expired->byte_count; | |
222 | nf_flow->tcp_flags = 0; | |
223 | } | |
224 | ||
225 | void | |
226 | netflow_run(struct netflow *nf) | |
227 | { | |
228 | if (nf->packet.size) { | |
229 | collectors_send(nf->collectors, nf->packet.data, nf->packet.size); | |
230 | nf->packet.size = 0; | |
231 | } | |
232 | } | |
233 | ||
234 | int | |
235 | netflow_set_options(struct netflow *nf, | |
236 | const struct netflow_options *nf_options) | |
237 | { | |
238 | int error = 0; | |
239 | long long int old_timeout; | |
240 | ||
241 | nf->engine_type = nf_options->engine_type; | |
242 | nf->engine_id = nf_options->engine_id; | |
243 | nf->add_id_to_iface = nf_options->add_id_to_iface; | |
244 | ||
245 | collectors_destroy(nf->collectors); | |
246 | collectors_create(&nf_options->collectors, 0, &nf->collectors); | |
247 | ||
248 | old_timeout = nf->active_timeout; | |
249 | if (nf_options->active_timeout >= 0) { | |
250 | nf->active_timeout = nf_options->active_timeout; | |
251 | } else { | |
252 | nf->active_timeout = NF_ACTIVE_TIMEOUT_DEFAULT; | |
253 | } | |
254 | nf->active_timeout *= 1000; | |
255 | if (old_timeout != nf->active_timeout) { | |
256 | nf->reconfig_time = time_msec(); | |
257 | } | |
258 | ||
259 | return error; | |
260 | } | |
261 | ||
262 | struct netflow * | |
263 | netflow_create(void) | |
264 | { | |
265 | struct netflow *nf = xmalloc(sizeof *nf); | |
266 | nf->engine_type = 0; | |
267 | nf->engine_id = 0; | |
268 | nf->boot_time = time_msec(); | |
269 | nf->collectors = NULL; | |
270 | nf->add_id_to_iface = false; | |
271 | nf->netflow_cnt = 0; | |
272 | ofpbuf_init(&nf->packet, 1500); | |
273 | return nf; | |
274 | } | |
275 | ||
276 | void | |
277 | netflow_destroy(struct netflow *nf) | |
278 | { | |
279 | if (nf) { | |
280 | ofpbuf_uninit(&nf->packet); | |
281 | collectors_destroy(nf->collectors); | |
282 | free(nf); | |
283 | } | |
284 | } | |
285 | ||
286 | void | |
287 | netflow_flow_clear(struct netflow_flow *nf_flow) | |
288 | { | |
289 | uint16_t output_iface = nf_flow->output_iface; | |
290 | ||
291 | memset(nf_flow, 0, sizeof *nf_flow); | |
292 | nf_flow->output_iface = output_iface; | |
293 | } | |
294 | ||
295 | void | |
296 | netflow_flow_update_time(struct netflow *nf, struct netflow_flow *nf_flow, | |
297 | long long int used) | |
298 | { | |
299 | if (!nf_flow->created) { | |
300 | nf_flow->created = used; | |
301 | } | |
302 | ||
303 | if (!nf || !nf->active_timeout || !nf_flow->last_expired || | |
304 | nf->reconfig_time > nf_flow->last_expired) { | |
305 | /* Keep the time updated to prevent a flood of expiration in | |
306 | * the future. */ | |
307 | nf_flow->last_expired = time_msec(); | |
308 | } | |
309 | } | |
310 | ||
311 | void | |
312 | netflow_flow_update_flags(struct netflow_flow *nf_flow, uint8_t tcp_flags) | |
313 | { | |
314 | nf_flow->tcp_flags |= tcp_flags; | |
315 | } | |
316 | ||
317 | bool | |
318 | netflow_active_timeout_expired(struct netflow *nf, struct netflow_flow *nf_flow) | |
319 | { | |
320 | if (nf->active_timeout) { | |
321 | return time_msec() > nf_flow->last_expired + nf->active_timeout; | |
322 | } | |
323 | ||
324 | return false; | |
325 | } |