pespin has uploaded this change for review. ( 
https://gerrit.osmocom.org/c/osmo-ggsn/+/15246


Change subject: ggsn: Move PCO handling code into its own file
......................................................................

ggsn: Move PCO handling code into its own file

This way ggsn.c is shrinked in size and get rid of a lot of code there,
which is of no interest unless the reader is interesting in that really
specific part.

Change-Id: Ieaa7e71f17c7fd9377c76ef53362eab596d669a6
---
M ggsn/Makefile.am
M ggsn/ggsn.c
M ggsn/ggsn.h
A ggsn/pco.c
A ggsn/pco.h
5 files changed, 341 insertions(+), 312 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/osmo-ggsn refs/changes/46/15246/1

diff --git a/ggsn/Makefile.am b/ggsn/Makefile.am
index eca385f..022cdef 100644
--- a/ggsn/Makefile.am
+++ b/ggsn/Makefile.am
@@ -12,4 +12,4 @@
 endif

 osmo_ggsn_DEPENDENCIES = ../gtp/libgtp.la ../lib/libmisc.a
-osmo_ggsn_SOURCES = ggsn_vty.c ggsn.c ggsn.h icmpv6.c icmpv6.h checksum.c 
checksum.h
+osmo_ggsn_SOURCES = ggsn_vty.c ggsn.c ggsn.h icmpv6.c icmpv6.h checksum.c 
checksum.h pco.c pco.h
diff --git a/ggsn/ggsn.c b/ggsn/ggsn.c
index 593c319..7832338 100644
--- a/ggsn/ggsn.c
+++ b/ggsn/ggsn.c
@@ -68,6 +68,7 @@
 #include "../gtp/pdp.h"
 #include "../gtp/gtp.h"
 #include "icmpv6.h"
+#include "pco.h"
 #include "ggsn.h"

 void *tall_ggsn_ctx;
@@ -79,14 +80,6 @@
 struct ul255_t qos;
 struct ul255_t apn;

-#define LOGPAPN(level, apn, fmt, args...)                      \
-       LOGP(DGGSN, level, "APN(%s): " fmt, (apn)->cfg.name, ## args)
-
-#define LOGPGGSN(level, ggsn, fmt, args...)                    \
-       LOGP(DGGSN, level, "GGSN(%s): " fmt, (ggsn)->cfg.name, ## args)
-
-#define LOGPPDP(level, pdp, fmt, args...) LOGPDPX(DGGSN, level, pdp, fmt, ## 
args)
-
 static int ggsn_tun_fd_cb(struct osmo_fd *fd, unsigned int what);
 static int cb_tun_ind(struct tun_t *tun, void *pack, unsigned len);

@@ -396,92 +389,12 @@
        return 0;
 }

-#include <osmocom/gsm/tlv.h>
-
-/* RFC 1332 */
-enum ipcp_options {
-       IPCP_OPT_IPADDR = 3,
-       IPCP_OPT_PRIMARY_DNS = 129,
-       IPCP_OPT_SECONDARY_DNS = 131,
-};
-
-struct ipcp_option_hdr {
-       uint8_t type;
-       uint8_t len;
-       uint8_t data[0];
-} __attribute__ ((packed));
-
-struct ipcp_hdr {
-       uint8_t code;
-       uint8_t id;
-       uint16_t len;
-       uint8_t options[0];
-} __attribute__ ((packed));
-
-/* determine if IPCP contains given option */
-static const uint8_t *ipcp_contains_option(const struct ipcp_hdr *ipcp, size_t 
ipcp_len,
-                                          enum ipcp_options opt, size_t 
opt_minlen)
-{
-       const uint8_t *cur_opt = ipcp->options;
-
-       /* iterate over Options and check if protocol contained */
-       while (cur_opt + sizeof(struct ipcp_option_hdr) <= (uint8_t*)ipcp + 
ipcp_len) {
-               const struct ipcp_option_hdr *cur_opt_hdr = (const struct 
ipcp_option_hdr *)cur_opt;
-               /* length value includes 2 bytes type/length */
-               if (cur_opt_hdr->len < sizeof(struct ipcp_option_hdr))
-                       return NULL;
-               if (cur_opt_hdr->type == opt &&
-                   cur_opt_hdr->len >= sizeof(struct ipcp_option_hdr) + 
opt_minlen)
-                       return cur_opt;
-               cur_opt += cur_opt_hdr->len;
-       }
-       return NULL;
-}
-
-/* 3GPP TS 24.008 10.6.5.3 */
-enum pco_protocols {
-       PCO_P_LCP               = 0xC021,
-       PCO_P_PAP               = 0xC023,
-       PCO_P_CHAP              = 0xC223,
-       PCO_P_IPCP              = 0x8021,
-       PCO_P_PCSCF_ADDR        = 0x0001,
-       PCO_P_IM_CN_SS_F        = 0x0002,
-       PCO_P_DNS_IPv6_ADDR     = 0x0003,
-       PCO_P_POLICY_CTRL_REJ   = 0x0004,       /* only in Network->MS */
-       PCO_P_MS_SUP_NETREQ_BCI = 0x0005,
-       /* reserved */
-       PCO_P_DSMIPv6_HA_ADDR   = 0x0007,
-       PCO_P_DSMIPv6_HN_PREF   = 0x0008,
-       PCO_P_DSMIPv6_v4_HA_ADDR= 0x0009,
-       PCO_P_IP_ADDR_VIA_NAS   = 0x000a,       /* only MS->Network */
-       PCO_P_IPv4_ADDR_VIA_DHCP= 0x000b,       /* only MS->Netowrk */
-       PCO_P_PCSCF_IPv4_ADDR   = 0x000c,
-       PCO_P_DNS_IPv4_ADDR     = 0x000d,
-       PCO_P_MSISDN            = 0x000e,
-       PCO_P_IFOM_SUPPORT      = 0x000f,
-       PCO_P_IPv4_LINK_MTU     = 0x0010,
-       PCO_P_MS_SUPP_LOC_A_TFT = 0x0011,
-       PCO_P_PCSCF_RESEL_SUP   = 0x0012,       /* only MS->Network */
-       PCO_P_NBIFOM_REQ        = 0x0013,
-       PCO_P_NBIFOM_MODE       = 0x0014,
-       PCO_P_NONIP_LINK_MTU    = 0x0015,
-       PCO_P_APN_RATE_CTRL_SUP = 0x0016,
-       PCO_P_PS_DATA_OFF_UE    = 0x0017,
-       PCO_P_REL_DATA_SVC      = 0x0018,
-};
-
-struct pco_element {
-       uint16_t protocol_id;   /* network byte order */
-       uint8_t length;         /* length of data below */
-       uint8_t data[0];
-} __attribute__((packed));
-
 /*! Get the peer of pdp based on IP version used.
  *  \param[in] pdp PDP context to select the peer from.
  *  \param[in] v4v6 IP version to select. Valid values are 4 and 6.
  *  \returns The selected peer matching the given IP version. NULL if not 
present.
  */
-static struct ippoolm_t *pdp_get_peer_ipv(struct pdp_t *pdp, bool is_ipv6) {
+struct ippoolm_t *pdp_get_peer_ipv(struct pdp_t *pdp, bool is_ipv6) {
        uint8_t i;

        for (i = 0; i < 2; i++) {
@@ -496,228 +409,6 @@
        return NULL;
 }

-/* RFC 1334, section 3.2. Packet Format */
-struct pap_element {
-       uint8_t code;
-       uint8_t id;
-       uint16_t len; /* length including header */
-       uint8_t data[0];
-} __attribute__((packed));
-
-enum pap_code {
-       PAP_CODE_AUTH_REQ = 1,
-       PAP_CODE_AUTH_ACK = 2,
-       PAP_CODE_AUTH_NAK = 3,
-};
-
-static const char *pap_welcome = "Welcome to OsmoGGSN " PACKAGE_VERSION;
-
-/* Handle PAP protocol according to RFC 1334 */
-static void process_pco_element_pap(const struct pco_element *pco_in, struct 
msgb *resp,
-                                   const struct apn_ctx *apn, struct pdp_t 
*pdp)
-{
-       const struct pap_element *pap_in = (const struct pap_element *) 
pco_in->data;
-       uint16_t pap_in_len;
-       uint8_t peer_id_len;
-       const uint8_t *peer_id;
-       unsigned int pap_welcome_len;
-       uint8_t pap_out_size;
-       struct pap_element *pap_out;
-
-       if (pco_in->length < sizeof(struct pap_element))
-               goto ret_broken;
-
-       pap_in_len = osmo_load16be(&pap_in->len);
-       if (pco_in->length < pap_in_len)
-               goto ret_broken;
-       /* "pco_in->length > pap_in_len" is allowed: RFC1334 2.2 states:
-          "Octets outside the range of the Length field should be treated as
-          Data Link Layer padding and should be ignored on reception."
-        */
-
-       switch (pap_in->code) {
-       case PAP_CODE_AUTH_REQ:
-               if (pap_in_len < sizeof(struct pap_element) + 1)
-                       goto ret_broken_auth;
-               peer_id_len = pap_in->data[0];
-               if (pap_in_len < sizeof(struct pap_element) + 1 + peer_id_len)
-                       goto ret_broken_auth;
-               peer_id = &pap_in->data[1];
-               LOGPPDP(LOGL_DEBUG, pdp, "PCO PAP PeerId = %s, ACKing\n",
-                       osmo_quote_str((const char *)peer_id, peer_id_len));
-               /* Password-Length + Password following here, but we don't care 
*/
-
-               /* Prepare response, we ACK all of them: */
-               pap_welcome_len = strlen(pap_welcome);
-               /* +1: Length field of pap_welcome Message */
-               pap_out_size = sizeof(struct pap_element) + 1 + pap_welcome_len;
-               pap_out = alloca(pap_out_size);
-               pap_out->code = PAP_CODE_AUTH_ACK;
-               pap_out->id = pap_in->id;
-               pap_out->len = htons(pap_out_size);
-               pap_out->data[0] = pap_welcome_len;
-               memcpy(pap_out->data+1, pap_welcome, pap_welcome_len);
-               msgb_t16lv_put(resp, PCO_P_PAP, pap_out_size, (uint8_t *) 
pap_out);
-               break;
-       case PAP_CODE_AUTH_ACK:
-       case PAP_CODE_AUTH_NAK:
-       default:
-               LOGPPDP(LOGL_NOTICE, pdp, "Unsupported PAP PCO Code %u, 
ignoring\n", pap_in->code);
-               break;
-       }
-       return;
-
-ret_broken_auth:
-       LOGPPDP(LOGL_NOTICE, pdp, "Invalid PAP AuthenticateReq: %s, ignoring\n",
-               osmo_hexdump_nospc((const uint8_t *)pco_in, pco_in->length));
-       return;
-
-ret_broken:
-       LOGPPDP(LOGL_NOTICE, pdp, "Invalid PAP PCO Length: %s, ignoring\n",
-               osmo_hexdump_nospc((const uint8_t *)pco_in, pco_in->length));
-}
-
-static void process_pco_element_ipcp(const struct pco_element *pco_elem, 
struct msgb *resp,
-                                    const struct apn_ctx *apn, struct pdp_t 
*pdp)
-{
-       struct ippoolm_t *peer_v4 = pdp_get_peer_ipv(pdp, false);
-       const struct in46_addr *dns1 = &apn->v4.cfg.dns[0];
-       const struct in46_addr *dns2 = &apn->v4.cfg.dns[1];
-       uint8_t *start = resp->tail;
-       const struct ipcp_hdr *ipcp;
-       uint16_t ipcp_len;
-       uint8_t *len1, *len2;
-       unsigned int len_appended;
-       ptrdiff_t consumed;
-       size_t remain;
-
-       if (!peer_v4) {
-               LOGPPDP(LOGL_ERROR, pdp, "IPCP but no IPv4 type ?!?\n");
-               return;
-       }
-
-       ipcp = (const struct ipcp_hdr *)pco_elem->data;
-       consumed = (pco_elem->data - &pdp->pco_req.v[0]);
-       remain = sizeof(pdp->pco_req.v) - consumed;
-       ipcp_len = osmo_load16be(&ipcp->len);
-       if (remain < 0 || remain < ipcp_len) {
-               LOGPPDP(LOGL_ERROR, pdp, "Malformed IPCP, ignoring\n");
-               return;
-       }
-
-       /* Three byte T16L header */
-       msgb_put_u16(resp, 0x8021);     /* IPCP */
-       len1 = msgb_put(resp, 1);       /* Length of contents: delay */
-
-       msgb_put_u8(resp, 0x02);        /* ACK */
-       msgb_put_u8(resp, ipcp->id);    /* ID: Needs to match request */
-       msgb_put_u8(resp, 0x00);        /* Length MSB */
-       len2 = msgb_put(resp, 1);       /* Length LSB: delay */
-
-       if (dns1->len == 4 && ipcp_contains_option(ipcp, ipcp_len, 
IPCP_OPT_PRIMARY_DNS, 4)) {
-               msgb_put_u8(resp, 0x81);                /* DNS1 Tag */
-               msgb_put_u8(resp, 2 + dns1->len);       /* DNS1 Length, incl. 
TL */
-               msgb_put_u32(resp, ntohl(dns1->v4.s_addr));
-       }
-
-       if (dns2->len == 4 && ipcp_contains_option(ipcp, ipcp_len, 
IPCP_OPT_SECONDARY_DNS, 4)) {
-               msgb_put_u8(resp, 0x83);                /* DNS2 Tag */
-               msgb_put_u8(resp, 2 + dns2->len);       /* DNS2 Length, incl. 
TL */
-               msgb_put_u32(resp, ntohl(dns2->v4.s_addr));
-       }
-
-       /* patch in length values */
-       len_appended = resp->tail - start;
-       *len1 = len_appended - 3;
-       *len2 = len_appended - 3;
-}
-
-static void process_pco_element_dns_ipv6(const struct pco_element *pco_elem, 
struct msgb *resp,
-                                        const struct apn_ctx *apn, struct 
pdp_t *pdp)
-{
-       unsigned int i;
-       const uint8_t *tail = resp->tail;
-
-       for (i = 0; i < ARRAY_SIZE(apn->v6.cfg.dns); i++) {
-               const struct in46_addr *i46a = &apn->v6.cfg.dns[i];
-               if (i46a->len != 16)
-                       continue;
-               msgb_t16lv_put(resp, PCO_P_DNS_IPv6_ADDR, i46a->len, 
i46a->v6.s6_addr);
-       }
-       if (resp->tail == tail)
-               LOGPPDP(LOGL_NOTICE, pdp, "MS requested IPv6 DNS, but APN has 
none configured\n");
-}
-
-static void process_pco_element_dns_ipv4(const struct pco_element *pco_elem, 
struct msgb *resp,
-                                        const struct apn_ctx *apn, struct 
pdp_t *pdp)
-{
-       unsigned int i;
-       const uint8_t *tail = resp->tail;
-
-       for (i = 0; i < ARRAY_SIZE(apn->v4.cfg.dns); i++) {
-               const struct in46_addr *i46a = &apn->v4.cfg.dns[i];
-               if (i46a->len != 4)
-                       continue;
-               msgb_t16lv_put(resp, PCO_P_DNS_IPv4_ADDR, i46a->len, (uint8_t 
*)&i46a->v4);
-       }
-       if (resp->tail == tail)
-               LOGPPDP(LOGL_NOTICE, pdp, "MS requested IPv4 DNS, but APN has 
none configured\n");
-}
-
-static void process_pco_element(const struct pco_element *pco_elem, struct 
msgb *resp,
-                               const struct apn_ctx *apn, struct pdp_t *pdp)
-{
-       uint16_t protocol_id = osmo_load16be(&pco_elem->protocol_id);
-
-       LOGPPDP(LOGL_DEBUG, pdp, "PCO Protocol 0x%04x\n", protocol_id);
-       switch (protocol_id) {
-       case PCO_P_PAP:
-               process_pco_element_pap(pco_elem, resp, apn, pdp);
-               break;
-       case PCO_P_IPCP:
-               process_pco_element_ipcp(pco_elem, resp, apn, pdp);
-               break;
-       case PCO_P_DNS_IPv6_ADDR:
-               process_pco_element_dns_ipv6(pco_elem, resp, apn, pdp);
-               break;
-       case PCO_P_DNS_IPv4_ADDR:
-               process_pco_element_dns_ipv4(pco_elem, resp, apn, pdp);
-               break;
-       default:
-               LOGPPDP(LOGL_INFO, pdp, "Unknown/Unimplemented PCO Protocol 
0x%04x: %s\n",
-                       protocol_id, osmo_hexdump_nospc(pco_elem->data, 
pco_elem->length));
-               break;
-       }
-}
-
-/* process one PCO request from a MS/UE, putting together the proper responses 
*/
-static void process_pco(const struct apn_ctx *apn, struct pdp_t *pdp)
-{
-       struct msgb *resp = msgb_alloc(256, "PCO.resp");
-       const struct ul255_t *pco = &pdp->pco_req;
-       const struct pco_element *pco_elem;
-       const uint8_t *cur;
-
-       /* build the header of the PCO response */
-       OSMO_ASSERT(resp);
-       msgb_put_u8(resp, 0x80); /* ext-bit + configuration protocol byte */
-
-       /* iterate over the PCO elements in the request; call 
process_pco_element() for each */
-       for (cur = pco->v + 1, pco_elem = (const struct pco_element *) cur;
-            cur + sizeof(struct pco_element) <= pco->v + pco->l;
-            cur += pco_elem->length + sizeof(*pco_elem), pco_elem = (const 
struct pco_element *) cur) {
-               process_pco_element(pco_elem, resp, apn, pdp);
-       }
-
-       /* copy the PCO response msgb and copy its contents over to the PDP 
context */
-       if (msgb_length(resp) > 1) {
-               memcpy(pdp->pco_neg.v, msgb_data(resp), msgb_length(resp));
-               pdp->pco_neg.l = msgb_length(resp);
-       } else
-               pdp->pco_neg.l = 0;
-       msgb_free(resp);
-}
-
 static bool apn_supports_ipv4(const struct apn_ctx *apn)
 {
        if (apn->v4.cfg.static_prefix.addr.len  || 
apn->v4.cfg.dynamic_prefix.addr.len)
diff --git a/ggsn/ggsn.h b/ggsn/ggsn.h
index 09cd0f6..a37381f 100644
--- a/ggsn/ggsn.h
+++ b/ggsn/ggsn.h
@@ -141,3 +141,12 @@
 extern int ggsn_stop(struct ggsn_ctx *ggsn);
 extern int apn_start(struct apn_ctx *apn);
 extern int apn_stop(struct apn_ctx *apn);
+extern struct ippoolm_t *pdp_get_peer_ipv(struct pdp_t *pdp, bool is_ipv6);
+
+#define LOGPAPN(level, apn, fmt, args...)                      \
+       LOGP(DGGSN, level, "APN(%s): " fmt, (apn)->cfg.name, ## args)
+
+#define LOGPGGSN(level, ggsn, fmt, args...)                    \
+       LOGP(DGGSN, level, "GGSN(%s): " fmt, (ggsn)->cfg.name, ## args)
+
+#define LOGPPDP(level, pdp, fmt, args...) LOGPDPX(DGGSN, level, pdp, fmt, ## 
args)
diff --git a/ggsn/pco.c b/ggsn/pco.c
new file mode 100644
index 0000000..a55b768
--- /dev/null
+++ b/ggsn/pco.c
@@ -0,0 +1,248 @@
+/*
+ * PCO parsing related code
+ * Copyright 2019 sysmocom - s.f.m.c. GmbH <[email protected]>
+ *
+ * The contents of this file may be used under the terms of the GNU
+ * General Public License Version 2, provided that the above copyright
+ * notice and this permission notice is included in all copies or
+ * substantial portions of the software.
+ *
+ */
+
+#include <unistd.h>
+#include <string.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+
+#include "pco.h"
+#include "ggsn.h"
+
+/* determine if IPCP contains given option */
+static const uint8_t *ipcp_contains_option(const struct ipcp_hdr *ipcp, size_t 
ipcp_len,
+                                          enum ipcp_options opt, size_t 
opt_minlen)
+{
+       const uint8_t *cur_opt = ipcp->options;
+
+       /* iterate over Options and check if protocol contained */
+       while (cur_opt + sizeof(struct ipcp_option_hdr) <= (uint8_t*)ipcp + 
ipcp_len) {
+               const struct ipcp_option_hdr *cur_opt_hdr = (const struct 
ipcp_option_hdr *)cur_opt;
+               /* length value includes 2 bytes type/length */
+               if (cur_opt_hdr->len < sizeof(struct ipcp_option_hdr))
+                       return NULL;
+               if (cur_opt_hdr->type == opt &&
+                   cur_opt_hdr->len >= sizeof(struct ipcp_option_hdr) + 
opt_minlen)
+                       return cur_opt;
+               cur_opt += cur_opt_hdr->len;
+       }
+       return NULL;
+}
+
+
+static const char *pap_welcome = "Welcome to OsmoGGSN " PACKAGE_VERSION;
+
+/* Handle PAP protocol according to RFC 1334 */
+static void process_pco_element_pap(const struct pco_element *pco_in, struct 
msgb *resp,
+                                   const struct apn_ctx *apn, struct pdp_t 
*pdp)
+{
+       const struct pap_element *pap_in = (const struct pap_element *) 
pco_in->data;
+       uint16_t pap_in_len;
+       uint8_t peer_id_len;
+       const uint8_t *peer_id;
+       unsigned int pap_welcome_len;
+       uint8_t pap_out_size;
+       struct pap_element *pap_out;
+
+       if (pco_in->length < sizeof(struct pap_element))
+               goto ret_broken;
+
+       pap_in_len = osmo_load16be(&pap_in->len);
+       if (pco_in->length < pap_in_len)
+               goto ret_broken;
+       /* "pco_in->length > pap_in_len" is allowed: RFC1334 2.2 states:
+          "Octets outside the range of the Length field should be treated as
+          Data Link Layer padding and should be ignored on reception."
+        */
+
+       switch (pap_in->code) {
+       case PAP_CODE_AUTH_REQ:
+               if (pap_in_len < sizeof(struct pap_element) + 1)
+                       goto ret_broken_auth;
+               peer_id_len = pap_in->data[0];
+               if (pap_in_len < sizeof(struct pap_element) + 1 + peer_id_len)
+                       goto ret_broken_auth;
+               peer_id = &pap_in->data[1];
+               LOGPPDP(LOGL_DEBUG, pdp, "PCO PAP PeerId = %s, ACKing\n",
+                       osmo_quote_str((const char *)peer_id, peer_id_len));
+               /* Password-Length + Password following here, but we don't care 
*/
+
+               /* Prepare response, we ACK all of them: */
+               pap_welcome_len = strlen(pap_welcome);
+               /* +1: Length field of pap_welcome Message */
+               pap_out_size = sizeof(struct pap_element) + 1 + pap_welcome_len;
+               pap_out = alloca(pap_out_size);
+               pap_out->code = PAP_CODE_AUTH_ACK;
+               pap_out->id = pap_in->id;
+               pap_out->len = htons(pap_out_size);
+               pap_out->data[0] = pap_welcome_len;
+               memcpy(pap_out->data+1, pap_welcome, pap_welcome_len);
+               msgb_t16lv_put(resp, PCO_P_PAP, pap_out_size, (uint8_t *) 
pap_out);
+               break;
+       case PAP_CODE_AUTH_ACK:
+       case PAP_CODE_AUTH_NAK:
+       default:
+               LOGPPDP(LOGL_NOTICE, pdp, "Unsupported PAP PCO Code %u, 
ignoring\n", pap_in->code);
+               break;
+       }
+       return;
+
+ret_broken_auth:
+       LOGPPDP(LOGL_NOTICE, pdp, "Invalid PAP AuthenticateReq: %s, ignoring\n",
+               osmo_hexdump_nospc((const uint8_t *)pco_in, pco_in->length));
+       return;
+
+ret_broken:
+       LOGPPDP(LOGL_NOTICE, pdp, "Invalid PAP PCO Length: %s, ignoring\n",
+               osmo_hexdump_nospc((const uint8_t *)pco_in, pco_in->length));
+}
+
+static void process_pco_element_ipcp(const struct pco_element *pco_elem, 
struct msgb *resp,
+                                    const struct apn_ctx *apn, struct pdp_t 
*pdp)
+{
+       struct ippoolm_t *peer_v4 = pdp_get_peer_ipv(pdp, false);
+       const struct in46_addr *dns1 = &apn->v4.cfg.dns[0];
+       const struct in46_addr *dns2 = &apn->v4.cfg.dns[1];
+       uint8_t *start = resp->tail;
+       const struct ipcp_hdr *ipcp;
+       uint16_t ipcp_len;
+       uint8_t *len1, *len2;
+       unsigned int len_appended;
+       ptrdiff_t consumed;
+       size_t remain;
+
+       if (!peer_v4) {
+               LOGPPDP(LOGL_ERROR, pdp, "IPCP but no IPv4 type ?!?\n");
+               return;
+       }
+
+       ipcp = (const struct ipcp_hdr *)pco_elem->data;
+       consumed = (pco_elem->data - &pdp->pco_req.v[0]);
+       remain = sizeof(pdp->pco_req.v) - consumed;
+       ipcp_len = osmo_load16be(&ipcp->len);
+       if (remain < 0 || remain < ipcp_len) {
+               LOGPPDP(LOGL_ERROR, pdp, "Malformed IPCP, ignoring\n");
+               return;
+       }
+
+       /* Three byte T16L header */
+       msgb_put_u16(resp, 0x8021);     /* IPCP */
+       len1 = msgb_put(resp, 1);       /* Length of contents: delay */
+
+       msgb_put_u8(resp, 0x02);        /* ACK */
+       msgb_put_u8(resp, ipcp->id);    /* ID: Needs to match request */
+       msgb_put_u8(resp, 0x00);        /* Length MSB */
+       len2 = msgb_put(resp, 1);       /* Length LSB: delay */
+
+       if (dns1->len == 4 && ipcp_contains_option(ipcp, ipcp_len, 
IPCP_OPT_PRIMARY_DNS, 4)) {
+               msgb_put_u8(resp, 0x81);                /* DNS1 Tag */
+               msgb_put_u8(resp, 2 + dns1->len);       /* DNS1 Length, incl. 
TL */
+               msgb_put_u32(resp, ntohl(dns1->v4.s_addr));
+       }
+
+       if (dns2->len == 4 && ipcp_contains_option(ipcp, ipcp_len, 
IPCP_OPT_SECONDARY_DNS, 4)) {
+               msgb_put_u8(resp, 0x83);                /* DNS2 Tag */
+               msgb_put_u8(resp, 2 + dns2->len);       /* DNS2 Length, incl. 
TL */
+               msgb_put_u32(resp, ntohl(dns2->v4.s_addr));
+       }
+
+       /* patch in length values */
+       len_appended = resp->tail - start;
+       *len1 = len_appended - 3;
+       *len2 = len_appended - 3;
+}
+
+static void process_pco_element_dns_ipv6(const struct pco_element *pco_elem, 
struct msgb *resp,
+                                        const struct apn_ctx *apn, struct 
pdp_t *pdp)
+{
+       unsigned int i;
+       const uint8_t *tail = resp->tail;
+
+       for (i = 0; i < ARRAY_SIZE(apn->v6.cfg.dns); i++) {
+               const struct in46_addr *i46a = &apn->v6.cfg.dns[i];
+               if (i46a->len != 16)
+                       continue;
+               msgb_t16lv_put(resp, PCO_P_DNS_IPv6_ADDR, i46a->len, 
i46a->v6.s6_addr);
+       }
+       if (resp->tail == tail)
+               LOGPPDP(LOGL_NOTICE, pdp, "MS requested IPv6 DNS, but APN has 
none configured\n");
+}
+
+static void process_pco_element_dns_ipv4(const struct pco_element *pco_elem, 
struct msgb *resp,
+                                        const struct apn_ctx *apn, struct 
pdp_t *pdp)
+{
+       unsigned int i;
+       const uint8_t *tail = resp->tail;
+
+       for (i = 0; i < ARRAY_SIZE(apn->v4.cfg.dns); i++) {
+               const struct in46_addr *i46a = &apn->v4.cfg.dns[i];
+               if (i46a->len != 4)
+                       continue;
+               msgb_t16lv_put(resp, PCO_P_DNS_IPv4_ADDR, i46a->len, (uint8_t 
*)&i46a->v4);
+       }
+       if (resp->tail == tail)
+               LOGPPDP(LOGL_NOTICE, pdp, "MS requested IPv4 DNS, but APN has 
none configured\n");
+}
+
+static void process_pco_element(const struct pco_element *pco_elem, struct 
msgb *resp,
+                               const struct apn_ctx *apn, struct pdp_t *pdp)
+{
+       uint16_t protocol_id = osmo_load16be(&pco_elem->protocol_id);
+
+       LOGPPDP(LOGL_DEBUG, pdp, "PCO Protocol 0x%04x\n", protocol_id);
+       switch (protocol_id) {
+       case PCO_P_PAP:
+               process_pco_element_pap(pco_elem, resp, apn, pdp);
+               break;
+       case PCO_P_IPCP:
+               process_pco_element_ipcp(pco_elem, resp, apn, pdp);
+               break;
+       case PCO_P_DNS_IPv6_ADDR:
+               process_pco_element_dns_ipv6(pco_elem, resp, apn, pdp);
+               break;
+       case PCO_P_DNS_IPv4_ADDR:
+               process_pco_element_dns_ipv4(pco_elem, resp, apn, pdp);
+               break;
+       default:
+               LOGPPDP(LOGL_INFO, pdp, "Unknown/Unimplemented PCO Protocol 
0x%04x: %s\n",
+                       protocol_id, osmo_hexdump_nospc(pco_elem->data, 
pco_elem->length));
+               break;
+       }
+}
+
+/* process one PCO request from a MS/UE, putting together the proper responses 
*/
+void process_pco(const struct apn_ctx *apn, struct pdp_t *pdp)
+{
+       struct msgb *resp = msgb_alloc(256, "PCO.resp");
+       const struct ul255_t *pco = &pdp->pco_req;
+       const struct pco_element *pco_elem;
+       const uint8_t *cur;
+
+       /* build the header of the PCO response */
+       OSMO_ASSERT(resp);
+       msgb_put_u8(resp, 0x80); /* ext-bit + configuration protocol byte */
+
+       /* iterate over the PCO elements in the request; call 
process_pco_element() for each */
+       for (cur = pco->v + 1, pco_elem = (const struct pco_element *) cur;
+            cur + sizeof(struct pco_element) <= pco->v + pco->l;
+            cur += pco_elem->length + sizeof(*pco_elem), pco_elem = (const 
struct pco_element *) cur) {
+               process_pco_element(pco_elem, resp, apn, pdp);
+       }
+
+       /* copy the PCO response msgb and copy its contents over to the PDP 
context */
+       if (msgb_length(resp) > 1) {
+               memcpy(pdp->pco_neg.v, msgb_data(resp), msgb_length(resp));
+               pdp->pco_neg.l = msgb_length(resp);
+       } else
+               pdp->pco_neg.l = 0;
+       msgb_free(resp);
+}
diff --git a/ggsn/pco.h b/ggsn/pco.h
new file mode 100644
index 0000000..7ebe05a
--- /dev/null
+++ b/ggsn/pco.h
@@ -0,0 +1,81 @@
+#pragma once
+
+#include <stdint.h>
+
+#include "../gtp/pdp.h"
+
+/* 3GPP TS 24.008 10.6.5.3 */
+enum pco_protocols {
+       PCO_P_LCP               = 0xC021,
+       PCO_P_PAP               = 0xC023,
+       PCO_P_CHAP              = 0xC223,
+       PCO_P_IPCP              = 0x8021,
+       PCO_P_PCSCF_ADDR        = 0x0001,
+       PCO_P_IM_CN_SS_F        = 0x0002,
+       PCO_P_DNS_IPv6_ADDR     = 0x0003,
+       PCO_P_POLICY_CTRL_REJ   = 0x0004,       /* only in Network->MS */
+       PCO_P_MS_SUP_NETREQ_BCI = 0x0005,
+       /* reserved */
+       PCO_P_DSMIPv6_HA_ADDR   = 0x0007,
+       PCO_P_DSMIPv6_HN_PREF   = 0x0008,
+       PCO_P_DSMIPv6_v4_HA_ADDR= 0x0009,
+       PCO_P_IP_ADDR_VIA_NAS   = 0x000a,       /* only MS->Network */
+       PCO_P_IPv4_ADDR_VIA_DHCP= 0x000b,       /* only MS->Netowrk */
+       PCO_P_PCSCF_IPv4_ADDR   = 0x000c,
+       PCO_P_DNS_IPv4_ADDR     = 0x000d,
+       PCO_P_MSISDN            = 0x000e,
+       PCO_P_IFOM_SUPPORT      = 0x000f,
+       PCO_P_IPv4_LINK_MTU     = 0x0010,
+       PCO_P_MS_SUPP_LOC_A_TFT = 0x0011,
+       PCO_P_PCSCF_RESEL_SUP   = 0x0012,       /* only MS->Network */
+       PCO_P_NBIFOM_REQ        = 0x0013,
+       PCO_P_NBIFOM_MODE       = 0x0014,
+       PCO_P_NONIP_LINK_MTU    = 0x0015,
+       PCO_P_APN_RATE_CTRL_SUP = 0x0016,
+       PCO_P_PS_DATA_OFF_UE    = 0x0017,
+       PCO_P_REL_DATA_SVC      = 0x0018,
+};
+
+struct pco_element {
+       uint16_t protocol_id;   /* network byte order */
+       uint8_t length;         /* length of data below */
+       uint8_t data[0];
+} __attribute__((packed));
+
+
+/* RFC 1332 */
+enum ipcp_options {
+       IPCP_OPT_IPADDR = 3,
+       IPCP_OPT_PRIMARY_DNS = 129,
+       IPCP_OPT_SECONDARY_DNS = 131,
+};
+
+struct ipcp_option_hdr {
+       uint8_t type;
+       uint8_t len;
+       uint8_t data[0];
+} __attribute__ ((packed));
+
+struct ipcp_hdr {
+       uint8_t code;
+       uint8_t id;
+       uint16_t len;
+       uint8_t options[0];
+} __attribute__ ((packed));
+
+/* RFC 1334, section 3.2. Packet Format */
+struct pap_element {
+       uint8_t code;
+       uint8_t id;
+       uint16_t len; /* length including header */
+       uint8_t data[0];
+} __attribute__((packed));
+
+enum pap_code {
+       PAP_CODE_AUTH_REQ = 1,
+       PAP_CODE_AUTH_ACK = 2,
+       PAP_CODE_AUTH_NAK = 3,
+};
+
+struct apn_ctx;
+void process_pco(const struct apn_ctx *apn, struct pdp_t *pdp);

--
To view, visit https://gerrit.osmocom.org/c/osmo-ggsn/+/15246
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-ggsn
Gerrit-Branch: master
Gerrit-Change-Id: Ieaa7e71f17c7fd9377c76ef53362eab596d669a6
Gerrit-Change-Number: 15246
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <[email protected]>
Gerrit-MessageType: newchange

Reply via email to