• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * (C) 2005-2011 by Pablo Neira Ayuso <pablo@netfilter.org>
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  */
9 
10 #include "internal/internal.h"
11 
12 /*
13  * XML output sample:
14  *
15  * <flow>
16  * 	<meta direction="original">
17  * 		<layer3 protonum="2" protoname="IPv4">
18  * 			<src>192.168.0.1</src>
19  * 			<dst>192.168.0.2</dst>
20  * 		</layer3>
21  * 		<layer4 protonum="16" protoname"udp">
22  * 			<sport>80</sport>
23  * 			<dport>56665</dport>
24  * 		</layer4>
25  * 		<counters>
26  * 			<bytes>10</bytes>
27  * 			<packets>1</packets>
28  * 		</counters>
29  * 	</meta>
30  * 	<meta direction="reply">
31  * 		<layer3 protonum="2" protoname="IPv4">
32  * 			<src>192.168.0.2</src>
33  * 			<dst>192.168.0.1</dst>
34  * 		</layer3>
35  * 		<layer4 protonum="16" protoname="udp">
36  * 			<sport>80</sport>
37  * 			<dport>56665</dport>
38  * 		</layer4>
39  * 		<counters>
40  * 			<bytes>5029</bytes>
41  * 			<packets>12</packets>
42  *		</counters>
43  * 	</meta>
44  * 	<meta direction="independent">
45  * 		<state>ESTABLISHED</state>
46  * 		<timeout>100</timeout>
47  * 		<mark>1</mark>
48  * 		<secmark>0</secmark>
49  * 		<id>453281439</id>
50  * 		<use>1</use>
51  * 		<assured/>
52  * 	</meta>
53  * </flow>
54  */
55 
__proto2str(uint8_t protonum)56 const char *__proto2str(uint8_t protonum)
57 {
58 	return proto2str[protonum] ? proto2str[protonum] : "unknown";
59 }
60 
__l3proto2str(uint8_t protonum)61 const char *__l3proto2str(uint8_t protonum)
62 {
63 	return l3proto2str[protonum] ? l3proto2str[protonum] : "unknown";
64 }
65 
__snprintf_ipv4_xml(char * buf,unsigned int len,const struct __nfct_tuple * tuple,unsigned int type)66 static int __snprintf_ipv4_xml(char *buf,
67 			       unsigned int len,
68 			       const struct __nfct_tuple *tuple,
69 			       unsigned int type)
70 {
71 	struct in_addr addr = {
72 		.s_addr = (type == __ADDR_SRC) ? tuple->src.v4 : tuple->dst.v4,
73 	};
74 
75 	return snprintf(buf, len, "%s", inet_ntoa(addr));
76 }
77 
__snprintf_ipv6_xml(char * buf,unsigned int len,const struct __nfct_tuple * tuple,unsigned int type)78 static int __snprintf_ipv6_xml(char *buf,
79 			       unsigned int len,
80 			       const struct __nfct_tuple *tuple,
81 			       unsigned int type)
82 {
83 	struct in6_addr addr;
84 	static char tmp[INET6_ADDRSTRLEN];
85 	const void *p = (type == __ADDR_SRC) ? &tuple->src.v6 : &tuple->dst.v6;
86 
87 	memcpy(&addr, p, sizeof(struct in6_addr));
88 
89 	if (!inet_ntop(AF_INET6, &addr, tmp, sizeof(tmp)))
90 		return -1;
91 
92 	return snprintf(buf, len, "%s", tmp);
93 }
94 
__snprintf_addr_xml(char * buf,unsigned int len,const struct __nfct_tuple * tuple,enum __nfct_addr type)95 int __snprintf_addr_xml(char *buf, unsigned int len,
96 			const struct __nfct_tuple *tuple,
97 			enum __nfct_addr type)
98 {
99 	int ret;
100 	unsigned int size = 0, offset = 0;
101 	const char *tag = (type == __ADDR_SRC) ? "src" : "dst";
102 
103 	ret = snprintf(buf, len, "<%s>", tag);
104 	BUFFER_SIZE(ret, size, len, offset);
105 
106 	switch (tuple->l3protonum) {
107 	case AF_INET:
108 		ret = __snprintf_ipv4_xml(buf+offset, len, tuple, type);
109 		BUFFER_SIZE(ret, size, len, offset);
110 		break;
111 	case AF_INET6:
112 		ret = __snprintf_ipv6_xml(buf+offset, len, tuple, type);
113 		BUFFER_SIZE(ret, size, len, offset);
114 		break;
115 	}
116 
117 	ret = snprintf(buf+offset, len, "</%s>", tag);
118 	BUFFER_SIZE(ret, size, len, offset);
119 
120 	return size;
121 }
122 
__snprintf_proto_xml(char * buf,unsigned int len,const struct __nfct_tuple * tuple,enum __nfct_addr type)123 int __snprintf_proto_xml(char *buf, unsigned int len,
124 			 const struct __nfct_tuple *tuple,
125 			 enum __nfct_addr type)
126 {
127 	int ret = 0;
128 	unsigned int size = 0, offset = 0;
129 
130 	switch(tuple->protonum) {
131 	case IPPROTO_TCP:
132 	case IPPROTO_UDP:
133 	case IPPROTO_UDPLITE:
134 	case IPPROTO_SCTP:
135 	case IPPROTO_DCCP:
136 		if (type == __ADDR_SRC) {
137 			ret = snprintf(buf, len, "<sport>%u</sport>",
138 				       ntohs(tuple->l4src.tcp.port));
139 			BUFFER_SIZE(ret, size, len, offset);
140 		} else {
141 			ret = snprintf(buf, len, "<dport>%u</dport>",
142 				       ntohs(tuple->l4dst.tcp.port));
143 			BUFFER_SIZE(ret, size, len, offset);
144 		}
145 		break;
146 	case IPPROTO_GRE:
147 		if (type == __ADDR_SRC) {
148 			ret = snprintf(buf, len, "<srckey>0x%x</srckey>",
149 				       ntohs(tuple->l4src.all));
150 			BUFFER_SIZE(ret, size, len, offset);
151 		} else {
152 			ret = snprintf(buf, len, "<dstkey>0x%x</dstkey>",
153 				       ntohs(tuple->l4dst.all));
154 			BUFFER_SIZE(ret, size, len, offset);
155 		}
156 		break;
157 	}
158 
159 	return ret;
160 }
161 
__snprintf_counters_xml(char * buf,unsigned int len,const struct nf_conntrack * ct,unsigned int type)162 static int __snprintf_counters_xml(char *buf,
163 				   unsigned int len,
164 				   const struct nf_conntrack *ct,
165 				   unsigned int type)
166 {
167 	int ret;
168 	unsigned int size = 0, offset = 0;
169 
170 	ret = snprintf(buf, len, "<packets>%llu</packets>",
171 		       (unsigned long long)ct->counters[type].packets);
172 	BUFFER_SIZE(ret, size, len, offset);
173 
174 	ret = snprintf(buf+offset, len, "<bytes>%llu</bytes>",
175 		       (unsigned long long)ct->counters[type].bytes);
176 	BUFFER_SIZE(ret, size, len, offset);
177 
178 	return size;
179 }
180 
181 static int
__snprintf_timestamp_start(char * buf,unsigned int len,const struct nf_conntrack * ct)182 __snprintf_timestamp_start(char *buf, unsigned int len,
183 			   const struct nf_conntrack *ct)
184 {
185 	int ret;
186 	unsigned int size = 0, offset = 0;
187 
188 	ret = snprintf(buf, len, "<start>%llu</start>",
189 		       (unsigned long long)ct->timestamp.start);
190 	BUFFER_SIZE(ret, size, len, offset);
191 
192 	return size;
193 }
194 
195 static int
__snprintf_timestamp_stop(char * buf,unsigned int len,const struct nf_conntrack * ct)196 __snprintf_timestamp_stop(char *buf, unsigned int len,
197 			  const struct nf_conntrack *ct)
198 {
199 	int ret;
200 	unsigned int size = 0, offset = 0;
201 
202 	ret = snprintf(buf, len, "<stop>%llu</stop>",
203 		       (unsigned long long)ct->timestamp.stop);
204 	BUFFER_SIZE(ret, size, len, offset);
205 
206 	return size;
207 }
208 
209 static int
__snprintf_deltatime_now(char * buf,unsigned int len,const struct nf_conntrack * ct)210 __snprintf_deltatime_now(char *buf, unsigned int len,
211 			 const struct nf_conntrack *ct)
212 {
213 	int ret;
214 	unsigned int size = 0, offset = 0;
215 	time_t now, delta_time;
216 
217 	time(&now);
218         delta_time = now - (time_t)(ct->timestamp.start / NSEC_PER_SEC);
219 
220 	ret = snprintf(buf+offset, len, "<deltatime>%llu</deltatime>",
221 		(unsigned long long)delta_time);
222 	BUFFER_SIZE(ret, size, len, offset);
223 
224 	return size;
225 }
226 
227 static int
__snprintf_deltatime(char * buf,unsigned int len,const struct nf_conntrack * ct)228 __snprintf_deltatime(char *buf, unsigned int len, const struct nf_conntrack *ct)
229 {
230 	int ret;
231 	unsigned int size = 0, offset = 0;
232 	time_t delta_time = (time_t)((ct->timestamp.stop -
233 				ct->timestamp.start) / NSEC_PER_SEC);
234 
235 	ret = snprintf(buf+offset, len, "<deltatime>%llu</deltatime>",
236 		(unsigned long long)delta_time);
237 	BUFFER_SIZE(ret, size, len, offset);
238 
239 	return size;
240 }
241 
242 static int
__snprintf_helper_name(char * buf,unsigned int len,const struct nf_conntrack * ct)243 __snprintf_helper_name(char *buf, unsigned int len, const struct nf_conntrack *ct)
244 {
245 	int ret;
246 	unsigned int size = 0, offset = 0;
247 
248 	ret = snprintf(buf+offset, len, "<helper>%s</helper>", ct->helper_name);
249 	BUFFER_SIZE(ret, size, len, offset);
250 
251 	return size;
252 }
253 
254 int
__snprintf_localtime_xml(char * buf,unsigned int len,const struct tm * tm)255 __snprintf_localtime_xml(char *buf, unsigned int len, const struct tm *tm)
256 {
257 	int ret = 0;
258 	unsigned int size = 0, offset = 0;
259 
260 	ret = snprintf(buf+offset, len, "<hour>%d</hour>", tm->tm_hour);
261 	BUFFER_SIZE(ret, size, len, offset);
262 
263 	ret = snprintf(buf+offset, len, "<min>%02d</min>", tm->tm_min);
264 	BUFFER_SIZE(ret, size, len, offset);
265 
266 	ret = snprintf(buf+offset, len, "<sec>%02d</sec>", tm->tm_sec);
267 	BUFFER_SIZE(ret, size, len, offset);
268 
269 	ret = snprintf(buf+offset, len, "<wday>%d</wday>", tm->tm_wday + 1);
270 	BUFFER_SIZE(ret, size, len, offset);
271 
272 	ret = snprintf(buf+offset, len, "<day>%d</day>", tm->tm_mday);
273 	BUFFER_SIZE(ret, size, len, offset);
274 
275 	ret = snprintf(buf+offset, len, "<month>%d</month>", tm->tm_mon + 1);
276 	BUFFER_SIZE(ret, size, len, offset);
277 
278 	ret = snprintf(buf+offset, len, "<year>%d</year>", 1900 + tm->tm_year);
279 	BUFFER_SIZE(ret, size, len, offset);
280 
281 	return size;
282 }
283 
__snprintf_tuple_xml(char * buf,unsigned int len,const struct nf_conntrack * ct,unsigned int dir,bool zone_incl)284 static int __snprintf_tuple_xml(char *buf,
285 				unsigned int len,
286 				const struct nf_conntrack *ct,
287 				unsigned int dir, bool zone_incl)
288 {
289 	int ret;
290 	unsigned int size = 0, offset = 0;
291 	const struct __nfct_tuple *tuple = NULL;
292 
293 	switch(dir) {
294 	case __DIR_ORIG:
295 		tuple = &ct->head.orig;
296 		break;
297 	case __DIR_REPL:
298 		tuple = &ct->repl;
299 		break;
300 	}
301 	ret = snprintf(buf, len, "<meta direction=\"%s\">",
302 		       dir == __DIR_ORIG ? "original" : "reply");
303 	BUFFER_SIZE(ret, size, len, offset);
304 
305 	ret = snprintf(buf+offset, len,
306 		       "<layer3 protonum=\"%d\" protoname=\"%s\">",
307 		       tuple->l3protonum, __l3proto2str(tuple->l3protonum));
308 	BUFFER_SIZE(ret, size, len, offset);
309 
310 	ret = __snprintf_addr_xml(buf+offset, len, tuple, __ADDR_SRC);
311 	BUFFER_SIZE(ret, size, len, offset);
312 
313 	ret = __snprintf_addr_xml(buf+offset, len, tuple, __ADDR_DST);
314 	BUFFER_SIZE(ret, size, len, offset);
315 
316 	ret = snprintf(buf+offset, len, "</layer3>");
317 	BUFFER_SIZE(ret, size, len, offset);
318 
319 	ret = snprintf(buf+offset, len,
320 		       "<layer4 protonum=\"%d\" protoname=\"%s\">",
321 		       tuple->protonum, __proto2str(tuple->protonum));
322 	BUFFER_SIZE(ret, size, len, offset);
323 
324 	ret = __snprintf_proto_xml(buf+offset, len, tuple, __DIR_ORIG);
325 	BUFFER_SIZE(ret, size, len, offset);
326 
327 	ret = __snprintf_proto_xml(buf+offset, len, tuple, __DIR_REPL);
328 	BUFFER_SIZE(ret, size, len, offset);
329 
330 	ret = snprintf(buf+offset, len, "</layer4>");
331 	BUFFER_SIZE(ret, size, len, offset);
332 
333 	if (zone_incl) {
334 		ret = snprintf(buf+offset, len, "<zone>%u</zone>", tuple->zone);
335 		BUFFER_SIZE(ret, size, len, offset);
336 	}
337 
338 	if (test_bit(ATTR_ORIG_COUNTER_PACKETS, ct->head.set) &&
339 	    test_bit(ATTR_ORIG_COUNTER_BYTES, ct->head.set)) {
340 		ret = snprintf(buf+offset, len, "<counters>");
341 		BUFFER_SIZE(ret, size, len, offset);
342 
343 		ret = __snprintf_counters_xml(buf+offset, len, ct, dir);
344 		BUFFER_SIZE(ret, size, len, offset);
345 
346 		ret = snprintf(buf+offset, len, "</counters>");
347 		BUFFER_SIZE(ret, size, len, offset);
348 	}
349 
350 	ret = snprintf(buf+offset, len, "</meta>");
351 	BUFFER_SIZE(ret, size, len, offset);
352 
353 	return size;
354 }
355 
356 static int
__snprintf_clabels_xml(char * buf,unsigned int len,const struct nf_conntrack * ct,struct nfct_labelmap * map)357 __snprintf_clabels_xml(char *buf, unsigned int len,
358 		       const struct nf_conntrack *ct, struct nfct_labelmap *map)
359 {
360 	const struct nfct_bitmask *b = nfct_get_attr(ct, ATTR_CONNLABELS);
361 	int ret, size = 0, offset = 0;
362 
363 	if (!b)
364 		return 0;
365 
366 	ret = snprintf(buf, len, "<labels>");
367 	BUFFER_SIZE(ret, size, len, offset);
368 
369 	ret = __snprintf_connlabels(buf + offset, len, map, b, "<label>%s</label>");
370 
371 	BUFFER_SIZE(ret, size, len, offset);
372 
373 	ret = snprintf(buf + offset, len, "</labels>");
374 	BUFFER_SIZE(ret, size, len, offset);
375 
376 	return size;
377 }
378 
__snprintf_conntrack_xml(char * buf,unsigned int len,const struct nf_conntrack * ct,const unsigned int msg_type,const unsigned int flags,struct nfct_labelmap * map)379 int __snprintf_conntrack_xml(char *buf,
380 			     unsigned int len,
381 			     const struct nf_conntrack *ct,
382 			     const unsigned int msg_type,
383 			     const unsigned int flags,
384 			     struct nfct_labelmap *map)
385 {
386 	int ret = 0;
387 	unsigned int size = 0, offset = 0;
388 
389 	switch(msg_type) {
390 		case NFCT_T_NEW:
391 			ret = snprintf(buf, len, "<flow type=\"new\">");
392 			break;
393 		case NFCT_T_UPDATE:
394 			ret = snprintf(buf, len, "<flow type=\"update\">");
395 			break;
396 		case NFCT_T_DESTROY:
397 			ret = snprintf(buf, len, "<flow type=\"destroy\">");
398 			break;
399 		default:
400 			ret = snprintf(buf, len, "<flow>");
401 			break;
402 	}
403 
404 	BUFFER_SIZE(ret, size, len, offset);
405 
406 	ret = __snprintf_tuple_xml(buf+offset, len, ct, __DIR_ORIG,
407 				   test_bit(ATTR_ORIG_ZONE, ct->head.set));
408 	BUFFER_SIZE(ret, size, len, offset);
409 
410 	ret = __snprintf_tuple_xml(buf+offset, len, ct, __DIR_REPL,
411 				   test_bit(ATTR_REPL_ZONE, ct->head.set));
412 	BUFFER_SIZE(ret, size, len, offset);
413 
414 	if (test_bit(ATTR_TCP_STATE, ct->head.set) ||
415 	    test_bit(ATTR_SCTP_STATE, ct->head.set) ||
416 	    test_bit(ATTR_DCCP_STATE, ct->head.set) ||
417 	    test_bit(ATTR_TIMEOUT, ct->head.set) ||
418 	    test_bit(ATTR_MARK, ct->head.set) ||
419 	    test_bit(ATTR_SECMARK, ct->head.set) ||
420 	    test_bit(ATTR_ZONE, ct->head.set) ||
421 	    test_bit(ATTR_USE, ct->head.set) ||
422 	    test_bit(ATTR_STATUS, ct->head.set) ||
423 	    test_bit(ATTR_ID, ct->head.set) ||
424 	    test_bit(ATTR_CONNLABELS, ct->head.set) ||
425 	    test_bit(ATTR_TIMESTAMP_START, ct->head.set) ||
426 	    test_bit(ATTR_TIMESTAMP_STOP, ct->head.set)) {
427 		ret = snprintf(buf+offset, len,
428 			       "<meta direction=\"independent\">");
429 		BUFFER_SIZE(ret, size, len, offset);
430 	}
431 
432 	if (test_bit(ATTR_TCP_STATE, ct->head.set)) {
433 		ret = snprintf(buf+offset, len, "<state>%s</state>",
434 			       ct->protoinfo.tcp.state < TCP_CONNTRACK_MAX ?
435 			       states[ct->protoinfo.tcp.state] :
436 			       states[TCP_CONNTRACK_NONE]);
437 		BUFFER_SIZE(ret, size, len, offset);
438 	}
439 
440 	if (test_bit(ATTR_SCTP_STATE, ct->head.set)) {
441 		ret = snprintf(buf+offset, len, "<state>%s</state>",
442 			       ct->protoinfo.sctp.state < SCTP_CONNTRACK_MAX ?
443 			       states[ct->protoinfo.sctp.state] :
444 			       states[SCTP_CONNTRACK_NONE]);
445 		BUFFER_SIZE(ret, size, len, offset);
446 	}
447 
448 	if (test_bit(ATTR_DCCP_STATE, ct->head.set)) {
449 		ret = snprintf(buf+offset, len, "<state>%s</state>",
450 			       ct->protoinfo.sctp.state < DCCP_CONNTRACK_MAX ?
451 			       states[ct->protoinfo.dccp.state] :
452 			       states[DCCP_CONNTRACK_NONE]);
453 		BUFFER_SIZE(ret, size, len, offset);
454 	}
455 
456 	if (test_bit(ATTR_TIMEOUT, ct->head.set)) {
457 		ret = snprintf(buf+offset, len,
458 				"<timeout>%u</timeout>", ct->timeout);
459 		BUFFER_SIZE(ret, size, len, offset);
460 	}
461 
462 	if (test_bit(ATTR_MARK, ct->head.set)) {
463 		ret = snprintf(buf+offset, len, "<mark>%u</mark>", ct->mark);
464 		BUFFER_SIZE(ret, size, len, offset);
465 	}
466 
467 	if (map && test_bit(ATTR_CONNLABELS, ct->head.set)) {
468 		ret = __snprintf_clabels_xml(buf+offset, len, ct, map);
469 		BUFFER_SIZE(ret, size, len, offset);
470 	}
471 
472 	if (test_bit(ATTR_SECMARK, ct->head.set)) {
473 		ret = snprintf(buf+offset, len,
474 				"<secmark>%u</secmark>", ct->secmark);
475 		BUFFER_SIZE(ret, size, len, offset);
476 	}
477 
478 	if (test_bit(ATTR_SECCTX, ct->head.set)) {
479 		ret = snprintf(buf+offset, len,
480 				"<secctx>%s</secctx>", ct->secctx);
481 		BUFFER_SIZE(ret, size, len, offset);
482 	}
483 
484 	if (test_bit(ATTR_ZONE, ct->head.set)) {
485 		ret = snprintf(buf+offset, len, "<zone>%u</zone>", ct->zone);
486 		BUFFER_SIZE(ret, size, len, offset);
487 	}
488 
489 	if (test_bit(ATTR_USE, ct->head.set)) {
490 		ret = snprintf(buf+offset, len, "<use>%u</use>", ct->use);
491 		BUFFER_SIZE(ret, size, len, offset);
492 	}
493 
494 	if (test_bit(ATTR_ID, ct->head.set)) {
495 		ret = snprintf(buf+offset, len, "<id>%u</id>", ct->id);
496 		BUFFER_SIZE(ret, size, len, offset);
497 	}
498 
499 	if (test_bit(ATTR_STATUS, ct->head.set)
500 	    && ct->status & IPS_ASSURED) {
501 		ret = snprintf(buf+offset, len, "<assured/>");
502 		BUFFER_SIZE(ret, size, len, offset);
503 	}
504 
505 	if (test_bit(ATTR_STATUS, ct->head.set)
506 	    && !(ct->status & IPS_SEEN_REPLY)) {
507 		ret = snprintf(buf+offset, len, "<unreplied/>");
508 		BUFFER_SIZE(ret, size, len, offset);
509 	}
510 
511 	if (flags & NFCT_OF_TIMESTAMP) {
512 		if (test_bit(ATTR_TIMESTAMP_START, ct->head.set) ||
513 		    test_bit(ATTR_TIMESTAMP_STOP, ct->head.set)) {
514 			ret = snprintf(buf+offset, len, "<timestamp>");
515 			BUFFER_SIZE(ret, size, len, offset);
516 		}
517 		if (test_bit(ATTR_TIMESTAMP_START, ct->head.set)) {
518 	 		ret = __snprintf_timestamp_start(buf+offset, len, ct);
519 			BUFFER_SIZE(ret, size, len, offset);
520 		}
521 		if (test_bit(ATTR_TIMESTAMP_STOP, ct->head.set)) {
522 	 		ret = __snprintf_timestamp_stop(buf+offset, len, ct);
523 			BUFFER_SIZE(ret, size, len, offset);
524 		}
525 		if (test_bit(ATTR_TIMESTAMP_START, ct->head.set) ||
526 		    test_bit(ATTR_TIMESTAMP_STOP, ct->head.set)) {
527 			ret = snprintf(buf+offset, len, "</timestamp>");
528 			BUFFER_SIZE(ret, size, len, offset);
529 		}
530 	}
531 	if (test_bit(ATTR_TIMESTAMP_START, ct->head.set) &&
532 	    test_bit(ATTR_TIMESTAMP_STOP, ct->head.set)) {
533 		ret = __snprintf_deltatime(buf+offset, len, ct);
534 		BUFFER_SIZE(ret, size, len, offset);
535 	} else if (test_bit(ATTR_TIMESTAMP_START, ct->head.set)) {
536 		ret = __snprintf_deltatime_now(buf+offset, len, ct);
537 		BUFFER_SIZE(ret, size, len, offset);
538 	}
539 
540 	if (test_bit(ATTR_TCP_STATE, ct->head.set) ||
541 	    test_bit(ATTR_SCTP_STATE, ct->head.set) ||
542 	    test_bit(ATTR_DCCP_STATE, ct->head.set) ||
543 	    test_bit(ATTR_TIMEOUT, ct->head.set) ||
544 	    test_bit(ATTR_MARK, ct->head.set) ||
545 	    test_bit(ATTR_SECMARK, ct->head.set) ||
546 	    test_bit(ATTR_ZONE, ct->head.set) ||
547 	    test_bit(ATTR_USE, ct->head.set) ||
548 	    test_bit(ATTR_STATUS, ct->head.set) ||
549 	    test_bit(ATTR_ID, ct->head.set) ||
550 	    test_bit(ATTR_CONNLABELS, ct->head.set) ||
551 	    test_bit(ATTR_TIMESTAMP_START, ct->head.set) ||
552 	    test_bit(ATTR_TIMESTAMP_STOP, ct->head.set)) {
553 	    	ret = snprintf(buf+offset, len, "</meta>");
554 		BUFFER_SIZE(ret, size, len, offset);
555 	}
556 
557 	if (flags & NFCT_OF_TIME) {
558 		time_t t;
559 		struct tm tm;
560 
561 		t = time(NULL);
562 		if (localtime_r(&t, &tm) == NULL)
563 			goto err_out;
564 
565 		ret = snprintf(buf+offset, len, "<when>");
566 		BUFFER_SIZE(ret, size, len, offset);
567 
568 		ret = __snprintf_localtime_xml(buf+offset, len, &tm);
569 		BUFFER_SIZE(ret, size, len, offset);
570 
571 		ret = snprintf(buf+offset, len, "</when>");
572 		BUFFER_SIZE(ret, size, len, offset);
573 	}
574 
575 	if (test_bit(ATTR_HELPER_NAME, ct->head.set)) {
576 		ret = __snprintf_helper_name(buf+offset, len, ct);
577 		BUFFER_SIZE(ret, size, len, offset);
578 	}
579 err_out:
580 	ret = snprintf(buf+offset, len, "</flow>");
581 	BUFFER_SIZE(ret, size, len, offset);
582 
583 	return size;
584 }
585