mgw: Allow auditing speciall 'null' endpoint

This is a special endpoint which can always be audited. This is useful
for clients who wish to submit requests to osmo-mgw periodically to find
out whether the MGW is still reachable. This endpoint will be used by
libomso-mgcp-client as default target endpoint to implement such
feature.
This "null" Endpoint is osmo-mgw specific, not described in MGCP specs.

Related: SYS#6481
Change-Id: Ia409b16e9211e6261e2e0f21288544289d6f3733
changes/12/33312/3
Pau Espin 2023-06-14 12:21:26 +02:00
parent 2e411f33a0
commit af0f58fac7
6 changed files with 188 additions and 8 deletions

View File

@ -91,4 +91,10 @@ encoding/decoding for 16K and 8K subslots. Endpoints with other bitrates are
not yet useable.
NOTE: the VTY command "show mgcp" can be used to get a list of all available
endpoints (including identifiers)
endpoints (including identifiers)
=== The `null` endpoint
OsmoMGW offers a special `null@<domain>` endpoint which can be audited at all times.
This is useful for MGCP clients who wish to submit requests to OsmoMGW
periodically to find out whether it is still reachable and in a working state.

View File

@ -133,6 +133,7 @@ struct mgcp_endpoint *mgcp_endp_alloc(struct mgcp_trunk *trunk, unsigned int ind
int mgcp_endp_claim(struct mgcp_endpoint *endp, const char *callid);
void mgcp_endp_update(struct mgcp_endpoint *endp);
bool mgcp_endp_is_wildcarded(const char *epname);
bool mgcp_endp_is_null(const char *epname);
struct mgcp_endpoint *mgcp_endp_by_name_trunk(int *cause, const char *epname,
const struct mgcp_trunk *trunk);
struct mgcp_endpoint *mgcp_endp_by_name(int *cause, const char *epname,

View File

@ -229,6 +229,17 @@ bool mgcp_endp_is_wildcarded(const char *epname)
return false;
}
/*! Check if the given epname refers to a "null" endpoint.
* \param[in] epname endpoint name to check
* \returns true if epname refers to "null"" endpoint, else false. */
bool mgcp_endp_is_null(const char *epname)
{
if (strncasecmp(epname, "null@", 5) == 0)
return true;
return false;
}
/*! Find an endpoint by its name on a specified trunk.
* \param[out] cause pointer to store cause code, can be NULL.
* \param[in] epname endpoint name to lookup.

View File

@ -86,6 +86,9 @@ struct mgcp_request_data {
/* set to true when the request has been classified as wildcarded */
bool wildcarded;
/* Set to true when the request is targeted at the "null" endpoint */
bool null_endp;
/* contains cause code in case of problems during endp/trunk resolution */
int mgcp_cause;
};
@ -390,7 +393,10 @@ struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg)
/* Locate endpoint and trunk, if no endpoint can be located try at least to identify the trunk. */
rq.pdata = &pdata;
rq.wildcarded = mgcp_endp_is_wildcarded(pdata.epname);
rq.endp = mgcp_endp_by_name(&rc, pdata.epname, pdata.cfg);
if (!rq.wildcarded)
rq.null_endp = mgcp_endp_is_null(pdata.epname);
if (!rq.null_endp)
rq.endp = mgcp_endp_by_name(&rc, pdata.epname, pdata.cfg);
rq.mgcp_cause = rc;
if (!rq.endp) {
rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_GENERAL_RX_FAIL_NO_ENDPOINT));
@ -407,14 +413,14 @@ struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg)
rq.name, pdata.epname);
return create_err_response(cfg, NULL, -rq.mgcp_cause, rq.name, pdata.trans);
}
} else {
} else if (!rq.null_endp) {
/* If the endpoint name suggests that the request refers to a specific endpoint, then the
* request cannot be handled and we must stop early. */
LOGP(DLMGCP, LOGL_NOTICE,
"%s: cannot find endpoint \"%s\", cause=%d -- abort\n", rq.name,
pdata.epname, -rq.mgcp_cause);
return create_err_response(cfg, NULL, -rq.mgcp_cause, rq.name, pdata.trans);
}
} /* else: Handle special "null" endpoint below (with rq.endp=NULL, rq.trunk=NULL) */
} else {
osmo_strlcpy(debug_last_endpoint_name, rq.endp->name, sizeof(debug_last_endpoint_name));
rq.trunk = rq.endp->trunk;
@ -460,6 +466,11 @@ struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg)
static struct msgb *handle_audit_endpoint(struct mgcp_request_data *rq)
{
LOGPENDP(rq->endp, DLMGCP, LOGL_NOTICE, "AUEP: auditing endpoint ...\n");
/* Auditing "null" endpoint is allowed for keepalive purposes. There's no rq->endp nor rq->trunk in this case. */
if (rq->null_endp)
return create_ok_response(rq->pdata->cfg, NULL, 200, "AUEP", rq->pdata->trans);
if (!rq->endp || !mgcp_endp_avail(rq->endp)) {
LOGPENDP(rq->endp, DLMGCP, LOGL_ERROR, "AUEP: selected endpoint not available!\n");
return create_err_response(rq->trunk, NULL, 501, "AUEP", rq->pdata->trans);
@ -855,7 +866,7 @@ static struct msgb *handle_create_con(struct mgcp_request_data *rq)
struct mgcp_parse_data *pdata = rq->pdata;
struct mgcp_trunk *trunk = rq->trunk;
struct mgcp_endpoint *endp = rq->endp;
struct rate_ctr_group *rate_ctrs = trunk->ratectr.mgcp_crcx_ctr_group;
struct rate_ctr_group *rate_ctrs;
int error_code = 400;
const char *local_options = NULL;
const char *callid = NULL;
@ -869,6 +880,14 @@ static struct msgb *handle_create_con(struct mgcp_request_data *rq)
LOGPENDP(endp, DLMGCP, LOGL_NOTICE, "CRCX: creating new connection ...\n");
if (rq->null_endp) {
/* trunk not available so rate_ctr aren't available either. */
LOGP(DLMGCP, LOGL_ERROR, "CRCX: Not allowed in 'null' endpoint!\n");
return create_err_response(pdata->cfg, NULL, 502, "CRCX", pdata->trans);
}
rate_ctrs = trunk->ratectr.mgcp_crcx_ctr_group;
/* we must have a free ep */
if (!endp) {
rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_CRCX_FAIL_AVAIL));
@ -1134,7 +1153,7 @@ static struct msgb *handle_modify_con(struct mgcp_request_data *rq)
struct mgcp_parse_data *pdata = rq->pdata;
struct mgcp_trunk *trunk = rq->trunk;
struct mgcp_endpoint *endp = rq->endp;
struct rate_ctr_group *rate_ctrs = trunk->ratectr.mgcp_mdcx_ctr_group;
struct rate_ctr_group *rate_ctrs;
char new_local_addr[INET6_ADDRSTRLEN];
int error_code = 500;
int silent = 0;
@ -1149,6 +1168,14 @@ static struct msgb *handle_modify_con(struct mgcp_request_data *rq)
LOGPENDP(endp, DLMGCP, LOGL_NOTICE, "MDCX: modifying existing connection ...\n");
if (rq->null_endp) {
/* trunk not available so rate_ctr aren't available either. */
LOGP(DLMGCP, LOGL_ERROR, "MDCX: Not allowed in 'null' endpoint!\n");
return create_err_response(pdata->cfg, NULL, 502, "MDCX", pdata->trans);
}
rate_ctrs = trunk->ratectr.mgcp_mdcx_ctr_group;
/* Prohibit wildcarded requests */
if (rq->wildcarded) {
LOGPENDP(endp, DLMGCP, LOGL_ERROR,
@ -1360,7 +1387,7 @@ static struct msgb *handle_delete_con(struct mgcp_request_data *rq)
struct mgcp_parse_data *pdata = rq->pdata;
struct mgcp_trunk *trunk = rq->trunk;
struct mgcp_endpoint *endp = rq->endp;
struct rate_ctr_group *rate_ctrs = trunk->ratectr.mgcp_dlcx_ctr_group;
struct rate_ctr_group *rate_ctrs;
int error_code = 400;
int silent = 0;
char *line;
@ -1370,10 +1397,18 @@ static struct msgb *handle_delete_con(struct mgcp_request_data *rq)
unsigned int i;
/* NOTE: In this handler we can not take it for granted that the endp
* pointer will be populated, however a trunk is always guaranteed. */
* pointer will be populated, however a trunk is always guaranteed (except for 'null' endp).
*/
LOGPEPTR(endp, trunk, DLMGCP, LOGL_NOTICE, "DLCX: deleting connection(s) ...\n");
if (rq->null_endp) {
/* trunk not available so rate_ctr aren't available either. */
LOGP(DLMGCP, LOGL_ERROR, "DLCX: Not allowed in 'null' endpoint!\n");
return create_err_response(pdata->cfg, NULL, 502, "DLCX", pdata->trans);
}
rate_ctrs = trunk->ratectr.mgcp_dlcx_ctr_group;
if (endp && !mgcp_endp_avail(endp)) {
rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_DLCX_FAIL_AVAIL));
LOGPENDP(endp, DLMGCP, LOGL_ERROR,
@ -1524,6 +1559,12 @@ static struct msgb *handle_rsip(struct mgcp_request_data *rq)
LOGP(DLMGCP, LOGL_NOTICE, "RSIP: resetting all endpoints ...\n");
if (rq->null_endp) {
/* trunk not available so rate_ctr aren't available either. */
LOGP(DLMGCP, LOGL_ERROR, "RSIP: Not allowed in 'null' endpoint!\n");
return create_err_response(rq->pdata->cfg, NULL, 502, "RSIP", rq->pdata->trans);
}
if (rq->pdata->cfg->reset_cb)
rq->pdata->cfg->reset_cb(rq->endp->trunk);
return NULL;
@ -1549,6 +1590,12 @@ static struct msgb *handle_noti_req(struct mgcp_request_data *rq)
LOGP(DLMGCP, LOGL_NOTICE, "RQNT: processing request for notification ...\n");
if (rq->null_endp) {
/* trunk not available so rate_ctr aren't available either. */
LOGP(DLMGCP, LOGL_ERROR, "RQNT: Not allowed in 'null' endpoint!\n");
return create_err_response(rq->pdata->cfg, NULL, 502, "RQNT", rq->pdata->trans);
}
for_each_line(line, rq->pdata->save) {
switch (toupper(line[0])) {
case 'S':

View File

@ -77,6 +77,8 @@ static void test_strline(void)
#define AUEP1_RET "500 158663169 FAIL\r\n"
#define AUEP2 "AUEP 18983213 ds/e1-2/1@mgw MGCP 1.0\r\n"
#define AUEP2_RET "500 18983213 FAIL\r\n"
#define AUEP_NULL "AUEP 18983215 null@mgw MGCP 1.0\r\n"
#define AUEP_NULL_RET "200 18983215 OK\r\n"
#define EMPTY "\r\n"
#define EMPTY_RET NULL
#define SHORT "CRCX \r\n"
@ -274,6 +276,12 @@ static void test_strline(void)
#define MDCX_TOO_LONG_CI_RET "510 18983224 FAIL\r\n"
#define MDCX_NULL \
"MDCX 9 null@mgw MGCP 1.0\r\n" \
"I: %s\n"
#define MDCX_NULL_RET "502 9 FAIL\r\n"
#define SHORT2 "CRCX 1"
#define SHORT2_RET "510 000000 FAIL\r\n"
#define SHORT3 "CRCX 1 1@mgw"
@ -392,6 +400,13 @@ static void test_strline(void)
#define DLCX_RET_OSMUX DLCX_RET \
"X-Osmo-CP: EC TI=0, TO=0\r\n"
#define DLCX_NULL \
"DLCX 8 null@mgw MGCP 1.0\r\n" \
"I: %s\r\n" \
"C: 2\r\n"
#define DLCX_NULL_RET "502 8 FAIL\r\n"
#define RQNT \
"RQNT 186908780 1@mgw MGCP 1.0\r\n" \
"X: B244F267488\r\n" \
@ -405,6 +420,13 @@ static void test_strline(void)
#define RQNT1_RET "200 186908780 OK\r\n"
#define RQNT2_RET "200 186908781 OK\r\n"
#define RQNT_NULL \
"RQNT 186908782 null@mgw MGCP 1.0\r\n" \
"X: B244F267488\r\n" \
"S: D/9\r\n"
#define RQNT_NULL_RET "502 186908782 FAIL\r\n"
#define PTYPE_IGNORE 0 /* == default initializer */
#define PTYPE_NONE 128
#define PTYPE_NYI PTYPE_NONE
@ -545,6 +567,20 @@ static void test_strline(void)
"m=audio 16008 RTP/AVP 0\r\n" \
"a=ptime:20\r\n"
#define CRCX_NULL \
"CRCX 2 null@mgw MGCP 1.0\r\n" \
"m: recvonly\r\n" \
"C: 2\r\n" \
"L: p:20\r\n" \
"\r\n" \
"v=0\r\n" \
"c=IN IP4 123.12.12.123\r\n" \
"m=audio 5904 RTP/AVP 97\r\n" \
"a=rtpmap:97 GSM-EFR/8000\r\n" \
"a=ptime:40\r\n"
#define CRCX_NULL_RET "502 2 FAIL\r\n"
struct mgcp_test {
const char *name;
const char *req;
@ -586,6 +622,11 @@ static const struct mgcp_test tests[] = {
{"CRCX", CRCX_X_OSMO_IGN, CRCX_X_OSMO_IGN_RET, 97},
{"MDCX_TOO_LONG_CI", MDCX_TOO_LONG_CI, MDCX_TOO_LONG_CI_RET},
{"CRCX", CRCX_AMR_WITH_FMTP, CRCX_AMR_WITH_FMTP_RET},
{"AUEP_NULL", AUEP_NULL, AUEP_NULL_RET},
{"CRCX_NULL", CRCX_NULL, CRCX_NULL_RET},
{"MDCX_NULL", MDCX_NULL, MDCX_NULL_RET},
{"DLCX_NULL", DLCX_NULL, DLCX_NULL_RET},
{"RQNT_NULL", RQNT_NULL, RQNT_NULL_RET},
};
static const struct mgcp_test retransmit[] = {

View File

@ -525,6 +525,80 @@ Response matches our expectations.
(response contains a connection id)
Dummy packets: 2
================================================
Testing AUEP_NULL
creating message from statically defined input:
---------8<---------
AUEP 18983215 null@mgw MGCP 1.0
---------8<---------
checking response:
using message as statically defined for comparison
Response matches our expectations.
(response does not contain a connection id)
================================================
Testing CRCX_NULL
creating message from statically defined input:
---------8<---------
CRCX 2 null@mgw MGCP 1.0
m: recvonly
C: 2
L: p:20
v=0
c=IN IP4 123.12.12.123
m=audio 5904 RTP/AVP 97
a=rtpmap:97 GSM-EFR/8000
a=ptime:40
---------8<---------
checking response:
using message as statically defined for comparison
Response matches our expectations.
(response does not contain a connection id)
================================================
Testing MDCX_NULL
creating message from statically defined input:
---------8<---------
MDCX 9 null@mgw MGCP 1.0
I: %s
---------8<---------
checking response:
using message as statically defined for comparison
Response matches our expectations.
(response does not contain a connection id)
================================================
Testing DLCX_NULL
creating message from statically defined input:
---------8<---------
DLCX 8 null@mgw MGCP 1.0
I: %s
C: 2
---------8<---------
checking response:
using message as statically defined for comparison
Response matches our expectations.
(response does not contain a connection id)
================================================
Testing RQNT_NULL
creating message from statically defined input:
---------8<---------
RQNT 186908782 null@mgw MGCP 1.0
X: B244F267488
S: D/9
---------8<---------
checking response:
using message as statically defined for comparison
Response matches our expectations.
(response does not contain a connection id)
================================================
Testing CRCX
creating message from statically defined input: