sgsnemu: Fix assumption ipv6 Interface-Identifier of public addr == announced Prefix

Until now, sgsnemu was able to identify pdp contexts of incoming packets
in the tun based on the assumption that the Interface-Identifier part of
public IPv6 addresses in incoming packets was equal to the announced
prefix path during Create Pdp Context Response (see changes in cb_tun_ind()).
This assumption works fine with osmo-ggsn due to implementation details but
breaks on other spec-conformant GGSNs.

In order to fix it, a new placeholder struct pdp_peer_sgsnemu_ctx is
introduced which will be assigned to each pdp_t "peer[0]" user-defined
pointer. This way, each pdp_t ctx upgrades from having only 1 iphash_t
item to 3 (hence being able to match against 3 different ip addresses).
This way, in IPv6 we can match against 2 different IP addresses set on
the tun iface:
* link-local: "fe80::IfId", where IfId is the Interface-Identifier
  received during Pdp Context Resp and which can be used to communicate
  with the nearest router (the GGSN).
* global: The global IPv6 addr set after SLAAC procedure, containing a
  the prefix announced by CreatePdpContextResp/RouterAdvertisement and
  an Interface-Identifier chosen by sgsnemu itself (currently ::ff).

This change is also a step forward towards supporting IPv4v6 APNs in sgsnemu.

Related: OS#4434
Change-Id: I0d36145250185e4cce699fdaedfe96bd969f5fa1
This commit is contained in:
Pau Espin 2020-04-15 19:44:01 +02:00
parent 962146085c
commit 04715d284f
1 changed files with 53 additions and 41 deletions

View File

