Compare commits

...

10 Commits

Author SHA1 Message Date
Alexander Couzens a0c13fba99 dirty WIP 2023-03-28 22:29:39 +02:00
Alexander Couzens 9852101624 step of epdge db 2023-03-28 21:23:25 +02:00
Alexander Couzens 8074d68e57 start implementing osmo_epdg 2023-03-28 21:02:23 +02:00
Alexander Couzens d776ea007c fixup Tunnel 2023-03-23 18:46:52 +01:00
Alexander Couzens 40974e78dd osmo-epdg: implement Tunnel Request/Response
Requires gsup message types in libosmocore
2023-03-23 17:44:19 +01:00
Alexander Couzens 46a01090bb gsup_client: fix coding style 2023-03-23 17:42:50 +01:00
Alexander Couzens 6d8bcb834a osmo_epdg_listener: add TODO to validate APN 2023-03-23 15:42:29 +01:00
Alexander Couzens 1c92c4d83e start of osmo-epdg plugin
- simple gsup/ipa working
- strongswan is requesting tuples via GSUP.
- strongswan client can authenticate
- SWu-IKEv2 can't authenticate

ToDos:
- gsup: disconnect/reconnect
- gsup: failures cases
- blocking queue needs to be cleaned up
- fix coding style
2023-03-23 15:29:57 +01:00
Alexander Couzens e7e6a51fb1 add documentation
Add a full example for both ePDG and UE.
2023-03-23 15:08:29 +01:00
Alexander Couzens dfa0f7daf5 blocking_queue: add remove() function
Allows to remove an object which is still in the queue.
2023-02-26 11:05:04 +01:00
31 changed files with 2368 additions and 689 deletions

694
README.md
View File

