Review at  https://gerrit.osmocom.org/3459

IPv6: Support PCO for IPv6 DNS addresses

In IPv6, DNS server information is not passed along as IPCP6 like
in IPv5 with IPCP.  The reason is that IPCP6 (for PPP) doesn't
support passing DNS server information.  Rather, the relevant RFCs
indicate DHCPv6 should be used even over point-to-point links.

3GPP decided to avoid DHCPv6 dependency for stateless autoconfiguration
(the only mandatory IPv6 configuration mechanism) and added some new
non-PPP-style PCO information elements ("containers") which can among
other things inform a MS about IPV6 DNS servers.

That same mechanism can also be used to inform the MS about IPv4 DNS
servers, so for IPv4 there are now two competing mechanisms: IPCP and
the new "native" PCO container.  With this patch, we support both
for IPv4.

Change-Id: I21499afd61def8c925f7838bde76f34d28214b56
---
M ggsn/ggsn.c
1 file changed, 137 insertions(+), 42 deletions(-)


  git pull ssh://gerrit.osmocom.org:29418/openggsn refs/changes/59/3459/1

diff --git a/ggsn/ggsn.c b/ggsn/ggsn.c
index c307177..991b54c 100644
--- a/ggsn/ggsn.c
+++ b/ggsn/ggsn.c
@@ -72,7 +72,7 @@
 struct in_addr listen_;
 struct in_addr netaddr, destaddr, net; /* Network interface       */
 size_t prefixlen;
-struct in_addr dns1, dns2;     /* PCO DNS address         */
+struct in46_addr dns1, dns2;   /* PCO DNS address         */
 char *ipup, *ipdown;           /* Filename of scripts     */
 int debug;                     /* Print debug output      */
 struct ul255_t pco;
@@ -175,6 +175,103 @@
        return 0;
 }
 
+#include <osmocom/gsm/tlv.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,
+};
+
+/* determine if PCO contains given protocol */
+static bool pco_contains_proto(struct ul255_t *pco, uint16_t prot)
+{
+       uint8_t *cur = pco->v + 1;
+
+       /* iterate over PCO and check if protocol contained */
+       while (cur + 2 < pco->v + pco->l) {
+               uint16_t cur_prot = osmo_load16be(cur);
+               uint8_t cur_len = cur[2];
+               if (cur_prot == prot)
+                       return true;
+               if (cur_len == 0)
+                       break;
+               cur += cur_len;
+       }
+       return false;
+}
+
+/* determine if PDP context has IPv6 support */
+static bool pdp_has_v4(struct pdp_t *pdp)
+{
+       if (pdp->eua.l == 4+2)
+               return true;
+       else
+               return false;
+}
+
+/* process one PCO request from a MS/UE, putting together the proper responses 
*/
+static void process_pco(struct pdp_t *pdp)
+{
+       struct msgb *msg = msgb_alloc(256, "PCO");
+       msgb_put_u8(msg, 0x80); /* ext-bit + configuration protocol byte */
+
+       /* FIXME: also check if primary / secondary DNS was requested */
+       if (pdp_has_v4(pdp) && pco_contains_proto(&pdp->pco_req, PCO_P_IPCP)) {
+               /* FIXME: properly implement this for IPCP */
+               uint8_t *cur = msgb_put(msg, pco.l-1);
+               memcpy(cur, pco.v+1, pco.l-1);
+       }
+
+       if (pco_contains_proto(&pdp->pco_req, PCO_P_DNS_IPv6_ADDR)) {
+               if (dns1.len == 16)
+                       msgb_t16lv_put(msg, PCO_P_DNS_IPv6_ADDR, dns1.len, 
dns1.v6.s6_addr);
+               if (dns2.len == 16)
+                       msgb_t16lv_put(msg, PCO_P_DNS_IPv6_ADDR, dns2.len, 
dns2.v6.s6_addr);
+       }
+
+       if (pco_contains_proto(&pdp->pco_req, PCO_P_DNS_IPv4_ADDR)) {
+               if (dns1.len == 4)
+                       msgb_t16lv_put(msg, PCO_P_DNS_IPv4_ADDR, dns1.len, 
(uint8_t *)&dns1.v4);
+               if (dns2.len == 4)
+                       msgb_t16lv_put(msg, PCO_P_DNS_IPv4_ADDR, dns2.len, 
(uint8_t *)&dns2.v4);
+       }
+
+       if (msgb_length(msg) > 1) {
+               memcpy(pdp->pco_neg.v, msgb_data(msg), msgb_length(msg));
+               pdp->pco_neg.l = msgb_length(msg);
+       } else
+               pdp->pco_neg.l = 0;
+
+       msgb_free(msg);
+}
+
 int create_context_ind(struct pdp_t *pdp)
 {
        struct in46_addr addr;
@@ -188,7 +285,6 @@
                pdp->eua.l = 2;
 
        memcpy(pdp->qos_neg0, pdp->qos_req0, sizeof(pdp->qos_req0));
-       memcpy(&pdp->pco_neg, &pco, sizeof(pdp->pco_neg));
 
        memcpy(pdp->qos_neg.v, pdp->qos_req.v, pdp->qos_req.l); /* TODO */
        pdp->qos_neg.l = pdp->qos_req.l;
@@ -235,6 +331,8 @@
                gtp_create_context_resp(gsn, pdp, GTPCAUSE_NO_RESOURCES);
                return 0;
        }
