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