@ -1,693 +1,9 @@
# strongSwan Configuration #
# strongSwan osmo-epdg component
## Overview ##
- strongswan + a osmo-epdg plugins
strongSwan is an OpenSource IPsec-based VPN solution.
## example configuration
This document is just a short introduction of the strongSwan **swanctl** command
which uses the modern [**vici**](src/libcharon/plugins/vici/README.md) *Versatile
IKE Configuration Interface*. The deprecated **ipsec** command using the legacy
**stroke** configuration interface is described [**here**](README_LEGACY.md).
For more detailed information consult the man pages and
[**our wiki**](https://wiki.strongswan.org).
See ./osmo-epdg for a full example configuration (both UE and ePDG).
## Quickstart ##
Certificates for users, hosts and gateways are issued by a fictitious
strongSwan CA. In our example scenarios the CA certificate `strongswanCert.pem`
must be present on all VPN endpoints in order to be able to authenticate the
peers. For your particular VPN application you can either use certificates from
any third-party CA or generate the needed private keys and certificates yourself
with the strongSwan **pki** tool, the use of which will be explained in one of
the sections following below.
### Site-to-Site Case ###
In this scenario two security gateways _moon_ and _sun_ will connect the
two subnets _moon-net_ and _sun-net_ with each other through a VPN tunnel
set up between the two gateways:
10.1.0.0/16 -- | 192.168.0.1 | === | 192.168.0.2 | -- 10.2.0.0/16
moon-net moon sun sun-net
Configuration on gateway _moon_:
/etc/swanctl/x509ca/strongswanCert.pem
/etc/swanctl/x509/moonCert.pem
/etc/swanctl/private/moonKey.pem
/etc/swanctl/swanctl.conf:
connections {
net-net {
remote_addrs = 192.168.0.2
local {
auth = pubkey
certs = moonCert.pem
}
remote {
auth = pubkey
id = "C=CH, O=strongSwan, CN=sun.strongswan.org"
}
children {
net-net {
local_ts = 10.1.0.0/16
remote_ts = 10.2.0.0/16
start_action = trap
}
}
}
}
Configuration on gateway _sun_:
/etc/swanctl/x509ca/strongswanCert.pem
/etc/swanctl/x509/sunCert.pem
/etc/swanctl/private/sunKey.pem
/etc/swanctl/swanctl.conf:
connections {
net-net {
remote_addrs = 192.168.0.1
local {
auth = pubkey
certs = sunCert.pem
}
remote {
auth = pubkey
id = "C=CH, O=strongSwan, CN=moon.strongswan.org"
}
children {
net-net {
local_ts = 10.2.0.0/16
remote_ts = 10.1.0.0/16
start_action = trap
}
}
}
}
The local and remote identities used in this scenario are the
*subjectDistinguishedNames* contained in the end entity certificates.
The certificates and private keys are loaded into the **charon** daemon with
the command
swanctl --load-creds
whereas
swanctl --load-conns
loads the connections defined in `swanctl.conf`. With `start_action = trap` the
IPsec connection is automatically set up with the first plaintext payload IP
packet wanting to go through the tunnel.
### Host-to-Host Case ###
This is a setup between two single hosts which don't have a subnet behind
them. Although IPsec transport mode would be sufficient for host-to-host
connections we will use the default IPsec tunnel mode.
| 192.168.0.1 | === | 192.168.0.2 |
moon sun
Configuration on host _moon_:
/etc/swanctl/x509ca/strongswanCert.pem
/etc/swanctl/x509/moonCert.pem
/etc/swanctl/private/moonKey.pem
/etc/swanctl/swanctl.conf:
connections {
host-host {
remote_addrs = 192.168.0.2
local {
auth=pubkey
certs = moonCert.pem
}
remote {
auth = pubkey
id = "C=CH, O=strongSwan, CN=sun.strongswan.org"
}
children {
net-net {
start_action = trap
}
}
}
}
Configuration on host _sun_:
/etc/swanctl/x509ca/strongswanCert.pem
/etc/swanctl/x509/sunCert.pem
/etc/swanctl/private/sunKey.pem
/etc/swanctl/swanctl.conf:
connections {
host-host {
remote_addrs = 192.168.0.1
local {
auth = pubkey
certs = sunCert.pem
}
remote {
auth = pubkey
id = "C=CH, O=strongSwan, CN=moon.strongswan.org"
}
children {
host-host {
start_action = trap
}
}
}
}
### Roadwarrior Case ###
This is a very common case where a strongSwan gateway serves an arbitrary
number of remote VPN clients usually having dynamic IP addresses.
10.1.0.0/16 -- | 192.168.0.1 | === | x.x.x.x |
moon-net moon carol
Configuration on gateway _moon_:
/etc/swanctl/x509ca/strongswanCert.pem
/etc/swanctl/x509/moonCert.pem
/etc/swanctl/private/moonKey.pem
/etc/swanctl/swanctl.conf:
connections {
rw {
local {
auth = pubkey
certs = moonCert.pem
id = moon.strongswan.org
}
remote {
auth = pubkey
}
children {
net-net {
local_ts = 10.1.0.0/16
}
}
}
}
Configuration on roadwarrior _carol_:
/etc/swanctl/x509ca/strongswanCert.pem
/etc/swanctl/x509/carolCert.pem
/etc/swanctl/private/carolKey.pem
/etc/swanctl/swanctl.conf:
connections {
home {
remote_addrs = moon.strongswan.org
local {
auth = pubkey
certs = carolCert.pem
id = carol@strongswan.org
}
remote {
auth = pubkey
id = moon.strongswan.org
}
children {
home {
local_ts = 10.1.0.0/16
start_action = start
}
}
}
}
For `remote_addrs` the hostname `moon.strongswan.org` was chosen which will be
resolved by DNS at runtime into the corresponding IP destination address.
In this scenario the identity of the roadwarrior `carol` is the email address
`carol@strongswan.org` which must be included as a *subjectAlternativeName* in
the roadwarrior certificate `carolCert.pem`.
### Roadwarrior Case with Virtual IP ###
Roadwarriors usually have dynamic IP addresses assigned by the ISP they are
currently attached to. In order to simplify the routing from _moon-net_ back
to the remote access client _carol_ it would be desirable if the roadwarrior had
an inner IP address chosen from a pre-defined pool.
10.1.0.0/16 -- | 192.168.0.1 | === | x.x.x.x | -- 10.3.0.1
moon-net moon carol virtual IP
In our example the virtual IP address is chosen from the address pool
`10.3.0.0/16` which can be configured by adding the section
pools {
rw_pool {
addrs = 10.3.0.0/16
}
}
to the gateway's `swanctl.conf` from where they are loaded into the **charon**
daemon using the command
swanctl --load-pools
To request an IP address from this pool a roadwarrior can use IKEv1 mode config
or IKEv2 configuration payloads. The configuration for both is the same
vips = 0.0.0.0
Configuration on gateway _moon_:
/etc/swanctl/x509ca/strongswanCert.pem
/etc/swanctl/x509/moonCert.pem
/etc/swanctl/private/moonKey.pem
/etc/swanctl/swanctl.conf:
connections {
rw {
pools = rw_pool
local {
auth = pubkey
certs = moonCert.pem
id = moon.strongswan.org
}
remote {
auth = pubkey
}
children {
net-net {
local_ts = 10.1.0.0/16
}
}
}
}
pools {
rw_pool {
addrs = 10.30.0.0/16
}
}
Configuration on roadwarrior _carol_:
/etc/swanctl/x509ca/strongswanCert.pem
/etc/swanctl/x509/carolCert.pem
/etc/swanctl/private/carolKey.pem
/etc/swanctl/swanctl.conf:
connections {
home {
remote_addrs = moon.strongswan.org
vips = 0.0.0.0
local {
auth = pubkey
certs = carolCert.pem
id = carol@strongswan.org
}
remote {
auth = pubkey
id = moon.strongswan.org
}
children {
home {
local_ts = 10.1.0.0/16
start_action = start
}
}
}
}
### Roadwarrior Case with EAP Authentication ###
This is a very common case where a strongSwan gateway serves an arbitrary
number of remote VPN clients which authenticate themselves via a password
based *Extended Authentication Protocol* as e.g. *EAP-MD5* or *EAP-MSCHAPv2*.
10.1.0.0/16 -- | 192.168.0.1 | === | x.x.x.x |
moon-net moon carol
Configuration on gateway _moon_:
/etc/swanctl/x509ca/strongswanCert.pem
/etc/swanctl/x509/moonCert.pem
/etc/swanctl/private/moonKey.pem
/etc/swanctl/swanctl.conf:
connections {
rw {
local {
auth = pubkey
certs = moonCert.pem
id = moon.strongswan.org
}
remote {
auth = eap-md5
}
children {
net-net {
local_ts = 10.1.0.0/16
}
}
send_certreq = no
}
}
The `swanctl.conf` file additionally contains a `secrets` section defining all
client credentials
secrets {
eap-carol {
id = carol@strongswan.org
secret = Ar3etTnp
}
eap-dave {
id = dave@strongswan.org
secret = W7R0g3do
}
}
Configuration on roadwarrior _carol_:
/etc/swanctl/x509ca/strongswanCert.pem
/etc/swanctl/swanctl.conf:
connections {
home {
remote_addrs = moon.strongswan.org
local {
auth = eap
id = carol@strongswan.org
}
remote {
auth = pubkey
id = moon.strongswan.org
}
children {
home {
local_ts = 10.1.0.0/16
start_action = start
}
}
}
}
secrets {
eap-carol {
id = carol@strongswan.org
secret = Ar3etTnp
}
}
### Roadwarrior Case with EAP Identity ###
Often a client EAP identity is exchanged via EAP which differs from the
external IKEv2 identity. In this example the IKEv2 identity defaults to
the IPv4 address of the client.
10.1.0.0/16 -- | 192.168.0.1 | === | x.x.x.x |
moon-net moon carol
Configuration on gateway _moon_:
/etc/swanctl/x509ca/strongswanCert.pem
/etc/swanctl/x509/moonCert.pem
/etc/swanctl/private/moonKey.pem
/etc/swanctl/swanctl.conf:
connections {
rw {
local {
auth = pubkey
certs = moonCert.pem
id = moon.strongswan.org
}
remote {
auth = eap-md5
eap_id = %any
}
children {
net-net {
local_ts = 10.1.0.0/16
}
}
send_certreq = no
}
}
secrets {
eap-carol {
id = carol
secret = Ar3etTnp
}
eap-dave {
id = dave
secret = W7R0g3do
}
}
Configuration on roadwarrior _carol_:
/etc/swanctl/x509ca/strongswanCert.pem
/etc/swanctl/swanctl.conf:
connections {
home {
remote_addrs = moon.strongswan.org
local {
auth = eap
eap_id = carol
}
remote {
auth = pubkey
id = moon.strongswan.org
}
children {
home {
local_ts = 10.1.0.0/16
start_action = start
}
}
}
}
secrets {
eap-carol {
id = carol
secret = Ar3etTnp
}
}
## Generating Certificates and CRLs ##
This section is not a full-blown tutorial on how to use the strongSwan **pki**
tool. It just lists a few points that are relevant if you want to generate your
own certificates and CRLs for use with strongSwan.
### Generating a CA Certificate ###
The pki statement
pki --gen --type ed25519 --outform pem > strongswanKey.pem
generates an elliptic Edwards-Curve key with a cryptographic strength of 128
bits. The corresponding public key is packed into a self-signed CA certificate
with a lifetime of 10 years (3652 days)
pki --self --ca --lifetime 3652 --in strongswanKey.pem \
--dn "C=CH, O=strongSwan, CN=strongSwan Root CA" \
--outform pem > strongswanCert.pem
which can be listed with the command
pki --print --in strongswanCert.pem
subject: "C=CH, O=strongSwan, CN=strongSwan Root CA"
issuer: "C=CH, O=strongSwan, CN=strongSwan Root CA"
validity: not before May 18 08:32:06 2017, ok
not after May 18 08:32:06 2027, ok (expires in 3651 days)
serial: 57:e0:6b:3a:9a:eb:c6:e0
flags: CA CRLSign self-signed
subjkeyId: 2b:95:14:5b:c3:22:87:de:d1:42:91:88:63:b3:d5:c1:92:7a:0f:5d
pubkey: ED25519 256 bits
keyid: a7:e1:6a:3f:e7:6f:08:9d:89:ec:23:92:a9:a1:14:3c:78:a8:7a:f7
subjkey: 2b:95:14:5b:c3:22:87:de:d1:42:91:88:63:b3:d5:c1:92:7a:0f:5d
If you prefer the CA private key and X.509 certificate to be in binary DER format
then just omit the `--outform pem` option. The directory `/etc/swanctl/x509ca`
contains all required CA certificates either in binary DER or in Base64 PEM
format. Irrespective of the file suffix the correct format will be determined
by strongSwan automagically.
### Generating a Host or User End Entity Certificate ###
Again we are using the command
pki --gen --type ed25519 --outform pem > moonKey.pem
to generate an Ed25519 private key for the host `moon`. Alternatively you could
type
pki --gen --type rsa --size 3072 > moonKey.der
to generate a traditional 3072 bit RSA key and store it in binary DER format.
As an alternative a **TPM 2.0** *Trusted Platform Module* available on every
recent Intel platform could be used as a virtual smartcard to securely store an
RSA or ECDSA private key. For details, refer to the TPM 2.0
[HOWTO](https://wiki.strongswan.org/projects/strongswan/wiki/TpmPlugin).
In a next step the command
pki --req --type priv --in moonKey.pem \
--dn "C=CH, O=strongswan, CN=moon.strongswan.org" \
--san moon.strongswan.org --outform pem > moonReq.pem
creates a PKCS#10 certificate request that has to be signed by the CA.
Through the [multiple] use of the `--san` parameter any number of desired
*subjectAlternativeNames* can be added to the request. These can be of the
form
--san sun.strongswan.org # fully qualified host name
--san carol@strongswan.org # RFC822 user email address
--san 192.168.0.1 # IPv4 address
--san fec0::1 # IPv6 address
Based on the certificate request the CA issues a signed end entity certificate
with the following command
pki --issue --cacert strongswanCert.pem --cakey strongswanKey.pem \
--type pkcs10 --in moonReq.pem --serial 01 --lifetime 1826 \
--outform pem > moonCert.pem
If the `--serial` parameter with a hexadecimal argument is omitted then a random
serial number is generated. Some third party VPN clients require that a VPN
gateway certificate contains the *TLS Server Authentication* Extended Key Usage
(EKU) flag which can be included with the following option
--flag serverAuth
If you want to use the dynamic CRL fetching feature described in one of the
following sections then you may include one or several *crlDistributionPoints*
in your end entity certificates using the `--crl` parameter
--crl http://crl.strongswan.org/strongswan.crl
--crl "ldap://ldap.strongswan.org/cn=strongSwan Root CA, o=strongSwan,c=CH?certificateRevocationList"
The issued host certificate can be listed with
pki --print --in moonCert.pem
subject: "C=CH, O=strongSwan, CN=moon.strongswan.org"
issuer: "C=CH, O=strongSwan, CN=strongSwan Root CA"
validity: not before May 19 10:28:19 2017, ok
not after May 19 10:28:19 2022, ok (expires in 1825 days)
serial: 01
altNames: moon.strongswan.org
flags: serverAuth
CRL URIs: http://crl.strongswan.org/strongswan.crl
authkeyId: 2b:95:14:5b:c3:22:87:de:d1:42:91:88:63:b3:d5:c1:92:7a:0f:5d
subjkeyId: 60:9d:de:30:a6:ca:b9:8e:87:bb:33:23:61:19:18:b8:c4:7e:23:8f
pubkey: ED25519 256 bits
keyid: 39:1b:b3:c2:34:72:1a:01:08:40:ce:97:75:b8:be:ce:24:30:26:29
subjkey: 60:9d:de:30:a6:ca:b9:8e:87:bb:33:23:61:19:18:b8:c4:7e:23:8f
Usually, a Windows, OSX, Android or iOS based VPN client needs its private key,
its host or user certificate and the CA certificate. The most convenient way
to load this information is to put everything into a PKCS#12 container:
openssl pkcs12 -export -inkey carolKey.pem \
-in carolCert.pem -name "carol" \
-certfile strongswanCert.pem -caname "strongSwan Root CA" \
-out carolCert.p12
The strongSwan **pki** tool currently is not able to create PKCS#12 containers
so that **openssl** must be used.
### Generating a CRL ###
An empty CRL that is signed by the CA can be generated with the command
pki --signcrl --cacert strongswanCert.pem --cakey strongswanKey.pem \
--lifetime 30 > strongswan.crl
If you omit the `--lifetime` option then the default value of 15 days is used.
CRLs can either be uploaded to a HTTP or LDAP server or put in binary DER or
Base64 PEM format into the `/etc/swanctl/x509crl` directory from where they are
loaded into the **charon** daemon with the command
swanctl --load-creds
### Revoking a Certificate ###
A specific end entity certificate is revoked with the command
pki --signcrl --cacert strongswanCert.pem --cakey strongswanKey.pem \
--lifetime 30 --lastcrl strongswan.crl \
--reason key-compromise --cert moonCert.pem > new.crl
Instead of the certificate file (in our example moonCert.pem), the serial number
of the certificate to be revoked can be indicated using the `--serial`
parameter. The `pki --signcrl --help` command documents all possible revocation
reasons but the `--reason` parameter can also be omitted. The content of the new
CRL file can be listed with the command
pki --print --type crl --in new.crl
issuer: "C=CH, O=strongSwan, CN=strongSwan Root CA"
update: this on May 19 11:13:01 2017, ok
next on Jun 18 11:13:01 2017, ok (expires in 29 days)
serial: 02
authKeyId: 2b:95:14:5b:c3:22:87:de:d1:42:91:88:63:b3:d5:c1:92:7a:0f:5d
1 revoked certificate:
01: May 19 11:13:01 2017, key compromise
### Local Caching of CRLs ###
The `strongswan.conf` option
charon {
cache_crls = yes
}
activates the local caching of CRLs that were dynamically fetched from an
HTTP or LDAP server. Cached copies are stored in `/etc/swanctl/x509crl` using a
unique filename formed from the issuer's *subjectKeyIdentifier* and the
suffix `.crl`.
With the cached copy the CRL is immediately available after startup. When the
local copy has become stale, an updated CRL is automatically fetched from one of
the defined CRL distribution points during the next IKEv2 authentication.
The ePDG must use the address 192.168.0.2

View File

@ -212,6 +212,7 @@ ARG_ENABL_SET([eap-peap], [enable EAP PEAP authentication module.])
ARG_ENABL_SET([eap-tnc], [enable EAP TNC trusted network connect module.])
ARG_ENABL_SET([eap-dynamic], [enable dynamic EAP proxy module.])
ARG_ENABL_SET([eap-radius], [enable RADIUS proxy authentication module.])
ARG_ENABL_SET([osmo-epdg], [enable osmo-epdg plugin.])
ARG_ENABL_SET([ext-auth], [enable plugin calling an external authorization script.])
ARG_ENABL_SET([ipseckey], [enable IPSECKEY authentication plugin.])
ARG_ENABL_SET([keychain], [enables OS X Keychain Services credential set.])
@ -431,6 +432,11 @@ if test x$eap_aka_3gpp2 = xtrue; then
gmp=true;
fi
if test x$osmo_epdg = xtrue; then
simaka=true;
eap_aka=true;
fi
if test x$eap_aka = xtrue; then
fips_prf=true;
simaka=true;
@ -1513,6 +1519,7 @@ ADD_PLUGIN([kernel-pfkey], [c charon starter nm cmd])
ADD_PLUGIN([kernel-pfroute], [c charon starter nm cmd])
ADD_PLUGIN([kernel-netlink], [c charon starter nm cmd])
ADD_PLUGIN([resolve], [c charon cmd])
ADD_PLUGIN([osmo-epdg], [c charon])
ADD_PLUGIN([save-keys], [c])
ADD_PLUGIN([socket-default], [c charon nm cmd])
ADD_PLUGIN([socket-dynamic], [c charon cmd])
@ -1720,6 +1727,7 @@ AM_CONDITIONAL(USE_EAP_PEAP, test x$eap_peap = xtrue)
AM_CONDITIONAL(USE_EAP_TNC, test x$eap_tnc = xtrue)
AM_CONDITIONAL(USE_EAP_DYNAMIC, test x$eap_dynamic = xtrue)
AM_CONDITIONAL(USE_EAP_RADIUS, test x$eap_radius = xtrue)
AM_CONDITIONAL(USE_OSMO_EPDG, test x$osmo_epdg = xtrue)
AM_CONDITIONAL(USE_XAUTH_GENERIC, test x$xauth_generic = xtrue)
AM_CONDITIONAL(USE_XAUTH_EAP, test x$xauth_eap = xtrue)
AM_CONDITIONAL(USE_XAUTH_PAM, test x$xauth_pam = xtrue)
@ -2005,6 +2013,7 @@ AC_CONFIG_FILES([
src/libcharon/plugins/eap_peap/Makefile
src/libcharon/plugins/eap_tnc/Makefile
src/libcharon/plugins/eap_radius/Makefile
src/libcharon/plugins/osmo_epdg/Makefile
src/libcharon/plugins/xauth_generic/Makefile
src/libcharon/plugins/xauth_eap/Makefile
src/libcharon/plugins/xauth_pam/Makefile

View File

@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIG8/fPyaM21uZp+R2ykAKpC2/yviDXL7MzGJzoZtVc6r
-----END PRIVATE KEY-----

View File

@ -0,0 +1,36 @@
connections {
rw {
local_addrs = 192.168.0.2
pools = rw_pool
local {
auth = pubkey
certs = epdgCert.pem
id = epdg
}
remote {
auth = eap-aka
}
children {
net {
local_ts = 172.16.24.0/24
updown = /usr/lib/ipsec/_updown iptables
esp_proposals = default
}
}
version = 2
# proposals = null-md5-prfmd5-null-ecp192
# proposals = AES_CBC_128-HMAC_SHA1_96-PRF_HMAC_SHA1-MODP_2048
}
}
secrets {
}
pools {
rw_pool {
addrs = 172.20.0.0/24
}
}

View File

@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE-----
MIIBWTCCAQugAwIBAgIBATAFBgMrZXAwPzELMAkGA1UEBhMCQ0gxEzARBgNVBAoT
CnN0cm9uZ1N3YW4xGzAZBgNVBAMTEnN0cm9uZ1N3YW4gUm9vdCBDQTAeFw0yMzAx
MjUwMjI0MzBaFw0yODAxMjUwMjI0MzBaMDExCzAJBgNVBAYTAkNIMRMwEQYDVQQK
EwpzdHJvbmdzd2FuMQ0wCwYDVQQDEwRlcGRnMCowBQYDK2VwAyEACh6O6jF06dqu
GBo3TGPm0kpqTxwBWKhLtqptpjnLIuyjOjA4MB8GA1UdIwQYMBaAFJVHEuUMeHiS
I+xjtCGE2ha+u8zlMBUGA1UdEQQOMAyHBMCoAAKCBGVwZGcwBQYDK2VwA0EAk+75
9+1Vvvrq7d2KmBF0rJ7yT0+kEOn9bArUvXFTkswCGxbvknLPKuJ+80nK2JDRhBi5
fBlIuS1GJHlSntX7Dw==
-----END CERTIFICATE-----

View File

@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE-----
MIIBdjCCASigAwIBAgIIBLX5AduBn1YwBQYDK2VwMD8xCzAJBgNVBAYTAkNIMRMw
EQYDVQQKEwpzdHJvbmdTd2FuMRswGQYDVQQDExJzdHJvbmdTd2FuIFJvb3QgQ0Ew
HhcNMjMwMTI1MDIyMTM2WhcNMzMwMTI0MDIyMTM2WjA/MQswCQYDVQQGEwJDSDET
MBEGA1UEChMKc3Ryb25nU3dhbjEbMBkGA1UEAxMSc3Ryb25nU3dhbiBSb290IENB
MCowBQYDK2VwAyEAC/rxt1BbzUYNLSvpMGAWXlYowpdfq16HvI45Nno4DRejQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSVRxLl
DHh4kiPsY7QhhNoWvrvM5TAFBgMrZXADQQDk6neTZfsA1PJKejYt2oW3dsZJmYwS
Yez/nEA8Jvyzmu4aQ0q6VahRXRSqESbTh5Z2wyVLEfsW9CyMUI/w8eEI
-----END CERTIFICATE-----

View File

@ -0,0 +1,34 @@
connections {
home {
local_addrs = 192.168.0.1
remote_addrs = 192.168.0.2
vips = 0.0.0.0
local {
auth = eap-aka
id = 0999421234567890@wlan.mnc999.mcc42.3gppnetwork.org
}
remote {
auth = pubkey
id = epdg
}
children {
home {
remote_ts = 172.16.24.0/24
updown = /usr/lib/ipsec/_updown iptables
# esp_proposals = aes128gcm128-x25519
}
}
version = 2
# proposals = null-md5-prfmd5-null-ecp192
}
}
secrets {
eap {
id = 0999421234567890@wlan.mnc999.mcc42.3gppnetwork.org
# 16 bytes key 0xff, 16 byte opc 0xee
secret = 0x1111111111111111111111111111111122222222222222222222222222222222
}
}

View File

@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE-----
MIIBdjCCASigAwIBAgIIBLX5AduBn1YwBQYDK2VwMD8xCzAJBgNVBAYTAkNIMRMw
EQYDVQQKEwpzdHJvbmdTd2FuMRswGQYDVQQDExJzdHJvbmdTd2FuIFJvb3QgQ0Ew
HhcNMjMwMTI1MDIyMTM2WhcNMzMwMTI0MDIyMTM2WjA/MQswCQYDVQQGEwJDSDET
MBEGA1UEChMKc3Ryb25nU3dhbjEbMBkGA1UEAxMSc3Ryb25nU3dhbiBSb290IENB
MCowBQYDK2VwAyEAC/rxt1BbzUYNLSvpMGAWXlYowpdfq16HvI45Nno4DRejQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSVRxLl
DHh4kiPsY7QhhNoWvrvM5TAFBgMrZXADQQDk6neTZfsA1PJKejYt2oW3dsZJmYwS
Yez/nEA8Jvyzmu4aQ0q6VahRXRSqESbTh5Z2wyVLEfsW9CyMUI/w8eEI
-----END CERTIFICATE-----

View File

@ -467,6 +467,13 @@ if MONOLITHIC
endif
endif
if USE_OSMO_EPDG
SUBDIRS += plugins/osmo_epdg
if MONOLITHIC
libcharon_la_LIBADD += plugins/osmo_epdg/libstrongswan-osmo-epdg.la
endif
endif
if USE_TLS
if MONOLITHIC
# otherwise this library is linked to eap_tls

View File

@ -949,6 +949,46 @@ METHOD(bus_t, ike_reestablish_post, void,
this->mutex->unlock(this->mutex);
}
METHOD(bus_t, eap_authorize, bool,
private_bus_t *this, identification_t *id, bool final)
{
enumerator_t *enumerator;
ike_sa_t *ike_sa;
entry_t *entry;
bool keep, success = TRUE;
ike_sa = this->thread_sa->get(this->thread_sa);
this->mutex->lock(this->mutex);
enumerator = this->listeners->create_enumerator(this->listeners);
while (enumerator->enumerate(enumerator, &entry))
{
if (entry->calling || !entry->listener->eap_authorize)
{
continue;
}
entry->calling++;
keep = entry->listener->eap_authorize(entry->listener, ike_sa, id,
final, &success);
entry->calling--;
if (!keep)
{
unregister_listener(this, entry, enumerator);
}
if (!success)
{
break;
}
}
enumerator->destroy(enumerator);
this->mutex->unlock(this->mutex);
if (!success)
{
alert(this, ALERT_EAP_AUTHORIZATION_FAILED);
}
return success;
}
METHOD(bus_t, authorize, bool,
private_bus_t *this, bool final)
{
@ -1154,6 +1194,7 @@ bus_t *bus_create()
.child_rekey = _child_rekey,
.children_migrate = _children_migrate,
.authorize = _authorize,
.eap_authorize = _eap_authorize,
.narrow = _narrow,
.assign_vips = _assign_vips,
.handle_vips = _handle_vips,

View File

@ -157,6 +157,8 @@ enum alert_t {
ALERT_CERT_EXCEEDED_PATH_LEN,
/** Certificate rejected; other policy violation, certificate_t */
ALERT_CERT_POLICY_VIOLATION,
/** an eap_authorize() hook failed, no argument */
ALERT_EAP_AUTHORIZATION_FAILED,
};
/**
@ -332,6 +334,14 @@ struct bus_t {
*/
bool (*authorize)(bus_t *this, bool final);
/**
* EAP authorization hook.
*
* @param final TRUE if this is the final invocation
* @return TRUE to establish IKE_SA, FALSE to send AUTH_FAILED
*/
bool (*eap_authorize)(bus_t *this, identification_t *id, bool final);
/**
* CHILD_SA traffic selector narrowing hook.
*

View File

@ -238,6 +238,21 @@ struct listener_t {
bool (*children_migrate)(listener_t *this, ike_sa_t *ike_sa,
ike_sa_id_t *new, uint32_t unique);
/**
* Hook called to invoke additional eap authorization rules.
*
* An eap authorization hook gets invoked only before sending the EAP_SUCCESS.
*
* @param ike_sa IKE_SA to authorize
* @param id EAP identification
* @param final TRUE if this is the final hook invocation
* @param success set to TRUE to complete IKE_SA, FALSE abort
* @return TRUE to stay registered, FALSE to unregister
*/
bool (*eap_authorize)(listener_t *this, ike_sa_t *ike_sa,
identification_t *id,
bool final, bool *success);
/**
* Hook called to invoke additional authorization rules.
*

View File

@ -116,6 +116,11 @@ struct private_eap_aka_server_t {
* Did the client send a synchronize request?
*/
bool synchronized;
/**
* Auth data contains porperty on EAP_SUCCESS.
*/
auth_cfg_t *auth;
};
/**
@ -430,6 +435,8 @@ static status_t process_challenge(private_eap_aka_server_t *this,
DBG1(DBG_IKE, "received RES does not match XRES");
return FAILED;
}
this->auth->add(this->auth, AUTH_RULE_EAP_IDENTITY, this->permanent->clone(this->permanent));
return SUCCESS;
}
@ -643,6 +650,12 @@ METHOD(eap_method_t, get_type, eap_type_t,
return EAP_AKA;
}
METHOD(eap_method_t, get_auth, auth_cfg_t*,
private_eap_aka_server_t *this)
{
return this->auth;
}
METHOD(eap_method_t, get_msk, status_t,
private_eap_aka_server_t *this, chunk_t *msk)
{
@ -677,6 +690,7 @@ METHOD(eap_method_t, destroy, void,
{
this->crypto->destroy(this->crypto);
this->permanent->destroy(this->permanent);
this->auth->destroy(this->auth);
DESTROY_IF(this->pseudonym);
DESTROY_IF(this->reauth);
free(this->xres.ptr);
@ -703,12 +717,14 @@ eap_aka_server_t *eap_aka_server_create(identification_t *server,
.get_type = _get_type,
.is_mutual = _is_mutual,
.get_msk = _get_msk,
.get_auth = _get_auth,
.get_identifier = _get_identifier,
.set_identifier = _set_identifier,
.destroy = _destroy,
},
},
.crypto = simaka_crypto_create(EAP_AKA),
.auth = auth_cfg_create(),
.mgr = lib->get(lib, "aka-manager"),
);

View File

@ -0,0 +1,26 @@
AM_CPPFLAGS = \
-I$(top_srcdir)/src/libstrongswan \
-I$(top_srcdir)/src/libcharon \
-I$(top_srcdir)/src/libsimaka
AM_CFLAGS = \
$(PLUGIN_CFLAGS)
libstrongswan_osmo_epdg_la_LDFLAGS = -module -avoid-version
libstrongswan_osmo_epdg_la_LIBADD = -lgmp -losmogsm
if MONOLITHIC
noinst_LTLIBRARIES = libstrongswan-osmo-epdg.la
else
plugin_LTLIBRARIES = libstrongswan-osmo-epdg.la
libstrongswan_osmo_epdg_la_LIBADD += $(top_builddir)/src/libsimaka/libsimaka.la
endif
libstrongswan_osmo_epdg_la_SOURCES = \
osmo_epdg_plugin.h osmo_epdg_plugin.c \
osmo_epdg_provider.h osmo_epdg_provider.c \
osmo_epdg_listener.h osmo_epdg_listener.c \
gsup_client.h gsup_client.c \
ipa_client.h ipa_client.c \
osmo_epdg_utils.h osmo_epdg_utils.c \
osmo_epdg_db.h osmo_epdg_db.c

View File

@ -0,0 +1,558 @@
/*
* 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.
*
*/
/* TODO: check license */
#include <collections/enumerator.h>
#include <collections/linked_list.h>
#include <collections/blocking_queue.h>
#include <processing/jobs/callback_job.h>
#include <threading/mutex.h>
#include <threading/thread.h>
#include <threading/condvar.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/gsm/protocol/ipaccess.h>
#include <library.h>
#include "ipa_client.h"
#include "gsup_client.h"
#include "osmo_epdg_utils.h"
typedef struct gsup_request_t gsup_request_t;
struct gsup_request_t {
/**
* Mutex used to synchronize access to the condvar
*/
mutex_t *mutex;
/**
* Condvar used to wait for a response
*/
condvar_t *condvar;
struct msgb *msg;
enum osmo_gsup_message_type msg_type;
osmo_epdg_gsup_response_t *resp;
};
typedef struct private_osmo_epdg_gsup_client_t private_osmo_epdg_gsup_client_t;
struct private_osmo_epdg_gsup_client_t {
/**
* Public osmo_epdg_gsup_client_t
*/
osmo_epdg_gsup_client_t public;
osmo_epdg_ipa_client_t *ipa;
/**
* List of all pending requests
*/
blocking_queue_t *pending;
/**
* Current request which isn't part of linked list
*/
gsup_request_t *current_request;
/**
* Mutex to protect current_request
*/
mutex_t *mutex;
char *uri;
stream_t *stream;
};
static gsup_request_t *gsup_request_create(enum osmo_gsup_message_type msg_type, struct msgb *msg)
{
gsup_request_t *req = calloc(1, sizeof(gsup_request_t));
if (!req)
{
return NULL;
}
req->mutex = mutex_create(MUTEX_TYPE_DEFAULT);
req->condvar = condvar_create(CONDVAR_TYPE_DEFAULT);
req->msg_type = msg_type;
req->msg = msg;
return req;
}
static void gsup_request_destroy(private_osmo_epdg_gsup_client_t *this, gsup_request_t *req)
{
if (!req)
{
return;
}
if (req->mutex)
{
req->mutex->destroy(req->mutex);
}
if (req->condvar)
{
req->condvar->destroy(req->condvar);
}
if (req->msg)
{
free(req->msg);
}
if (req->resp)
{
free(req->resp);
}
free(req);
}
static struct msgb *encode_to_msgb(struct osmo_gsup_message *gsup_msg)
{
chunk_t msg_chunk;
struct msgb *msg;
int ret;
msg_chunk = chunk_alloc(4000);
if (msg_chunk.ptr == NULL)
{
return NULL;
}
msg = chunk_to_msgb(&msg_chunk);
if (!msg)
{
goto free_msg;
}
/* reserve headroom */
msgb_reserve(msg, 64);
ret = osmo_gsup_encode(msg, gsup_msg);
if (ret)
{
DBG1(DBG_NET, "GSUP: couldn't encode gsup message %d.", ret);
goto free_msg;
}
return msg;
free_msg:
chunk_free(&msg_chunk);
return NULL;
}
/**
* enqueue a message/request to be send out and wait for the response.
*
* when exiting enqueue, it must be guaranteed the req isn't referenced by anything
* @param timeout_ms A timeout in ms
* @return TRUE is the request timed out.
*/
static bool enqueue(private_osmo_epdg_gsup_client_t *this, gsup_request_t *req, u_int timeout_ms)
{
bool ret = FALSE;
DBG1(DBG_NET, "Enqueuing message. Waiting %d ms for an answer", timeout_ms);
req->mutex->lock(req->mutex);
this->pending->enqueue(this->pending, req);
ret = req->condvar->timed_wait(req->condvar, req->mutex, timeout_ms);
if (ret)
{
void *found = this->pending->remove(this->pending, req);
if (!found)
{
this->mutex->lock(this->mutex);
if (this->current_request == req)
{
this->current_request = NULL;
}
this->mutex->unlock(this->mutex);
}
DBG1(DBG_NET, "Message timedout!");
}
return ret;
}
METHOD(osmo_epdg_gsup_client_t, tunnel_request, osmo_epdg_gsup_response_t*,
private_osmo_epdg_gsup_client_t *this, char *imsi, char *apn)
{
struct osmo_gsup_message gsup_msg = {0};
struct osmo_gsup_pdp_info *pdp;
struct msgb *msg;
bool timedout;
DBG1(DBG_NET, "Tunnel Request Request for %s", imsi);
gsup_msg.message_type = OSMO_GSUP_MSGT_EPDG_TUNNEL_REQUEST;
gsup_msg.current_rat_type = OSMO_RAT_EUTRAN_SGS;
if (!imsi || strlen(imsi) == 0)
{
/* TODO: inval imsi! */
return NULL;
}
strncpy(gsup_msg.imsi, imsi, sizeof(gsup_msg.imsi))
if (apn && strlen(apn) > 0)
{
gsup_msg->num_pdp_infos = 1;
pdp = gsup_msg.pdp_infos[0];
pdp->context_id = 1;
pdp->have_info = 1;
pdp->apn_enc = apn;
pdp->apn_enc_len = strlen(apn);
}
msg = encode_to_msgb(&gsup_msg);
if (!msg)
{
DBG1(DBG_NET, "Couldn't alloc/encode gsup message.");
return NULL;
}
gsup_request_t *req = gsup_request_create(OSMO_GSUP_MSGT_EPDG_TUNNEL_REQUEST, msg);
osmo_epdg_gsup_response_t *resp = NULL;
timedout = enqueue(this, req, 5000);
if (timedout)
{
gsup_request_destroy(this, req);
return NULL;
}
resp = req->resp;
req->resp = NULL;
gsup_request_destroy(this, req);
return ret;
}
METHOD(osmo_epdg_gsup_client_t, send_auth_request, osmo_epdg_gsup_response_t*,
private_osmo_epdg_gsup_client_t *this, char *imsi, uint8_t cn_domain, chunk_t *auts, chunk_t *auts_rand)
{
struct osmo_gsup_message gsup_msg = {0};
struct msgb *msg;
bool timedout;
DBG1(DBG_NET, "Send Auth Request for %s", imsi);
gsup_msg.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST;
gsup_msg.num_auth_vectors = 1;
gsup_msg.current_rat_type = OSMO_RAT_EUTRAN_SGS;
if (!imsi || strlen(imsi) == 0)
{
/* TODO: inval imsi! */
return NULL;
}
strncpy(gsup_msg.imsi, imsi, sizeof(gsup_msg.imsi));
switch (cn_domain)
{
case 0:
/* empty cn_domain */
break;
case OSMO_GSUP_CN_DOMAIN_PS:
case OSMO_GSUP_CN_DOMAIN_CS:
gsup_msg.cn_domain = cn_domain;
break;
default:
DBG1(DBG_NET, "GSUP: SAIR: Ignoring invalid cn_domain message.");
break;
}
if (auts && auts->ptr && auts->len != 0)
{
if (auts->len != 14)
{
/* TODO: inval auts */
return NULL;
}
gsup_msg.auts = auts->ptr;
}
/* TODO check for other sizes */
if (auts_rand && auts_rand->ptr && auts_rand->len != 0)
{
if (auts_rand->len != 16)
{
/* TODO: inval auts */
return NULL;
}
gsup_msg.rand = auts_rand->ptr;
}
msg = encode_to_msgb(&gsup_msg);
if (!msg)
{
DBG1(DBG_NET, "Couldn't alloc/encode gsup message.");
return NULL;
}
gsup_request_t *req = gsup_request_create(OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST, msg);
osmo_epdg_gsup_response_t *resp = NULL;
timedout = enqueue(this, req, 5000);
if (timedout)
{
gsup_request_destroy(this, req);
return NULL;
}
resp = req->resp;
req->resp = NULL;
gsup_request_destroy(this, req);
return resp;
}
METHOD(osmo_epdg_gsup_client_t, update_location, osmo_epdg_gsup_response_t *,
private_osmo_epdg_gsup_client_t *this, char *imsi, uint8_t cn_domain)
{
struct osmo_gsup_message gsup_msg = {0};
struct msgb *msg;
bool timedout;
gsup_msg.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST;
gsup_msg.current_rat_type = OSMO_RAT_EUTRAN_SGS;
if (!imsi || strlen(imsi) == 0)
{
DBG1(DBG_NET, "GSUP: ULR: Invalid IMSI!");
return NULL;
}
strncpy(gsup_msg.imsi, imsi, sizeof(gsup_msg.imsi));
switch (cn_domain)
{
case 0:
/* empty cn_domain */
break;
case OSMO_GSUP_CN_DOMAIN_PS:
case OSMO_GSUP_CN_DOMAIN_CS:
gsup_msg.cn_domain = cn_domain;
break;
default:
DBG1(DBG_NET, "GSUP: ULR: Ignoring invalid cn_domain message.");
break;
}
msg = encode_to_msgb(&gsup_msg);
if (!msg)
{
DBG1(DBG_NET, "GSUP: ULR: Couldn't alloc/encode gsup message.");
return NULL;
}
gsup_request_t *req = gsup_request_create(OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST, msg);
osmo_epdg_gsup_response_t *resp = NULL;
timedout = enqueue(this, req, 5000);
if (timedout)
{
gsup_request_destroy(this, req);
return NULL;
}
resp = req->resp;
req->resp = NULL;
gsup_request_destroy(this, req);
return resp;
}
METHOD(osmo_epdg_gsup_client_t, destroy, void,
private_osmo_epdg_gsup_client_t *this)
{
free(this->uri);
free(this);
}
void tx_insert_data_result(private_osmo_epdg_gsup_client_t *this, char *imsi, uint8_t cn_domain)
{
struct osmo_gsup_message gsup_msg = {0};
struct msgb *msg;
gsup_msg.message_type = OSMO_GSUP_MSGT_INSERT_DATA_RESULT;
if (!imsi || strlen(imsi) == 0)
{
DBG1(DBG_NET, "GSUP: ULR: Invalid IMSI!");
}
strncpy(gsup_msg.imsi, imsi, sizeof(gsup_msg.imsi));
switch (cn_domain)
{
case 0:
/* empty cn_domain */
break;
case OSMO_GSUP_CN_DOMAIN_PS:
case OSMO_GSUP_CN_DOMAIN_CS:
gsup_msg.cn_domain = cn_domain;
break;
default:
DBG1(DBG_NET, "GSUP: ULR: Ignoring invalid cn_domain message.");
break;
}
msg = encode_to_msgb(&gsup_msg);
if (!msg)
{
DBG1(DBG_NET, "GSUP: ULR: Couldn't alloc/encode gsup message.");
}
this->ipa->send(this->ipa, IPAC_PROTO_EXT_GSUP, msg);
}
static void signal_request(gsup_request_t *req, osmo_epdg_gsup_response_t *resp)
{
req->mutex->lock(req->mutex);
req->resp = resp;
req->condvar->signal(req->condvar);
req->mutex->unlock(req->mutex);
}
static bool on_recv_pdu(void *data, osmo_epdg_ipa_client_t *client, struct msgb *pdu)
{
private_osmo_epdg_gsup_client_t *this = data;
osmo_epdg_gsup_response_t *resp;
int ret;
resp = calloc(1, sizeof(*resp));
if (!resp)
{
return TRUE;
}
ret = osmo_gsup_decode(msgb_l2(pdu), msgb_l2len(pdu), &resp->gsup);
if (ret) {
// TODO: failed to decode response. Close connection
goto out;
}
switch (resp->gsup.message_type)
{
case OSMO_GSUP_MSGT_INSERT_DATA_REQUEST:
tx_insert_data_result(this, resp->gsup.imsi, resp->gsup.cn_domain);
goto out;
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR:
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT:
case OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR:
case OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT:
this->mutex->lock(this->mutex);
if ((this->current_request->msg_type & 0xfffffffc) != (resp->gsup.message_type & 0xfffffffc))
{
/* Request, Result, Error, Other are encoded in the last 2 bits */
DBG1(DBG_NET, "GSUP: received non matching Result. Requested %s but received %s",
osmo_gsup_message_type_name(this->current_request->msg_type),
osmo_gsup_message_type_name(resp->gsup.message_type));
goto out;
}
if (!this->current_request)
{
DBG2(DBG_NET, "GSUP: received response when no request waiting %02x. This might came too late.", resp->gsup.message_type);
this->mutex->unlock(this->mutex);
goto out;
}
signal_request(this->current_request, resp);
this->current_request = NULL;
this->mutex->unlock(this->mutex);
break;
default:
DBG1(DBG_NET, "GSUP received unknown message type %02x", resp->gsup.message_type);
goto out;
}
free(pdu);
return TRUE;
out:
free(resp);
free(pdu);
return TRUE;
}
static int disconnect_gsup(private_osmo_epdg_gsup_client_t *this)
{
this->ipa->disconnect(this->ipa);
return 0;
}
/* TODO: worker thread which sends out enqueue'd message ! */
static job_requeue_t queue_worker(private_osmo_epdg_gsup_client_t *this)
{
int ret;
gsup_request_t *req;
this->mutex->lock(this->mutex);
if (this->current_request)
{
/* TODO: should we join the signal? */
this->mutex->unlock(this->mutex);
return JOB_REQUEUE_FAIR;
}
this->mutex->unlock(this->mutex);
/* TODO: replace pending with a thread safe queue, but non-blocking */
req = this->pending->dequeue(this->pending);
this->mutex->lock(this->mutex);
if (this->current_request)
{
/* TODO: how could this happen? */
this->mutex->unlock(this->mutex);
signal_request(req, NULL);
return JOB_REQUEUE_FAIR;
}
this->current_request = req;
this->mutex->unlock(this->mutex);
ret = this->ipa->send(this->ipa, IPAC_PROTO_EXT_GSUP, req->msg);
req->msg = NULL;
if (ret < 0)
{
/* TODO: disconnect & reconnect, but request is lost for now */
/* TODO: wake up request */
signal_request(req, NULL);
}
return JOB_REQUEUE_FAIR;
}
osmo_epdg_gsup_client_t *osmo_epdg_gsup_client_create(char *uri)
{
private_osmo_epdg_gsup_client_t *this;
DBG1(DBG_NET, "Starting osmo-epdg");
INIT(this,
.public = {
.send_auth_request = _send_auth_request,
.update_location = _update_location,
.tunnel_request = _tunnel_request,
.destroy = _destroy,
},
.uri = strdup(uri),
.pending = blocking_queue_create(),
.current_request = NULL,
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
.ipa = osmo_epdg_ipa_client_create(uri),
);
this->ipa->on_recv(this->ipa, IPAC_PROTO_EXT_GSUP, on_recv_pdu, this);
/* I would more like to have either an internal event which unblocks the queue. */
/* src/libipsec/ipsec_event_relay.c */
lib->processor->queue_job(lib->processor,
(job_t*)callback_job_create_with_prio((callback_job_cb_t)queue_worker,
this, NULL, (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL));
return &this->public;
}

View File

@ -0,0 +1,85 @@
/*
* 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.
*
*/
// TODO: check license
/**
* @defgroup osmo_epdg_gsup_client osmo_epdg_gsup_client
* @{ @ingroup eap_simaka_sql
*/
#ifndef OSMO_EPDG_GSUP_CLIENT_H_
#define OSMO_EPDG_GSUP_CLIENT_H_
#include <utils/chunk.h>
#include <osmocom/gsm/gsup.h>
struct osmo_epdg_gsup_response_t {
struct osmo_gsup_message gsup;
};
typedef struct osmo_epdg_gsup_response_t osmo_epdg_gsup_response_t;
typedef struct osmo_epdg_gsup_client_t osmo_epdg_gsup_client_t;
/**
* foo
*/
struct osmo_epdg_gsup_client_t {
/**
* Send Authentication Info Request
*
* @param imsi IMSI encoded as human-readable string (IMSI MAX = 15)
* @param auts (optional)
* @param auts_rand (optional)
* @return NULL or the osmo_epdg_gsup_response_t
*/
osmo_epdg_gsup_response_t *(*send_auth_request)(osmo_epdg_gsup_client_t *this,
char *imsi, uint8_t cn_domain, chunk_t *auts, chunk_t *auts_rand);
/**
* Update Location Request
*
* @return NULL or the osmo_gsup_message
*/
osmo_epdg_gsup_response_t *(*update_location)(osmo_epdg_gsup_client_t *this,
char *imsi,
uint8_t cn_domain);
/**
* Tunnel Request
*
* @return NULL or the osmo_gsup_message
*/
osmo_epdg_gsup_response_t *(*tunnel_request)(osmo_epdg_gsup_client_t *this,
char *imsi, char *apn);
/**
* Destroy a osmo_epdg_gsup_client_t.
*/
void (*destroy)(osmo_epdg_gsup_client_t *this);
};
/**
* Create a osmo_epdg_gsup_client instance.
*
* @param address the address of the gsup server */
osmo_epdg_gsup_client_t *osmo_epdg_gsup_client_create(char *addr);
#endif /** OSMO_EPDG_GSUP_CLIENT_H_ @}*/

View File

@ -0,0 +1,398 @@
/*
* 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.
*
*/
// TODO: check license
#include <collections/enumerator.h>
#include <collections/linked_list.h>
#include <collections/blocking_queue.h>
#include <threading/mutex.h>
#include <threading/thread.h>
#include <threading/condvar.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/protocol/ipaccess.h>
#include <osmocom/gsm/ipa.h>
#include <library.h>
#include <errno.h>
#include "ipa_client.h"
#include "osmo_epdg_utils.h"
typedef struct private_osmo_epdg_ipa_client_t private_osmo_epdg_ipa_client_t;
struct private_osmo_epdg_ipa_client_t {
/**
* Public osmo_epdg_ipa_client_t
*/
osmo_epdg_ipa_client_t public;
char *uri;
stream_t *stream;
ipa_cb_t osmo_cb;
void *osmo_cb_data;
};
METHOD(osmo_epdg_ipa_client_t, on_error, int,
private_osmo_epdg_ipa_client_t *this, uint8_t osmo_proto, ipa_cb_t cb, void *data)
{
/* TODO: on error ? */
return TRUE;
}
METHOD(osmo_epdg_ipa_client_t, on_recv, int,
private_osmo_epdg_ipa_client_t *this, uint8_t osmo_proto, ipa_cb_t cb, void *data)
{
/* TODO: protect it by mutex?! */
this->osmo_cb = NULL;
this->osmo_cb_data = data;
this->osmo_cb = cb;
return TRUE;
}
METHOD(osmo_epdg_ipa_client_t, destroy, void,
private_osmo_epdg_ipa_client_t *this)
{
free(this->uri);
free(this);
}
METHOD(osmo_epdg_ipa_client_t, disconnect, int,
private_osmo_epdg_ipa_client_t *this)
{
this->stream->destroy(this->stream);
this->stream = NULL;
return 0;
}
METHOD(osmo_epdg_ipa_client_t, send_pdu, ssize_t,
private_osmo_epdg_ipa_client_t *this, uint8_t osmo_proto, struct msgb *msg)
{
struct ipaccess_head *head;
int len;
head = (struct ipaccess_head *) msgb_push(msg, sizeof(struct ipaccess_head) + 1);
head->proto = IPAC_PROTO_OSMO;
head->len = htons(msgb_length(msg) - (sizeof(struct ipaccess_head)));
head->data[0] = osmo_proto;
len = msgb_length(msg);
if (!this->stream->write_all(this->stream, msgb_data(msg), msgb_length(msg)))
{
// TODO: write error
free(msg);
return -EINVAL;
}
free(msg);
return len;
}
static bool read_error(private_osmo_epdg_ipa_client_t *this, int read_err)
{
DBG1(DBG_NET, "IPA client failed to read with %d. Disconnecting", read_err);
this->public.disconnect(&this->public);
return FALSE;
}
/* send a simple IPA PDU with the base protocol */
static void ipa_pdu_base_send_simple(private_osmo_epdg_ipa_client_t *this, uint8_t msg_type)
{
struct ipaccess_head *head = calloc(1, sizeof(*head) + 1);
head->proto = IPAC_PROTO_IPACCESS;
head->len = htons(1);
head->data[0] = msg_type;
if (!this->stream->write_all(this->stream, head, sizeof(*head) + 1))
{
/* TODO: write error */
}
free(head);
return;
}
static inline void ipa_tag_put_str(struct msgb *resp, uint8_t tag, const char *value)
{
char *buf;
ssize_t len = strlen(value);
msgb_put_u16(resp, len + 1);
msgb_put_u8(resp, tag);
buf = msgb_put(resp, len);
memcpy(buf, value, len);
}
static void ipa_resp_tag_encode(private_osmo_epdg_ipa_client_t *this, struct msgb *resp, uint8_t tag)
{
switch (tag)
{
case IPAC_IDTAG_SERNR:
case IPAC_IDTAG_UNITNAME:
ipa_tag_put_str(resp, tag, "SWAN-00-00-00-00-00-00");
break;
case IPAC_IDTAG_LOCATION1:
case IPAC_IDTAG_LOCATION2:
case IPAC_IDTAG_EQUIPVERS:
case IPAC_IDTAG_SWVERSION:
ipa_tag_put_str(resp, tag, "00:00:00:00:00:00");
break;
case IPAC_IDTAG_UNIT:
ipa_tag_put_str(resp, tag, "0/0/0");
break;
}
}
static void protocol_error(private_osmo_epdg_ipa_client_t *this, char *error_msg)
{
/* TODO: protocol error */
DBG1(DBG_NET, error_msg);
return;
}
/* send a IPA PDU (base protocol) ID Response message */
static void ipa_pdu_base_send_id_resp(private_osmo_epdg_ipa_client_t *this, struct msgb *req)
{
struct msgb *resp;
struct ipaccess_head *resp_head;
chunk_t resp_pdu = chunk_alloc(IPA_ALLOC_SIZE);
if (!resp_pdu.ptr)
{
/* TODO: alloc err */
return;
}
resp = chunk_to_msgb(&resp_pdu);
/* remove the ipaccess header so we can use msg_pull on the message */
msgb_pull(req, sizeof(struct ipaccess_head));
if (msgb_length(req) < 1)
{
protocol_error(this, "Invalid IPA ID Request message.");
goto out;
}
/* prepare our response message */
msgb_reserve(resp, 128);
resp_head = (struct ipaccess_head *) msgb_put(resp, sizeof(struct ipaccess_head));
resp->l1h = (void *)resp_head;
resp->l2h = resp->tail;
resp_head->proto = IPAC_PROTO_IPACCESS;
msgb_put_u8(resp, IPAC_MSGT_ID_RESP);
/* remove IPA message type */
msgb_pull_u8(req);
/* ID Request contains: a list of [0x1, tag] */
while (msgb_length(req) >= 2)
{
uint8_t len = msgb_pull_u8(req);
if (len != 1)
{
if (msgb_length(req) < len)
{
protocol_error(this, "Invalid IPA ID Request message");
goto out;
}
/* ignoring this requested LValue */
DBG1(DBG_NET, "IPA ignoring IPA ID Request tag with size != 1");
msgb_pull(req, len);
continue;
}
uint8_t tag = msgb_pull_u8(req);
ipa_resp_tag_encode(this, resp, tag);
}
resp_head->len = htons(msgb_l2len(resp));
if (!this->stream->write_all(this->stream, msgb_l1(resp), msgb_l1len(resp)))
{
// TODO: write error
return;
}
ipa_pdu_base_send_simple(this, IPAC_MSGT_ID_ACK);
out:
chunk_free(&resp_pdu);
return;
}
static void on_recv_ipa_pdu(private_osmo_epdg_ipa_client_t *this, struct msgb *pdu)
{
if (msgb_length(pdu) < sizeof(struct ipaccess_head) + 1)
{
/* TODO: invalid package */
return;
}
struct ipaccess_head *head = (struct ipaccess_head *) msgb_data(pdu);
uint8_t msg_type = head->data[0];
switch (msg_type)
{
case IPAC_MSGT_PING:
ipa_pdu_base_send_simple(this, IPAC_MSGT_PONG);
break;
case IPAC_MSGT_PONG:
/* ignore. We don't implement an own PING/PONG timer */
break;
case IPAC_MSGT_ID_GET:
ipa_pdu_base_send_id_resp(this, pdu);
break;
case IPAC_MSGT_ID_ACK:
/* ignore. An ACK means everything the ID got accepted */
break;
default:
DBG1(DBG_NET, "IPA client Received an unknown IPA PDU %02x", msg_type);
break;
}
}
CALLBACK(on_stream_read, bool,
private_osmo_epdg_ipa_client_t *this, stream_t *stream)
{
uint16_t len;
ssize_t hlen;
chunk_t req_chunk;
struct ipaccess_head head;
struct msgb *req;
DBG2(DBG_NET, "on stream read!");
hlen = stream->read(stream, &head, sizeof(head), FALSE);
if (hlen <= 0)
{
if (errno == EWOULDBLOCK)
{
DBG2(DBG_NET, "on stream read EWOULDBLOCK!");
return TRUE;
}
DBG2(DBG_NET, "on stream errno not EWOULDBLOCK %d!", hlen);
return read_error(this, errno);
}
DBG2(DBG_NET, "on stream hlen %d!", hlen);
if (hlen < sizeof(head))
{
if (!stream->read_all(stream, ((void*)&head) + hlen, sizeof(head) - hlen))
{
return read_error(this, errno);
}
}
len = ntohs(head.len);
if ((len + sizeof(head)) > IPA_ALLOC_SIZE)
{
/* TODO: pkg too big */
return read_error(this, EINVAL);
}
req_chunk = chunk_alloc(IPA_ALLOC_SIZE + sizeof(struct msgb));
if (!req_chunk.ptr)
{
/* TODO: -ENOMEM; */
return FALSE;
}
req = chunk_to_msgb(&req_chunk);
memcpy(msgb_put(req, sizeof(head)), &head, sizeof(head));
/* TODO: either wait here with a timeout or don't care on this stream read */
if (!stream->read_all(stream, msgb_put(req, len), len))
{
chunk_free(&req_chunk);
return read_error(this, errno);
}
switch (head.proto)
{
case IPAC_PROTO_IPACCESS:
on_recv_ipa_pdu(this, req);
break;
case IPAC_PROTO_OSMO:
/* will take care of the response */
if (msgb_length(req) < sizeof(struct ipaccess_head) + 1)
{
/* TODO: inval pdu */
chunk_free(&req_chunk);
break;
}
req->l1h = req->head;
req->l2h = req->l1h + sizeof(struct ipaccess_head) + 1;
DBG2(DBG_NET, "IPA client: pushing osmo pdu");
if (this->osmo_cb)
{
this->osmo_cb(this->osmo_cb_data, &this->public, req);
}
else
{
chunk_free(&req_chunk);
}
break;
default:
DBG1(DBG_NET, "IPA client: ignoring unknown proto %02x", head.proto);
chunk_free(&req_chunk);
break;
}
return TRUE;
}
static int connect_ipa(private_osmo_epdg_ipa_client_t *this)
{
DBG1(DBG_NET, "IPA client connecting to %s", this->uri);
if (this->stream != NULL)
{
DBG1(DBG_NET, "closing old ipa conncetion %s", this->uri);
this->public.disconnect(&this->public);
}
this->stream = lib->streams->connect(lib->streams, this->uri);
if (!this->stream)
{
/* TODO: failed to connect */
/* TODO: re-schedule the connect */
DBG1(DBG_NET, "failed to connect the ipa %s", this->uri);
return -EINVAL;
}
DBG1(DBG_NET, "IPA client connected");
/* TODO: check if we need this
* ensure we get the first bytes after the connect
* on_stream_read(this, this->stream); */
/* TODO: can a race happen here when data arrives between those 2 calls? */
this->stream->on_read(this->stream, on_stream_read, this);
on_stream_read(this, this->stream);
return 0;
}
osmo_epdg_ipa_client_t *osmo_epdg_ipa_client_create(char *uri)
{
private_osmo_epdg_ipa_client_t *this;
INIT(this,
.public = {
.on_recv = _on_recv,
.on_error = _on_error,
.send = _send_pdu,
.disconnect = _disconnect,
.destroy = _destroy,
},
.uri = strdup(uri),
.stream = NULL,
);
connect_ipa(this);
return &this->public;
}

View File

@ -0,0 +1,76 @@
/*
* 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.
*
*/
// TODO: check license
/**
* @defgroup osmo_epdg_ipa_client osmo_epdg_ipa_client
* @{ @ingroup eap_simaka_sql
*/
#ifndef OSMO_EPDG_IPA_CLIENT_H_
#define OSMO_EPDG_IPA_CLIENT_H_
#include <utils/chunk.h>
struct msgb;
typedef struct osmo_epdg_ipa_client_t osmo_epdg_ipa_client_t;
typedef bool (*ipa_cb_t)(void *data, osmo_epdg_ipa_client_t *client, struct msgb *pdu);
/**
* IP Access Protocol
*/
struct osmo_epdg_ipa_client_t {
/**
* Register a callback to invoke when a IPA PDU of proto arrived.
*
* Only called when a full PDU has arrived.
*
* @param cb callback function, NULL to unregister
* @param data data to pass to callback
*/
int (*on_recv)(osmo_epdg_ipa_client_t *this, uint8_t osmo_proto, ipa_cb_t cb, void *data);
/**
* Send a PDU over the IPA connection
*
* @param proto define the protocol
* @param buf data buffer to write
* @param len number of bytes to write
* @return number of bytes written, -1 on error
*/
ssize_t (*send)(osmo_epdg_ipa_client_t *this, uint8_t osmo_proto, struct msgb *msg);
int (*on_error)(osmo_epdg_ipa_client_t *this, uint8_t osmo_proto, ipa_cb_t cb, void *data);
// TODO: unsure if we need this int (*connect)(osmo_epdg_ipa_client_t *this);
int (*disconnect)(osmo_epdg_ipa_client_t *this);
/**
* Destroy a osmo_epdg_ipa_client_t.
*/
void (*destroy)(osmo_epdg_ipa_client_t *this);
};
/**
* Create a osmo_epdg_ipa_client instance.
*
* @param address the address of the gsup server */
osmo_epdg_ipa_client_t *osmo_epdg_ipa_client_create(char *addr);
#endif /** OSMO_EPDG_IPA_CLIENT_H_ @}*/

View File

@ -0,0 +1,129 @@
/*
* 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 <daemon.h>
#include <plugins/plugin.h>
#include <unistd.h>
#include "osmo_epdg_plugin.h"
#include "osmo_epdg_db.h"
#include "osmo_epdg_utils.h"
typedef struct private_osmo_epdg_db_t private_osmo_epdg_db_t;
/**
* Private data of an osmo_epdg_db_t object.
*/
struct private_osmo_epdg_db_t {
/**
* Public osmo_epdg_db_t interface.
*/
osmo_epdg_db_t public;
/**
* GSUP client
*/
osmo_epdg_gsup_client_t *gsup;
/**
* subscriber hash by ID
*/
hashtable_t *subscribers;
/**
* subscriber hash by imsi (how to handle multiple?)
*/
hashtable_t *subscribers_imsi;
/**
* subscriber by ike_sa
*/
hashtable_t *subscribers_ike_sa_t;
};
METHOD(osmo_epdg_db_t, create_subscriber_imsi, osmo_epdg_ue_t *,
private_osmo_epdg_db_t *this, ike_sa_t *ike_sa,
char *imsi)
{
return NULL;
}
METHOD(osmo_epdg_db_t, get_subscriber_imsi, osmo_epdg_ue_t *,
private_osmo_epdg_db_t *this, char *imsi, int offset)
{
return NULL;
}
METHOD(osmo_epdg_db_t, get_subscriber_id, osmo_epdg_ue_t *,
private_osmo_epdg_db_t *this, uint32_t id)
{
return NULL;
}
METHOD(osmo_epdg_db_t, get_subscriber_ike, osmo_epdg_ue_t *,
private_osmo_epdg_db_t *this, ike_sa_t *ike_sa)
{
return NULL;
}
METHOD(osmo_epdg_db_t, destroy_subscriber_id, void,
private_osmo_epdg_db_t *this, uint32_t id)
{
return NULL;
}
METHOD(osmo_epdg_db_t, destroy_subscriber_ike, void,
private_osmo_epdg_db_t *this, ike_sa_t *ike_sa)
{
return NULL;
}
METHOD(osmo_epdg_db_t, destroy_subscriber, void,
private_osmo_epdg_db_t *this)
{
return NULL;
}
METHOD(osmo_epdg_db_t, destroy, void,
private_osmo_epdg_db_t *this)
{
free(this);
}
/**
* See header
*/
osmo_epdg_db_t *osmo_epdg_db_create(osmo_epdg_gsup_client_t *gsup)
{
private_osmo_epdg_db_t *this;
INIT(this,
.public = {
.create_subscriber = _create_subscriber_imsi,
.get_subscriber_id = _get_subscriber_id,
.get_subscriber_imsi = _get_subscriber_imsi,
.get_subscriber_ike = _get_subscriber_ike,
.destroy_subscriber_ike = _destroy_subscriber_ike,
.destroy_subscriber_id = _destroy_subscriber_id,
.destroy_subscriber = _destroy_subscriber,
.destroy = _destroy,
},
);
return &this->public;
}

View File

@ -0,0 +1,84 @@
/*
* 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.
*
*/
// TODO: check license
/**
* @defgroup osmo_epdg_db osmo_epdg_db
* @{ @ingroup osmo_epdg
*/
#ifndef OSMO_EPDG_LISTENER_H_
#define OSMO_EPDG_LISTENER_H_
#include <bus/listeners/listener.h>
#include "gsup_client.h"
typedef struct osmo_epdg_db_t osmo_epdg_db_t;
/**
* SIM listener implementation using a set of AKA functions.
*/
struct osmo_epdg_db_t {
/**
* Create new subscriber by imsi, before sending authentication
*/
osmo_epdg_ue_t *(*create_subscriber)(osmo_epdg_db_t *this, ike_sa_t *ike_sa, char *imsi);
/**
* Get subscriber by imsi, there might be multiple UE by this IMSI
*/
osmo_epdg_ue_t *(*get_subscriber_imsi)(osmo_epdg_db_t *this, char *imsi, int offset);
/**
* Get subscriber by ike
*/
osmo_epdg_ue_t *(*get_subscriber_ike)(osmo_epdg_db_t *this, ike_sa_t *ike_sa);
/**
* Get subscriber by id
*/
osmo_epdg_ue_t *(*get_subscriber_id)(osmo_epdg_db_t *this, uint32_t id);
/**
* Destroy subscriber by imsi
*/
void (*destroy_subscriber_ike)(osmo_epdg_db_t *this, ike_sa_t *ike_sa);
/**
* Destroy subscriber by imsi
*/
void (*destroy_subscriber_id)(osmo_epdg_db_t *this, uint32_t id);
/**
* Destroy subscriber by object
*/
void (*destroy_subscriber)(osmo_epdg_db_t *this, osmo_epdg_ue_t *ue);
/**
* Destroy a osmo_epdg_db_t.
*/
void (*destroy)(osmo_epdg_db_t *this);
};
/**
* Create a osmo_epdg_db instance.
*/
osmo_epdg_db_t *osmo_epdg_db_create(osmo_epdg_gsup_client_t *gsup);
#endif /** OSMO_EPDG_LISTENER_H_ @}*/

View File

@ -0,0 +1,176 @@
/*
* 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.
*
*/
// TODO: check license
#include <daemon.h>
#include <plugins/plugin.h>
#include <errno.h>
#include <unistd.h>
#include <osmocom/gsm/apn.h>
#include "osmo_epdg_plugin.h"
#include "osmo_epdg_listener.h"
#include "osmo_epdg_utils.h"
typedef struct private_osmo_epdg_listener_t private_osmo_epdg_listener_t;
/**
* Private data of an osmo_epdg_listener_t object.
*/
struct private_osmo_epdg_listener_t {
/**
* Public osmo_epdg_listener_t interface.
*/
osmo_epdg_listener_t public;
osmo_epdg_gsup_client_t *gsup;
};
METHOD(listener_t, eap_authorize, bool,
private_osmo_epdg_listener_t *this, ike_sa_t *ike_sa,
identification_t *id, bool final, bool *success)
{
char imsi[16] = {0};
if (!id)
{
DBG1(DBG_NET, "epdg: authorize: no id given. Failing.");
goto err;
}
if (get_imsi(id, imsi, sizeof(imsi) - 1))
{
DBG1(DBG_NET, "epdg: authorize: Can't find IMSI in EAP identity.");
goto err;
}
osmo_epdg_gsup_response_t *resp = this->gsup->update_location(this->gsup, imsi, OSMO_GSUP_CN_DOMAIN_PS);
if (!resp)
{
DBG1(DBG_NET, "epdg: GSUP: couldn't send Update Location.");
goto err;
}
/* TODO: validate if this APN is allowed! */
if (resp->gsup.message_type != OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT)
{
DBG1(DBG_NET, "epdg_listener: Update Location Error! Cause: %02x", resp->gsup.cause);
goto err;
}
return TRUE;
err:
*success = FALSE;
/* keep still subscribed */
return TRUE;
}
METHOD(listener_t, authorize, bool,
private_osmo_epdg_listener_t *this, ike_sa_t *ike_sa,
bool final, bool *success)
{
int ret;
identification_t* imsi_id;
char apn[APN_MAXLEN];
char imsi[16] = {0};
DBG1(DBG_NET, "Authorized: uniq 0x%08x, name %s final: %d, eap: %d!",
ike_sa->get_unique_id(ike_sa),
ike_sa->get_name(ike_sa),
final,
ike_sa->has_condition(ike_sa, COND_EAP_AUTHENTICATED));
if (!final)
{
return TRUE;
}
imsi_id = ike_sa->get_other_id(ike_sa);
if (!imsi_id)
{
DBG1(DBG_NET, "epdg: authorize: Can't get EAP identity.");
goto err;
}
if (get_imsi(imsi_id, imsi, sizeof(imsi) - 1))
{
DBG1(DBG_NET, "epdg: authorize: Can't find IMSI in EAP identity.");
goto err;
}
apn[0] = 0;
ret = get_apn(ike_sa, apn, APN_MAXLEN);
if (!ret && ret != -EINVAL)
{
DBG1(DBG_NET, "epdg_listener: Tunnel Request: Couldn't get APN!");
goto err;
}
osmo_epdg_gsup_response_t *resp = this->gsup->tunnel_request(this->gsup, imsi, apn, strlen(apn));
if (!resp)
{
DBG1(DBG_NET, "epdg_listener: Tunnel Request: GSUP: couldn't send.");
goto err;
}
if (resp->gsup.message_type == OSMO_GSUP_MSGT_EPDG_TUNNEL_ERROR)
{
DBG1(DBG_NET, "epdg_listener: Tunnel Error! Cause: %02x", resp->gsup.cause);
goto err;
}
else if (resp->gsup.message_type != OSMO_GSUP_MSGT_EPDG_TUNNEL_RESULT)
{
DBG1(DBG_NET, "epdg_listener: Tunnel Response: unexpected message type: %02x", resp->gsup.message_type);
goto err;
}
return TRUE;
err:
*success = FALSE;
/* keep still subscribed */
return TRUE;
}
METHOD(osmo_epdg_listener_t, destroy, void,
private_osmo_epdg_listener_t *this)
{
free(this);
}
/**
* See header
*/
osmo_epdg_listener_t *osmo_epdg_listener_create(osmo_epdg_gsup_client_t *gsup)
{
private_osmo_epdg_listener_t *this;
INIT(this,
.public = {
.listener = {
.authorize = _authorize,
.eap_authorize = _eap_authorize,
},
.destroy = _destroy,
},
.gsup = gsup,
);
return &this->public;
}

View File

@ -0,0 +1,55 @@
/*
* 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.
*
*/
// TODO: check license
/**
* @defgroup osmo_epdg_listener osmo_epdg_listener
* @{ @ingroup osmo_epdg
*/
#ifndef OSMO_EPDG_LISTENER_H_
#define OSMO_EPDG_LISTENER_H_
#include <bus/listeners/listener.h>
#include "gsup_client.h"
typedef struct osmo_epdg_listener_t osmo_epdg_listener_t;
/**
* SIM listener implementation using a set of AKA functions.
*/
struct osmo_epdg_listener_t {
/**
* Implements listener_t interface.
*/
listener_t listener;
/**
* Destroy a osmo_epdg_listener_t.
*/
void (*destroy)(osmo_epdg_listener_t *this);
};
/**
* Create a osmo_epdg_listener instance.
*/
osmo_epdg_listener_t *osmo_epdg_listener_create(osmo_epdg_gsup_client_t *gsup);
#endif /** OSMO_EPDG_LISTENER_H_ @}*/

View File

@ -0,0 +1,120 @@
/*
* 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.
*
*/
// TODO: check license
#include <daemon.h>
#include <plugins/plugin.h>
#include "osmo_epdg_plugin.h"
#include "osmo_epdg_provider.h"
#include "osmo_epdg_listener.h"
typedef struct private_osmo_epdg_t private_osmo_epdg_t;
/**
* Private data of an eap_osmo_epdg_t object.
*/
struct private_osmo_epdg_t {
/**
* Public osmo_epdg_plugin_t interface.
*/
osmo_epdg_plugin_t public;
/**
* SIM AKA provider
*/
osmo_epdg_provider_t *provider;
osmo_epdg_listener_t *listener;
};
METHOD(plugin_t, get_name, char*,
private_osmo_epdg_t *this)
{
return "osmo-epdg";
}
static bool register_functions(private_osmo_epdg_t *this,
plugin_feature_t *feature, bool reg, void *data)
{
if (reg)
{
osmo_epdg_gsup_client_t *gsup = osmo_epdg_gsup_client_create("tcp://127.0.0.1:4222");
this->provider = osmo_epdg_provider_create(gsup);
this->listener = osmo_epdg_listener_create(gsup);
charon->bus->add_listener(charon->bus, &this->listener->listener);
return TRUE;
}
if (this->listener)
{
charon->bus->remove_listener(charon->bus, &this->listener->listener);
}
this->provider->destroy(this->provider);
this->provider = NULL;
return TRUE;
}
/**
* Callback providing our provider to register
*/
static simaka_provider_t* get_provider(private_osmo_epdg_t *this)
{
return &this->provider->provider;
}
METHOD(plugin_t, get_features, int,
private_osmo_epdg_t *this, plugin_feature_t *features[])
{
static plugin_feature_t f[] = {
PLUGIN_CALLBACK((void*)register_functions, NULL),
PLUGIN_PROVIDE(CUSTOM, "osmo-epdg"),
PLUGIN_CALLBACK(simaka_manager_register, get_provider),
PLUGIN_PROVIDE(CUSTOM, "aka-provider"),
PLUGIN_DEPENDS(CUSTOM, "aka-manager"),
};
*features = f;
return countof(f);
}
METHOD(plugin_t, destroy, void,
private_osmo_epdg_t *this)
{
free(this);
}
/**
* See header
*/
plugin_t *osmo_epdg_plugin_create()
{
private_osmo_epdg_t *this;
INIT(this,
.public = {
.plugin = {
.get_name = _get_name,
.get_features = _get_features,
.destroy = _destroy,
},
},
);
return &this->public.plugin;
}

View File

@ -0,0 +1,44 @@
/*
* 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.
*
*/
// TODO: check license
/**
* @defgroup eap_osmo_epdg eap_aka_3gpp2
* @ingroup cplugins
*
* @defgroup eap_osmo_epdg_plugin eap_aka_3gpp2_plugin
* @{ @ingroup eap_osmo_epdg
*/
#ifndef OSMO_EPDG_PLUGIN_H_
#define OSMO_EPDG_PLUGIN_H_
#include <plugins/plugin.h>
typedef struct osmo_epdg_plugin_t osmo_epdg_plugin_t;
struct osmo_epdg_plugin_t {
/**
* implements plugin interface
*/
plugin_t plugin;
};
#endif /** OSMO_EPDG_PLUGIN_H_ @}*/

View File

@ -0,0 +1,153 @@
/*
* 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 "osmo_epdg_provider.h"
#include "osmo_epdg_utils.h"
#include "gsup_client.h"
#include <daemon.h>
#include <credentials/keys/shared_key.h>
#define AKA_SQN_LEN 6
#define AKA_K_LEN 16
#define AKA_OPC_LEN 16
#define AKA_MAC_LEN 8
#define AKA_AK_LEN 6
#define AKA_AMF_LEN 2
#define AKA_RES_LEN 8
typedef struct private_osmo_epdg_provider_t private_osmo_epdg_provider_t;
/**
* Private data of an osmo_epdg_provider_t object.
*/
struct private_osmo_epdg_provider_t {
/**
* Public osmo_epdg_provider_t interface.
*/
osmo_epdg_provider_t public;
osmo_epdg_gsup_client_t *gsup;
};
/**
* Get a shared key K from the credential database
*/
bool osmo_epdg_get_k(identification_t *id, char k[AKA_K_LEN])
{
shared_key_t *shared;
chunk_t key;
shared = lib->credmgr->get_shared(lib->credmgr, SHARED_EAP, id, NULL);
if (shared == NULL)
{
return FALSE;
}
key = shared->get_key(shared);
memset(k, '\0', AKA_K_LEN);
memcpy(k, key.ptr, min(key.len, AKA_K_LEN));
shared->destroy(shared);
return TRUE;
}
METHOD(simaka_provider_t, get_quintuplet, bool,
private_osmo_epdg_provider_t *this, identification_t *id,
char rand[AKA_RAND_LEN], char xres[AKA_RES_MAX], int *xres_len,
char ck[AKA_CK_LEN], char ik[AKA_IK_LEN], char autn[AKA_AUTN_LEN])
{
char imsi[17] = {0};
if (get_imsi(id, imsi, sizeof(imsi) - 1))
{
DBG1(DBG_NET, "epdg: get_quintuplet: Can't find IMSI in EAP identity.");
return FALSE;
}
/* TODO: check before if this is a null terminated string */
osmo_epdg_gsup_response_t *resp = this->gsup->send_auth_request(
this->gsup, imsi, OSMO_GSUP_CN_DOMAIN_PS, NULL, NULL);
if (!resp)
{
return FALSE;
}
if (resp->gsup.message_type != OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT)
{
DBG1(DBG_NET, "epdg_provider: SendAuthInfo Error! Cause: %02x", resp->gsup.cause);
return FALSE;
}
struct osmo_auth_vector *auth = &resp->gsup.auth_vectors[0];
if (resp->gsup.num_auth_vectors == 0)
{
/* TODO: invalid auth data received */
return FALSE;
}
memcpy(rand, auth->rand, AKA_RAND_LEN);
memcpy(ck, auth->ck, AKA_CK_LEN);
memcpy(ik, auth->ik, AKA_IK_LEN);
memcpy(autn, auth->autn, AKA_AUTN_LEN);
memcpy(xres, auth->res, auth->res_len);
*xres_len = auth->res_len;
free(resp);
return TRUE;
}
METHOD(simaka_provider_t, resync, bool,
private_osmo_epdg_provider_t *this, identification_t *id,
char rand[AKA_RAND_LEN], char auts[AKA_AUTS_LEN])
{
/* TODO: invalid auth data received */
/* prepare and fill up the struct */
/* send pdu blocking */
return FALSE;
}
METHOD(osmo_epdg_provider_t, destroy, void,
private_osmo_epdg_provider_t *this)
{
free(this);
}
/**
* See header
*/
osmo_epdg_provider_t *osmo_epdg_provider_create(osmo_epdg_gsup_client_t *gsup)
{
private_osmo_epdg_provider_t *this;
INIT(this,
.public = {
.provider = {
.get_triplet = (void*)return_false,
.get_quintuplet = _get_quintuplet,
.resync = _resync,
.is_pseudonym = (void*)return_null,
.gen_pseudonym = (void*)return_null,
.is_reauth = (void*)return_null,
.gen_reauth = (void*)return_null,
},
.destroy = _destroy,
},
.gsup = gsup,
);
return &this->public;
}

View File

@ -0,0 +1,55 @@
/*
* 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.
*
*/
// TODO: check license
/**
* @defgroup osmo_epdg_listener osmo_epdg_listener
* @{ @ingroup osmo_epdg
*/
#ifndef OSMO_EPDG_PROVIDER_H_
#define OSMO_EPDG_PROVIDER_H_
#include <simaka_provider.h>
#include "gsup_client.h"
typedef struct osmo_epdg_provider_t osmo_epdg_provider_t;
/**
* SIM provider implementation using a set of AKA functions.
*/
struct osmo_epdg_provider_t {
/**
* Implements simaka_provider_t interface.
*/
simaka_provider_t provider;
/**
* Destroy a osmo_epdg_provider_t.
*/
void (*destroy)(osmo_epdg_provider_t *this);
};
/**
* Create a osmo_epdg_provider instance.
*/
osmo_epdg_provider_t *osmo_epdg_provider_create(osmo_epdg_gsup_client_t *gsup);
#endif /** OSMO_EPDG_PROVIDER_H_ @}*/

View File

@ -0,0 +1,88 @@
/*
* 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 <osmocom/core/msgb.h>
#include <sa/ike_sa.h>
#include <utils/utils.h>
#include <utils/debug.h>
#include "osmo_epdg_utils.h"
struct msgb *chunk_to_msgb(chunk_t *chunk)
{
struct msgb *msg;
if (chunk->len < sizeof(*msg))
{
return NULL;
}
msg = (struct msgb *) chunk->ptr;
memset(msg, 0x00, sizeof(*msg));
msg->data_len = chunk->len - sizeof(*msg);
msg->len = 0;
msg->data = msg->_data;
msg->head = msg->_data;
msg->tail = msg->_data;
return msg;
}
int get_imsi(identification_t *id, char *imsi, size_t imsi_len)
{
chunk_t nai = id->get_encoding(id);
/* TODO: maybe use regex? */
/* 099942123456789@mnc042.mcc999.3gpp... */
if (nai.len < 17)
{
DBG1(DBG_NET, "epdg: Invalid NAI %s.", nai);
return -EINVAL;
}
if (nai.ptr[0] != '0')
{
DBG1(DBG_NET, "epdg: Invalid NAI. Only support IMSI (starting with 0). %s.",
nai);
return -EINVAL;
}
strncpy(imsi, nai.ptr + 1, min(15, imsi_len));
return 0;
}
int get_apn(ike_sa_t *sa, char *apn, size_t apn_len)
{
identification_t* apn_id;
chunk_t apn_chunk;
apn_id = sa->get_my_id(sa);
if (!apn_id)
{
return -EINVAL;
}
apn_chunk = apn_id->get_encoding(apn_id);
if (apn_chunk.len >= apn_len)
{
return -ENOMEM;
}
memcpy(apn, apn_chunk.ptr, apn_chunk.len);
apn[apn_chunk_len] = 0;
return -1;
}

View File

@ -0,0 +1,58 @@
/*
* 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 <osmocom/core/msgb.h>
#include <sa/ike_sa.h>
#include <utils/chunk.h>
#include <utils/identification.h>
#define IPA_ALLOC_SIZE 1200
enum ue_state {
/* Initial */
UE_UNAUTHENTICATED,
/* Authenticated */
UE_AUTHENTICATED,
/* Wait for GSUP Update Location Request */
UE_WAIT_LOCATION_UPDATE,
/* Wait for GSUP Tunnel Request */
UE_WAIT_TUNNEL,
/* Everything ready, data can flow */
UE_CONNECTED,
/* Notify the osmo-epdg about destruction, wait for an answer */
UE_DISCONNECTING,
UE_DESTROYED,
};
/* TODO: how to clean up/garbage collect */
struct osmo_epdg_ue {
/* increasing uniq id */
uint32_t id;
/* imsi should be uniq, need protected against fake UE */
char *imsi;
enum ue_state state;
/* TODO: missing strongswan session pointer */
ike_sa_t *ike_sa;
};
typedef struct osmo_epdg_ue osmo_epdg_ue_t;
struct msgb *chunk_to_msgb(chunk_t *chunk);
int get_imsi(identification_t *id, char *imsi, size_t imsi_len);
int get_apn(ike_sa_t *sa, char *apn, size_t apn_len);

View File

@ -240,6 +240,32 @@ static void replace_eap_identity(private_eap_authenticator_t *this)
cfg->add(cfg, AUTH_RULE_EAP_IDENTITY, eap_identity);
}
static identification_t *get_identification(private_eap_authenticator_t *this)
{
identification_t *id;
auth_cfg_t *auth = NULL;
if (this->eap_identity)
{
return this->eap_identity;
}
if (this->method->get_auth)
{
auth = this->method->get_auth(this->method);
if (auth)
{
id = auth->get(auth, AUTH_RULE_EAP_IDENTITY);
if (id)
{
return id;
}
}
}
return NULL;
}
/**
* Handle EAP exchange as server
*/
@ -320,6 +346,14 @@ static eap_payload_t* server_process_eap(private_eap_authenticator_t *this,
DBG1(DBG_IKE, "EAP method %N succeeded, %sMSK established",
eap_type_names, type, this->msk.ptr ? "" : "no ");
}
if (!charon->bus->eap_authorize(charon->bus, get_identification(this), TRUE))
{
DBG1(DBG_IKE, "eap_authorization hook forbids EAP_SUCCESS, canceling");
this->eap_complete = FALSE;
return eap_payload_create_code(EAP_FAILURE, in->get_identifier(in));
}
this->ike_sa->set_condition(this->ike_sa, COND_EAP_AUTHENTICATED,
TRUE);
this->eap_complete = TRUE;

View File

@ -51,6 +51,20 @@ struct private_blocking_queue_t {
};
METHOD(blocking_queue_t, remove_, void*,
private_blocking_queue_t *this, void *item)
{
int removed = 0;
this->mutex->lock(this->mutex);
removed = this->list->remove(this->list, item, NULL);
this->mutex->unlock(this->mutex);
if (removed)
return item;
return NULL;
}
METHOD(blocking_queue_t, enqueue, void,
private_blocking_queue_t *this, void *item)
{
@ -115,6 +129,7 @@ blocking_queue_t *blocking_queue_create()
.public = {
.enqueue = _enqueue,
.dequeue = _dequeue,
.remove = _remove_,
.destroy = _destroy,
.destroy_offset = _destroy_offset,
.destroy_function = _destroy_function,

View File

@ -49,6 +49,14 @@ struct blocking_queue_t {
*/
void *(*dequeue)(blocking_queue_t *this);
/**
* Removes a specific item from the queue.
*
* @param item item to be removed from the queue
* @return item if item was on the queue. Otherwise NULL`
*/
void *(*remove)(blocking_queue_t *this, void *item);
/**
* Destroys a blocking_queue_t object.
*