@ -67,15 +67,23 @@
#define MAXCONTEXTS 1024 /* Max number of allowed contexts */ #define MAXCONTEXTS 1024 /* Max number of allowed contexts */
/* HASH tables for IP address allocation */ /* HASH tables for IP address allocation */
struct pdp_peer_sgsnemu_ctx;
struct iphash_t { struct iphash_t {
uint8_t inuse; /* 0=free. 1=used by somebody */ uint8_t inuse; /* 0=free. 1=used by somebody */
struct iphash_t *ipnext; struct iphash_t *ipnext;
struct pdp_t *pdp; struct pdp_peer_sgsnemu_ctx *ctx;
struct in46_addr addr; struct in46_addr addr;
}; };
struct iphash_t iparr[MAXCONTEXTS];
struct iphash_t *iphash[MAXCONTEXTS]; struct iphash_t *iphash[MAXCONTEXTS];
struct pdp_peer_sgsnemu_ctx {
struct iphash_t hash_v4;
struct iphash_t hash_v6_ll;
struct iphash_t hash_v6_global;
struct pdp_t *pdp;
};
struct pdp_peer_sgsnemu_ctx ctx_arr[MAXCONTEXTS];
/* State variable used for ping */ /* State variable used for ping */
/* 0: Idle */ /* 0: Idle */
/* 1: Wait_connect */ /* 1: Wait_connect */
@ -192,6 +200,9 @@ static int ipset(struct iphash_t *ipaddr, struct in46_addr *addr)
int hash = ippool_hash(addr) % MAXCONTEXTS; int hash = ippool_hash(addr) % MAXCONTEXTS;
struct iphash_t *h; struct iphash_t *h;
struct iphash_t *prev = NULL; struct iphash_t *prev = NULL;
printf("Adding IP to local pool: %s\n", in46a_ntoa(addr));
ipaddr->ipnext = NULL; ipaddr->ipnext = NULL;
ipaddr->addr = *addr; ipaddr->addr = *addr;
for (h = iphash[hash]; h; h = h->ipnext) for (h = iphash[hash]; h; h = h->ipnext)
@ -1550,19 +1561,8 @@ static int cb_tun_ind(struct tun_t *tun, void *pack, unsigned len)
src.len = 4; src.len = 4;
src.v4.s_addr = iph->saddr; src.v4.s_addr = iph->saddr;
} else if (iph->version == 6) { } else if (iph->version == 6) {
/* We only have a single entry in the hash table, and it consists of the link-local
* address "fe80::prefix". So we need to make sure to convert non-link-local source
* addresses to that format before looking up the hash table via ippool_getip() */
src.len = 16; src.len = 16;
if (!memcmp(ip6h->ip6_src.s6_addr, ll_prefix, sizeof(ll_prefix))) {
/* is a link-local address, we can do the hash lookup 1:1 */
src.v6 = ip6h->ip6_src; src.v6 = ip6h->ip6_src;
} else {
/* it is not a link-local address, so we must convert from the /64 prefix
* to the link-local format that's used in the hash table */
memcpy(&src.v6.s6_addr[0], ll_prefix, sizeof(ll_prefix));
memcpy(&src.v6.s6_addr[sizeof(ll_prefix)], ip6h->ip6_src.s6_addr, 16-sizeof(ll_prefix));
}
} else { } else {
printf("Dropping packet with invalid IP version %u\n", iph->version); printf("Dropping packet with invalid IP version %u\n", iph->version);
return 0; return 0;
@ -1574,8 +1574,8 @@ static int cb_tun_ind(struct tun_t *tun, void *pack, unsigned len)
return 0; return 0;
} }
if (ipm->pdp) /* Check if a peer protocol is defined */ if (ipm->ctx->pdp) /* Check if a peer protocol is defined */
gtp_data_req(gsn, ipm->pdp, pack, len); gtp_data_req(gsn, ipm->ctx->pdp, pack, len);
return 0; return 0;
} }
@ -1587,19 +1587,19 @@ static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause)
sigset_t oldmask; sigset_t oldmask;
#endif #endif
struct iphash_t *iph = (struct iphash_t *)cbp; struct pdp_peer_sgsnemu_ctx *ctx = (struct pdp_peer_sgsnemu_ctx *) cbp;
if (cause < 0) { if (cause < 0) {
printf("Create PDP Context Request timed out\n"); printf("Create PDP Context Request timed out\n");
if (iph->pdp->version == 1) { if (ctx->pdp->version == 1) {
printf("Retrying with version 0\n"); printf("Retrying with version 0\n");
iph->pdp->version = 0; ctx->pdp->version = 0;
gtp_create_context_req(gsn, iph->pdp, iph); gtp_create_context_req(gsn, ctx->pdp, ctx);
return 0; return 0;
} else { } else {
state = 0; state = 0;
pdp_freepdp(iph->pdp); pdp_freepdp(ctx->pdp);
iph->pdp = NULL; ctx->pdp = NULL;
return EOF; return EOF;
} }
} }
@ -1609,8 +1609,8 @@ static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause)
("Received create PDP context response. Cause value: %d\n", ("Received create PDP context response. Cause value: %d\n",
cause); cause);
state = 0; state = 0;
pdp_freepdp(iph->pdp); pdp_freepdp(ctx->pdp);
iph->pdp = NULL; ctx->pdp = NULL;
return EOF; /* Not what we expected */ return EOF; /* Not what we expected */
} }
@ -1618,8 +1618,8 @@ static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause)
printf printf
("Received create PDP context response. Cause value: %d\n", ("Received create PDP context response. Cause value: %d\n",
cause); cause);
pdp_freepdp(iph->pdp); pdp_freepdp(ctx->pdp);
iph->pdp = NULL; ctx->pdp = NULL;
state = 0; state = 0;
return EOF; /* Not a valid IP address */ return EOF; /* Not a valid IP address */
} }
@ -1641,13 +1641,18 @@ static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause)
switch (addr[i].len) { switch (addr[i].len) {
case 16: /* IPv6 */ case 16: /* IPv6 */
/* we have to enable the kernel to perform stateless autoconfiguration, /* Convert address to link local using the lower 64bits
* i.e. send a router solicitation using the lover 64bits of the allocated of the allocated EUA as Interface-Identifier to
* EUA as interface identifier, as per 3GPP TS 29.061 Section 11.2.1.3.2 */ send router solicitation, as per 3GPP TS 29.061
Section 11.2.1.3.2 */
memcpy(addr[i].v6.s6_addr, ll_prefix, sizeof(ll_prefix)); memcpy(addr[i].v6.s6_addr, ll_prefix, sizeof(ll_prefix));
printf("Derived IPv6 link-local address: %s\n", in46a_ntoa(&addr[i])); printf("Derived IPv6 link-local address: %s\n", in46a_ntoa(&addr[i]));
ctx->hash_v6_ll.inuse = 1;
ipset(&ctx->hash_v6_ll, &addr[i]);
break; break;
case 4: /* IPv4 */ case 4: /* IPv4 */
ctx->hash_v4.inuse = 1;
ipset(&ctx->hash_v4, &addr[i]);
break; break;
} }
@ -1669,8 +1674,6 @@ static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause)
if (options.ipup) if (options.ipup)
tun_runscript(tun, options.ipup); tun_runscript(tun, options.ipup);
} }
ipset(iph, &addr[i]);
} }
if (options.createif && options.pdp_type == PDP_EUA_TYPE_v6) { if (options.createif && options.pdp_type == PDP_EUA_TYPE_v6) {
@ -1686,7 +1689,7 @@ static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause)
} }
SYS_ERR(DSGSN, LOGL_INFO, 0, "Sending ICMPv6 Router Soliciation to GGSN..."); SYS_ERR(DSGSN, LOGL_INFO, 0, "Sending ICMPv6 Router Soliciation to GGSN...");
msg = icmpv6_construct_rs(saddr6); msg = icmpv6_construct_rs(saddr6);
gtp_data_req(gsn, iph->pdp, msgb_data(msg), msgb_length(msg)); gtp_data_req(gsn, ctx->pdp, msgb_data(msg), msgb_length(msg));
msgb_free(msg); msgb_free(msg);
} }
@ -1751,8 +1754,9 @@ static int _gtp_cb_conf(int type, int cause, struct pdp_t *pdp, void *cbp)
} }
} }
static void handle_router_adv(struct ip6_hdr *ip6h, struct icmpv6_radv_hdr *ra, size_t ra_len) static void handle_router_adv(struct pdp_t *pdp, struct ip6_hdr *ip6h, struct icmpv6_radv_hdr *ra, size_t ra_len)
{ {
struct pdp_peer_sgsnemu_ctx* ctx = (struct pdp_peer_sgsnemu_ctx*)pdp->peer[0];
struct icmpv6_opt_hdr *opt_hdr; struct icmpv6_opt_hdr *opt_hdr;
struct icmpv6_opt_prefix *opt_prefix; struct icmpv6_opt_prefix *opt_prefix;
int rc; int rc;
@ -1778,6 +1782,12 @@ static void handle_router_adv(struct ip6_hdr *ip6h, struct icmpv6_radv_hdr *ra,
addr.v6.s6_addr[15] = 0x02; addr.v6.s6_addr[15] = 0x02;
SYS_ERR(DSGSN, LOGL_INFO, 0, "Adding addr %s to tun %s", SYS_ERR(DSGSN, LOGL_INFO, 0, "Adding addr %s to tun %s",
in46a_ntoa(&addr), tun->devname); in46a_ntoa(&addr), tun->devname);
if (!ctx->hash_v6_global.inuse) {
ctx->hash_v6_global.inuse = 1;
ipset(&ctx->hash_v6_global, &addr);
} else {
SYS_ERR(DSGSN, LOGL_ERROR, 0, "First v6 global address in hash already in use!");
}
#if defined(__linux__) #if defined(__linux__)
if ((options.netns)) { if ((options.netns)) {
@ -1822,7 +1832,7 @@ static int encaps_tun(struct pdp_t *pdp, void *pack, unsigned len)
case 6: case 6:
if ((ra = icmpv6_validate_router_adv(pack, len))) { if ((ra = icmpv6_validate_router_adv(pack, len))) {
size_t ra_len = (uint8_t*)ra - (uint8_t*)pack; size_t ra_len = (uint8_t*)ra - (uint8_t*)pack;
handle_router_adv((struct ip6_hdr *)pack, ra, ra_len); handle_router_adv(pdp, (struct ip6_hdr *)pack, ra, ra_len);
return 0; return 0;
} }
break; break;
@ -1966,7 +1976,7 @@ int main(int argc, char **argv)
/* Initialise hash tables */ /* Initialise hash tables */
memset(&iphash, 0, sizeof(iphash)); memset(&iphash, 0, sizeof(iphash));
memset(&iparr, 0, sizeof(iparr)); memset(&ctx_arr, 0, sizeof(ctx_arr));
printf("Done initialising GTP library\n\n"); printf("Done initialising GTP library\n\n");
@ -1978,7 +1988,6 @@ int main(int argc, char **argv)
for (n = 0; n < options.contexts; n++) { for (n = 0; n < options.contexts; n++) {
uint64_t myimsi; uint64_t myimsi;
printf("Setting up PDP context #%d\n", n); printf("Setting up PDP context #%d\n", n);
iparr[n].inuse = 1; /* TODO */
imsi_add(options.imsi, &myimsi, n); imsi_add(options.imsi, &myimsi, n);
@ -1987,9 +1996,12 @@ int main(int argc, char **argv)
/* Otherwise it is deallocated by gtplib */ /* Otherwise it is deallocated by gtplib */
gtp_pdp_newpdp(gsn, &pdp, myimsi, options.nsapi, NULL); gtp_pdp_newpdp(gsn, &pdp, myimsi, options.nsapi, NULL);
pdp->peer[0] = &iparr[n]; /* FIXME: support v4v6, have 2 peers */ pdp->peer[0] = &ctx_arr[n];
pdp->ipif = tun; /* TODO */ pdp->ipif = tun; /* TODO */
iparr[n].pdp = pdp; ctx_arr[n].pdp = pdp;
ctx_arr[n].hash_v4.ctx = &ctx_arr[n];
ctx_arr[n].hash_v6_ll.ctx = &ctx_arr[n];
ctx_arr[n].hash_v6_global.ctx = &ctx_arr[n];
if (options.gtpversion == 0) { if (options.gtpversion == 0) {
if (options.qos.l - 1 > sizeof(pdp->qos_req0)) { if (options.qos.l - 1 > sizeof(pdp->qos_req0)) {
@ -2077,7 +2089,7 @@ int main(int argc, char **argv)
/* Create context */ /* Create context */
/* We send this of once. Retransmissions are handled by gtplib */ /* We send this of once. Retransmissions are handled by gtplib */
gtp_create_context_req(gsn, pdp, &iparr[n]); gtp_create_context_req(gsn, pdp, &ctx_arr[n]);
} }
state = 1; /* Enter wait_connection state */ state = 1; /* Enter wait_connection state */
@ -2127,7 +2139,7 @@ int main(int argc, char **argv)
for (n = 0; n < options.contexts; n++) { for (n = 0; n < options.contexts; n++) {
/* Delete context */ /* Delete context */
printf("Disconnecting PDP context #%d\n", n); printf("Disconnecting PDP context #%d\n", n);
gtp_delete_context_req2(gsn, iparr[n].pdp, NULL, 1); gtp_delete_context_req2(gsn, ctx_arr[n].pdp, NULL, 1);
if ((options.pinghost.len) if ((options.pinghost.len)
&& ntransmitted) && ntransmitted)
ping_finish(); ping_finish();
@ -2149,7 +2161,7 @@ int main(int argc, char **argv)
if (options.debug) if (options.debug)
printf("Create_ping %d\n", diff); printf("Create_ping %d\n", diff);
create_ping(gsn, create_ping(gsn,
iparr[pingseq % ctx_arr[pingseq %
options.contexts].pdp, options.contexts].pdp,
&options.pinghost, pingseq, &options.pinghost, pingseq,
options.pingsize); options.pingsize);