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:
Alexander Couzens 2024-02-23 01:29:08 +01:00
parent bbed633f93
commit 90a99d3318
6 changed files with 357 additions and 10 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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);

View File

@ -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,
);

View File

@ -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
*/

View File

@ -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));