This is merely a historical archive of years 2008-2021, before the migration to mailman3.
A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/OpenBSC@lists.osmocom.org/.
Neels Hofmeyr nhofmeyr at sysmocom.deHi Pablo, I am currently entering the same trajectory as you have: I'm tweaking and forwarding GTP messages in the openbsc:neels/gtphub branch. The more I try to use the gtp.h API (from openggsn), the more I am forced to re-implement it. In that sense, a new and proper library for GTP messages is highly interesting to me. GTPv0 is in fact less interesting, as seemingly *all* messages I need to deal with are GTPv1. Adding v1 should actually be pretty quick, though. Let me take a look at the patch, comments are inline... I'm not implying that you should work on it more. That would be great, but if not, they are remarks for whomever would like to go on with it. Probably me anyway. Thank you loads for sharing this! ~Neels comments follow: On Thu, Oct 15, 2015 at 08:12:02AM +0200, pablo at gnumonks.org wrote: > From: Pablo Neira Ayuso <pablo at gnumonks.org> > > I started this libosmo-gtp library after spending quite some time trying to > clean up libgtp and add IPv6 support to it in some clean way. > > The result was this library that I started from scratch in my spare time. > > The basic idea behind this is to avoid mixing the carrier (socket) code with > the message building and parsing, and avoid overly the complicated callback > scheme of libgtp. +1 !!! > This implements minimal support for GTPv0, it also includes automated tests > by generating GTPv0 messages then hand it over to gtp0_recv() which generates > the reply. > > I remember that I also validated this code by sending GTPv0 messages over UDP > to inspect them through wireshark, and they were OK. gtphub would be interesting testing grounds: it deals entirely with "real" GTP messages coming from elsewhere, but also recodes them. > It's fairly incomplete. The only client I have for this library is an initial > (fairly incomplete) osmo-gtp daemon using this library to replace opengtp. > > BTW, there's a src/libosmocore.c file that contains helper functions to > provide a more consistent way to work with different TLV types. > > Probably it can be uploaded to the osmocom repository even if incomplete? > > Well, just wanted to get this code out there, it got stuck here and I didn't > find so far more time to make more progress on it, but we'll try to update > it when traveling or something. > > .gitignore | 43 +++ > Makefile.am | 13 + > configure.ac | 42 +++ > git-version-gen | 151 +++++++++++ > include/Makefile.am | 3 + > include/gtp.h | 102 +++++++ > include/libosmocore.h | 20 ++ > include/osmocom/gtp/gtp.h | 49 ++++ > libosmogtp.pc.in | 11 + > src/Makefile.am | 15 ++ > src/gtp.c | 669 ++++++++++++++++++++++++++++++++++++++++++++++ > src/libosmocore.c | 70 +++++ > tests/Makefile.am | 52 ++++ > tests/gtp/gtp_test.ok | 0 > tests/gtp_test.c | 121 +++++++++ > tests/gtp_test.ok | 4 + > tests/testsuite.at | 18 ++ > 17 files changed, 1383 insertions(+) > create mode 100644 .gitignore > create mode 100644 Makefile.am > create mode 100644 configure.ac > create mode 100755 git-version-gen > create mode 100644 include/Makefile.am > create mode 100644 include/gtp.h > create mode 100644 include/libosmocore.h > create mode 100644 include/osmocom/gtp/gtp.h > create mode 100644 libosmogtp.pc.in > create mode 100644 src/Makefile.am > create mode 100644 src/gtp.c > create mode 100644 src/libosmocore.c > create mode 100644 tests/Makefile.am > create mode 100644 tests/gtp/gtp_test.ok > create mode 100644 tests/gtp_test.c > create mode 100644 tests/gtp_test.ok > create mode 100644 tests/testsuite.at [...] > diff --git a/include/gtp.h b/include/gtp.h > new file mode 100644 > index 0000000..3a47de6 > --- /dev/null > +++ b/include/gtp.h > @@ -0,0 +1,102 @@ > +#ifndef _GTP_PROTOCOL_H_ > +#define _GTP_PROTOCOL_H_ > + > +enum gtp_type { > + GTP_UNUSED = 0, /* GSM 09.60 says for future use */ > + GTP_ECHO_REQ = 1, /* 7.4.1 GSM 09.60 */ > + GTP_ECHO_RESP = 2, /* 7.4.2 GSM 09.60 */ > + GTP_VERSION_NOTSUPP = 3, /* 7.4.3 GSM 09.60 */ > + GTP_PDP_CREATE_REQ = 16, /* 7.5.1 GSM 09.60 */ > + GTP_PDP_CREATE_RESP = 17, /* 7.5.2 GSM 09.60 */ > + GTP_PDP_UPDATE_REQ = 18, /* 7.5.3 GSM 09.60 */ > + GTP_PDP_UPDATE_RESP = 19, /* 7.5.4 GSM 09.60 */ > + GTP_PDP_DELETE_REQ = 20, /* 7.5.5 GSM 09.60 */ > + GTP_PDP_DELETE_RESP = 21, /* 7.5.6 GSM 09.60 */ > + GTP_TYPE_MAX, > +}; > + > +struct gtp0_header { > +#if BYTE_ORDER == BIG_ENDIAN > + uint8_t version:3, > + pt:1, > + spare:3, > + snn:1; > +#elif BYTE_ORDER == LITTLE_ENDIAN > + uint8_t snn:1, > + spare:3, > + pt:1, > + version:3; Hmm, endianness is about *byte* order, not *bit* order, right? I suggest to use one of the existing decoding functions for endianness instead: - ntohs()/ntohl() - osmo_loadXXbe() - decode_big_endian() (yet static in openbsc/src/gprs/gprs_gsup_messages.c, see also the first commit it openbsc:neels/sgsn-id-3) Anyway, this single octet should not be affected by endianness, but those uint16,32,64_t below are. > +#else > +#warn "BYTE_ORDER is not defined, please fix your headers" > +#endif > + uint8_t type; > + uint16_t length; > + uint16_t seq; > + uint16_t flow; > + uint8_t number; > + uint8_t spare1; > + uint8_t spare2; > + uint8_t spare3; > + uint64_t tid; > +} __attribute__((packed)); > + > +/* > + * Information elements > + */ > +/* TV */ > +#define GTPV0_IE_CAUSE 1 /* 8 bits */ > +#define GTPV0_IE_QOS_PROFILE 6 /* 24 bits */ > +#define GTPV0_IE_REORDERING_REQ 8 /* 1 bit */ > +#define GTPV0_IE_RECOVERY 14 /* 8 bit */ > +#define GTPV0_IE_SELECT_MODE 15 /* 16 bits */ > +#define GTPV0_IE_FLOW_LABEL_DATA 16 /* 16 bits */ > +#define GTPV0_IE_FLOW_LABEL_SIGNAL 17 /* 16 bits */ > +#define GTPV0_IE_CHARGING_ID 127 /* 32 bits */ > +/* TLV >= 128 */ > +#define GTPV0_IE_ENDUSER_ADDR 128 > +#define GTPV0_IE_AP_NAME 131 > +#define GTPV0_IE_PROTO_CONF_OPTS 132 > +#define GTPV0_IE_GSN_ADDR 133 > +#define GTPV0_IE_MSISDN 134 > +#define GTPV0_IE_CHARGING_GW_ADDR 251 > + > +/* > + * Other > + */ > +#define GTPV0_CAUSE_REQ_ACCEPTED 128 /* GSM 09.60 7.9.1 */ > + > +#include <netinet/in.h> > + > +struct enduser_addr_ie_payload_ipv4 { heh, amazing name :) > +#if BYTE_ORDER == BIG_ENDIAN > + uint8_t spare:4, > + pdp_org_type:4; > +#elif BYTE_ORDER == LITTLE_ENDIAN > + uint8_t pdp_org_type:4, > + spare:4; > +#else > +#warn "BYTE_ORDER is not defined, please fix your headers" > +#endif > + uint8_t pdp_type_number; > + struct in_addr addr; > +} __attribute__((packed)); > + > +struct enduser_addr_ie_payload_ipv6 { > +#if BYTE_ORDER == BIG_ENDIAN > + uint8_t spare:4, > + pdp_org_type:4; > +#elif BYTE_ORDER == LITTLE_ENDIAN > + uint8_t pdp_org_type:4, > + spare:4; > +#else > +#warn "BYTE_ORDER is not defined, please fix your headers" > +#endif > + uint8_t pdp_type_number; > + struct in6_addr addr; > +} __attribute__((packed)); > + > +#define PDP_ORG_IETF 1 > +#define PDP_TYPE_IPV4 0x21 > +#define PDP_TYPE_IPV6 0x57 > + > +#endif > diff --git a/include/libosmocore.h b/include/libosmocore.h > new file mode 100644 > index 0000000..dab9265 > --- /dev/null > +++ b/include/libosmocore.h > @@ -0,0 +1,20 @@ > +#ifndef _GTP_LIBOSMOCORE_H_ > +#define _GTP_LIBOSMOCORE_H_ > + > +#include <osmocom/gsm/tlv.h> > + > +void *msgb_tv_put_be8(struct msgb *msg, int type, uint8_t value); > +void *msgb_tv_put_be16(struct msgb *msg, int type, uint16_t value); > +void *msgb_tv_put_be24(struct msgb *msg, int type, uint32_t value); > +void *msgb_tv_put_be32(struct msgb *msg, int type, uint32_t value); > +void *msgb_tlv_put_be32(struct msgb *msg, int type, uint32_t data); > +void *msgb_tlv_put_data(struct msgb *msg, int type, int len, void *data); > + > +uint8_t tv_get_be8(struct tlv_parsed *tp, int type); > +uint16_t tv_get_be16(struct tlv_parsed *tp, int type); > +uint32_t tv_get_be24(struct tlv_parsed *tp, int type); > +uint32_t tv_get_be32(struct tlv_parsed *tp, int type); I like how endianness is dealt with at the msgb put/get level. The same should probably happen with the header struct above, with msgb_v_get_beXX(). The old gtp.h also has a "big endian struct"... it's cumbersome as one cannot simply use the struct fields, but one needs to remember to ntohX() first. Might make for more optimized code, though, unless read often. > +const char *tv_get_strdup(struct tlv_parsed *tp, int type); > +const char *tlv_get_strdup(struct tlv_parsed *tp, int type); > + > +#endif > diff --git a/include/osmocom/gtp/gtp.h b/include/osmocom/gtp/gtp.h > new file mode 100644 > index 0000000..cb1b362 > --- /dev/null > +++ b/include/osmocom/gtp/gtp.h > @@ -0,0 +1,49 @@ [...] > +enum osmo_gsn_proto { > + OSMO_GSN_IPV4 = 0, > + OSMO_GSN_IPV6, > +}; It would be great if IPv6 were handled implicitly, by passing the addr size around and let getaddrinfo() et al deal with it. Am I missing something that's standing in the way there? [...] > diff --git a/src/gtp.c b/src/gtp.c > new file mode 100644 > index 0000000..672af5a > --- /dev/null > +++ b/src/gtp.c > @@ -0,0 +1,669 @@ > +#include <stdio.h> > +#include <stdlib.h> > +#include <stdint.h> > +#include <stdbool.h> > +#include <arpa/inet.h> > + > +#include <gtp.h> > +#include <osmocom/core/logging.h> > +#include <osmocom/core/msgb.h> > +#include <osmocom/core/write_queue.h> > +#include <osmocom/gtp/gtp.h> > +#include <osmocom/gsm/tlv.h> > + > +#include "libosmocore.h" > + > +/* Generic context pointer to data in the event context */ > +struct osmo_gtp_ctx { > + struct osmo_gtp_pdp *pdp; > + /* XXX Add more event context here */ > +}; > + > +/* Events are called from the GTP stack to notify upper layer application */ > +struct gtp_event_cb { > + int (*cb)(enum osmo_gtp_event event, struct osmo_gtp_ctx *ctx); > +}; > + > +struct osmo_gsn { > + struct { > + enum osmo_gsn_proto proto; > + bool reordering_required; > + union { > + struct in_addr ip_addr; > + struct in6_addr ip6_addr; > + } ggsn; > + } cfg; > + > + struct gtp_event_cb event[OSMO_GTP_EVENT_MAX]; > + > + uint8_t restart_counter; > +}; > + > +struct osmo_gtp_pdp { > + uint32_t qos_profile:24; > + uint16_t flow_label_data; > + uint16_t flow_label_signal; > + uint8_t sel_mode; > + const char *ap_name; > + union { > + struct in_addr ip_addr; > + struct in6_addr ip6_addr; > + } enduser; > + union { > + struct in_addr ip_addr; > + struct in6_addr ip6_addr; > + } sgsn; > + const char *msisdn; > +}; > + > +#define OSMO_GTP_MTU 1500 > + > +#define GTPV0 0 /* GSM 09.60 */ > +#define GTP_PRIME 0 /* GSM 09.60 */ > +#define GTP_NON_PRIME 1 /* GSM 12.15 */ > +#define GTP_SNDCP_NPDU_UNSET 0 > + > +struct gtp0_header *gtp0_header_put(struct msgb *msg, uint32_t type, > + uint16_t seq, uint16_t flow, uint64_t tid) > +{ > + struct gtp0_header *gtp0h = (struct gtp0_header *)msg->data; > + > + gtp0h->version = GTPV0; > + gtp0h->pt = GTP_NON_PRIME; > + gtp0h->spare = 0x7; > + gtp0h->snn = GTP_SNDCP_NPDU_UNSET; > + gtp0h->type = type; > + gtp0h->seq = htons(seq); > + gtp0h->tid = htobe64(tid); Ah, here it is: htons() stored in the header struct. I'd prefer a host-byte-order struct. > + gtp0h->spare1 = 0xff; > + gtp0h->spare2 = 0xff; > + gtp0h->spare3 = 0xff; > + gtp0h->number = 0xff; > + gtp0h->flow = flow; > + msgb_put(msg, sizeof(*gtp0h)); > + > + return gtp0h; > +} > + > +void gtp0_header_end(struct gtp0_header *gtp0h, struct msgb *msg) > +{ > + gtp0h->length = htons(msg->len - sizeof(*gtp0h)); With msgb_alloc_headroom(), it is possible to first write the IEs to the msgb, and then prepend the header with the correct size later on. See for example in openbsc, gprs_gsup_msgb_alloc() and ipa_msg_push_header(). [...] > +static struct msgb *gtp_unsupp(struct msgb *msg) > +{ > + struct msgb *reply; > + struct gtp0_header *gtp0h; > + > + reply = msgb_alloc(OSMO_GTP_MTU, "gtp"); > + if (reply == NULL) > + return NULL; > + > + gtp0h = gtp0_header_put(reply, GTP_VERSION_NOTSUPP, gtp0h_seq(msg), 0, > + gtp0h_tid(msg)); > + gtp0_header_end(gtp0h, reply); > + > + return reply; > +} [...] > +static int ip_alloc(struct osmo_gtp_pdp *pdp) > +{ > + pdp->enduser.ip_addr.s_addr= 0x01020304; > + return 0; > +} > + > +static int ip6_alloc(struct osmo_gtp_pdp *pdp) > +{ > + return 0; > +} Why are these two called *alloc? > +static const struct tlv_definition gtp_pdp_create_req_attr_tlvdef = { > + .def = { > + [GTPV0_IE_QOS_PROFILE] = { TLV_TYPE_FIXED, 3 }, > + [GTPV0_IE_RECOVERY] = { TLV_TYPE_FIXED, 1 }, > + [GTPV0_IE_SELECT_MODE] = { TLV_TYPE_FIXED, 1 }, > + [GTPV0_IE_FLOW_LABEL_DATA] = { TLV_TYPE_FIXED, 2 }, > + [GTPV0_IE_FLOW_LABEL_SIGNAL] = { TLV_TYPE_FIXED, 2 }, > + [GTPV0_IE_ENDUSER_ADDR] = { TLV_TYPE_TL16V }, > + [GTPV0_IE_AP_NAME] = { TLV_TYPE_TL16V }, > + [GTPV0_IE_PROTO_CONF_OPTS] = { TLV_TYPE_TL16V }, > + [GTPV0_IE_GSN_ADDR] = { TLV_TYPE_TL16V }, > + [GTPV0_IE_MSISDN] = { TLV_TYPE_TL16V }, > + }, > +}; I can't begin to express how much better this looks than the old gtpie.h! > + > +static struct msgb *gtp_pdp_create_req_handler(struct osmo_gsn *gsn, > + struct msgb *msg, > + struct osmo_gtp_err *err) > +{ > + struct msgb *reply; > + struct tlv_parsed tp; > + struct osmo_gtp_pdp _pdp, *pdp = &_pdp; > + int ret; > + > + memset(pdp, 0, sizeof(_pdp)); > + > + ret = tlv_parse(&tp, >p_pdp_create_req_attr_tlvdef, > + msg->data + sizeof(struct gtp0_header), > + msg->len - sizeof(struct gtp0_header), 0, 0); > + if (ret < 0) { > + LOGP(DLINP, LOGL_ERROR, "cannot parse TLVs: %u\n", ret); > + return NULL; > + } > + > + /* Mandatory attributes: > + * > + * 1) 7.9.6. QoS profile. 32 bits. GSM 04.08 > + * 2) 7.9.13. Selection mode. 8 bits & 0x3. > + * 3) 7.9.14. Flow label data. 16 bits > + * 4) 7.9.15. Flow label signalling. 16 bits > + * 5) 7.9.18. End User Address (see struct enduser_addr_ie_payload). > + * 6) 7.9.21. Access Point (AP) Name (variable length) > + * 7) 7.9.23. GSN address (variable length) > + * 8) 7.9.24. MSISDN (variable length) > + */ > + if (!TLVP_PRESENT(&tp, GTPV0_IE_QOS_PROFILE) || > + !TLVP_PRESENT(&tp, GTPV0_IE_SELECT_MODE) || > + !TLVP_PRESENT(&tp, GTPV0_IE_FLOW_LABEL_DATA) || > + !TLVP_PRESENT(&tp, GTPV0_IE_FLOW_LABEL_SIGNAL) || > + !TLVP_PRESENT(&tp, GTPV0_IE_ENDUSER_ADDR) || > + !TLVP_PRESENT(&tp, GTPV0_IE_AP_NAME) || > + !TLVP_PRESENT(&tp, GTPV0_IE_GSN_ADDR) || > + !TLVP_PRESENT(&tp, GTPV0_IE_MSISDN)) { > + LOGP(DLINP, LOGL_ERROR, "missing mandatory TLV\n"); > + return NULL; > + } Oh, how pleasing to the openggsn ridden eye :) > + > + pdp->qos_profile = tv_get_be24(&tp, GTPV0_IE_QOS_PROFILE); > + > + /* Validate spare 6 bits to one (7.9.13)? */ > + pdp->sel_mode = tv_get_be8(&tp, GTPV0_IE_SELECT_MODE) & 0x03; > + pdp->flow_label_data = tv_get_be16(&tp, GTPV0_IE_FLOW_LABEL_DATA); > + pdp->flow_label_signal = tv_get_be16(&tp, GTPV0_IE_FLOW_LABEL_SIGNAL); > + pdp->ap_name = tv_get_strdup(&tp, GTPV0_IE_AP_NAME); > + > + if (gtp0_parse_enduser_addr(&tp, pdp) < 0 || > + gtp0_parse_gsn_addr(&tp, pdp) < 0) > + return NULL; > + > + pdp->msisdn = tlv_get_strdup(&tp, GTPV0_IE_MSISDN); > + > + /* TODO: Optional attributes: > + * 1) 7.9.21. Protocol configuration options. GSM 04.08. > + */ > + if (TLVP_PRESENT(&tp, GTPV0_IE_PROTO_CONF_OPTS)) { > + } > + > + reply = gtp_pdp_ctx_create_resp(gsn, msg, pdp); For gtphub, I would need to have the response composition separate from parsing. Well, not much left to do, is there, with a nice and clean API like this ;) In fact, I'd like to have message decoding/encoding in an entirely separate header/c file pair from message handling. > + if (reply == NULL) > + return NULL; > + > + return reply; > +} > + [...] > +static struct osmo_gtp_handler gtp0_handler[GTP_TYPE_MAX] = { > + [GTP_ECHO_REQ] = { > + .request = true, > + .handler = gtp_echo_req_handler, > + }, > + [GTP_PDP_CREATE_REQ] = { > + .request = true, > + .handler = gtp_pdp_create_req_handler, > + }, > + [GTP_PDP_UPDATE_REQ] = { > + .request = true, > + .handler = gtp_pdp_update_req_handler, > + }, > + [GTP_PDP_DELETE_REQ] = { > + .request = true, > + .handler = gtp_pdp_delete_req_handler, > + }, > +}; nice. Though it seems to be an implementation detail for the client you were writing (I'm always thinking in terms of gtphub, which doesn't usually take own actions, but just forwards tweaked GTP data). > +struct msgb *gtp0_recv(struct osmo_gsn *gsn, struct msgb *msg, > + struct osmo_gtp_err *err) > +{ > + struct gtp0_header *gtp0h = (struct gtp0_header *)msg->data; > + struct msgb *reply = NULL; > + > + if (gtp0h->version != 0) { > + LOGP(DLINP, LOGL_ERROR, "wrong GTP packet version %u\n", > + gtp0h->version); > + return gtp_unsupp(msg); > + } > + if (msg->len < sizeof(*gtp0h)) { > + LOGP(DLINP, LOGL_ERROR, "GTPv0 packet too short msg->len %u\n", > + msg->len); > + return NULL; > + } > + if (msg->len != ntohs(gtp0h->length) + sizeof(*gtp0h)) { > + LOGP(DLINP, LOGL_ERROR, > + "truncated GTPv0 header msg->len %u != %u\n", > + msg->len, ntohs(gtp0h->length) + (int)sizeof(*gtp0h)); > + return NULL; > + } Heh, this is pretty much 1:1 what I've pasted/written for gtphub :) > + > + if ((gtp0h->type < GTP_TYPE_MAX) && gtp0_handler[gtp0h->type].handler) > + reply = gtp0_handler[gtp0h->type].handler(gsn, msg, err); > + > + return reply; > +} > + [...] > diff --git a/src/libosmocore.c b/src/libosmocore.c > new file mode 100644 > index 0000000..28b60d0 > --- /dev/null > +++ b/src/libosmocore.c > @@ -0,0 +1,70 @@ > +#include <osmocom/gsm/tlv.h> [...] > +void *msgb_tv_put_be24(struct msgb *msg, int type, uint32_t value) > +{ > + value = htonl(value & 0x00ffffff) >> 8; heh, interesting :) This won't work on a BE system. You can't portably do a host CPU shift-right on a network byte order value. Imagining a class: Big Endian 101, Professor asks: "Can anyone explain the results of this code on a big endian and a little endian system?" Half an hour of vigorous discussion follows. > + return msgb_tv_fixed_put(msg, type, 3, (uint8_t *)&value); > +} [...] Thanks again! -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 819 bytes Desc: Digital signature URL: <http://lists.osmocom.org/pipermail/openbsc/attachments/20151015/c44bfbfc/attachment.bin>