diff options
-rw-r--r-- | openbsc/include/openbsc/gtphub.h | 1 | ||||
-rw-r--r-- | openbsc/src/gprs/gtphub.c | 169 | ||||
-rw-r--r-- | openbsc/tests/gtphub/gtphub_test.c | 76 | ||||
-rw-r--r-- | openbsc/tests/gtphub/gtphub_test.ok | 2 |
4 files changed, 229 insertions, 19 deletions
diff --git a/openbsc/include/openbsc/gtphub.h b/openbsc/include/openbsc/gtphub.h index c72a0cfbd..ea0f964a6 100644 --- a/openbsc/include/openbsc/gtphub.h +++ b/openbsc/include/openbsc/gtphub.h @@ -443,6 +443,7 @@ struct gtphub { struct nr_pool tei_pool; struct llist_head tunnels; /* struct gtphub_tunnel */ + struct llist_head pending_deletes; /* opaque (gtphub.c) */ struct llist_head ggsn_lookups; /* opaque (gtphub_ares.c) */ struct llist_head resolved_ggsns; /* struct gtphub_resolved_ggsn */ diff --git a/openbsc/src/gprs/gtphub.c b/openbsc/src/gprs/gtphub.c index 465520ee4..dcfa823fa 100644 --- a/openbsc/src/gprs/gtphub.c +++ b/openbsc/src/gprs/gtphub.c @@ -95,6 +95,15 @@ struct gtp_packet_desc { union gtpie_member *ie[GTPIE_SIZE]; }; +struct pending_delete { + struct llist_head entry; + struct expiring_item expiry_entry; + + struct gtphub_tunnel *tun; + uint8_t teardown_ind; + uint8_t nsapi; +}; + /* counters */ @@ -1325,7 +1334,8 @@ static void gtphub_tunnel_refresh(struct gtphub *hub, static struct gtphub_tunnel_endpoint *gtphub_unmap_tei(struct gtphub *hub, struct gtp_packet_desc *p, - struct gtphub_peer_port *from) + struct gtphub_peer_port *from, + struct gtphub_tunnel **unmapped_from_tun) { OSMO_ASSERT(from); int other_side = other_side_idx(p->side_idx); @@ -1341,9 +1351,14 @@ static struct gtphub_tunnel_endpoint *gtphub_unmap_tei(struct gtphub *hub, && gsn_addr_same(&te_from->peer->peer_addr->addr, &from->peer_addr->addr)) { gtphub_tunnel_refresh(hub, tun, p->timestamp); + if (unmapped_from_tun) + *unmapped_from_tun = tun; return te_to; } } + + if (unmapped_from_tun) + *unmapped_from_tun = NULL; return NULL; } @@ -1376,12 +1391,15 @@ static void gtphub_map_restart_counter(struct gtphub *hub, } static int gtphub_unmap_header_tei(struct gtphub_peer_port **to_port_p, + struct gtphub_tunnel **unmapped_from_tun, struct gtphub *hub, struct gtp_packet_desc *p, struct gtphub_peer_port *from_port) { OSMO_ASSERT(p->version == 1); *to_port_p = NULL; + if (unmapped_from_tun) + *unmapped_from_tun = NULL; /* If the header's TEI is zero, no PDP context has been established * yet. If nonzero, a mapping should actually already exist for this @@ -1392,7 +1410,7 @@ static int gtphub_unmap_header_tei(struct gtphub_peer_port **to_port_p, /* to_peer has previously announced a TEI, which was stored and * mapped in a tunnel struct. */ struct gtphub_tunnel_endpoint *to; - to = gtphub_unmap_tei(hub, p, from_port); + to = gtphub_unmap_tei(hub, p, from_port, unmapped_from_tun); if (!to) { LOG(LOGL_ERROR, "Received unknown TEI %" PRIx32 " from %s\n", p->header_tei_rx, gtphub_port_str(from_port)); @@ -1549,12 +1567,132 @@ static int gtphub_handle_create_pdp_ctx(struct gtphub *hub, return 0; } +static void pending_delete_del_cb(struct expiring_item *expi) +{ + struct pending_delete *pd; + pd = container_of(expi, struct pending_delete, expiry_entry); + + llist_del(&pd->entry); + INIT_LLIST_HEAD(&pd->entry); + + pd->expiry_entry.del_cb = 0; + expiring_item_del(&pd->expiry_entry); + + talloc_free(pd); +} + +static struct pending_delete *pending_delete_new(void) +{ + struct pending_delete *pd = talloc_zero(osmo_gtphub_ctx, struct pending_delete); + INIT_LLIST_HEAD(&pd->entry); + expiring_item_init(&pd->expiry_entry); + pd->expiry_entry.del_cb = pending_delete_del_cb; + return pd; +} + static int gtphub_handle_delete_pdp_ctx(struct gtphub *hub, struct gtp_packet_desc *p, + struct gtphub_tunnel *known_tun, struct gtphub_peer_port *from_ctrl, struct gtphub_peer_port *to_ctrl) { - /* TODO */ + if (p->type == GTP_DELETE_PDP_REQ) { + if (!known_tun) { + LOG(LOGL_ERROR, "Cannot find tunnel for Delete PDP Context Request.\n"); + return -1; + } + + /* Store the Delete Request until a successful Response is seen. */ + uint8_t teardown_ind; + uint8_t nsapi; + + if (gtpie_gettv1(p->ie, GTPIE_TEARDOWN, 0, &teardown_ind) != 0) { + LOG(LOGL_ERROR, "Missing Teardown Ind IE in Delete PDP Context Request.\n"); + return -1; + } + + if (gtpie_gettv1(p->ie, GTPIE_NSAPI, 0, &nsapi) != 0) { + LOG(LOGL_ERROR, "Missing NSAPI IE in Delete PDP Context Request.\n"); + return -1; + } + + struct pending_delete *pd = NULL; + + struct pending_delete *pdi = NULL; + llist_for_each_entry(pdi, &hub->pending_deletes, entry) { + if ((pdi->tun == known_tun) + && (pdi->teardown_ind == teardown_ind) + && (pdi->nsapi == nsapi)) { + pd = pdi; + break; + } + } + + if (!pd) { + pd = pending_delete_new(); + pd->tun = known_tun; + pd->teardown_ind = teardown_ind; + pd->nsapi = nsapi; + + LOG(LOGL_DEBUG, "Tunnel delete pending: %s\n", + gtphub_tunnel_str(known_tun)); + llist_add(&pd->entry, &hub->pending_deletes); + } + + /* Add or refresh timeout. */ + expiry_add(&hub->expire_quickly, &pd->expiry_entry, p->timestamp); + + /* If a pending_delete should expire before the response to + * indicate success comes in, the responding peer will have the + * tunnel deactivated, while the requesting peer gets no reply + * and keeps the tunnel. The hope is that the requesting peer + * will try again and get a useful response. */ + } else if (p->type == GTP_DELETE_PDP_RSP) { + /* Find the Delete Request for this Response. */ + struct pending_delete *pd = NULL; + + struct pending_delete *pdi; + llist_for_each_entry(pdi, &hub->pending_deletes, entry) { + if (known_tun == pdi->tun) { + pd = pdi; + break; + } + } + + if (!pd) { + LOG(LOGL_ERROR, "Delete PDP Context Response:" + " Cannot find matching request."); + /* If we delete the tunnel now, anyone can send a + * Delete response to kill tunnels at will. */ + return -1; + } + + /* TODO handle teardown_ind and nsapi */ + + expiring_item_del(&pd->expiry_entry); + + uint8_t cause; + if (gtpie_gettv1(p->ie, GTPIE_CAUSE, 0, &cause) != 0) { + LOG(LOGL_ERROR, "Delete PDP Context Response:" + " Missing Cause IE."); + /* If we delete the tunnel now, at least one of the + * peers may still think it is active. */ + return -1; + } + + if (cause != GTPCAUSE_ACC_REQ) { + LOG(LOGL_NOTICE, + "Delete PDP Context Response indicates failure;" + "for %s\n", + gtphub_tunnel_str(known_tun)); + return -1; + } + + LOG(LOGL_DEBUG, "Delete PDP Context: removing tunnel %s\n", + gtphub_tunnel_str(known_tun)); + expiring_item_del(&known_tun->expiry_entry); + } + return 0; } @@ -1573,6 +1711,7 @@ static int gtphub_handle_update_pdp_ctx(struct gtphub *hub, * the packet p. */ static int gtphub_handle_pdp_ctx(struct gtphub *hub, struct gtp_packet_desc *p, + struct gtphub_tunnel *known_tun, struct gtphub_peer_port *from_ctrl, struct gtphub_peer_port *to_ctrl) { @@ -1586,7 +1725,7 @@ static int gtphub_handle_pdp_ctx(struct gtphub *hub, case GTP_DELETE_PDP_REQ: case GTP_DELETE_PDP_RSP: - return gtphub_handle_delete_pdp_ctx(hub, p, + return gtphub_handle_delete_pdp_ctx(hub, p, known_tun, from_ctrl, to_ctrl); case GTP_UPDATE_PDP_REQ: @@ -1601,7 +1740,6 @@ static int gtphub_handle_pdp_ctx(struct gtphub *hub, } - static int gtphub_write(const struct osmo_fd *to, const struct osmo_sockaddr *to_addr, const uint8_t *buf, size_t buf_len) @@ -1663,7 +1801,7 @@ static int gtphub_unmap(struct gtphub *hub, struct gtphub_peer_port *to_proxy, struct gtphub_peer_port **final_unmapped, struct gtphub_peer_port **unmapped_from_seq, - struct gtphub_peer_port **unmapped_from_tei) + struct gtphub_tunnel **unmapped_from_tun) { /* Always (try to) unmap sequence and TEI numbers, which need to be * replaced in the packet. Either way, give precedence to the proxy, if @@ -1672,17 +1810,18 @@ static int gtphub_unmap(struct gtphub *hub, struct gtphub_peer_port *from_seq = NULL; struct gtphub_peer_port *from_tei = NULL; struct gtphub_peer_port *unmapped = NULL; + struct gtphub_tunnel *tun = NULL; if (unmapped_from_seq) *unmapped_from_seq = from_seq; - if (unmapped_from_tei) - *unmapped_from_tei = from_tei; + if (unmapped_from_tun) + *unmapped_from_tun = tun; if (final_unmapped) *final_unmapped = unmapped; from_seq = gtphub_unmap_seq(p, from); - if (gtphub_unmap_header_tei(&from_tei, hub, p, from) < 0) + if (gtphub_unmap_header_tei(&from_tei, &tun, hub, p, from) < 0) return -1; struct gtphub_peer *from_peer = from->peer_addr->peer; @@ -1719,8 +1858,8 @@ static int gtphub_unmap(struct gtphub *hub, if (unmapped_from_seq) *unmapped_from_seq = from_seq; - if (unmapped_from_tei) - *unmapped_from_tei = from_tei; + if (unmapped_from_tun) + *unmapped_from_tun = tun; if (final_unmapped) *final_unmapped = unmapped; return 0; @@ -1894,11 +2033,10 @@ int gtphub_handle_buf(struct gtphub *hub, struct gtphub_peer_port *to_peer_from_seq; struct gtphub_peer_port *to_peer; + struct gtphub_tunnel *tun; if (gtphub_unmap(hub, &p, from_peer, hub->proxy[other_side_idx(side_idx)][plane_idx], - &to_peer, &to_peer_from_seq, - NULL /* not interested, got it in &to_peer already */ - ) + &to_peer, &to_peer_from_seq, &tun) != 0) { return -1; } @@ -1918,7 +2056,7 @@ int gtphub_handle_buf(struct gtphub *hub, /* This may be a Create PDP Context response. If it is, there * are other addresses in the GTP message to set up apart from * the sender. */ - if (gtphub_handle_pdp_ctx(hub, &p, from_peer, to_peer) + if (gtphub_handle_pdp_ctx(hub, &p, tun, from_peer, to_peer) != 0) return -1; } @@ -2112,6 +2250,7 @@ void gtphub_init(struct gtphub *hub) gtphub_zero(hub); INIT_LLIST_HEAD(&hub->tunnels); + INIT_LLIST_HEAD(&hub->pending_deletes); expiry_init(&hub->expire_quickly, GTPH_EXPIRE_QUICKLY_SECS); expiry_init(&hub->expire_slowly, GTPH_EXPIRE_SLOWLY_MINUTES * 60); diff --git a/openbsc/tests/gtphub/gtphub_test.c b/openbsc/tests/gtphub/gtphub_test.c index b0ede11b9..fea55e03d 100644 --- a/openbsc/tests/gtphub/gtphub_test.c +++ b/openbsc/tests/gtphub/gtphub_test.c @@ -954,9 +954,75 @@ static int create_pdp_ctx() return 1; } -static void test_create_pdp_ctx(void) +#define MSG_DEL_PDP_CTX_REQ(tei, seq) \ + "32" /* 0b001'1 0010: version 1, protocol GTP, with seq nr. */ \ + "14" /* type 20: Delete PDP Context Request */ \ + "0008" /* msg length = 8 + len (2 octets) */ \ + tei /* TEI Ctrl */ \ + seq /* Sequence nr (2 octets) */ \ + "00" /* N-PDU 0 */ \ + "00" /* No extensions */ \ + /* IEs */ \ + "13fe" /* 19: Teardown ind = 0 */ \ + "1400" /* 20: NSAPI = 0*/ \ + +#define MSG_DEL_PDP_CTX_RSP(tei, seq) \ + "32" /* 0b001'1 0010: version 1, protocol GTP, with seq nr. */ \ + "15" /* type 21: Delete PDP Context Response */ \ + "0006" /* msg length = 8 + len (2 octets) */ \ + tei /* TEI Ctrl */ \ + seq /* Sequence nr (2 octets) */ \ + "00" /* N-PDU 0 */ \ + "00" /* No extensions */ \ + /* IEs */ \ + "01" /* 1: Cause */ \ + "80" /* value = 0b10000000 = response, no rejection. */ \ + +static int delete_pdp_ctx(void) +{ + now += GTPH_EXPIRE_QUICKLY_SECS + 1; + gtphub_gc(hub, now); + + LVL2_ASSERT(tunnels_are( + "192.168.42.23 (TEI C 321=1 / U 123=2)" + " <-> 192.168.43.34 (TEI C 765=3 / U 567=4)" + " @21945\n")); + + /* TEI Ctrl from above and next sequence after abcd. */ + const char *gtp_req_from_sgsn = MSG_DEL_PDP_CTX_REQ("00000003", "abce"); + const char *gtp_req_to_ggsn = MSG_DEL_PDP_CTX_REQ("00000765", "6d32"); + + LVL2_ASSERT(msg_from_sgsn_c(&sgsn_sender, + &resolved_ggsn_addr, + gtp_req_from_sgsn, + gtp_req_to_ggsn)); + + /* 21945 + 31 = 21976 */ + LVL2_ASSERT(tunnels_are( + "192.168.42.23 (TEI C 321=1 / U 123=2)" + " <-> 192.168.43.34 (TEI C 765=3 / U 567=4)" + " @21976\n")); + + const char *gtp_resp_from_ggsn = + MSG_DEL_PDP_CTX_RSP("00000001", "6d32"); + const char *gtp_resp_to_sgsn = + MSG_DEL_PDP_CTX_RSP("00000321", "abce"); + + /* The response should go back to whichever port the request came from + * (unmapped by sequence nr) */ + LVL2_ASSERT(msg_from_ggsn_c(&resolved_ggsn_addr, + &sgsn_sender, + gtp_resp_from_ggsn, + gtp_resp_to_sgsn)); + + LVL2_ASSERT(tunnels_are("")); + + return 1; +} + +static void test_one_pdp_ctx(void) { - LOG("test_create_pdp_ctx"); + LOG("test_one_pdp_ctx"); OSMO_ASSERT(setup_test_hub()); OSMO_ASSERT(create_pdp_ctx()); @@ -982,6 +1048,10 @@ static void test_create_pdp_ctx(void) "192.168.42.23 (TEI C 321=1 / U 123=2)" " <-> 192.168.43.34 (TEI C 765=3 / U 567=4)" " @21945\n")); + + OSMO_ASSERT(delete_pdp_ctx()); + OSMO_ASSERT(tunnels_are("")); + OSMO_ASSERT(clear_test_hub()); } @@ -1108,7 +1178,7 @@ int main(int argc, char **argv) test_nr_map_wrap(); test_expiry(); test_echo(); - test_create_pdp_ctx(); + test_one_pdp_ctx(); test_user_data(); printf("Done\n"); diff --git a/openbsc/tests/gtphub/gtphub_test.ok b/openbsc/tests/gtphub/gtphub_test.ok index 24b9aad12..8f66b9d59 100644 --- a/openbsc/tests/gtphub/gtphub_test.ok +++ b/openbsc/tests/gtphub/gtphub_test.ok @@ -1,5 +1,5 @@ test_echo -test_create_pdp_ctx +test_one_pdp_ctx - __wrap_gtphub_resolve_ggsn_addr(): returning GGSN addr from imsi 240010123456789 ni internet: 192.168.43.34 port 2123 test_user_data |