Home | History | Annotate | Download | only in conntrack
      1 /*
      2  * (C) 2005-2011 by Pablo Neira Ayuso <pablo (at) 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 
     56 const char *__proto2str(uint8_t protonum)
     57 {
     58 	return proto2str[protonum] ? proto2str[protonum] : "unknown";
     59 }
     60 
     61 const char *__l3proto2str(uint8_t protonum)
     62 {
     63 	return l3proto2str[protonum] ? l3proto2str[protonum] : "unknown";
     64 }
     65 
     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 
     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 
     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 
    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 
    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
    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
    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
    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
    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
    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
    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 
    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
    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 
    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