+
+       process_pco(pdp);
 
        gtp_create_context_resp(gsn, pdp, GTPCAUSE_ACC_REQ);
        return 0;               /* Success */
@@ -486,59 +584,56 @@
        }
 
        /* DNS1 and DNS2 */
-#ifdef HAVE_INET_ATON
-       dns1.s_addr = 0;
+       memset(&dns1, 0, sizeof(dns1));
        if (args_info.pcodns1_arg) {
-               if (0 == inet_aton(args_info.pcodns1_arg, &dns1)) {
+               size_t tmp;
+               if (ippool_aton(&dns1, &tmp, args_info.pcodns1_arg, 0) != 0) {
                        SYS_ERR(DGGSN, LOGL_ERROR, 0,
                                "Failed to convert pcodns1!");
                        exit(1);
                }
        }
-       dns2.s_addr = 0;
+       memset(&dns2, 0, sizeof(dns2));
        if (args_info.pcodns2_arg) {
-               if (0 == inet_aton(args_info.pcodns2_arg, &dns2)) {
+               size_t tmp;
+               if (ippool_aton(&dns2, &tmp, args_info.pcodns2_arg, 0) != 0) {
                        SYS_ERR(DGGSN, LOGL_ERROR, 0,
                                "Failed to convert pcodns2!");
                        exit(1);
                }
        }
-#else
-       dns1.s_addr = 0;
-       if (args_info.pcodns1_arg) {
-               dns1.s_addr = inet_addr(args_info.pcodns1_arg);
-               if (dns1.s_addr == -1) {
-                       SYS_ERR(DGGSN, LOGL_ERROR, 0,
-                               "Failed to convert pcodns1!");
-                       exit(1);
-               }
-       }
-       dns2.s_addr = 0;
-       if (args_info.pcodns2_arg) {
-               dns2.s_addr = inet_addr(args_info.pcodns2_arg);
-               if (dns2.s_addr == -1) {
-                       SYS_ERR(DGGSN, LOGL_ERROR, 0,
-                               "Failed to convert pcodns2!");
-                       exit(1);
-               }
-       }
-#endif
 
-       pco.l = 20;
-       pco.v[0] = 0x80;        /* x0000yyy x=1, yyy=000: PPP */
-       pco.v[1] = 0x80;        /* IPCP */
-       pco.v[2] = 0x21;
-       pco.v[3] = 0x10;        /* Length of contents */
-       pco.v[4] = 0x02;        /* ACK */
-       pco.v[5] = 0x00;        /* ID: Need to match request */
-       pco.v[6] = 0x00;        /* Length */
-       pco.v[7] = 0x10;
-       pco.v[8] = 0x81;        /* DNS 1 */
-       pco.v[9] = 0x06;
-       memcpy(&pco.v[10], &dns1, sizeof(dns1));
-       pco.v[14] = 0x83;
-       pco.v[15] = 0x06;       /* DNS 2 */
-       memcpy(&pco.v[16], &dns2, sizeof(dns2));
+       unsigned int cur = 0;
+       pco.v[cur++] = 0x80;    /* x0000yyy x=1, yyy=000: PPP */
+       pco.v[cur++] = 0x80;    /* IPCP */
+       pco.v[cur++] = 0x21;
+       pco.v[cur++] = 0xFF;    /* Length of contents */
+       pco.v[cur++] = 0x02;    /* ACK */
+       pco.v[cur++] = 0x00;    /* ID: Need to match request */
+       pco.v[cur++] = 0x00;    /* Length */
+       pco.v[cur++] = 0xFF;    /* overwritten  */
+       if (dns1.len == 4) {
+               pco.v[cur++] = 0x81;    /* DNS 1 */
+               pco.v[cur++] = 2 + dns1.len;
+               if (dns1.len == 4)
+                       memcpy(&pco.v[cur], &dns1.v4, dns1.len);
+               else
+                       memcpy(&pco.v[cur], &dns1.v6, dns1.len);
+               cur += dns1.len;
+       }
+       if (dns2.len == 4) {
+               pco.v[cur++] = 0x83;
+               pco.v[cur++] = 2 + dns2.len;    /* DNS 2 */
+               if (dns2.len == 4)
+                       memcpy(&pco.v[cur], &dns2.v4, dns2.len);
+               else
+                       memcpy(&pco.v[cur], &dns2.v6, dns2.len);
+               cur += dns2.len;
+       }
+       pco.l = cur;
+       /* patch in length values */
+       pco.v[3] = pco.l - 4;
+       pco.v[7] = pco.l - 4;
 
        /* ipup */
        ipup = args_info.ipup_arg;

-- 
To view, visit https://gerrit.osmocom.org/3459
To unsubscribe, visit https://gerrit.osmocom.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I21499afd61def8c925f7838bde76f34d28214b56
Gerrit-PatchSet: 1
Gerrit-Project: openggsn
Gerrit-Branch: master
Gerrit-Owner: Harald Welte <lafo...@gnumonks.org>

Reply via email to