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/gerrit-log@lists.osmocom.org/.
Pau Espin Pedrol gerrit-no-reply at lists.osmocom.orgHello Jenkins Builder, I'd like you to reexamine a change. Please visit https://gerrit.osmocom.org/5216 to look at the new patch set (#2). Add support for IPv4v6 End User Addresses Before this commit, when an MS requested an ipv4v6 context osmo-ggsn returned an error stating the type was unknown, and this text was printed in the log: Processing create PDP context request for APN 'ims' Cannot decode EUA from MS/SGSN: f1 8d This patch has been tested with an MS running the 3 types of addresses: - IPv4 and IPv6: no regressions observed, the context is activated and packets are sent to the ggsn. - IPv4v6: Wireshark correctly parses request and reponse, and then ICMPv6 traffic from both sides. Finally I see the MS using the IPv4 and IPv6 DNS addresses advertised and TCP traffic over IPv4 (because probably my IPv6 network setup is not correct). I also checked I can disable/enable data (pdp ctx delete and activate) several times without any issue. Change-Id: Ic820759167fd3bdf329cb11d4b942e903fe50af5 --- M ggsn/ggsn.c M ggsn/icmpv6.c M gtp/pdp.h M lib/in46_addr.c M lib/in46_addr.h M sgsnemu/sgsnemu.c M tests/lib/in46a_test.c M tests/lib/in46a_v6_test.ok 8 files changed, 218 insertions(+), 78 deletions(-) git pull ssh://gerrit.osmocom.org:29418/osmo-ggsn refs/changes/16/5216/2 diff --git a/ggsn/ggsn.c b/ggsn/ggsn.c index 1c1276f..eba7c2a 100644 --- a/ggsn/ggsn.c +++ b/ggsn/ggsn.c @@ -335,17 +335,23 @@ static int delete_context(struct pdp_t *pdp) { struct gsn_t *gsn = pdp->gsn; - struct ippoolm_t *ipp = (struct ippoolm_t *)pdp->peer; struct apn_ctx *apn = pdp->priv; + struct ippoolm_t *member; LOGPPDP(LOGL_INFO, pdp, "Deleting PDP context\n"); - struct ippoolm_t *member = pdp->peer; - if (pdp->peer) { + if (pdp->peer[0]) { + member = pdp->peer[0]; send_trap(gsn, pdp, member, "imsi-rem-ip"); /* TRAP with IP removal */ - ippool_freeip(ipp->pool, ipp); + ippool_freeip(member->pool, member); } else LOGPPDP(LOGL_ERROR, pdp, "Cannot find/free IP Pool member\n"); + + if (pdp->peer[1]) { /* ipv4v6 */ + member = pdp->peer[1]; + send_trap(gsn, pdp, member, "imsi-rem-ip"); /* TRAP with IP removal */ + ippool_freeip(member->pool, member); + } if (gtp_kernel_tunnel_del(pdp, apn->tun.cfg.dev_name)) { LOGPPDP(LOGL_ERROR, pdp, "Cannot delete tunnel from kernel:%s\n", @@ -512,10 +518,10 @@ static char name_buf[256]; struct gsn_t *gsn = pdp->gsn; struct ggsn_ctx *ggsn = gsn->priv; - struct in46_addr addr; - struct ippoolm_t *member; + struct in46_addr addr[2]; + struct ippoolm_t *member = NULL; struct apn_ctx *apn; - int rc; + int rc, num_addr, i; osmo_apn_to_str(name_buf, pdp->apn_req.v, pdp->apn_req.l); @@ -550,55 +556,63 @@ memcpy(pdp->qos_neg.v, pdp->qos_req.v, pdp->qos_req.l); /* TODO */ pdp->qos_neg.l = pdp->qos_req.l; - if (in46a_from_eua(&pdp->eua, &addr)) { + memset(addr, 0, sizeof(addr[0])*2); + if ((num_addr = in46a_from_eua(&pdp->eua, addr)) < 0) { LOGPPDP(LOGL_ERROR, pdp, "Cannot decode EUA from MS/SGSN: %s\n", osmo_hexdump(pdp->eua.v, pdp->eua.l)); gtp_create_context_resp(gsn, pdp, GTPCAUSE_UNKNOWN_PDP); return 0; } - if (addr.len == sizeof(struct in_addr)) { - /* does this APN actually have an IPv4 pool? */ - if (!apn_supports_ipv4(apn)) - goto err_wrong_af; + /* Allocate dynamic addresses from the pool */ + for (i = 0; i < num_addr; i++) { + if (addr[i].len == sizeof(struct in_addr)) { + /* does this APN actually have an IPv4 pool? */ + if (!apn_supports_ipv4(apn)) + goto err_wrong_af; - rc = ippool_newip(apn->v4.pool, &member, &addr, 0); - if (rc < 0) - goto err_pool_full; - in46a_to_eua(&member->addr, &pdp->eua); + rc = ippool_newip(apn->v4.pool, &member, &addr[i], 0); + if (rc < 0) + goto err_pool_full; + /* copy back */ + memcpy(&addr[i].v4.s_addr, &member->addr.v4, 4); + } else if (addr[i].len == sizeof(struct in6_addr)) { + + /* does this APN actually have an IPv6 pool? */ + if (!apn_supports_ipv6(apn)) + goto err_wrong_af; + + rc = ippool_newip(apn->v6.pool, &member, &addr[i], 0); + if (rc < 0) + goto err_pool_full; + + /* IPv6 doesn't really send the real/allocated address at this point, but just + * the link-identifier which the MS shall use for router solicitation */ + /* initialize upper 64 bits to prefix, they are discarded by MS anyway */ + memcpy(addr[i].v6.s6_addr, &member->addr.v6, 8); + /* use allocated 64bit prefix as lower 64bit, used as link id by MS */ + memcpy(addr[i].v6.s6_addr+8, &member->addr.v6, 8); + } else + OSMO_ASSERT(0); + + pdp->peer[i] = member; + member->peer = pdp; + } + + in46a_to_eua(addr, num_addr, &pdp->eua); + + if (apn_supports_ipv4(apn)) { /* TODO: In IPv6, EUA doesn't contain the actual IP addr/prefix! */ if (gtp_kernel_tunnel_add(pdp, apn->tun.cfg.dev_name) < 0) { LOGPPDP(LOGL_ERROR, pdp, "Cannot add tunnel to kernel: %s\n", strerror(errno)); gtp_create_context_resp(gsn, pdp, GTPCAUSE_SYS_FAIL); return 0; } - } else if (addr.len == sizeof(struct in6_addr)) { - struct in46_addr tmp; + } - /* does this APN actually have an IPv6 pool? */ - if (!apn_supports_ipv6(apn)) - goto err_wrong_af; - - rc = ippool_newip(apn->v6.pool, &member, &addr, 0); - if (rc < 0) - goto err_pool_full; - - /* IPv6 doesn't really send the real/allocated address at this point, but just - * the link-identifier which the MS shall use for router solicitation */ - tmp.len = addr.len; - /* initialize upper 64 bits to prefix, they are discarded by MS anyway */ - memcpy(tmp.v6.s6_addr, &member->addr.v6, 8); - /* use allocated 64bit prefix as lower 64bit, used as link id by MS */ - memcpy(tmp.v6.s6_addr+8, &member->addr.v6, 8); - in46a_to_eua(&tmp, &pdp->eua); - } else - OSMO_ASSERT(0); - - pdp->peer = member; pdp->ipif = apn->tun.tun; /* TODO */ pdp->priv = apn; - member->peer = pdp; if (!send_trap(gsn, pdp, member, "imsi-ass-ip")) { /* TRAP with IP assignment */ gtp_create_context_resp(gsn, pdp, GTPCAUSE_NO_RESOURCES); diff --git a/ggsn/icmpv6.c b/ggsn/icmpv6.c index 11ced24..6564a54 100644 --- a/ggsn/icmpv6.c +++ b/ggsn/icmpv6.c @@ -183,12 +183,17 @@ int handle_router_mcast(struct gsn_t *gsn, struct pdp_t *pdp, const struct in6_addr *own_ll_addr, const uint8_t *pack, unsigned len) { - struct ippoolm_t *member = pdp->peer; + struct ippoolm_t *member; const struct ip6_hdr *ip6h = (struct ip6_hdr *)pack; const struct icmpv6_hdr *ic6h = (struct icmpv6_hdr *) (pack + sizeof(*ip6h)); struct msgb *msg; OSMO_ASSERT(pdp); + + member = pdp->peer[0]; + OSMO_ASSERT(member); + if (member->addr.len == sizeof(struct in_addr)) /* ipv4v6 context */ + member = pdp->peer[1]; OSMO_ASSERT(member); if (len < sizeof(*ip6h)) { diff --git a/gtp/pdp.h b/gtp/pdp.h index f1d8ad6..b581952 100644 --- a/gtp/pdp.h +++ b/gtp/pdp.h @@ -26,6 +26,7 @@ #define PDP_EUA_ORG_IETF 0xF1 #define PDP_EUA_TYPE_v4 0x21 #define PDP_EUA_TYPE_v6 0x57 +#define PDP_EUA_TYPE_v4v6 0x8D /* GTP Information elements from 29.060 v3.9.0 7.7 Information Elements */ /* Also covers version 0. Note that version 0 6: QOS Profile was superceded * @@ -121,7 +122,7 @@ /* Parameters shared by all PDP context belonging to the same MS */ void *ipif; /* IP network interface */ - void *peer; /* Pointer to peer protocol */ + void *peer[2]; /* Pointer to peer protocol */ void *asap; /* Application specific service access point */ uint64_t imsi; /* International Mobile Subscriber Identity. */ diff --git a/lib/in46_addr.c b/lib/in46_addr.c index 36ad6af..dda2ef9 100644 --- a/lib/in46_addr.c +++ b/lib/in46_addr.c @@ -253,33 +253,54 @@ } } -/*! Convert given PDP End User Address to in46_addr +/*! Convert given array of in46_addr to PDP End User Address + * \param[in] dst Array containing 1 or 2 in46_addr + * \param[out] eua End User Address structure to fill * \returns 0 on success; negative on error */ -int in46a_to_eua(const struct in46_addr *src, struct ul66_t *eua) +int in46a_to_eua(const struct in46_addr *src, unsigned int size, struct ul66_t *eua) { - switch (src->len) { - case 4: - eua->l = 6; - eua->v[0] = PDP_EUA_ORG_IETF; - eua->v[1] = PDP_EUA_TYPE_v4; - memcpy(&eua->v[2], &src->v4, 4); /* Copy a 4 byte address */ - break; - case 8: - case 16: - eua->l = 18; - eua->v[0] = PDP_EUA_ORG_IETF; - eua->v[1] = PDP_EUA_TYPE_v6; - memcpy(&eua->v[2], &src->v6, 16); /* Copy a 16 byte address */ - break; - default: - OSMO_ASSERT(0); - return -1; + const struct in46_addr *src_v4, *src_v6; + if (size == 1) { + switch (src->len) { + case 4: + eua->l = 6; + eua->v[0] = PDP_EUA_ORG_IETF; + eua->v[1] = PDP_EUA_TYPE_v4; + memcpy(&eua->v[2], &src->v4, 4); /* Copy a 4 byte address */ + break; + case 8: + case 16: + eua->l = 18; + eua->v[0] = PDP_EUA_ORG_IETF; + eua->v[1] = PDP_EUA_TYPE_v6; + memcpy(&eua->v[2], &src->v6, 16); /* Copy a 16 byte address */ + break; + default: + OSMO_ASSERT(0); + return -1; + } + return 0; } + + if (src[0].len == src[1].len) + return -1; /* we should have a v4 and a v6 address */ + + src_v4 = (src[0].len == 4) ? &src[0] : &src[1]; + src_v6 = (src[0].len == 4) ? &src[1] : &src[0]; + + eua->l = 22; + eua->v[0] = PDP_EUA_ORG_IETF; + eua->v[1] = PDP_EUA_TYPE_v4v6; + memcpy(&eua->v[2], &src_v4->v4, 4); + memcpy(&eua->v[6], &src_v6->v6, 16); + return 0; } -/*! Convert given in46_addr to PDP End User Address - * \returns 0 on success; negative on error */ +/*! Convert given PDP End User Address to an array of in46_addr + * \param[in] eua End User Address structure to parse + * \param[out] dst Array containing 2 in46_addr + * \returns number of parsed addresses on success; negative on error */ int in46a_from_eua(const struct ul66_t *eua, struct in46_addr *dst) { if (eua->l < 2) @@ -295,22 +316,46 @@ memcpy(&dst->v4, &eua->v[2], 4); /* Copy a 4 byte address */ else dst->v4.s_addr = 0; - break; + return 1; case PDP_EUA_TYPE_v6: dst->len = 16; if (eua->l >= 18) memcpy(&dst->v6, &eua->v[2], 16); /* Copy a 16 byte address */ else memset(&dst->v6, 0, 16); - break; + return 1; + case PDP_EUA_TYPE_v4v6: + /* 3GPP TS 29.060, section 7.7.27 */ + switch (eua->l) { + case 2: /* v4 & v6 dynamic */ + dst[0].v4.s_addr = 0; + memset(&dst[1].v6, 0, 16); + break; + case 6: /* v4 static, v6 dynamic */ + memcpy(&dst[0].v4, &eua->v[2], 4); + memset(&dst[1].v6, 0, 16); + break; + case 18: /* v4 dynamic, v6 static */ + dst[0].v4.s_addr = 0; + memcpy(&dst[1].v6, &eua->v[2], 16); + break; + case 22: /* v4 & v6 static */ + memcpy(&dst[0].v4, &eua->v[2], 4); + memcpy(&dst[1].v6, &eua->v[6], 16); + break; + default: + return -1; + } + dst[0].len = 4; + dst[1].len = 16; + return 2; default: return -1; } - return 0; default_to_dyn_v4: /* assume dynamic IPv4 by default */ dst->len = 4; dst->v4.s_addr = 0; - return 0; + return 1; } diff --git a/lib/in46_addr.h b/lib/in46_addr.h index ff26521..e4654cc 100644 --- a/lib/in46_addr.h +++ b/lib/in46_addr.h @@ -29,5 +29,5 @@ extern int in46a_within_mask(const struct in46_addr *addr, const struct in46_addr *net, size_t prefixlen); unsigned int in46a_netmasklen(const struct in46_addr *netmask); -int in46a_to_eua(const struct in46_addr *src, struct ul66_t *eua); +int in46a_to_eua(const struct in46_addr *src, unsigned int size, struct ul66_t *eua); int in46a_from_eua(const struct ul66_t *eua, struct in46_addr *dst); diff --git a/sgsnemu/sgsnemu.c b/sgsnemu/sgsnemu.c index c31f875..300183b 100644 --- a/sgsnemu/sgsnemu.c +++ b/sgsnemu/sgsnemu.c @@ -1617,7 +1617,7 @@ /* Otherwise it is deallocated by gtplib */ pdp_newpdp(&pdp, myimsi, options.nsapi, NULL); - pdp->peer = &iparr[n]; + pdp->peer[0] = &iparr[n]; /* FIXME: support v4v6, have 2 peers */ pdp->ipif = tun; /* TODO */ iparr[n].pdp = pdp; diff --git a/tests/lib/in46a_test.c b/tests/lib/in46a_test.c index c0bb670..b22da16 100644 --- a/tests/lib/in46a_test.c +++ b/tests/lib/in46a_test.c @@ -137,7 +137,7 @@ #endif /* IPv4 address */ - OSMO_ASSERT(in46a_to_eua(&g_ia4, &eua) == 0); + OSMO_ASSERT(in46a_to_eua(&g_ia4, 1, &eua) == 0); OSMO_ASSERT(eua.v[0] == PDP_EUA_ORG_IETF); OSMO_ASSERT(eua.v[1] == PDP_EUA_TYPE_v4); OSMO_ASSERT(osmo_load32le(&eua.v[2]) == g_ia4.v4.s_addr); @@ -154,7 +154,7 @@ printf("Testing in46a_from_eua() with IPv4 addresses\n"); /* default: v4 unspec */ - OSMO_ASSERT(in46a_from_eua(&eua, &ia) == 0); + OSMO_ASSERT(in46a_from_eua(&eua, &ia) == 1); OSMO_ASSERT(ia.len == 4); OSMO_ASSERT(ia.v4.s_addr == 0); @@ -173,14 +173,14 @@ /* unspecified V4 */ memcpy(eua.v, v4_unspec, sizeof(v4_unspec)); eua.l = sizeof(v4_unspec); - OSMO_ASSERT(in46a_from_eua(&eua, &ia) == 0); + OSMO_ASSERT(in46a_from_eua(&eua, &ia) == 1); OSMO_ASSERT(ia.len == 4); OSMO_ASSERT(ia.v4.s_addr == 0); /* specified V4 */ memcpy(eua.v, v4_spec, sizeof(v4_spec)); eua.l = sizeof(v4_spec); - OSMO_ASSERT(in46a_from_eua(&eua, &ia) == 0); + OSMO_ASSERT(in46a_from_eua(&eua, &ia) == 1); OSMO_ASSERT(ia.len == 4); OSMO_ASSERT(ia.v4.s_addr == htonl(0x01020304)); } @@ -278,19 +278,41 @@ }; struct ul66_t eua; - printf("testing in46a_to_eua() with IPv6 addresses\n"); + printf("Testing in46a_to_eua() with IPv6 addresses\n"); /* IPv6 address */ - OSMO_ASSERT(in46a_to_eua(&g_ia6, &eua) == 0); + OSMO_ASSERT(in46a_to_eua(&g_ia6, 1, &eua) == 0); OSMO_ASSERT(eua.v[0] == PDP_EUA_ORG_IETF); OSMO_ASSERT(eua.v[1] == PDP_EUA_TYPE_v6); OSMO_ASSERT(!memcmp(&eua.v[2], &g_ia6.v6, 16)); /* IPv6 address with prefix / length 8 */ - OSMO_ASSERT(in46a_to_eua(&ia_v6_8, &eua) == 0); + OSMO_ASSERT(in46a_to_eua(&ia_v6_8, 1, &eua) == 0); OSMO_ASSERT(eua.v[0] == PDP_EUA_ORG_IETF); OSMO_ASSERT(eua.v[1] == PDP_EUA_TYPE_v6); OSMO_ASSERT(!memcmp(&eua.v[2], &ia_v6_8.v6, 16)); +} + +static void test_in46a_to_eua_v4v6() { + const struct in46_addr ia_v4v6[2] = { + { + .len = 16, + .v6.s6_addr = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 }, + }, + { + .len = 4, + .v4.s_addr = 0x0d0c0b0a, + } + }; + struct ul66_t eua; + printf("Testing in46a_to_eua() with IPv4v6 addresses\n"); + + /* IPv4 address */ + OSMO_ASSERT(in46a_to_eua(ia_v4v6, 2, &eua) == 0); + OSMO_ASSERT(eua.v[0] == PDP_EUA_ORG_IETF); + OSMO_ASSERT(eua.v[1] == PDP_EUA_TYPE_v4v6); + OSMO_ASSERT(osmo_load32le(&eua.v[2]) == g_ia4.v4.s_addr); + OSMO_ASSERT(!memcmp(&eua.v[6], &g_ia6.v6, 16)); } static void test_in46a_from_eua_v6(void) @@ -308,16 +330,65 @@ /* unspecified V6 */ memcpy(eua.v, v6_unspec, sizeof(v6_unspec)); eua.l = sizeof(v6_unspec); - OSMO_ASSERT(in46a_from_eua(&eua, &ia) == 0); + OSMO_ASSERT(in46a_from_eua(&eua, &ia) == 1); OSMO_ASSERT(ia.len == 16); OSMO_ASSERT(IN6_IS_ADDR_UNSPECIFIED(&ia.v6)); /* specified V6 */ memcpy(eua.v, v6_spec, sizeof(v6_spec)); eua.l = sizeof(v6_spec); - OSMO_ASSERT(in46a_from_eua(&eua, &ia) == 0); + OSMO_ASSERT(in46a_from_eua(&eua, &ia) == 1); OSMO_ASSERT(ia.len == 16); OSMO_ASSERT(!memcmp(&ia.v6, v6_spec+2, ia.len)); +} + +static void test_in46a_from_eua_v4v6(void) { + struct in46_addr ia[2]; + struct ul66_t eua; + const uint8_t v4_unspec_v6_unspec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v4v6 }; + const uint8_t v4_spec_v6_unspec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v4v6, 1,2,3,4 }; + const uint8_t v4_unspec_v6_spec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v4v6, 1,2,3,4,5,6,7,8,9,0xa,0xb,0xc,0xd,0xe,0xf,0x10 }; + const uint8_t v4_spec_v6_spec[] = { PDP_EUA_ORG_IETF, PDP_EUA_TYPE_v4v6, 1,2,3,4, 1,2,3,4,5,6,7,8,9,0xa,0xb,0xc,0xd,0xe,0xf,0x10 }; + + memset(&eua, 0, sizeof(eua)); + + printf("Testing in46a_from_eua() with IPv4v6 addresses\n"); + + /* unspecified V4 & V6 */ + memcpy(eua.v, v4_unspec_v6_unspec, sizeof(v4_unspec_v6_unspec)); + eua.l = sizeof(v4_unspec_v6_unspec); + OSMO_ASSERT(in46a_from_eua(&eua, ia) == 2); + OSMO_ASSERT(ia[0].len == 4); + OSMO_ASSERT(ia[1].len == 16); + OSMO_ASSERT(ia[0].v4.s_addr == 0); + OSMO_ASSERT(IN6_IS_ADDR_UNSPECIFIED(&ia[1].v6)); + + /* specified V4, unspecified V6 */ + memcpy(eua.v, v4_spec_v6_unspec, sizeof(v4_spec_v6_unspec)); + eua.l = sizeof(v4_spec_v6_unspec); + OSMO_ASSERT(in46a_from_eua(&eua, ia) == 2); + OSMO_ASSERT(ia[0].len == 4); + OSMO_ASSERT(ia[1].len == 16); + OSMO_ASSERT(ia[0].v4.s_addr == htonl(0x01020304)); + OSMO_ASSERT(IN6_IS_ADDR_UNSPECIFIED(&ia[1].v6)); + + /* unspecified V4, specified V6 */ + memcpy(eua.v, v4_unspec_v6_spec, sizeof(v4_unspec_v6_spec)); + eua.l = sizeof(v4_unspec_v6_spec); + OSMO_ASSERT(in46a_from_eua(&eua, ia) == 2); + OSMO_ASSERT(ia[0].len == 4); + OSMO_ASSERT(ia[1].len == 16); + OSMO_ASSERT(ia[0].v4.s_addr == 0); + OSMO_ASSERT(!memcmp(&ia[1].v6, v4_unspec_v6_spec+2, ia[1].len)); + + /* specified V4, specified V6 */ + memcpy(eua.v, v4_spec_v6_spec, sizeof(v4_spec_v6_spec)); + eua.l = sizeof(v4_spec_v6_spec); + OSMO_ASSERT(in46a_from_eua(&eua, ia) == 2); + OSMO_ASSERT(ia[0].len == 4); + OSMO_ASSERT(ia[1].len == 16); + OSMO_ASSERT(ia[0].v4.s_addr == htonl(0x01020304)); + OSMO_ASSERT(!memcmp(&ia[1].v6, v4_spec_v6_spec+6, ia[1].len)); } static void test_in46a_netmasklen_v6(void) @@ -378,6 +449,8 @@ test_in46a_equal_v6(); test_in46a_to_eua_v6(); test_in46a_from_eua_v6(); + test_in46a_to_eua_v4v6(); + test_in46a_from_eua_v4v6(); test_in46a_netmasklen_v6(); } return 0; diff --git a/tests/lib/in46a_v6_test.ok b/tests/lib/in46a_v6_test.ok index d092591..10dc7f4 100644 --- a/tests/lib/in46a_v6_test.ok +++ b/tests/lib/in46a_v6_test.ok @@ -3,6 +3,8 @@ Testing in46a_ntop() with IPv6 addresses res = 102:304:506:708:90a:b0c:d0e:f10 Testing in46a_equal() with IPv6 addresses -testing in46a_to_eua() with IPv6 addresses +Testing in46a_to_eua() with IPv6 addresses Testing in46a_from_eua() with IPv6 addresses +Testing in46a_to_eua() with IPv4v6 addresses +Testing in46a_from_eua() with IPv4v6 addresses Testing in46a_netmasklen() with IPv6 addresses -- To view, visit https://gerrit.osmocom.org/5216 To unsubscribe, visit https://gerrit.osmocom.org/settings Gerrit-MessageType: newpatchset Gerrit-Change-Id: Ic820759167fd3bdf329cb11d4b942e903fe50af5 Gerrit-PatchSet: 2 Gerrit-Project: osmo-ggsn Gerrit-Branch: master Gerrit-Owner: Pau Espin Pedrol <pespin at sysmocom.de> Gerrit-Reviewer: Jenkins Builder