From f6fe9f6a64a115f58f5be10d77d4efb41fa1e836 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 22 Jun 2022 09:57:25 +0200 Subject: [PATCH] SNMP: fix SNMP set access of table entries [Conversion of ethernet address (PhysAddress) and IP address (IPAddress) crashes agent](https://github.com/clicon/clixon/issues/340) Hwaddress and IP adress for scalar and table set should now work --- apps/snmp/snmp_handler.c | 208 ++++++++++++++++++++++++++++----- apps/snmp/snmp_lib.c | 46 ++++++-- apps/snmp/snmp_lib.h | 1 + apps/snmp/snmp_register.c | 5 - include/clixon_custom.h | 1 + lib/src/clixon_path.c | 2 +- test/mibs/CLIXON-TYPES-MIB.txt | 11 +- test/test_snmp_set.sh | 155 +++++++++++++++--------- 8 files changed, 335 insertions(+), 94 deletions(-) diff --git a/apps/snmp/snmp_handler.c b/apps/snmp/snmp_handler.c index a9cc5f9a..3bbb893b 100644 --- a/apps/snmp/snmp_handler.c +++ b/apps/snmp/snmp_handler.c @@ -111,7 +111,6 @@ snmp_common_handler(netsnmp_mib_handler *handler, return retval; } -#ifdef SNMP_TABLE_DYNAMIC /*! */ static int @@ -193,7 +192,6 @@ snmp_scalar_return(cxobj *xs, free(reason); return retval; } -#endif /* SNMP_TABLE_DYNAMIC */ /*! Scalar handler, set a value to clixon * get xpath: see yang2api_path_fmt / api_path2xpath @@ -233,7 +231,7 @@ snmp_scalar_get(clicon_handle h, /* Prepare backend call by constructing namespace context */ if (xml_nsctx_yang(ys, &nsc) < 0) goto done; - /* Create xpath from yang (XXX works not for lists) */ + /* Create xpath from yang */ if (snmp_yang2xpath(ys, cvk, &xpath) < 0) goto done; /* Do the backend call */ @@ -315,11 +313,13 @@ snmp_scalar_get(clicon_handle h, static int snmp_scalar_set(clicon_handle h, yang_stmt *ys, + cvec *cvk, netsnmp_agent_request_info *reqinfo, netsnmp_request_info *requests) { int retval = -1; char *api_path = NULL; + char *api_path_fmt = NULL; cxobj *xtop = NULL; cxobj *xbot = NULL; cxobj *xb; @@ -328,6 +328,11 @@ snmp_scalar_set(clicon_handle h, char *valstr = NULL; cbuf *cb = NULL; netsnmp_variable_list *requestvb = requests->requestvb; + cvec *cvk1; + int i; + int asn1_type; + + clicon_debug(1, "%s", __FUNCTION__); if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); @@ -335,7 +340,16 @@ snmp_scalar_set(clicon_handle h, } if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL) goto done; - if (yang2api_path_fmt(ys, 0, &api_path) < 0) + if (yang2api_path_fmt(ys, 0, &api_path_fmt) < 0) + goto done; + /* Need to prepend an element to fit api_path_fmt2api_path cvv parameter */ + if ((cvk1 = cvec_new(1)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_new"); + goto done; + } + for (i=0; itype, asn1_type); if ((ret = netsnmp_request_set_error(requests, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){ clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); - goto done; + goto ok; } } break; case MODE_SET_RESERVE2: /* 1 */ break; case MODE_SET_ACTION: /* 2 */ - if (snmp_scalar_set(sh->sh_h, sh->sh_ys, reqinfo, requests) < 0) + if (snmp_scalar_set(sh->sh_h, sh->sh_ys, NULL, reqinfo, requests) < 0) goto done; break; - case MODE_SET_UNDO: /* 5 */ - if (clicon_rpc_discard_changes(sh->sh_h) < 0) - goto done; - break; - case MODE_SET_COMMIT: /* 3 */ if (clicon_rpc_commit(sh->sh_h) < 0) goto done; break; case MODE_SET_FREE: /* 4 */ break; + case MODE_SET_UNDO: /* 5 */ + if (clicon_rpc_discard_changes(sh->sh_h) < 0) + goto done; + break; } + ok: retval = SNMP_ERR_NOERROR; done: return retval; } -#ifdef SNMP_TABLE_DYNAMIC /*! Create xpath from YANG table OID + 1 + n + cvk/key = requestvb->name * Get yang of leaf from first part of OID * Create xpath with right keys from later part of OID @@ -556,6 +576,127 @@ snmp_table_get(clicon_handle h, goto done; } +/*! Set value in table + * Get yang of leaf from first part of OID + * Create xpath with right keys from later part of OID + * Query clixon if object exists, if so return value + * @param[in] h Clixon handle + * @param[in] yt Yang of table (of list type) + * @param[in] oids OID of ultimate scalar value + * @param[in] oidslen OID length of scalar + * @param[in] reginfo + * @param[in] requests + * @retval -1 Error + * @retval 0 Object not found + * @retval 1 OK + */ +static int +snmp_table_set(clicon_handle h, + yang_stmt *yt, + oid *oids, + size_t oidslen, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + int retval = -1; + oid oidt[MAX_OID_LEN] = {0,}; /* Table / list oid */ + size_t oidtlen = MAX_OID_LEN; + oid oidleaf[MAX_OID_LEN] = {0,}; /* Leaf */ + size_t oidleaflen = MAX_OID_LEN; + oid *oidi; + size_t oidilen; + yang_stmt *ys; + yang_stmt *yk; + char *xpath = NULL; + cvec *cvk_orig; + cvec *cvk_val; + int i; + cg_var *cv; + int ret; + int asn1_type; + netsnmp_variable_list *requestvb; + + /* Get OID from table /list */ + if ((ret = yangext_oid_get(yt, oidt, &oidtlen, NULL)) < 0) + goto done; + if (ret == 0) + goto done; + /* Get yang of leaf from first part of OID */ + ys = NULL; + while ((ys = yn_each(yt, ys)) != NULL) { + if (yang_keyword_get(ys) != Y_LEAF) + continue; + /* reset oid */ + oidleaflen = MAX_OID_LEN; + if ((ret = yangext_oid_get(ys, oidleaf, &oidleaflen, NULL)) < 0) + goto done; + if (ret == 0) + goto done; + if (oidtlen + 1 != oidleaflen) /* Indexes may be from other OID scope, skip those */ + continue; + if (oids[oidleaflen-1] == oidleaf[oidleaflen-1]) + break; + } + if (ys == NULL){ + /* No leaf with matching OID */ + goto fail; + } + if (type_yang2asn1(ys, &asn1_type, 0) < 0) + goto done; + requestvb = requests->requestvb; + if (requestvb->type != asn1_type){ + clicon_debug(1, "%s Expected type:%d, got: %d", __FUNCTION__, requestvb->type, asn1_type); + if ((ret = netsnmp_request_set_error(requests, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto ok; + } + } + /* Create xpath with right keys from later part of OID + * Inverse of snmp_str2oid + */ + if ((cvk_orig = yang_cvec_get(yt)) == NULL){ + clicon_err(OE_YANG, 0, "No keys"); + goto done; + } + if ((cvk_val = cvec_dup(cvk_orig)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_dup"); + goto done; + } + /* read through keys and create cvk */ + oidilen = oidslen-(oidtlen+1); + oidi = oids+oidtlen+1; + /* Add keys */ + for (i=0; irequestvb; -#endif switch(reqinfo->mode){ case MODE_GET: // 160 -#ifdef SNMP_TABLE_DYNAMIC /* Create xpath from YANG table OID + 1 + n + cvk/key = requestvb->name */ if ((ret = snmp_table_get(sh->sh_h, sh->sh_ys, @@ -728,10 +863,8 @@ clixon_snmp_table_handler(netsnmp_mib_handler *handler, } clicon_debug(1, "%s Nosuchinstance", __FUNCTION__); } -#endif break; case MODE_GETNEXT: // 161 -#ifdef SNMP_TABLE_DYNAMIC /* Register table sub-oid:s of existing entries in clixon */ if ((ret = snmp_table_getnext(sh->sh_h, sh->sh_ys, requestvb->name, requestvb->name_length, @@ -746,14 +879,35 @@ clixon_snmp_table_handler(netsnmp_mib_handler *handler, } clicon_debug(1, "%s No such object", __FUNCTION__); } -#endif break; - case MODE_SET_RESERVE1: - case MODE_SET_RESERVE2: - case MODE_SET_ACTION: - case MODE_SET_COMMIT: + case MODE_SET_RESERVE1: // 0 + // Check types: compare type in requestvb to yang type (or do later) + break; + case MODE_SET_RESERVE2: // 1 + break; + case MODE_SET_ACTION: // 2 + if ((ret = snmp_table_set(sh->sh_h, sh->sh_ys, + requestvb->name, requestvb->name_length, + reqinfo, requests)) < 0) + goto done; + if (ret == 0){ + if ((ret = netsnmp_request_set_error(requests, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto done; + } + clicon_debug(1, "%s Nosuchinstance", __FUNCTION__); + } + break; + case MODE_SET_COMMIT: // 3 + if (clicon_rpc_commit(sh->sh_h) < 0) + goto done; + break; + case MODE_SET_FREE: // 4 + break; + case MODE_SET_UNDO : // 5 + if (clicon_rpc_discard_changes(sh->sh_h) < 0) + goto done; break; - } ok: retval = SNMP_ERR_NOERROR; diff --git a/apps/snmp/snmp_lib.c b/apps/snmp/snmp_lib.c index d76a95b9..2c2e41d3 100644 --- a/apps/snmp/snmp_lib.c +++ b/apps/snmp/snmp_lib.c @@ -58,6 +58,9 @@ #include #include #include +#include +#include /* inet_addr */ +#include #include /* ether_aton */ /* net-snmp */ @@ -104,6 +107,7 @@ static const map_str2int snmp_type_map[] = { {"uint32", ASN_TIMETICKS}, // 0x43 / 67 {"uint64", ASN_COUNTER64}, // 0x46 / 70 {"boolean", ASN_INTEGER}, // 2 special case -> enumeration + {"string", ASN_IPADDRESS}, // 64 {NULL, -1} }; @@ -467,10 +471,11 @@ type_yang2asn1(yang_stmt *ys, * @retval 1 OK, and valstr set * @retval 0 Invalid value or type * @retval -1 Error - * @see type_xml2snmpstr for snmpget + * @see type_xml2snmp for snmpget */ int type_snmp2xml(yang_stmt *ys, + int *asn1type, netsnmp_variable_list *requestvb, netsnmp_agent_request_info *reqinfo, netsnmp_request_info *requests, @@ -481,6 +486,7 @@ type_snmp2xml(yang_stmt *ys, enum cv_type cvtype; cg_var *cv = NULL; char *restype = NULL; /* resolved type */ + char *origtype = NULL; /* original type */ yang_stmt *yrestype = NULL; int ret; @@ -489,9 +495,12 @@ type_snmp2xml(yang_stmt *ys, clicon_err(OE_UNIX, EINVAL, "valstr is NULL"); goto done; } - cvstr = (char*)clicon_int2str(snmp_type_map, requestvb->type); + if ((cvstr = (char*)clicon_int2str(snmp_type_map, requestvb->type)) == NULL){ + clicon_err(OE_XML, 0, "No mapping for snmp type %d", requestvb->type); + goto done; + } /* Get yang type of leaf and trasnslate to ASN.1 */ - if (snmp_yang_type_get(ys, NULL, NULL, &yrestype, &restype) < 0) + if (snmp_yang_type_get(ys, NULL, &origtype, &yrestype, &restype) < 0) goto done; /* special case for enum */ if (strcmp(cvstr, "int32")==0 && strcmp(restype, "enumeration") == 0) @@ -503,7 +512,7 @@ type_snmp2xml(yang_stmt *ys, clicon_err(OE_UNIX, errno, "cv_new"); goto done; } - switch (requestvb->type){ + switch (*asn1type){ case ASN_TIMETICKS: // 67 case ASN_INTEGER: // 2 if (cvtype == CGV_STRING){ /* special case for enum */ @@ -534,9 +543,19 @@ type_snmp2xml(yang_stmt *ys, case ASN_GAUGE: // 0x42 cv_uint32_set(cv, *requestvb->val.integer); break; - case CLIXON_ASN_ADMIN_STRING: // XXX - case CLIXON_ASN_PHYS_ADDR: // XXX - assert(0); + case ASN_IPADDRESS:{ + struct in_addr addr; + memcpy(&addr.s_addr, requestvb->val.string, 4); + cv_string_set(cv, inet_ntoa(addr)); + break; + } + case CLIXON_ASN_ADMIN_STRING: + cv_string_set(cv, (char*)requestvb->val.string); + *asn1type = ASN_OCTET_STR; + break; + case CLIXON_ASN_PHYS_ADDR: + cv_string_set(cv, ether_ntoa((const struct ether_addr *)requestvb->val.string)); + *asn1type = ASN_OCTET_STR; break; case ASN_OCTET_STR: // 4 cv_string_set(cv, (char*)requestvb->val.string); @@ -567,6 +586,8 @@ type_snmp2xml(yang_stmt *ys, retval = 1; done: clicon_debug(2, "%s %d", __FUNCTION__, retval); + if (origtype) + free(origtype); if (cv) cv_free(cv); return retval; @@ -734,6 +755,17 @@ type_xml2snmp(char *snmpstr, goto fail; } break; + case ASN_IPADDRESS:{ + in_addr_t saddr; + *snmplen = 4; + if ((*snmpval = malloc(*snmplen)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + saddr = (int32_t)inet_addr(snmpstr); + memcpy(*snmpval, &saddr, 4); + break; + } case CLIXON_ASN_PHYS_ADDR:{ struct ether_addr *eaddr; *snmplen = sizeof(*eaddr); diff --git a/apps/snmp/snmp_lib.h b/apps/snmp/snmp_lib.h index ae840ae9..61790203 100644 --- a/apps/snmp/snmp_lib.h +++ b/apps/snmp/snmp_lib.h @@ -81,6 +81,7 @@ void *snmp_handle_clone(void *arg); void snmp_handle_free(void *arg); int type_yang2asn1(yang_stmt *ys, int *asn1_type, int extended); int type_snmp2xml(yang_stmt *ys, + int *asn1type, netsnmp_variable_list *requestvb, netsnmp_agent_request_info *reqinfo, netsnmp_request_info *requests, diff --git a/apps/snmp/snmp_register.c b/apps/snmp/snmp_register.c index f3589a00..b3e85b3c 100644 --- a/apps/snmp/snmp_register.c +++ b/apps/snmp/snmp_register.c @@ -459,11 +459,6 @@ mibyang_traverse(clicon_handle h, /* Register table entry handler itself (not column/row leafs) */ if (mibyang_table_register(h, yn) < 0) goto done; -#ifndef SNMP_TABLE_DYNAMIC - /* Register table sub-oid:s of existing entries in clixon */ - if (mibyang_table_poll(h, yn) < 0) - goto done; -#endif goto ok; } break; diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 064d8973..ca327354 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -163,3 +163,4 @@ * If not set, client will exit */ #define PROTO_RESTART_RECONNECT + diff --git a/lib/src/clixon_path.c b/lib/src/clixon_path.c index ffb3488e..09fd5b59 100644 --- a/lib/src/clixon_path.c +++ b/lib/src/clixon_path.c @@ -426,7 +426,7 @@ yang2api_path_fmt(yang_stmt *ys, * @param[in] api_path_fmt XML key format, eg /aaa/%s/name * @param[in] cvv cligen variable vector, one for every wildchar in api_path_fmt * @param[out] api_path api_path, eg /aaa/17. Free after use - * @param[out] cvvi 1..cvv-len. Index into cvv of last cvv entry used, For example, + * @param[out] cvv_i 1..cvv-len. Index into cvv of last cvv entry used, For example, * if same as len of cvv, all were used, if < some entries were not * @retval 0 OK * @retval -1 Error diff --git a/test/mibs/CLIXON-TYPES-MIB.txt b/test/mibs/CLIXON-TYPES-MIB.txt index 7972a941..a73869ac 100644 --- a/test/mibs/CLIXON-TYPES-MIB.txt +++ b/test/mibs/CLIXON-TYPES-MIB.txt @@ -214,7 +214,15 @@ ifStackStatus OBJECT-TYPE interfaces, and many implementations will choose not to support write-access for any type of interface." ::= { clixonExampleScalars 12 } - + +ifIpAddr OBJECT-TYPE + SYNTAX InetAddress + MAX-ACCESS read-write + STATUS current + DESCRIPTION + "Example IP" + ::= { clixonExampleScalars 13 } + -- -- Example Tables -- @@ -265,6 +273,7 @@ nsIETFWGChair2 OBJECT-TYPE "The other name, if one exists, of the chairs for the IETF working group." ::= { clixonIETFWGEntry 3 } + -- -- A table used in a table_iterator example -- (agent/mibgroup/examples/clixonHostsTable*.[ch]) diff --git a/test/test_snmp_set.sh b/test/test_snmp_set.sh index bf77d746..22994401 100755 --- a/test/test_snmp_set.sh +++ b/test/test_snmp_set.sh @@ -5,7 +5,7 @@ # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi -APPNAME=snmp +APPNAME=example # XXX skip for now if [ ${ENABLE_NETSNMP} != "yes" ]; then @@ -17,7 +17,7 @@ snmpd=$(type -p snmpd) snmpget="$(type -p snmpget) -On -c public -v2c localhost " snmpset="$(type -p snmpset) -On -c public -v2c localhost " -cfg=$dir/conf_startup.xml +cfg=$dir/conf.xml fyang=$dir/clixon-example.yang # AgentX unix socket @@ -35,7 +35,12 @@ cat < $cfg $dir unix:$SOCK CLIXON-TYPES-MIB + IF-MIB true + ietf-netconf:startup + $APPNAME + /usr/local/lib/$APPNAME/cli + /usr/local/lib/$APPNAME/clispec EOF @@ -47,14 +52,48 @@ module clixon-example{ import CLIXON-TYPES-MIB { prefix "clixon-types"; } + import IF-MIB { + prefix "if-mib"; + } deviation "/clixon-types:CLIXON-TYPES-MIB" { deviate replace { config true; } } + deviation "/if-mib:IF-MIB" { + deviate replace { + config true; + } + } } EOF +if true; then # Dont start with a state (default) +cat < $dir/startup_db +EOF + +else # Start with a state (debug) + +cat < $dir/startup_db +<${DATASTORE_TOP}> + + + 42 + 4.3.2.1 + + + + + + 1 + aa:bb:cc:dd:ee:ff + + + + +EOF +fi + function testinit(){ new "test params: -f $cfg" @@ -69,7 +108,7 @@ function testinit(){ sudo pkill -f clixon_backend new "Starting backend" - start_backend -s init -f $cfg + start_backend -s startup -f $cfg fi new "wait backend" @@ -88,6 +127,59 @@ function testinit(){ wait_snmp } +# Set value via SNMP, read value via SNMP and CLI +# Args: +# 1: name +# 2: type +# 3: value SNMP value +# 4: xvalue XML/Clixon value +# 5: OID +function testrun() +{ + name=$1 + type=$2 + value=$3 + xvalue=$4 + oid=$5 + + # Type from man snmpset + case $type in + "INTEGER") + set_type="i" + ;; + "STRING") + set_type="s" + ;; + "TIMETICKS") + set_type="t" + ;; + "IPADDRESS") + set_type="a" + ;; + *) + set_type="s" + ;; + esac + + new "Set $name via SNMP" + if [ $type == "STRING" ]; then + echo "$snmpset $oid $set_type $value" + expectpart "$($snmpset $oid $set_type $value)" 0 "$type:" "$value" + else + echo "$snmpset $oid $set_type $value" + expectpart "$($snmpset $oid $set_type $value)" 0 "$type: $value" + fi + new "Check $name via SNMP" + if [ $type == "STRING" ]; then + expectpart "$($snmpget $oid)" 0 "$type:" "$value" + else + expectpart "$($snmpget $oid)" 0 "$type: $value" + fi + + new "Check $name via CLI" + expectpart "$($clixon_cli -1 -f $cfg show config xml)" 0 "<$name>$xvalue" +} + function testexit(){ stop_snmp } @@ -95,59 +187,16 @@ function testexit(){ new "SNMP tests" testinit -# NET-SNMP-EXAMPLES-MIB::netSnmpExamples MIB=".1.3.6.1.4.1.8072.200" -OID1="${MIB}.1.1" # netSnmpExampleInteger -OID2="${MIB}.1.2" # netSnmpExampleSleeper -OID3="${MIB}.1.3" # netSnmpExampleString +IFMIB=".1.3.6.1.2.1" -OID15="${MIB}.2.1.1.1" # nsIETFWGName -OID16="${MIB}.2.1.1.2" # nsIETFWGChair1 -OID17="${MIB}.2.1.1.3" # nsIETFWGChair2 -OID18="${MIB}.2.2" # netSnmpHostsTable -OID19="${MIB}.2.2.1.1" # netSnmpHostName -OID20="${MIB}.2.2.1.2" # netSnmpHostAddressType -OID21="${MIB}.2.2.1.3" # netSnmpHostAddress -OID22="${MIB}.2.2.1.4" # netSnmpHostStorage -OID23="${MIB}.2.2.1.5" # netSnmpHostRowStatus +testrun clixonExampleInteger INTEGER 1234 1234 ${MIB}.1.1 +testrun clixonExampleSleeper INTEGER -1 -1 ${MIB}.1.2 +testrun clixonExampleString STRING foobar foobar ${MIB}.1.3 +testrun ifPromiscuousMode INTEGER 1 true ${MIB}.1.10 # boolean +testrun ifIpAddr IPADDRESS 1.2.3.4 1.2.3.4 ${MIB}.1.13 # InetAddress -new "Setting netSnmpExampleInteger" -validate_set $OID1 "INTEGER" 1234 -validate_oid $OID1 $OID1 "INTEGER" 1234 - -new "Setting netSnmpExampleSleeper" -validate_set $OID2 "INTEGER" -1 -validate_oid $OID2 $OID2 "INTEGER" -1 - -new "Set new value via NETCONF" -expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "none999" "" "" - -new "netconf commit" -expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" - -new "Validate value set from NETCONF" -validate_oid $OID1 $OID1 "INTEGER" 999 - -new "Setting netSnmpExampleString" -validate_oid $OID3 $OID3 "STRING" "\"So long, and thanks for all the fish!\"" -validate_set $OID3 "STRING" "foo bar" -validate_oid $OID3 $OID3 "STRING" "\"foo bar\"" - -# new "Setting column nsIETFWGChair1" -# validate_set $OID16 "STRING" "asd" -# validate_oid $OID16 $OID16 "STRING" "asd" - -# new "Setting column nsIETFWGChair2" -# validate_set $OID17 "STRING" "asd" -# validate_oid $OID17 $OID16 "STRING" "asdasd" - -# new "Setting column netSnmpHostName" -# validate_set $OID19 "STRING" "asd" -# validate_oid $OID19 $OID19 "STRING" "asdasd" - -# new "Setting netSnmpHostName" -# validate_set $OID20 "STRING" ipv6 -# validate_oid $OID20 $OID20 "STRING" "asdasd" +testrun ifPhysAddress STRING ff:ee:dd:cc:bb:aa ff:ee:dd:cc:bb:aa ${IFMIB}.2.2.1.6.1 # active new "Cleaning up" testexit