[PATCH 1/3] mncc: Implement helper functions to convert MNCC cause to a string.
--- openbsc/include/openbsc/mncc.h | 5 +- openbsc/src/libmsc/mncc.c | 103 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/openbsc/include/openbsc/mncc.h b/openbsc/include/openbsc/mncc.h index 46ba237..92389c2 100644 --- a/openbsc/include/openbsc/mncc.h +++ b/openbsc/include/openbsc/mncc.h @@ -191,8 +191,11 @@ struct gsm_mncc_rtp { uint32_t payload_msg_type; }; -char *get_mncc_name(int value); +const char *get_mncc_name(int value); void mncc_set_cause(struct gsm_mncc *data, int loc, int val); +int mncc_has_cause(struct gsm_mncc *data); +const char *get_mncc_location(int value); +const char *get_mncc_cause(int value); void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg); /* input from CC code into mncc_builtin */ diff --git a/openbsc/src/libmsc/mncc.c b/openbsc/src/libmsc/mncc.c index 2767faa..e8bd9ba 100644 --- a/openbsc/src/libmsc/mncc.c +++ b/openbsc/src/libmsc/mncc.c @@ -34,10 +34,12 @@ #include <openbsc/transaction.h> #include <openbsc/rtp_proxy.h> -static struct mncc_names { +struct name_value { char *name; int value; -} mncc_names[] = { +}; + +static struct name_value mncc_names[] = { {"MNCC_SETUP_REQ", 0x0101}, {"MNCC_SETUP_IND", 0x0102}, {"MNCC_SETUP_RSP", 0x0103}, @@ -95,16 +97,103 @@ static struct mncc_names { {NULL, 0} }; -char *get_mncc_name(int value) +static struct name_value mncc_locations[] = { + {"GSM48_CAUSE_LOC_USER", 0x00}, + {"GSM48_CAUSE_LOC_PRN_S_LU", 0x01}, + {"GSM48_CAUSE_LOC_PUN_S_LU", 0x02}, + {"GSM48_CAUSE_LOC_TRANS_NET", 0x03}, + {"GSM48_CAUSE_LOC_PUN_S_RU", 0x04}, + {"GSM48_CAUSE_LOC_PRN_S_RU", 0x05}, + /* not defined */ + {"GSM48_CAUSE_LOC_INN_NET", 0x07}, + {"GSM48_CAUSE_LOC_NET_BEYOND", 0x0a}, + + {NULL, 0} }; + +static struct name_value mncc_causes[] = { + {"GSM48_CC_CAUSE_UNASSIGNED_NR", 1}, + {"GSM48_CC_CAUSE_NO_ROUTE", 3}, + {"GSM48_CC_CAUSE_CHAN_UNACCEPT", 6}, + {"GSM48_CC_CAUSE_OP_DET_BARRING", 8}, + {"GSM48_CC_CAUSE_NORM_CALL_CLEAR", 16}, + {"GSM48_CC_CAUSE_USER_BUSY", 17}, + {"GSM48_CC_CAUSE_USER_NOTRESPOND", 18}, + {"GSM48_CC_CAUSE_USER_ALERTING_NA", 19}, + {"GSM48_CC_CAUSE_CALL_REJECTED", 21}, + {"GSM48_CC_CAUSE_NUMBER_CHANGED", 22}, + {"GSM48_CC_CAUSE_PRE_EMPTION", 25}, + {"GSM48_CC_CAUSE_NONSE_USER_CLR", 26}, + {"GSM48_CC_CAUSE_DEST_OOO", 27}, + {"GSM48_CC_CAUSE_INV_NR_FORMAT", 28}, + {"GSM48_CC_CAUSE_FACILITY_REJ", 29}, + {"GSM48_CC_CAUSE_RESP_STATUS_INQ", 30}, + {"GSM48_CC_CAUSE_NORMAL_UNSPEC", 31}, + {"GSM48_CC_CAUSE_NO_CIRCUIT_CHAN", 34}, + {"GSM48_CC_CAUSE_NETWORK_OOO", 38}, + {"GSM48_CC_CAUSE_TEMP_FAILURE", 41}, + {"GSM48_CC_CAUSE_SWITCH_CONG", 42}, + {"GSM48_CC_CAUSE_ACC_INF_DISCARD", 43}, + {"GSM48_CC_CAUSE_REQ_CHAN_UNAVAIL", 44}, + {"GSM48_CC_CAUSE_RESOURCE_UNAVAIL", 47}, + {"GSM48_CC_CAUSE_QOS_UNAVAIL", 49}, + {"GSM48_CC_CAUSE_REQ_FAC_NOT_SUBSC", 50}, + {"GSM48_CC_CAUSE_INC_BARRED_CUG", 55}, + {"GSM48_CC_CAUSE_BEARER_CAP_UNAUTH", 57}, + {"GSM48_CC_CAUSE_BEARER_CA_UNAVAIL", 58}, + {"GSM48_CC_CAUSE_SERV_OPT_UNAVAIL", 63}, + {"GSM48_CC_CAUSE_BEARERSERV_UNIMPL", 65}, + {"GSM48_CC_CAUSE_ACM_GE_ACM_MAX", 68}, + {"GSM48_CC_CAUSE_REQ_FAC_NOTIMPL", 69}, + {"GSM48_CC_CAUSE_RESTR_BCAP_AVAIL", 70}, + {"GSM48_CC_CAUSE_SERV_OPT_UNIMPL", 79}, + {"GSM48_CC_CAUSE_INVAL_TRANS_ID", 81}, + {"GSM48_CC_CAUSE_USER_NOT_IN_CUG", 87}, + {"GSM48_CC_CAUSE_INCOMPAT_DEST", 88}, + {"GSM48_CC_CAUSE_INVAL_TRANS_NET", 91}, + {"GSM48_CC_CAUSE_SEMANTIC_INCORR", 95}, + {"GSM48_CC_CAUSE_INVAL_MAND_INF", 96}, + {"GSM48_CC_CAUSE_MSGTYPE_NOTEXIST", 97}, + {"GSM48_CC_CAUSE_MSGTYPE_INCOMPAT", 98}, + {"GSM48_CC_CAUSE_IE_NOTEXIST", 99}, + {"GSM48_CC_CAUSE_COND_IE_ERR", 100}, + {"GSM48_CC_CAUSE_MSG_INCOMP_STATE", 101}, + {"GSM48_CC_CAUSE_RECOVERY_TIMER", 102}, + {"GSM48_CC_CAUSE_PROTO_ERR", 111}, + {"GSM48_CC_CAUSE_INTERWORKING", 127}, + + {NULL, 0} }; + +const char *get_name_by_value(struct name_value *pairs, int value, const char *default_val) { int i; - for (i = 0; mncc_names[i].name; i++) { - if (mncc_names[i].value == value) - return mncc_names[i].name; + for (i = 0; pairs[i].name; i++) { + if (pairs[i].value == value) + return pairs[i].name; } - return "MNCC_Unknown"; + return default_val; +} + + +const char *get_mncc_name(int value) +{ + return get_name_by_value(mncc_names, value, "MNCC_Unknown"); +} + +const char *get_mncc_location(int value) +{ + return get_name_by_value(mncc_locations, value, "GSM48_CAUSE_LOC_Unknown"); +} + +const char *get_mncc_cause(int value) +{ + return get_name_by_value(mncc_causes, value, "GSM48_CC_CAUSE_Unknown"); +} + +int mncc_has_cause(struct gsm_mncc *data) +{ + return data->fields & MNCC_F_CAUSE; } void mncc_set_cause(struct gsm_mncc *data, int loc, int val) -- 1.9.1
--- openbsc/src/libmsc/gsm_04_08.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/openbsc/src/libmsc/gsm_04_08.c b/openbsc/src/libmsc/gsm_04_08.c index 7db7586..5dd9247 100644 --- a/openbsc/src/libmsc/gsm_04_08.c +++ b/openbsc/src/libmsc/gsm_04_08.c @@ -1349,20 +1349,26 @@ static int mncc_recvmsg(struct gsm_network *net, struct gsm_trans *trans, if (trans) if (trans->conn && trans->conn->lchan) DEBUGP(DCC, "(bts %d trx %d ts %d ti %x sub %s) " - "Sending '%s' to MNCC.\n", + "Sending '%s' cause %s/%s to MNCC.\n", trans->conn->lchan->ts->trx->bts->nr, trans->conn->lchan->ts->trx->nr, trans->conn->lchan->ts->nr, trans->transaction_id, (trans->subscr)?(trans->subscr->extension):"-", - get_mncc_name(msg_type)); + get_mncc_name(msg_type), + (mncc_has_cause(mncc))?get_mncc_location(mncc->cause.location):"-", + (mncc_has_cause(mncc))?get_mncc_cause(mncc->cause.value):"-"); else DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) " - "Sending '%s' to MNCC.\n", + "Sending '%s' cause %s/%s to MNCC.\n", (trans->subscr)?(trans->subscr->extension):"-", - get_mncc_name(msg_type)); + get_mncc_name(msg_type), + (mncc_has_cause(mncc))?get_mncc_location(mncc->cause.location):"-", + (mncc_has_cause(mncc))?get_mncc_cause(mncc->cause.value):"-"); else DEBUGP(DCC, "(bts - trx - ts - ti -- sub -) " - "Sending '%s' to MNCC.\n", get_mncc_name(msg_type)); + "Sending '%s' cause %s/%s to MNCC.\n", get_mncc_name(msg_type), + (mncc_has_cause(mncc))?get_mncc_location(mncc->cause.location):"-", + (mncc_has_cause(mncc))?get_mncc_cause(mncc->cause.value):"-"); mncc->msg_type = msg_type; -- 1.9.1
Previously we were sending a generic "Resource unavailable" cause code making it impossible to distinguish real error cases from a regular radio link failure. This was the reason for many "unknown" call errors we've seen at Rhizomatica installations. Now they are properly classified as non-erroneous call failures. --- openbsc/include/openbsc/transaction.h | 1 + openbsc/src/libbsc/bsc_api.c | 2 +- openbsc/src/libmsc/gsm_04_08.c | 14 ++++++++++---- openbsc/src/libmsc/transaction.c | 9 +++++++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/openbsc/include/openbsc/transaction.h b/openbsc/include/openbsc/transaction.h index 6ef1612..303ef6e 100644 --- a/openbsc/include/openbsc/transaction.h +++ b/openbsc/include/openbsc/transaction.h @@ -70,6 +70,7 @@ struct gsm_trans *trans_alloc(struct gsm_network *net, struct gsm_subscriber *subscr, uint8_t protocol, uint8_t trans_id, uint32_t callref); +void trans_free_cause(struct gsm_trans *trans, int gsm0808_cause); void trans_free(struct gsm_trans *trans); int trans_assign_trans_id(struct gsm_network *net, struct gsm_subscriber *subscr, diff --git a/openbsc/src/libbsc/bsc_api.c b/openbsc/src/libbsc/bsc_api.c index 504f044..54083f3 100644 --- a/openbsc/src/libbsc/bsc_api.c +++ b/openbsc/src/libbsc/bsc_api.c @@ -830,7 +830,7 @@ static void handle_release(struct gsm_subscriber_connection *conn, /* clear the connection now */ if (bsc->clear_request) - destruct = bsc->clear_request(conn, 0); + destruct = bsc->clear_request(conn, GSM0808_CAUSE_RADIO_INTERFACE_FAILURE); /* now give up all channels */ if (conn->lchan == lchan) diff --git a/openbsc/src/libmsc/gsm_04_08.c b/openbsc/src/libmsc/gsm_04_08.c index 5dd9247..94d186d 100644 --- a/openbsc/src/libmsc/gsm_04_08.c +++ b/openbsc/src/libmsc/gsm_04_08.c @@ -58,6 +58,7 @@ #include <osmocom/gsm/gsm48.h> #include <osmocom/gsm/gsm0480.h> +#include <osmocom/gsm/protocol/gsm_08_08.h> #include <osmocom/gsm/gsm_utils.h> #include <osmocom/core/msgb.h> #include <osmocom/core/talloc.h> @@ -410,7 +411,7 @@ void gsm0408_clear_request(struct gsm_subscriber_connection *conn, uint32_t caus restart: llist_for_each_entry_safe(trans, temp, &conn->bts->network->trans_list, entry) { if (trans->conn == conn) { - trans_free(trans); + trans_free_cause(trans, cause); goto restart; } } @@ -1399,16 +1400,21 @@ int mncc_release_ind(struct gsm_network *net, struct gsm_trans *trans, /* Call Control Specific transaction release. * gets called by trans_free, DO NOT CALL YOURSELF! */ -void _gsm48_cc_trans_free(struct gsm_trans *trans) +void _gsm48_cc_trans_free(struct gsm_trans *trans, int gsm0808_cause) { gsm48_stop_cc_timer(trans); /* send release to L4, if callref still exists */ if (trans->callref) { - /* Ressource unavailable */ + /* Release CC connection */ + int cc_cause = GSM48_CC_CAUSE_RESOURCE_UNAVAIL; + if (gsm0808_cause == GSM0808_CAUSE_RADIO_INTERFACE_FAILURE) + { + cc_cause = GSM48_CC_CAUSE_DEST_OOO; + } mncc_release_ind(trans->net, trans, trans->callref, GSM48_CAUSE_LOC_PRN_S_LU, - GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + cc_cause); } if (trans->cc.state != GSM_CSTATE_NULL) new_cc_state(trans, GSM_CSTATE_NULL); diff --git a/openbsc/src/libmsc/transaction.c b/openbsc/src/libmsc/transaction.c index a750362..95939d3 100644 --- a/openbsc/src/libmsc/transaction.c +++ b/openbsc/src/libmsc/transaction.c @@ -31,7 +31,7 @@ void *tall_trans_ctx; -void _gsm48_cc_trans_free(struct gsm_trans *trans); +void _gsm48_cc_trans_free(struct gsm_trans *trans, int gsm0808_cause); struct gsm_trans *trans_find_by_id(struct gsm_subscriber_connection *conn, uint8_t proto, uint8_t trans_id) @@ -89,9 +89,14 @@ struct gsm_trans *trans_alloc(struct gsm_network *net, void trans_free(struct gsm_trans *trans) { + trans_free_cause(trans, -1); +} + +void trans_free_cause(struct gsm_trans *trans, int gsm0808_cause) +{ switch (trans->protocol) { case GSM48_PDISC_CC: - _gsm48_cc_trans_free(trans); + _gsm48_cc_trans_free(trans, gsm0808_cause); break; case GSM48_PDISC_SMS: _gsm411_sms_trans_free(trans); -- 1.9.1
bump. Still hope to get this merged. On Wed, Nov 25, 2015 at 3:31 PM, Alexander Chemeris <alexander.chemeris@gmail.com> wrote:
--- openbsc/include/openbsc/mncc.h | 5 +- openbsc/src/libmsc/mncc.c | 103 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 100 insertions(+), 8 deletions(-)
diff --git a/openbsc/include/openbsc/mncc.h b/openbsc/include/openbsc/mncc.h index 46ba237..92389c2 100644 --- a/openbsc/include/openbsc/mncc.h +++ b/openbsc/include/openbsc/mncc.h @@ -191,8 +191,11 @@ struct gsm_mncc_rtp { uint32_t payload_msg_type; };
-char *get_mncc_name(int value); +const char *get_mncc_name(int value); void mncc_set_cause(struct gsm_mncc *data, int loc, int val); +int mncc_has_cause(struct gsm_mncc *data); +const char *get_mncc_location(int value); +const char *get_mncc_cause(int value); void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg);
/* input from CC code into mncc_builtin */ diff --git a/openbsc/src/libmsc/mncc.c b/openbsc/src/libmsc/mncc.c index 2767faa..e8bd9ba 100644 --- a/openbsc/src/libmsc/mncc.c +++ b/openbsc/src/libmsc/mncc.c @@ -34,10 +34,12 @@ #include <openbsc/transaction.h> #include <openbsc/rtp_proxy.h>
-static struct mncc_names { +struct name_value { char *name; int value; -} mncc_names[] = { +}; + +static struct name_value mncc_names[] = { {"MNCC_SETUP_REQ", 0x0101}, {"MNCC_SETUP_IND", 0x0102}, {"MNCC_SETUP_RSP", 0x0103}, @@ -95,16 +97,103 @@ static struct mncc_names {
{NULL, 0} };
-char *get_mncc_name(int value) +static struct name_value mncc_locations[] = { + {"GSM48_CAUSE_LOC_USER", 0x00}, + {"GSM48_CAUSE_LOC_PRN_S_LU", 0x01}, + {"GSM48_CAUSE_LOC_PUN_S_LU", 0x02}, + {"GSM48_CAUSE_LOC_TRANS_NET", 0x03}, + {"GSM48_CAUSE_LOC_PUN_S_RU", 0x04}, + {"GSM48_CAUSE_LOC_PRN_S_RU", 0x05}, + /* not defined */ + {"GSM48_CAUSE_LOC_INN_NET", 0x07}, + {"GSM48_CAUSE_LOC_NET_BEYOND", 0x0a}, + + {NULL, 0} }; + +static struct name_value mncc_causes[] = { + {"GSM48_CC_CAUSE_UNASSIGNED_NR", 1}, + {"GSM48_CC_CAUSE_NO_ROUTE", 3}, + {"GSM48_CC_CAUSE_CHAN_UNACCEPT", 6}, + {"GSM48_CC_CAUSE_OP_DET_BARRING", 8}, + {"GSM48_CC_CAUSE_NORM_CALL_CLEAR", 16}, + {"GSM48_CC_CAUSE_USER_BUSY", 17}, + {"GSM48_CC_CAUSE_USER_NOTRESPOND", 18}, + {"GSM48_CC_CAUSE_USER_ALERTING_NA", 19}, + {"GSM48_CC_CAUSE_CALL_REJECTED", 21}, + {"GSM48_CC_CAUSE_NUMBER_CHANGED", 22}, + {"GSM48_CC_CAUSE_PRE_EMPTION", 25}, + {"GSM48_CC_CAUSE_NONSE_USER_CLR", 26}, + {"GSM48_CC_CAUSE_DEST_OOO", 27}, + {"GSM48_CC_CAUSE_INV_NR_FORMAT", 28}, + {"GSM48_CC_CAUSE_FACILITY_REJ", 29}, + {"GSM48_CC_CAUSE_RESP_STATUS_INQ", 30}, + {"GSM48_CC_CAUSE_NORMAL_UNSPEC", 31}, + {"GSM48_CC_CAUSE_NO_CIRCUIT_CHAN", 34}, + {"GSM48_CC_CAUSE_NETWORK_OOO", 38}, + {"GSM48_CC_CAUSE_TEMP_FAILURE", 41}, + {"GSM48_CC_CAUSE_SWITCH_CONG", 42}, + {"GSM48_CC_CAUSE_ACC_INF_DISCARD", 43}, + {"GSM48_CC_CAUSE_REQ_CHAN_UNAVAIL", 44}, + {"GSM48_CC_CAUSE_RESOURCE_UNAVAIL", 47}, + {"GSM48_CC_CAUSE_QOS_UNAVAIL", 49}, + {"GSM48_CC_CAUSE_REQ_FAC_NOT_SUBSC", 50}, + {"GSM48_CC_CAUSE_INC_BARRED_CUG", 55}, + {"GSM48_CC_CAUSE_BEARER_CAP_UNAUTH", 57}, + {"GSM48_CC_CAUSE_BEARER_CA_UNAVAIL", 58}, + {"GSM48_CC_CAUSE_SERV_OPT_UNAVAIL", 63}, + {"GSM48_CC_CAUSE_BEARERSERV_UNIMPL", 65}, + {"GSM48_CC_CAUSE_ACM_GE_ACM_MAX", 68}, + {"GSM48_CC_CAUSE_REQ_FAC_NOTIMPL", 69}, + {"GSM48_CC_CAUSE_RESTR_BCAP_AVAIL", 70}, + {"GSM48_CC_CAUSE_SERV_OPT_UNIMPL", 79}, + {"GSM48_CC_CAUSE_INVAL_TRANS_ID", 81}, + {"GSM48_CC_CAUSE_USER_NOT_IN_CUG", 87}, + {"GSM48_CC_CAUSE_INCOMPAT_DEST", 88}, + {"GSM48_CC_CAUSE_INVAL_TRANS_NET", 91}, + {"GSM48_CC_CAUSE_SEMANTIC_INCORR", 95}, + {"GSM48_CC_CAUSE_INVAL_MAND_INF", 96}, + {"GSM48_CC_CAUSE_MSGTYPE_NOTEXIST", 97}, + {"GSM48_CC_CAUSE_MSGTYPE_INCOMPAT", 98}, + {"GSM48_CC_CAUSE_IE_NOTEXIST", 99}, + {"GSM48_CC_CAUSE_COND_IE_ERR", 100}, + {"GSM48_CC_CAUSE_MSG_INCOMP_STATE", 101}, + {"GSM48_CC_CAUSE_RECOVERY_TIMER", 102}, + {"GSM48_CC_CAUSE_PROTO_ERR", 111}, + {"GSM48_CC_CAUSE_INTERWORKING", 127}, + + {NULL, 0} }; + +const char *get_name_by_value(struct name_value *pairs, int value, const char *default_val) { int i;
- for (i = 0; mncc_names[i].name; i++) { - if (mncc_names[i].value == value) - return mncc_names[i].name; + for (i = 0; pairs[i].name; i++) { + if (pairs[i].value == value) + return pairs[i].name; }
- return "MNCC_Unknown"; + return default_val; +} + + +const char *get_mncc_name(int value) +{ + return get_name_by_value(mncc_names, value, "MNCC_Unknown"); +} + +const char *get_mncc_location(int value) +{ + return get_name_by_value(mncc_locations, value, "GSM48_CAUSE_LOC_Unknown"); +} + +const char *get_mncc_cause(int value) +{ + return get_name_by_value(mncc_causes, value, "GSM48_CC_CAUSE_Unknown"); +} + +int mncc_has_cause(struct gsm_mncc *data) +{ + return data->fields & MNCC_F_CAUSE; }
void mncc_set_cause(struct gsm_mncc *data, int loc, int val) -- 1.9.1
-- Regards, Alexander Chemeris. CEO, Fairwaves, Inc. https://fairwaves.co
On Sat, Dec 12, 2015 at 11:16 AM, Holger Freyther <holger@freyther.de> wrote:
On 12 Dec 2015, at 15:49, Alexander Chemeris <alexander.chemeris@gmail.com> wrote:
bump. Still hope to get this merged.
on my list. but will probably be after christmas.
Thanks! No pressure, I just wanted to make sure it's not lost again.
Do you have an idea why patchwork is not picking up your patches?
Not really :\ It seems like we're not the only ones affected, but there is no solution offered: https://lists.ozlabs.org/pipermail/patchwork/2015-October/001888.html I'm happy to do more testing if needed, as it seems my patches get lost routinely. -- Regards, Alexander Chemeris. CEO, Fairwaves, Inc. https://fairwaves.co
Hi Alexander, I may just have broken your patch-set accidentially by converting mncc_names to value_string, sorry for that :/ Regarding your patch, I have to disagree about several topics: On Wed, Nov 25, 2015 at 03:31:04PM -0500, Alexander Chemeris wrote:
-static struct mncc_names { +struct name_value { char *name; int value; -} mncc_names[] = { +};
Is there a specific reason to introduce a new structure rather than use struct value_string from libosmocore, which is used all over the Osmo* code for this kind of name-value mapping? All code should use that infrastructure, and the occasional separate implementation should bec converted to value_string.
+static struct name_value mncc_locations[] = { + {"GSM48_CAUSE_LOC_USER", 0x00},
1) The cause values and cause locations are GSM 04.08, not MNCC specific. As such, they belong into libosmogsm, not OsmoNITB. Also, they shouldn't be called 'mncc_locations' if in fact they are GSM 04.08 locations. 2) Also, rather than introducing magic numbers, the defnitions from <osmocmo/gsm/protocol/gsm_04_08.h> should be used, making the above an struct value_string gsm48_cause_loc_names[] = { { GSM48_CAUSE_LOC_USER, "GSM48_CAUSE_LOC_USER" }, 3) And finally, the string should be more user-readable than the cryptic #define/enum value, i.e. struct value_string gsm48_cause_loc_names[] = { { GSM48_CAUSE_LOC_USER, "User" }, Regards, Harald -- - Harald Welte <laforge@gnumonks.org> http://laforge.gnumonks.org/ ============================================================================ "Privacy in residential applications is a desirable marketing option." (ETSI EN 300 175-7 Ch. A6)
participants (3)
-
Alexander Chemeris -
Harald Welte -
Holger Freyther