From bf983d7ca4331e257107dde367c46bfb030aed98 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 21 Mar 2022 11:02:49 +0100 Subject: [PATCH] * New `clixon-config@2022-03-21.yang` revision * Added option: * `CLICON_NETCONF_BASE_CAPABILITY` * Removed `NETCONF_1_1_ANNOUNCE` compile option --- apps/netconf/netconf_lib.c | 184 ----------------- apps/netconf/netconf_lib.h | 78 ------- include/clixon_custom.h | 6 - lib/src/clixon_netconf_lib.c | 12 +- test/test_netconf_framing.sh | 91 +++++++++ yang/clixon/Makefile.in | 2 +- yang/clixon/clixon-config@2022-02-11.yang | 2 +- ...-05.yang => clixon-config@2022-03-21.yang} | 191 +++++++----------- 8 files changed, 177 insertions(+), 389 deletions(-) delete mode 100644 apps/netconf/netconf_lib.c delete mode 100644 apps/netconf/netconf_lib.h create mode 100755 test/test_netconf_framing.sh rename yang/clixon/{clixon-config@2021-12-05.yang => clixon-config@2022-03-21.yang} (88%) diff --git a/apps/netconf/netconf_lib.c b/apps/netconf/netconf_lib.c deleted file mode 100644 index a045436e..00000000 --- a/apps/netconf/netconf_lib.c +++ /dev/null @@ -1,184 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren - Copyright (C) 2017-2019 Olof Hagsand - Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate) - - This file is part of CLIXON. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the "GPL"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK ***** - - * - * netconf lib - *****************************************************************************/ -#ifdef HAVE_CONFIG_H -#include "clixon_config.h" /* generated by config & autoconf */ -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* cligen */ -#include - -/* clicon */ -#include - -#include "netconf_rpc.h" -#include "netconf_lib.h" - -/* - * Exported variables - */ -enum transport_type transport = NETCONF_SSH; /* XXX Remove SOAP support */ -int cc_closed = 0; /* XXX Please remove (or at least hide in handle) this global variable */ - -/*! Add netconf xml postamble of message. I.e, xml after the body of the message. - * @param[in] cb Netconf packet (cligen buffer) - */ -int -add_preamble(cbuf *cb) -{ - return 0; -} - -/*! Add netconf xml postamble of message. I.e, xml after the body of the message. - * for soap this is the envelope stuff, for ssh this is ]]>]]> - * @param[in] cb Netconf packet (cligen buffer) - */ -int -add_postamble(cbuf *cb) -{ - switch (transport){ - case NETCONF_SSH: - cprintf(cb, "]]>]]>"); /* Add RFC4742 end-of-message marker */ - break; - } - return 0; -} - -/*! Add error postamble - * compared to regular messages (see add_postamble), error message differ in some - * protocols (eg soap) by adding a longer and deeper header. - * @param[in] cb Netconf packet (cligen buffer) - */ -int -add_error_postamble(cbuf *cb) -{ - switch (transport){ - default: /* fall through */ - if (add_postamble(cb) < 0) - return -1; - break; - } - return 0; -} - -/*! Send netconf message from cbuf on socket - * @param[in] s - * @param[in] cb Cligen buffer that contains the XML message - * @param[in] msg Only for debug - * @retval 0 OK - * @retval -1 Error - * @see netconf_output_encap for function with encapsulation - */ -int -netconf_output(int s, - cbuf *cb, - char *msg) -{ - char *buf = cbuf_get(cb); - int len = cbuf_len(cb); - int retval = -1; - - clicon_debug(1, "SEND %s", msg); - if (clicon_debug_get() > 1){ /* XXX: below only works to stderr, clicon_debug may log to syslog */ - cxobj *xt = NULL; - if (clixon_xml_parse_string(buf, YB_NONE, NULL, &xt, NULL) == 0){ - clicon_xml2file(stderr, xml_child_i(xt, 0), 0, 0); - fprintf(stderr, "\n"); - xml_free(xt); - } - } - if (write(s, buf, len) < 0){ - if (errno == EPIPE) - ; - else - clicon_log(LOG_ERR, "%s: write: %s", __FUNCTION__, strerror(errno)); - goto done; - } - retval = 0; - done: - return retval; -} - - -/*! Encapsulate and send outgoing netconf packet as cbuf on socket - * @param[in] s - * @param[in] cb Cligen buffer that contains the XML message - * @param[in] msg Only for debug - * @retval 0 OK - * @retval -1 Error - * @note Assumes "cb" contains valid XML - * @see netconf_output without encapsulation - */ -int -netconf_output_encap(int s, - cbuf *cb, - char *msg) -{ - int retval = -1; - cbuf *cb1 = NULL; - - if ((cb1 = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } - add_preamble(cb1); - cprintf(cb1, "%s", cbuf_get(cb)); - add_postamble(cb1); - retval = netconf_output(s, cb1, msg); - done: - if (cb1) - cbuf_free(cb1); - return retval; -} diff --git a/apps/netconf/netconf_lib.h b/apps/netconf/netconf_lib.h deleted file mode 100644 index 43c4baed..00000000 --- a/apps/netconf/netconf_lib.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren - Copyright (C) 2017-2019 Olof Hagsand - Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC (Netgate) - - This file is part of CLIXON. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the "GPL"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK ***** - - * - * Netconf lib - *****************************************************************************/ -#ifndef _NETCONF_LIB_H_ -#define _NETCONF_LIB_H_ - -/* - * Types - */ -enum target_type{ /* netconf */ - RUNNING, - CANDIDATE -}; -enum transport_type{ - NETCONF_SSH, /* RFC 4742 */ -}; - -enum test_option{ /* edit-config */ - SET, - TEST_THEN_SET, - TEST_ONLY -}; - -enum error_option{ /* edit-config */ - STOP_ON_ERROR, - CONTINUE_ON_ERROR -}; - -/* - * Variables - */ -extern enum transport_type transport; -extern int cc_closed; - -/* - * Prototypes - */ -int add_preamble(cbuf *xf); -int add_postamble(cbuf *xf); -int netconf_output(int s, cbuf *xf, char *msg); -int netconf_output_encap(int s, cbuf *cb, char *msg); - -#endif /* _NETCONF_LIB_H_ */ diff --git a/include/clixon_custom.h b/include/clixon_custom.h index f316aee4..b7d09558 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -157,9 +157,3 @@ */ #define PROTO_RESTART_RECONNECT -/*! Announce Netconf 1.1 capability as defined by RFC 6242 - * Problem wih 1.1 is it requires "chunked framing" which Clixon at this point does not - * support. - * See https://github.com/clicon/clixon/issues/50 and https://github.com/clicon/clixon/issues/314 - */ -#undef NETCONF_1_1_ANNOUNCE diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 374dcf21..c65d594d 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -1690,12 +1690,12 @@ netconf_hello_server(clicon_handle h, cprintf(cb, "", NETCONF_BASE_NAMESPACE, 42); cprintf(cb, ""); -#ifdef NETCONF_1_1_ANNOUNCE - /* Each peer MUST send at least the base NETCONF capability, "urn:ietf:params:netconf:base:1.1" - * RFC 6241 Sec 8.1 - */ - cprintf(cb, "%s", NETCONF_BASE_CAPABILITY_1_1); -#endif + if (clicon_option_int(h, "CLICON_NETCONF_BASE_CAPABILITY") > 0){ + /* Each peer MUST send at least the base NETCONF capability, "urn:ietf:params:netconf:base:1.1" + * RFC 6241 Sec 8.1 + */ + cprintf(cb, "%s", NETCONF_BASE_CAPABILITY_1_1); + } /* A peer MAY include capabilities for previous NETCONF versions, to indicate that it supports multiple protocol versions. */ cprintf(cb, "%s", NETCONF_BASE_CAPABILITY_1_0); diff --git a/test/test_netconf_framing.sh b/test/test_netconf_framing.sh new file mode 100755 index 00000000..85fa9bbc --- /dev/null +++ b/test/test_netconf_framing.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# Netconf framing functionality +# See RFC6242 and RFC 4742 +# See test_netconf_hello.sh for hello protocol negotiation only + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +cfg=$dir/conf_yang.xml +fyang=$dir/clixon-example.yang +tmp=$dir/tmp.x + +cat < $cfg + + $cfg + ietf-netconf:startup + 42 + ${YANG_INSTALLDIR} + $fyang + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/backend + example_backend.so$ + /usr/local/lib/$APPNAME/netconf + 1 + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + $APPNAME + $dir/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + true + +EOF + +cat < $fyang +module clixon-example{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; + /* Generic config data */ + container table{ + list parameter{ + key name; + leaf name{ + type string; + } + leaf value{ + type string; + } + } + } +} +EOF + +new "test params: -f $cfg -- -s" +# Bring your own backend +if [ $BE -ne 0 ]; then + # kill old backend (if any) + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -- -s" + start_backend -s init -f $cfg -- -s +fi + +new "wait backend" +wait_backend + +# Hello +new "Netconf 1.0 eom framing" +expecteof "$clixon_netconf -qef $cfg -o CLICON_NETCONF_BASE_CAPABILITY=0" 0 "urn:ietf:params:netconf:base:1.0]]>]]>mergea
]]>]]>$" "^]]>]]>$" + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg +fi + +rm -rf $dir + +new "endtest" +endtest diff --git a/yang/clixon/Makefile.in b/yang/clixon/Makefile.in index 6947c57c..537b6d32 100644 --- a/yang/clixon/Makefile.in +++ b/yang/clixon/Makefile.in @@ -42,7 +42,7 @@ datarootdir = @datarootdir@ YANG_INSTALLDIR = @YANG_INSTALLDIR@ # Note: mirror these to test/config.sh.in -YANGSPECS = clixon-config@2022-02-11.yang # 5.6 +YANGSPECS = clixon-config@2022-03-21.yang # 5.7 YANGSPECS += clixon-lib@2021-12-05.yang # 5.5 YANGSPECS += clixon-rfc5277@2008-07-01.yang YANGSPECS += clixon-xml-changelog@2019-03-21.yang diff --git a/yang/clixon/clixon-config@2022-02-11.yang b/yang/clixon/clixon-config@2022-02-11.yang index a29e5be1..cdb8fb6d 100644 --- a/yang/clixon/clixon-config@2022-02-11.yang +++ b/yang/clixon/clixon-config@2022-02-11.yang @@ -73,7 +73,7 @@ module clixon-config { CLICON_CLI_GENMODEL (use autocli/enable-autocli instead) CLICON_CLI_GENMODEL_TYPE (use autocli/list-keyword-default and compress rules instead) CLICON_CLI_GENMODEL_COMPLETION (use autocli/completion-default instead) - CLICON_CLI_AUTOCLI_EXCLUDE (use autocli/exclude logic instead) + CLICON_CLI_AUTOCLI_EXCLUDE (use autocli/module-default, rule/enable logic instead) CLICON_CLI_MODEL_TREENAME (use constant AUTOCLI_TREENAME instead) Released in Clixon 5.5"; } diff --git a/yang/clixon/clixon-config@2021-12-05.yang b/yang/clixon/clixon-config@2022-03-21.yang similarity index 88% rename from yang/clixon/clixon-config@2021-12-05.yang rename to yang/clixon/clixon-config@2022-03-21.yang index fb3935fc..09e12abd 100644 --- a/yang/clixon/clixon-config@2021-12-05.yang +++ b/yang/clixon/clixon-config@2022-03-21.yang @@ -19,7 +19,7 @@ module clixon-config { "Clixon configuration file ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2019 Olof Hagsand - Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate) + Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate) This file is part of CLIXON @@ -46,6 +46,29 @@ module clixon-config { ***** END LICENSE BLOCK *****"; + revision 2022-03-21 { + description + "Added option: + CLICON_NETCONF_BASE_CAPABILITY + Released in Clixon 5.7"; + } + revision 2022-02-11 { + description + "Added option: + CLICON_LOG_STRING_LIMIT + CLICON_YANG_LIBRARY + Changed default value: + CLICON_MODULE_LIBRARY_RFC7895 to false + Removed (previosly marked) obsolete options: + CLICON_RESTCONF_PATH + CLICON_RESTCONF_PRETTY + CLICON_CLI_GENMODEL + CLICON_CLI_GENMODEL_TYPE + CLICON_CLI_GENMODEL_COMPLETION + CLICON_CLI_AUTOCLI_EXCLUDE + CLICON_CLI_MODEL_TREENAME + Released in Clixon 5.6"; + } revision 2021-12-05 { description "Imported @@ -56,7 +79,7 @@ module clixon-config { CLICON_CLI_GENMODEL (use autocli/enable-autocli instead) CLICON_CLI_GENMODEL_TYPE (use autocli/list-keyword-default and compress rules instead) CLICON_CLI_GENMODEL_COMPLETION (use autocli/completion-default instead) - CLICON_CLI_AUTOCLI_EXCLUDE (use autocli/exclude logic instead) + CLICON_CLI_AUTOCLI_EXCLUDE (use autocli/module-default, rule/enable logic instead) CLICON_CLI_MODEL_TREENAME (use constant AUTOCLI_TREENAME instead) Released in Clixon 5.5"; } @@ -257,28 +280,6 @@ module clixon-config { } } } - typedef cli_genmodel_type{ - description - "How to generate auto CLI from YANG model, - eg {container c {list a{ key x; leaf x; leaf y;}}"; - type enumeration{ - enum NONE{ - description "No extra keywords: c a "; - } - enum VARS{ - description "Keywords on non-key variables: c a y "; - } - enum ALL{ - description "Keywords on all variables: c a x y "; - } - enum HIDE{ - description "Keywords on non-key variables and hide container around lists: a y "; - } - enum OC_COMPRESS{ - description "See: https://github.com/openconfig/ygot/blob/master/docs/design.md#openconfig-path-compression"; - } - } - } typedef nacm_mode{ description "Mode of RFC8341 Network Configuration Access Control Model. @@ -476,16 +477,6 @@ module clixon-config { only loading from startup but may occur in other circumstances as well. This means that sanity checks of erroneous XML/JSON may not be properly signalled."; } - leaf CLICON_RESTCONF_PATH { - type string; - default "/www-data/fastcgi_restconf.sock"; - description - "FastCGI unix socket. Should be specified in webserver - Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock - Only if with-restconf=fcgi, NOT native - Note: Obsolete, use fcgi-socket in clixon-restconf.yang instead"; - status obsolete; - } leaf CLICON_BACKEND_DIR { type string; description @@ -526,6 +517,20 @@ module clixon-config { If true, an RPC can be sent without a message-id. This applies to both external NETCONF and internal (IPC) netconf"; } + leaf CLICON_NETCONF_BASE_CAPABILITY { + type int32; + default 1; + description + "This option relates to RFC6241 Sec 8.1 capabilities exchange. + This number is the highest netconf base capability announced during + the hello protocol. + Specifically, If the option number is 0, only 'urn:ietf:params:netconf:base:1.0' + is announced, if it is 1, both 'urn:ietf:params:netconf:base:1.0' and + 'urn:ietf:params:netconf:base:1.1' are announced. + Base capability '1' includes switching over to chunked framing as defined in + RFC6242 for example. + This only applies to the external NETCONF"; + } leaf CLICON_RESTCONF_DIR { type string; description @@ -562,21 +567,6 @@ module clixon-config { automatically updated. If this option is false, the startup is autoamtically updated following the RFC"; } - leaf CLICON_RESTCONF_PRETTY { - type boolean; - default true; - description - "Restconf return value pretty print. - Restconf clients may add HTTP header: - Accept: application/yang-data+json, or - Accept: application/yang-data+xml - to get return value in XML or JSON. - RFC 8040 examples print XML and JSON in pretty-printed form. - Setting this value to false makes restconf return not pretty-printed - which may be desirable for performance or tests - Note: Obsolete, use pretty in clixon-restconf.yang instead"; - status obsolete; - } leaf CLICON_RESTCONF_USER { type string; description @@ -635,59 +625,6 @@ module clixon-config { "Startup CLI mode. This should match a CLICON_MODE variable set in one of the clispec files"; } - leaf CLICON_CLI_GENMODEL { - type int32; - default 1; - description - "0: Do not generate CLISPEC syntax for the auto-cli. - 1: Generate a CLI specification for CLI completion of all loaded Yang modules. - This CLI tree can be accessed in CLI-spec files using the tree reference syntax (eg - @datamodel). - 2: Same including state syntax in a tree called @datamodelstate and @datamodelshow - See also CLICON_CLI_MODEL_TREENAME. - Obsolete, use clixon-autocli.yang enable-autocli instead"; - status obsolete; - } - leaf CLICON_CLI_MODEL_TREENAME { - type string; - default "datamodel"; - description - "If CLICON_CLI_GENMODEL is set, CLI specs can reference the - model syntax using a model tree set by this option. - Three trees are generated with this name as a base, (assuming base is datamodel): - - @datamodel - a clispec for navigating in editing a configuration (set/merge/delete) - - @datamodelshow - a clispec for navigating in showing a configuration - - @datamodelstate - a clispec for navigating in showing a configuration WITH state - Example: set @datamodel, cli_set(); - show @datamodelshow, cli_show_auto(); - show state @datamodelstate, cli_show_auto_state(); - Obsolete, use constant AUTOCLI_TREENAME instead; - "; - status obsolete; - } - leaf CLICON_CLI_GENMODEL_COMPLETION { - type int32; - default 1; - description "Generate code for CLI completion of existing db symbols. - Obsolete, use autocli/completion-default instead"; - status obsolete; - } - leaf CLICON_CLI_GENMODEL_TYPE { - type cli_genmodel_type; - default "VARS"; - description "How to generate and show auto CLI syntax: VARS|ALL|HIDE|OC_COMPRESS"; - } - leaf CLICON_CLI_AUTOCLI_EXCLUDE { - type string; - description - "List of module names that should not be generated autocli from - Example: - clixon-restconf - means generate autocli for all models except clixon-restconf.yang - The value can be a list of space separated module names - Obsolete, use autocli/exclude logic instead"; - status obsolete; - } leaf CLICON_CLI_VARONLY { type int32; default 1; @@ -885,7 +822,9 @@ module clixon-config { default cache; description "Clixon datastore cache behaviour. There are three values: no cache, - cache with copy, or cache without copy."; + cache with copy, or cache without copy. + Note: 'cache' is default value and supported with regressions etc. + Others are experimental (in Clixon 5.5)"; } leaf CLICON_XMLDB_FORMAT { type datastore_format; @@ -1043,24 +982,41 @@ module clixon-config { If this option is set, Clixon disables NACM if a datastore does NOT contain a NACM config on load."; } - leaf CLICON_MODULE_LIBRARY_RFC7895 { + leaf CLICON_YANG_LIBRARY { type boolean; default true; description - "Enable RFC 7895 YANG Module library support as state data. If - enabled, module info will appear when doing netconf get or + "Enable YANG library support as state data according to RFC8525. + If enabled, module info will appear when doing netconf get or restconf GET. - See also CLICON_XMLDB_MODSTATE"; + The module state data is on the form: + ... + If CLICON_MODULE_LIBRARY_RFC7895 is set (as well), the module state uses RFC7895 + instead where the modile state is on the form: + ... + See also CLICON_XMLDB_MODSTATE where the module state info is used to tag datastores + with module information."; + } + leaf CLICON_MODULE_LIBRARY_RFC7895 { + type boolean; + default false; + description + "Enable RFC 7895 YANG Module library support as state data, instead of RFC8525. + Note CLICON_YANG_LIBRARY must be enabled for this to have effect. + See also CLICON_YANG_LIBRARY and CLICON_MODULE_SET_ID"; + status obsolete; } leaf CLICON_MODULE_SET_ID { type string; default "0"; - description "If RFC 7895 YANG Module library enabled: - Contains a server-specific identifier representing - the current set of modules and submodules. The - server MUST change the value of this leaf if the - information represented by the 'module' list instances - has changed."; + description + "Only if CLICON_YANG_LIBRARY enabled. + Contains a server-specific identifier representing the current set of modules + and submodules. The server MUST change the value of this leaf if the + information represented by the 'module' list instances has changed. + The /yang-library/content-id state-data leaf is set with this value + If CLICON_MODULE_LIBRARY_RFC7895 is enabled, it sets the modules-state/module-set-id + instead"; } leaf CLICON_STREAM_DISCOVERY_RFC5277 { type boolean; @@ -1111,5 +1067,14 @@ module clixon-config { data to store before dropping. 0 means no retention"; } + leaf CLICON_LOG_STRING_LIMIT { + type uint32; + default 0; + description + "Length limitation of debug and log strings. + Especially useful for dynamic debug strings, such as packet dumps. + 0 means no limit"; + + } } }