strongswan/src/libcharon/plugins/osmo_epdg/osmo_epdg_ue.c

474 lines
10 KiB
C

/*
* Copyright (C) 2023 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.
*
*/
#include <errno.h>
#include <sa/ike_sa.h>
#include <threading/rwlock.h>
#include <collections/linked_list.h>
#include <utils/utils.h>
#include <utils/debug.h>
#include "pco.h"
#include "osmo_epdg_ue.h"
#include "osmo_epdg_utils.h"
typedef struct private_osmo_epdg_ue_t private_osmo_epdg_ue_t;
/**
* Private data of an osmo_epdg_ue_t object.
*/
struct private_osmo_epdg_ue_t {
/**
* Public osmo_epdg_ue_t interface.
*/
osmo_epdg_ue_t public;
/**
* a unique id.
* Same as ike_sa_t->get_unique_id().
*/
uint32_t id;
/**
* IMSI encoded as character
*/
char *imsi;
/**
* APN encoded as character (foo.example)
*/
char *apn;
/**
* IP address of the client. Might become a llist_t in the future
*/
host_t *address;
/**
* The requested attributes/PCO options on GTP
* e.g. P-CSCF requests, DNS, ..
* holds attribute_entry_t
*/
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.
* It will call destroy() when refcount reaches 0
*/
refcount_t refcount;
/**
* rwlock to lock access for changes
*/
rwlock_t *lock;
enum osmo_epdg_ue_state state;
};
METHOD(osmo_epdg_ue_t, get_imsi, const char *,
private_osmo_epdg_ue_t *this)
{
return this->imsi;
}
METHOD(osmo_epdg_ue_t, get_apn, const char *,
private_osmo_epdg_ue_t *this)
{
return this->apn;
}
METHOD(osmo_epdg_ue_t, get_id, uint32_t,
private_osmo_epdg_ue_t *this)
{
return this->id;
}
METHOD(osmo_epdg_ue_t, set_id, void,
private_osmo_epdg_ue_t *this, uint32_t unique_id)
{
this->id = unique_id;
}
METHOD(osmo_epdg_ue_t, set_address, void,
private_osmo_epdg_ue_t *this, host_t *address)
{
this->lock->write_lock(this->lock);
if (this->address)
{
this->address->destroy(this->address);
}
this->address = address->clone(address);
this->lock->unlock(this->lock);
}
METHOD(osmo_epdg_ue_t, get_address, host_t *,
private_osmo_epdg_ue_t *this)
{
host_t *address = NULL;
this->lock->read_lock(this->lock);
if (this->address)
{
address = this->address->clone(this->address);
}
this->lock->unlock(this->lock);
return address;
}
METHOD(osmo_epdg_ue_t, set_state, void,
private_osmo_epdg_ue_t *this, enum osmo_epdg_ue_state state)
{
this->lock->write_lock(this->lock);
/* TODO: implement a FSM. At least we can get debug information out of it. */
this->state = state;
this->lock->unlock(this->lock);
}
METHOD(osmo_epdg_ue_t, get_state, enum osmo_epdg_ue_state,
private_osmo_epdg_ue_t *this)
{
enum osmo_epdg_ue_state state;
this->lock->read_lock(this->lock);
/* TODO: implement a FSM. At least we can get debug information out of it. */
state = this->state;
this->lock->unlock(this->lock);
return state;
}
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->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,
private_osmo_epdg_ue_t *this)
{
ref_get(&this->refcount);
}
METHOD(osmo_epdg_ue_t, put, void,
private_osmo_epdg_ue_t *this)
{
if (ref_put(&this->refcount))
{
this->public.destroy(&this->public);
}
}
CALLBACK(destroy_attribute, void,
osmo_epdg_attribute_t *attr)
{
if (attr->valid)
{
chunk_free(&attr->value);
}
free(attr);
}
METHOD(osmo_epdg_ue_t, destroy, void,
private_osmo_epdg_ue_t *this)
{
this->lock->destroy(this->lock);
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);
free(this);
}
osmo_epdg_ue_t *osmo_epdg_ue_create(uint32_t id, const char *imsi, const char *apn)
{
private_osmo_epdg_ue_t *this;
if (epdg_validate_apn(apn) ||
epdg_validate_imsi(imsi))
{
return NULL;
}
INIT(this,
.public = {
.get = _get,
.put = _put,
.get_apn = _get_apn,
.get_imsi = _get_imsi,
.get_id = _get_id,
.set_id = _set_id,
.get_address = _get_address,
.set_address = _set_address,
.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),
.imsi = strdup(imsi),
.id = id,
.lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
.state = UE_WAIT_LOCATION_UPDATE,
.request_attributes = linked_list_create(),
.response_attributes = linked_list_create(),
.refcount = 1,
);
return &this->public;
}