Merge remote-tracking branch 'origin/master' into daniel/onwaves

This commit is contained in:
Daniel Willmann
2018-08-31 18:03:16 +02:00
4 changed files with 192 additions and 107 deletions

4
debian/rules vendored
View File

@@ -17,7 +17,3 @@ export DEB_BUILD_MAINT_OPTIONS = hardening=+all
override_dh_strip:
dh_strip -posmo-ggsn --dbg-package=osmo-ggsn-dbg
dh_strip -plibgtp3 --dbg-package=libgtp-dbg
override_dh_autoreconf:
echo $(VERSION) > .tarball-version
dh_autoreconf

View File

@@ -413,16 +413,19 @@ struct ipcp_hdr {
} __attribute__ ((packed));
/* determine if IPCP contains given option */
static struct ipcp_option_hdr *ipcp_contains_option(struct ipcp_hdr *ipcp, enum ipcp_options opt)
static uint8_t *ipcp_contains_option(uint8_t *ipcp, size_t ipcp_len, enum ipcp_options opt, size_t opt_minlen)
{
uint8_t *cur = ipcp->options;
uint8_t *cur_opt = ipcp + sizeof(struct ipcp_hdr);
/* iterate over Options and check if protocol contained */
while (cur + 2 <= ((uint8_t *)ipcp) + ntohs(ipcp->len)) {
struct ipcp_option_hdr *cur_opt = (struct ipcp_option_hdr *) cur;
if (cur_opt->type == opt)
while (cur_opt + 2 <= ipcp + ipcp_len) {
uint8_t type = cur_opt[0];
uint8_t len = cur_opt[1]; /* length value includes 2 bytes type/length */
if (len < 2)
return NULL;
if (type == opt && len >= 2 + opt_minlen)
return cur_opt;
cur += cur_opt->len;
cur_opt += len;
}
return NULL;
}
@@ -460,15 +463,15 @@ enum pco_protocols {
};
/* determine if PCO contains given protocol */
static uint8_t *pco_contains_proto(struct ul255_t *pco, uint16_t prot)
static uint8_t *pco_contains_proto(struct ul255_t *pco, size_t offset, uint16_t prot, size_t prot_minlen)
{
uint8_t *cur = pco->v + 1;
uint8_t *cur = pco->v + 1 + offset;
/* iterate over PCO and check if protocol contained */
while (cur + 3 <= pco->v + pco->l) {
uint16_t cur_prot = osmo_load16be(cur);
uint8_t cur_len = cur[2];
if (cur_prot == prot)
if (cur_prot == prot && cur_len >= prot_minlen)
return cur;
cur += cur_len + 3;
}
@@ -500,46 +503,59 @@ static struct ippoolm_t *pdp_get_peer_ipv(struct pdp_t *pdp, bool is_ipv6) {
}
/* construct an IPCP PCO response from request*/
static int build_ipcp_pco(struct apn_ctx *apn, struct pdp_t *pdp, struct msgb *msg)
static void build_ipcp_pco(struct apn_ctx *apn, struct pdp_t *pdp, struct msgb *msg)
{
const struct in46_addr *dns1 = &apn->v4.cfg.dns[0];
const struct in46_addr *dns2 = &apn->v4.cfg.dns[1];
struct ipcp_hdr *ipcp;
uint8_t *ipcp;
uint16_t ipcp_len;
uint8_t *len1, *len2, *pco_ipcp;
uint8_t *start = msg->tail;
unsigned int len_appended;
ptrdiff_t consumed;
size_t remain, offset = 0;
if (!(pco_ipcp = pco_contains_proto(&pdp->pco_req, PCO_P_IPCP)))
return 0;
ipcp = (struct ipcp_hdr*) (pco_ipcp + 3); /* 2=type + 1=len */
/* pco_contains_proto() returns a potentially unaligned pointer into pco_req->v (see OS#3194) */
pco_ipcp = pco_contains_proto(&pdp->pco_req, offset, PCO_P_IPCP, sizeof(struct ipcp_hdr));
while (pco_ipcp) {
uint8_t *start = msg->tail;
/* Three byte T16L header */
msgb_put_u16(msg, 0x8021); /* IPCP */
len1 = msgb_put(msg, 1); /* Length of contents: delay */
ipcp = (pco_ipcp + 3); /* 2=type + 1=len */
consumed = (ipcp - &pdp->pco_req.v[0]);
remain = sizeof(pdp->pco_req.v) - consumed;
ipcp_len = osmo_load16be(ipcp + 2); /* 1=code + 1=id */
if (remain < 0 || remain < ipcp_len)
return;
msgb_put_u8(msg, 0x02); /* ACK */
msgb_put_u8(msg, ipcp->id); /* ID: Needs to match request */
msgb_put_u8(msg, 0x00); /* Length MSB */
len2 = msgb_put(msg, 1); /* Length LSB: delay */
/* Three byte T16L header */
msgb_put_u16(msg, 0x8021); /* IPCP */
len1 = msgb_put(msg, 1); /* Length of contents: delay */
if (dns1->len == 4 && ipcp_contains_option(ipcp, IPCP_OPT_PRIMARY_DNS)) {
msgb_put_u8(msg, 0x81); /* DNS1 Tag */
msgb_put_u8(msg, 2 + dns1->len);/* DNS1 Length, incl. TL */
msgb_put_u32(msg, ntohl(dns1->v4.s_addr));
msgb_put_u8(msg, 0x02); /* ACK */
msgb_put_u8(msg, ipcp[1]); /* ID: Needs to match request */
msgb_put_u8(msg, 0x00); /* Length MSB */
len2 = msgb_put(msg, 1); /* Length LSB: delay */
if (dns1->len == 4 && ipcp_contains_option(ipcp, ipcp_len, IPCP_OPT_PRIMARY_DNS, 4)) {
msgb_put_u8(msg, 0x81); /* DNS1 Tag */
msgb_put_u8(msg, 2 + dns1->len);/* DNS1 Length, incl. TL */
msgb_put_u32(msg, ntohl(dns1->v4.s_addr));
}
if (dns2->len == 4 && ipcp_contains_option(ipcp, ipcp_len, IPCP_OPT_SECONDARY_DNS, 4)) {
msgb_put_u8(msg, 0x83); /* DNS2 Tag */
msgb_put_u8(msg, 2 + dns2->len);/* DNS2 Length, incl. TL */
msgb_put_u32(msg, ntohl(dns2->v4.s_addr));
}
/* patch in length values */
len_appended = msg->tail - start;
*len1 = len_appended - 3;
*len2 = len_appended - 3;
offset += 3 + ipcp_len;
pco_ipcp = pco_contains_proto(&pdp->pco_req, offset, PCO_P_IPCP, sizeof(struct ipcp_hdr));
}
if (dns2->len == 4 && ipcp_contains_option(ipcp, IPCP_OPT_SECONDARY_DNS)) {
msgb_put_u8(msg, 0x83); /* DNS2 Tag */
msgb_put_u8(msg, 2 + dns2->len);/* DNS2 Length, incl. TL */
msgb_put_u32(msg, ntohl(dns2->v4.s_addr));
}
/* patch in length values */
len_appended = msg->tail - start;
*len1 = len_appended - 3;
*len2 = len_appended - 3;
return 0;
}
/* process one PCO request from a MS/UE, putting together the proper responses */
@@ -555,7 +571,7 @@ static void process_pco(struct apn_ctx *apn, struct pdp_t *pdp)
if (peer_v4)
build_ipcp_pco(apn, pdp, msg);
if (pco_contains_proto(&pdp->pco_req, PCO_P_DNS_IPv6_ADDR)) {
if (pco_contains_proto(&pdp->pco_req, 0, PCO_P_DNS_IPv6_ADDR, 0)) {
for (i = 0; i < ARRAY_SIZE(apn->v6.cfg.dns); i++) {
struct in46_addr *i46a = &apn->v6.cfg.dns[i];
if (i46a->len != 16)
@@ -564,7 +580,7 @@ static void process_pco(struct apn_ctx *apn, struct pdp_t *pdp)
}
}
if (pco_contains_proto(&pdp->pco_req, PCO_P_DNS_IPv4_ADDR)) {
if (pco_contains_proto(&pdp->pco_req, 0, PCO_P_DNS_IPv4_ADDR, 0)) {
for (i = 0; i < ARRAY_SIZE(apn->v4.cfg.dns); i++) {
struct in46_addr *i46a = &apn->v4.cfg.dns[i];
if (i46a->len != 4)
@@ -1111,7 +1127,8 @@ int main(int argc, char **argv)
if (rc < 0)
exit(1);
g_ctrlh = ctrl_interface_setup(NULL, OSMO_CTRL_PORT_GGSN, NULL);
g_ctrlh = ctrl_interface_setup_dynip(NULL, ctrl_vty_get_bind_addr(),
OSMO_CTRL_PORT_GGSN, NULL);
if (!g_ctrlh) {
LOGP(DGGSN, LOGL_ERROR, "Failed to create CTRL interface.\n");
exit(1);

184
gtp/gtp.c
View File

@@ -190,6 +190,15 @@ int gtp_set_cb_conf(struct gsn_t *gsn,
return 0;
}
static void emit_cb_recovery(struct gsn_t *gsn, struct sockaddr_in * peer,
struct pdp_t * pdp, uint8_t recovery)
{
if (gsn->cb_recovery)
gsn->cb_recovery(peer, recovery);
if (gsn->cb_recovery2)
gsn->cb_recovery2(peer, pdp, recovery);
}
int gtp_set_cb_recovery(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer, uint8_t recovery))
{
@@ -197,6 +206,20 @@ int gtp_set_cb_recovery(struct gsn_t *gsn,
return 0;
}
/* cb_recovery()
* pdp may be NULL if Recovery IE was received from a message independent
* of any PDP ctx (such as Echo Response), or because pdp ctx is unknown to the
* local setup. In case pdp is known, caller may want to keep that pdp alive to
* handle subsequent msg cb as this specific pdp ctx is still valid according to
* specs.
*/
int gtp_set_cb_recovery2(struct gsn_t *gsn,
int (*cb_recovery2) (struct sockaddr_in * peer, struct pdp_t * pdp, uint8_t recovery))
{
gsn->cb_recovery2 = cb_recovery2;
return 0;
}
int gtp_set_cb_data_ind(struct gsn_t *gsn,
int (*cb_data_ind) (struct pdp_t * pdp,
void *pack, unsigned len))
@@ -977,8 +1000,7 @@ int gtp_echo_conf(struct gsn_t *gsn, int version, struct sockaddr_in *peer,
if (gsn->cb_conf)
gsn->cb_conf(type, recovery, NULL, cbp);
if (gsn->cb_recovery)
gsn->cb_recovery(peer, recovery);
emit_cb_recovery(gsn, peer, NULL, recovery);
return 0;
}
@@ -1310,6 +1332,8 @@ int gtp_create_pdp_ind(struct gsn_t *gsn, int version,
struct pdp_t pdp_buf;
union gtpie_member *ie[GTPIE_SIZE];
uint8_t recovery;
bool recovery_recvd = false;
int rc;
uint16_t seq = get_seq(pack);
int hlen = get_hlen(pack);
@@ -1410,8 +1434,8 @@ int gtp_create_pdp_ind(struct gsn_t *gsn, int version,
/* Recovery (optional) */
if (!gtpie_gettv1(ie, GTPIE_RECOVERY, 0, &recovery)) {
if (gsn->cb_recovery)
gsn->cb_recovery(peer, recovery);
/* we use recovery futher down after announcing new pdp ctx to user */
recovery_recvd = true;
}
/* Selection mode (conditional) */
@@ -1612,6 +1636,9 @@ int gtp_create_pdp_ind(struct gsn_t *gsn, int version,
/* Switch to using the old pdp context */
pdp = pdp_old;
if (recovery_recvd)
emit_cb_recovery(gsn, peer, pdp, recovery);
/* Confirm to peer that things were "successful" */
return gtp_create_pdp_resp(gsn, version, pdp,
GTPCAUSE_ACC_REQ);
@@ -1633,13 +1660,16 @@ int gtp_create_pdp_ind(struct gsn_t *gsn, int version,
/* Callback function to validata login */
if (gsn->cb_create_context_ind != 0)
return gsn->cb_create_context_ind(pdp);
rc = gsn->cb_create_context_ind(pdp);
else {
GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
"No create_context_ind callback defined\n");
return gtp_create_pdp_resp(gsn, version, pdp,
rc = gtp_create_pdp_resp(gsn, version, pdp,
GTPCAUSE_NOT_SUPPORTED);
}
if (recovery_recvd)
emit_cb_recovery(gsn, peer, pdp, recovery);
return rc;
}
/* Handle Create PDP Context Response */
@@ -1696,8 +1726,7 @@ int gtp_create_pdp_conf(struct gsn_t *gsn, int version,
/* Extract recovery (optional) */
if (!gtpie_gettv1(ie, GTPIE_RECOVERY, 0, &recovery)) {
if (gsn->cb_recovery)
gsn->cb_recovery(peer, recovery);
emit_cb_recovery(gsn, peer, pdp, recovery);
}
/* Extract protocol configuration options (optional) */
@@ -2106,8 +2135,7 @@ static int gtp_update_pdp_ind(struct gsn_t *gsn, uint8_t version,
/* Recovery (optional) */
if (!gtpie_gettv1(ie, GTPIE_RECOVERY, 0, &recovery)) {
if (gsn->cb_recovery)
gsn->cb_recovery(peer, recovery);
emit_cb_recovery(gsn, peer, pdp, recovery);
}
if (version == 0) {
@@ -2267,8 +2295,7 @@ static int gtp_update_pdp_conf(struct gsn_t *gsn, uint8_t version,
/* Extract recovery (optional) */
if (!gtpie_gettv1(ie, GTPIE_RECOVERY, 0, &recovery)) {
if (gsn->cb_recovery)
gsn->cb_recovery(peer, recovery);
emit_cb_recovery(gsn, peer, pdp, recovery);
}
/* Check all conditional information elements */
@@ -2337,16 +2364,66 @@ err_out:
return EOF;
}
/* API: Send Delete PDP Context Request */
/* API: Deprecated. Send Delete PDP Context Request And free pdp ctx. */
int gtp_delete_context_req(struct gsn_t *gsn, struct pdp_t *pdp, void *cbp,
int teardown)
{
struct pdp_t *linked_pdp;
struct pdp_t *secondary_pdp;
int n;
if (pdp_getgtp1(&linked_pdp, pdp->teic_own)) {
LOGP(DLGTP, LOGL_ERROR,
"Unknown linked PDP context: %u\n", pdp->teic_own);
return EOF;
}
if (gtp_delete_context_req2(gsn, pdp, cbp, teardown) == EOF)
return EOF;
if (teardown) { /* Remove all contexts */
for (n = 0; n < PDP_MAXNSAPI; n++) {
if (linked_pdp->secondary_tei[n]) {
if (pdp_getgtp1
(&secondary_pdp,
linked_pdp->secondary_tei[n])) {
LOGP(DLGTP, LOGL_ERROR,
"Unknown secondary PDP context\n");
return EOF;
}
if (linked_pdp != secondary_pdp) {
if (gsn->cb_delete_context)
gsn->cb_delete_context
(secondary_pdp);
pdp_freepdp(secondary_pdp);
}
}
}
if (gsn->cb_delete_context)
gsn->cb_delete_context(linked_pdp);
pdp_freepdp(linked_pdp);
} else {
if (gsn->cb_delete_context)
gsn->cb_delete_context(pdp);
if (pdp == linked_pdp) {
linked_pdp->secondary_tei[pdp->nsapi & 0xf0] = 0;
linked_pdp->nodata = 1;
} else
pdp_freepdp(pdp);
}
return 0;
}
/* API: Send Delete PDP Context Request. PDP CTX shall be free'd by user at cb_conf(GTP_DELETE_PDP_RSP) */
int gtp_delete_context_req2(struct gsn_t *gsn, struct pdp_t *pdp, void *cbp,
int teardown)
{
union gtp_packet packet;
unsigned int length =
get_default_gtp(pdp->version, GTP_DELETE_PDP_REQ, &packet);
struct in_addr addr;
struct pdp_t *linked_pdp;
struct pdp_t *secondary_pdp;
int n;
int count = 0;
@@ -2383,37 +2460,6 @@ int gtp_delete_context_req(struct gsn_t *gsn, struct pdp_t *pdp, void *cbp,
gtp_req(gsn, pdp->version, pdp, &packet, length, &addr, cbp);
if (teardown) { /* Remove all contexts */
for (n = 0; n < PDP_MAXNSAPI; n++) {
if (linked_pdp->secondary_tei[n]) {
if (pdp_getgtp1
(&secondary_pdp,
linked_pdp->secondary_tei[n])) {
LOGP(DLGTP, LOGL_ERROR,
"Unknown secondary PDP context\n");
return EOF;
}
if (linked_pdp != secondary_pdp) {
if (gsn->cb_delete_context)
gsn->cb_delete_context
(secondary_pdp);
pdp_freepdp(secondary_pdp);
}
}
}
if (gsn->cb_delete_context)
gsn->cb_delete_context(linked_pdp);
pdp_freepdp(linked_pdp);
} else {
if (gsn->cb_delete_context)
gsn->cb_delete_context(pdp);
if (pdp == linked_pdp) {
linked_pdp->secondary_tei[pdp->nsapi & 0xf0] = 0;
linked_pdp->nodata = 1;
} else
pdp_freepdp(pdp);
}
return 0;
}
@@ -2553,6 +2599,9 @@ int gtp_delete_pdp_ind(struct gsn_t *gsn, int version,
if (linked_pdp->secondary_tei[n])
count++;
if (count <= 1) {
GTP_LOGPKG(LOGL_NOTICE, peer, pack, len,
"Ignoring CTX DEL without teardown and count=%d\n",
count);
return 0; /* 29.060 7.3.5 Ignore message */
}
}
@@ -2570,19 +2619,32 @@ int gtp_delete_pdp_conf(struct gsn_t *gsn, int version,
uint8_t cause;
void *cbp = NULL;
uint8_t type = 0;
struct pdp_t *pdp = NULL;
int hlen = get_hlen(pack);
/* Remove packet from queue */
if (gtp_conf(gsn, version, peer, pack, len, &type, &cbp))
return EOF;
/* Find the context in question. It may not be available if gtp_delete_context_req
* was used and as a result the PDP ctx was already freed */
if (pdp_getgtp1(&pdp, get_tei(pack))) {
gsn->err_unknownpdp++;
GTP_LOGPKG(LOGL_NOTICE, peer, pack, len,
"Unknown PDP context: %u (expected if gtp_delete_context_req is used)\n",
get_tei(pack));
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, NULL, cbp);
return EOF;
}
/* Decode information elements */
if (gtpie_decaps(ie, version, pack + hlen, len - hlen)) {
gsn->invalid++;
GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
"Invalid message format\n");
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, NULL, cbp);
gsn->cb_conf(type, EOF, pdp, cbp);
return EOF;
}
@@ -2592,7 +2654,7 @@ int gtp_delete_pdp_conf(struct gsn_t *gsn, int version,
GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
"Missing mandatory information field\n");
if (gsn->cb_conf)
gsn->cb_conf(type, EOF, NULL, cbp);
gsn->cb_conf(type, EOF, pdp, cbp);
return EOF;
}
@@ -2602,13 +2664,13 @@ int gtp_delete_pdp_conf(struct gsn_t *gsn, int version,
GTP_LOGPKG(LOGL_ERROR, peer, pack, len,
"Unexpected cause value received: %d\n", cause);
if (gsn->cb_conf)
gsn->cb_conf(type, cause, NULL, cbp);
gsn->cb_conf(type, cause, pdp, cbp);
return EOF;
}
/* Callback function to notify application */
if (gsn->cb_conf)
gsn->cb_conf(type, cause, NULL, cbp);
gsn->cb_conf(type, cause, pdp, cbp);
return 0;
}
@@ -2830,23 +2892,23 @@ int gtp_decaps0(struct gsn_t *gsn)
if ((gsn->mode == GTP_MODE_GGSN) &&
((pheader->type == GTP_CREATE_PDP_RSP) ||
(pheader->type == GTP_UPDATE_PDP_RSP) ||
(pheader->type == GTP_DELETE_PDP_RSP))) {
(pheader->type == GTP_UPDATE_PDP_RSP))) {
gsn->unexpect++;
GTP_LOGPKG(LOGL_ERROR, &peer, buffer,
status,
"Unexpected GTPv0 Signalling Message\n");
"Unexpected GTPv0 Signalling Message '%s'\n",
get_value_string(gtp_type_names, pheader->type));
continue; /* Silently discard 29.60: 11.1.4 */
}
if ((gsn->mode == GTP_MODE_SGSN) &&
((pheader->type == GTP_CREATE_PDP_REQ) ||
(pheader->type == GTP_UPDATE_PDP_REQ) ||
(pheader->type == GTP_DELETE_PDP_REQ))) {
(pheader->type == GTP_UPDATE_PDP_REQ))) {
gsn->unexpect++;
GTP_LOGPKG(LOGL_ERROR, &peer, buffer,
status,
"Unexpected GTPv0 Signalling Message\n");
"Unexpected GTPv0 Signalling Message '%s'\n",
get_value_string(gtp_type_names, pheader->type));
continue; /* Silently discard 29.60: 11.1.4 */
}
@@ -3007,23 +3069,23 @@ int gtp_decaps1c(struct gsn_t *gsn)
if ((gsn->mode == GTP_MODE_GGSN) &&
((pheader->type == GTP_CREATE_PDP_RSP) ||
(pheader->type == GTP_UPDATE_PDP_RSP) ||
(pheader->type == GTP_DELETE_PDP_RSP))) {
(pheader->type == GTP_UPDATE_PDP_RSP))) {
gsn->unexpect++;
GTP_LOGPKG(LOGL_ERROR, &peer, buffer,
status,
"Unexpected GTPv1 Signalling Message\n");
"Unexpected GTPv1 Signalling Message '%s'\n",
get_value_string(gtp_type_names, pheader->type));
continue; /* Silently discard 29.60: 11.1.4 */
}
if ((gsn->mode == GTP_MODE_SGSN) &&
((pheader->type == GTP_CREATE_PDP_REQ) ||
(pheader->type == GTP_UPDATE_PDP_REQ) ||
(pheader->type == GTP_DELETE_PDP_REQ))) {
(pheader->type == GTP_UPDATE_PDP_REQ))) {
gsn->unexpect++;
GTP_LOGPKG(LOGL_ERROR, &peer, buffer,
status,
"Unexpected GTPv1 Signalling Message\n");
"Unexpected GTPv1 Signalling Message '%s'\n",
get_value_string(gtp_type_names, pheader->type));
continue; /* Silently discard 29.60: 11.1.4 */
}

View File

@@ -13,6 +13,7 @@
#define _GTP_H
#include <osmocom/core/utils.h>
#include <osmocom/core/defs.h>
#define GTP_MODE_GGSN 1
#define GTP_MODE_SGSN 2
@@ -270,6 +271,7 @@ struct gsn_t {
int (*cb_conf) (int type, int cause, struct pdp_t * pdp, void *cbp);
int (*cb_data_ind) (struct pdp_t * pdp, void *pack, unsigned len);
int (*cb_recovery) (struct sockaddr_in * peer, uint8_t recovery);
int (*cb_recovery2) (struct sockaddr_in * peer, struct pdp_t * pdp, uint8_t recovery);
/* Counters */
@@ -323,7 +325,10 @@ extern int gtp_update_context(struct gsn_t *gsn, struct pdp_t *pdp,
void *cbp, struct in_addr *inetaddr);
extern int gtp_delete_context_req(struct gsn_t *gsn, struct pdp_t *pdp,
void *cbp, int teardown);
void *cbp, int teardown)
OSMO_DEPRECATED("Use gtp_delete_context_req2() instead, to avoid freeing pdp ctx before reply");
extern int gtp_delete_context_req2(struct gsn_t *gsn, struct pdp_t *pdp,
void *cbp, int teardown);
extern int gtp_data_req(struct gsn_t *gsn, struct pdp_t *pdp,
void *pack, unsigned len);
@@ -357,6 +362,11 @@ extern int gtp_set_cb_conf(struct gsn_t *gsn,
int gtp_set_cb_recovery(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer,
uint8_t recovery))
OSMO_DEPRECATED("Use gtp_set_cb_recovery2() instead, to obtain pdp ctx originating the recovery");
int gtp_set_cb_recovery2(struct gsn_t *gsn,
int (*cb) (struct sockaddr_in * peer,
struct pdp_t * pdp,
uint8_t recovery));
/* Internal functions (not part of the API */