osmo-epdg: parse and generate PCOs out of config requests
The UE will request via IKEv2 as Config Request Payload certain address (DNS, P-CSCF).
This commit is contained in:
parent
bbed633f93
commit
90a99d3318
|
@ -329,11 +329,10 @@ static bool enqueue(private_osmo_epdg_gsup_client_t *this, gsup_request_t *req,
|
|||
}
|
||||
|
||||
METHOD(osmo_epdg_gsup_client_t, tunnel_request, osmo_epdg_gsup_response_t*,
|
||||
private_osmo_epdg_gsup_client_t *this, const char *imsi)
|
||||
private_osmo_epdg_gsup_client_t *this, const char *imsi, const char *pco, uint8_t pco_len)
|
||||
{
|
||||
struct osmo_gsup_message gsup_msg = {0};
|
||||
struct msgb *msg;
|
||||
const char *pco = "\x80\x00\x0d\x00\x00\x0c\x00";
|
||||
bool timedout;
|
||||
|
||||
DBG1(DBG_NET, "epdg: gsupc: Tunnel Request Request for %s", imsi);
|
||||
|
@ -347,7 +346,7 @@ METHOD(osmo_epdg_gsup_client_t, tunnel_request, osmo_epdg_gsup_response_t*,
|
|||
}
|
||||
|
||||
gsup_msg.pco = pco;
|
||||
gsup_msg.pco_len = 7;
|
||||
gsup_msg.pco_len = pco_len;
|
||||
|
||||
msg = encode_to_msgb(&gsup_msg);
|
||||
if (!msg)
|
||||
|
|
|
@ -85,7 +85,7 @@ struct osmo_epdg_gsup_client_t {
|
|||
* @return NULL or the osmo_gsup_message
|
||||
*/
|
||||
osmo_epdg_gsup_response_t *(*tunnel_request)(osmo_epdg_gsup_client_t *this,
|
||||
const char *imsi);
|
||||
const char *imsi, const char *pco, uint8_t pco_len);
|
||||
|
||||
/**
|
||||
* Destroy a osmo_epdg_gsup_client_t.
|
||||
|
|
|
@ -110,7 +110,9 @@ METHOD(listener_t, authorize, bool,
|
|||
host_t *address = NULL;
|
||||
struct osmo_gsup_pdp_info *pdp_info;
|
||||
osmo_epdg_gsup_response_t *resp = NULL;
|
||||
|
||||
char *pco;
|
||||
uint8_t pco_len;
|
||||
int ret;
|
||||
|
||||
DBG1(DBG_NET, "Authorized: uniq 0x%08x, name %s final: %d, eap: %d!",
|
||||
ike_sa->get_unique_id(ike_sa),
|
||||
|
@ -143,8 +145,21 @@ METHOD(listener_t, authorize, bool,
|
|||
goto err;
|
||||
}
|
||||
|
||||
if (ue->fill_request_attributes(ue, ike_sa->create_attribute_enumerator(ike_sa)))
|
||||
{
|
||||
DBG1(DBG_NET, "epdg: authorize: Can't pass requested_attributes towards UE for imsi %s via EAP identity.", imsi);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = ue->generate_pco(ue, &pco, &pco_len);
|
||||
if (ret)
|
||||
{
|
||||
DBG1(DBG_NET, "epdg: authorize: Can't encode PCO for imsi %s via EAP identity. Ret = %d.", imsi, ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ue->set_state(ue, UE_WAIT_TUNNEL);
|
||||
resp = this->gsup->tunnel_request(this->gsup, imsi);
|
||||
resp = this->gsup->tunnel_request(this->gsup, imsi, pco, pco_len);
|
||||
if (!resp)
|
||||
{
|
||||
DBG1(DBG_NET, "epdg_listener: Tunnel Request: GSUP: couldn't send.");
|
||||
|
@ -187,17 +202,31 @@ METHOD(listener_t, authorize, bool,
|
|||
goto err;
|
||||
}
|
||||
|
||||
/* Convert PCO back into Attributes */
|
||||
ret = ue->convert_pco(ue, resp->gsup.pco, resp->gsup.pco_len);
|
||||
if (ret)
|
||||
{
|
||||
DBG1(DBG_NET, "epdg_listener: Tunnel Response: %s failed to convert Response PCO back into Config Attributes with %d", imsi, ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ue->set_address(ue, address);
|
||||
ue->set_state(ue, UE_TUNNEL_READY);
|
||||
ue->put(ue);
|
||||
|
||||
address->destroy(address);
|
||||
free(pco);
|
||||
osmo_epdg_gsup_resp_free(resp);
|
||||
return TRUE;
|
||||
|
||||
err:
|
||||
osmo_epdg_gsup_resp_free(resp);
|
||||
|
||||
if (pco)
|
||||
{
|
||||
free(pco);
|
||||
}
|
||||
|
||||
if (ue)
|
||||
{
|
||||
ue->set_state(ue, UE_FAIL);
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <utils/utils.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include "pco.h"
|
||||
#include "osmo_epdg_ue.h"
|
||||
#include "osmo_epdg_utils.h"
|
||||
|
||||
|
@ -65,7 +66,14 @@ struct private_osmo_epdg_ue_t {
|
|||
* e.g. P-CSCF requests, DNS, ..
|
||||
* holds attribute_entry_t
|
||||
*/
|
||||
linked_list_t *attributes;
|
||||
linked_list_t *request_attributes;
|
||||
|
||||
/**
|
||||
* The response attributes/PCO options on GTP
|
||||
* e.g. P-CSCF requests, DNS, ..
|
||||
* holds attribute_entry_t
|
||||
*/
|
||||
linked_list_t *response_attributes;
|
||||
|
||||
/**
|
||||
* Refcount to track this object.
|
||||
|
@ -157,7 +165,232 @@ METHOD(osmo_epdg_ue_t, get_attributes, linked_list_t *,
|
|||
private_osmo_epdg_ue_t *this)
|
||||
{
|
||||
/* TODO: check if we need to also take locking .. also refcounting would be great here */
|
||||
return this->attributes;
|
||||
return this->response_attributes;
|
||||
}
|
||||
|
||||
/* Fill all request_attributes into the UE object to generate PCO later out of it */
|
||||
METHOD(osmo_epdg_ue_t, fill_request_attributes, int,
|
||||
private_osmo_epdg_ue_t *this, enumerator_t *enumerator)
|
||||
{
|
||||
configuration_attribute_type_t type;
|
||||
chunk_t chunk;
|
||||
bool handled;
|
||||
osmo_epdg_attribute_t *entry;
|
||||
|
||||
if (!enumerator)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
while (enumerator->enumerate(enumerator, &type, &chunk, &handled))
|
||||
{
|
||||
INIT(entry,
|
||||
.type = type,
|
||||
.value = chunk_empty,
|
||||
.valid = FALSE,
|
||||
);
|
||||
this->request_attributes->insert_last(this->request_attributes, entry);
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* requires a enumerator from attributes. The enumerator left intact. */
|
||||
static int count_pcos(enumerator_t *enumerator)
|
||||
{
|
||||
osmo_epdg_attribute_t *entry;
|
||||
int count = 0;
|
||||
|
||||
while (enumerator->enumerate(enumerator, (void **) &entry))
|
||||
{
|
||||
switch (entry->type)
|
||||
{
|
||||
case INTERNAL_IP4_DNS:
|
||||
case INTERNAL_IP6_DNS:
|
||||
case P_CSCF_IP4_ADDRESS:
|
||||
case P_CSCF_IP6_ADDRESS:
|
||||
count++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static inline int encode_pco_req(char *data, enum pco_protocols pco_protocol)
|
||||
{
|
||||
struct pco_element pco = { .length = 0 };
|
||||
pco.protocol_id = htons(pco_protocol);
|
||||
memcpy(data, &pco, sizeof(struct pco_element));
|
||||
return sizeof(struct pco_element);
|
||||
}
|
||||
|
||||
#define MAX_PCO_LEN 251
|
||||
|
||||
METHOD(osmo_epdg_ue_t, generate_pco, int,
|
||||
private_osmo_epdg_ue_t *this, char **pco, uint8_t *pco_len)
|
||||
{
|
||||
enumerator_t *enumerator;
|
||||
osmo_epdg_attribute_t *entry;
|
||||
int pcos_num;
|
||||
size_t max_size;
|
||||
uint8_t iter = 0;
|
||||
|
||||
if (!pco || !pco_len)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
enumerator = this->request_attributes->create_enumerator(this->request_attributes);
|
||||
if (!enumerator)
|
||||
{
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
pcos_num = count_pcos(enumerator);
|
||||
if (pcos_num == 0)
|
||||
{
|
||||
*pco = NULL;
|
||||
*pco_len = 0;
|
||||
enumerator->destroy(enumerator);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 3GPP TS 10.5.6.3: Encode here octet 3 - ZA
|
||||
* Octet: 3 as header
|
||||
* a PCO with zero length requires 3 bytes. zero length because we request it
|
||||
*/
|
||||
max_size = 1 + pcos_num * 3;
|
||||
if (max_size > MAX_PCO_LEN)
|
||||
{
|
||||
enumerator->destroy(enumerator);
|
||||
return -E2BIG;
|
||||
}
|
||||
|
||||
*pco = calloc(1, 1 + pcos_num * 3);
|
||||
this->request_attributes->reset_enumerator(this->request_attributes, enumerator);
|
||||
|
||||
/* Config protocol: 0x00 + Ext bit = 0x80 */
|
||||
iter = 0;
|
||||
(*pco)[iter++] = 0x80;
|
||||
|
||||
while (enumerator->enumerate(enumerator, (void **) &entry))
|
||||
{
|
||||
switch (entry->type)
|
||||
{
|
||||
case P_CSCF_IP6_ADDRESS:
|
||||
iter += encode_pco_req(*pco + iter, PCO_P_PCSCF_ADDR);
|
||||
break;
|
||||
case INTERNAL_IP6_DNS:
|
||||
iter += encode_pco_req(*pco + iter, PCO_P_DNS_IPv6_ADDR);
|
||||
break;
|
||||
case P_CSCF_IP4_ADDRESS:
|
||||
iter += encode_pco_req(*pco + iter, PCO_P_PCSCF_IPv4_ADDR);
|
||||
break;
|
||||
case INTERNAL_IP4_DNS:
|
||||
iter += encode_pco_req(*pco + iter, PCO_P_DNS_IPv4_ADDR);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
*pco_len = iter;
|
||||
enumerator->destroy(enumerator);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Fill the attribute of type *type* with the value, len
|
||||
*/
|
||||
static void set_attribute(private_osmo_epdg_ue_t *this,
|
||||
configuration_attribute_type_t type, const char *value, uint8_t len)
|
||||
{
|
||||
osmo_epdg_attribute_t *entry;
|
||||
|
||||
INIT(entry,
|
||||
.type = type,
|
||||
.value = chunk_clone(chunk_create((char *) value, len)),
|
||||
.valid = TRUE,
|
||||
);
|
||||
this->response_attributes->insert_last(this->response_attributes, entry);
|
||||
}
|
||||
|
||||
/* Take the PCO response from the PGW and fill the attributes */
|
||||
METHOD(osmo_epdg_ue_t, convert_pco, int,
|
||||
private_osmo_epdg_ue_t *this, const uint8_t *pco, uint8_t pco_len)
|
||||
{
|
||||
int iter = 0;
|
||||
uint16_t a_pco = 0;
|
||||
uint8_t a_pco_len = 0;
|
||||
const char *value;
|
||||
|
||||
if (pco_len == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!pco)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (pco[iter++] != 0x80)
|
||||
{
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
/* first run validate length of TLVs */
|
||||
while (iter + 2 < pco_len)
|
||||
{
|
||||
/* Skip Type field */
|
||||
iter += 2;
|
||||
/* uint8_t length field */
|
||||
iter += (uint8_t) pco[iter] + 1;
|
||||
}
|
||||
|
||||
/* TLV doesn't add up. Either additional data or not enough data */
|
||||
if (iter != pco_len)
|
||||
{
|
||||
return -EFBIG;
|
||||
}
|
||||
iter = 1;
|
||||
|
||||
/* second run parse known TLVs */
|
||||
while (iter + 2 < pco_len)
|
||||
{
|
||||
a_pco = pco[iter] << 8 | pco[iter + 1];
|
||||
a_pco_len = pco[iter + 2];
|
||||
|
||||
/* header size + value */
|
||||
iter += 3 + a_pco_len;
|
||||
/* we ignore empty PCO */
|
||||
if (!a_pco_len)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
value = &pco[iter + 3];
|
||||
switch (a_pco)
|
||||
{
|
||||
case PCO_P_PCSCF_IPv4_ADDR:
|
||||
set_attribute(this, P_CSCF_IP4_ADDRESS, value, a_pco_len);
|
||||
break;
|
||||
case PCO_P_PCSCF_ADDR: /* IPv6 */
|
||||
set_attribute(this, P_CSCF_IP6_ADDRESS, value, a_pco_len);
|
||||
break;
|
||||
case PCO_P_DNS_IPv4_ADDR:
|
||||
set_attribute(this, P_CSCF_IP4_ADDRESS, value, a_pco_len);
|
||||
break;
|
||||
case PCO_P_DNS_IPv6_ADDR:
|
||||
set_attribute(this, P_CSCF_IP6_ADDRESS, value, a_pco_len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
METHOD(osmo_epdg_ue_t, get, void,
|
||||
|
@ -190,7 +423,8 @@ METHOD(osmo_epdg_ue_t, destroy, void,
|
|||
private_osmo_epdg_ue_t *this)
|
||||
{
|
||||
this->lock->destroy(this->lock);
|
||||
this->attributes->destroy_function(this->attributes, destroy_attribute);
|
||||
this->request_attributes->destroy_function(this->request_attributes, destroy_attribute);
|
||||
this->response_attributes->destroy_function(this->response_attributes, destroy_attribute);
|
||||
|
||||
free(this->apn);
|
||||
free(this->imsi);
|
||||
|
@ -220,6 +454,9 @@ osmo_epdg_ue_t *osmo_epdg_ue_create(uint32_t id, const char *imsi, const char *a
|
|||
.get_state = _get_state,
|
||||
.set_state = _set_state,
|
||||
.get_attributes = _get_attributes,
|
||||
.fill_request_attributes = _fill_request_attributes,
|
||||
.generate_pco = _generate_pco,
|
||||
.convert_pco = _convert_pco,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
.apn = strdup(apn),
|
||||
|
@ -227,7 +464,8 @@ osmo_epdg_ue_t *osmo_epdg_ue_create(uint32_t id, const char *imsi, const char *a
|
|||
.id = id,
|
||||
.lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
|
||||
.state = UE_WAIT_LOCATION_UPDATE,
|
||||
.attributes = linked_list_create(),
|
||||
.request_attributes = linked_list_create(),
|
||||
.response_attributes = linked_list_create(),
|
||||
.refcount = 1,
|
||||
);
|
||||
|
||||
|
|
|
@ -84,6 +84,27 @@ struct osmo_epdg_ue_t {
|
|||
*/
|
||||
linked_list_t *(*get_attributes)(osmo_epdg_ue_t *this);
|
||||
|
||||
/**
|
||||
* The attributes the UE requested. Pass ike->create_attribute_enumerator() towards it.
|
||||
* An enumerator(configuration_attribute_type_t, chunk_t, bool).
|
||||
* The enumerator will be destroyed by request_attributes.
|
||||
*/
|
||||
int (*fill_request_attributes)(osmo_epdg_ue_t *this, enumerator_t *enumerator);
|
||||
|
||||
/**
|
||||
* Get PCO encoded elements. It will return attributes encoded as PCO.
|
||||
* On error returns != 0.
|
||||
* On success, the caller must free *pco.
|
||||
*/
|
||||
int (*generate_pco)(osmo_epdg_ue_t *this, char **pco, uint8_t *pco_len);
|
||||
|
||||
/**
|
||||
* Get PCO encoded elements. It will return attributes encoded as PCO.
|
||||
* On error returns != 0.
|
||||
* On success, the caller must free *pco.
|
||||
*/
|
||||
int (*convert_pco)(osmo_epdg_ue_t *this, const uint8_t *pco, uint8_t pco_len);
|
||||
|
||||
/**
|
||||
* Get address. Returns NULL or a cloned' host_t object
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (C) 2024 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* Author: Alexander Couzens <acouzens@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
/* from OsmoGGSN ./ggsn/pco.h under GPLv2 */
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* 3GPP TS 24.008 10.5.6.3 */
|
||||
enum pco_protocols {
|
||||
PCO_P_LCP = 0xC021,
|
||||
PCO_P_PAP = 0xC023,
|
||||
PCO_P_CHAP = 0xC223,
|
||||
PCO_P_IPCP = 0x8021,
|
||||
PCO_P_PCSCF_ADDR = 0x0001,
|
||||
PCO_P_IM_CN_SS_F = 0x0002,
|
||||
PCO_P_DNS_IPv6_ADDR = 0x0003,
|
||||
PCO_P_POLICY_CTRL_REJ = 0x0004, /* only in Network->MS */
|
||||
PCO_P_MS_SUP_NETREQ_BCI = 0x0005,
|
||||
/* reserved */
|
||||
PCO_P_DSMIPv6_HA_ADDR = 0x0007,
|
||||
PCO_P_DSMIPv6_HN_PREF = 0x0008,
|
||||
PCO_P_DSMIPv6_v4_HA_ADDR= 0x0009,
|
||||
PCO_P_IP_ADDR_VIA_NAS = 0x000a, /* only MS->Network */
|
||||
PCO_P_IPv4_ADDR_VIA_DHCP= 0x000b, /* only MS->Netowrk */
|
||||
PCO_P_PCSCF_IPv4_ADDR = 0x000c,
|
||||
PCO_P_DNS_IPv4_ADDR = 0x000d,
|
||||
PCO_P_MSISDN = 0x000e,
|
||||
PCO_P_IFOM_SUPPORT = 0x000f,
|
||||
PCO_P_IPv4_LINK_MTU = 0x0010,
|
||||
PCO_P_MS_SUPP_LOC_A_TFT = 0x0011,
|
||||
PCO_P_PCSCF_RESEL_SUP = 0x0012, /* only MS->Network */
|
||||
PCO_P_NBIFOM_REQ = 0x0013,
|
||||
PCO_P_NBIFOM_MODE = 0x0014,
|
||||
PCO_P_NONIP_LINK_MTU = 0x0015,
|
||||
PCO_P_APN_RATE_CTRL_SUP = 0x0016,
|
||||
PCO_P_PS_DATA_OFF_UE = 0x0017,
|
||||
PCO_P_REL_DATA_SVC = 0x0018,
|
||||
};
|
||||
|
||||
struct pco_element {
|
||||
uint16_t protocol_id; /* network byte order */
|
||||
uint8_t length; /* length of data below */
|
||||
uint8_t data[0];
|
||||
} __attribute__((packed));
|
||||
|
Loading…
Reference in New Issue