/* * OpenGGSN - Gateway GPRS Support Node * Copyright (C) 2002 Mondru AB. * * 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. * * The initial developer of the original code is * Jens Jakobsen * * Contributor(s): * */ /* * gtp.c: Contains all GTP functionality. Should be able to handle multiple * tunnels in the same program. * * TODO: * - Do we need to handle fragmentation? */ #ifdef __linux__ #define _GNU_SOURCE 1 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* #include ISO C99 types */ #include "../config.h" #include "pdp.h" #include "gtp.h" #include "gtpie.h" #include "queue.h" /* Error reporting functions */ void gtp_err(int priority, char *filename, int linenum, char *fmt, ...) { va_list args; char buf[ERRMSG_SIZE]; va_start(args, fmt); vsnprintf(buf, ERRMSG_SIZE, fmt, args); va_end(args); buf[ERRMSG_SIZE-1] = 0; syslog(priority, "%s: %d: %s", filename, linenum, buf); } void gtp_errpack(int pri, char *fn, int ln, struct sockaddr_in *peer, void *pack, unsigned len, char *fmt, ...) { va_list args; char buf[ERRMSG_SIZE]; char buf2[ERRMSG_SIZE]; int n; int pos; va_start(args, fmt); vsnprintf(buf, ERRMSG_SIZE, fmt, args); va_end(args); buf[ERRMSG_SIZE-1] = 0; snprintf(buf2, ERRMSG_SIZE, "Packet from %s:%u, length: %d, content:", inet_ntoa(peer->sin_addr), ntohs(peer->sin_port), len); buf2[ERRMSG_SIZE-1] = 0; pos = strlen(buf2); for(n=0; nfd0; } /* gtp_decaps */ /* gtp_retrans */ /* gtp_retranstimeout */ int gtp_set_cb_unsup_ind(struct gsn_t *gsn, int (*cb) (struct sockaddr_in *peer)) { gsn->cb_unsup_ind = cb; return 0; } int gtp_set_cb_extheader_ind(struct gsn_t *gsn, int (*cb) (struct sockaddr_in *peer)) { gsn->cb_extheader_ind = cb; return 0; } /* API: Initialise delete context callback */ /* Called whenever a pdp context is deleted for any reason */ int gtp_set_cb_delete_context(struct gsn_t *gsn, int (*cb) (struct pdp_t* pdp)) { gsn->cb_delete_context = cb; return 0; } int gtp_set_cb_conf(struct gsn_t *gsn, int (*cb) (int type, int cause, struct pdp_t* pdp, void *cbp)) { gsn->cb_conf = cb; return 0; } extern int gtp_set_cb_data_ind(struct gsn_t *gsn, int (*cb_data_ind) (struct pdp_t* pdp, void* pack, unsigned len)) { gsn->cb_data_ind = cb_data_ind; return 0; } /** * get_default_gtp() * Generate a GPRS Tunneling Protocol signalling packet header, depending * on GTP version and message type. pdp is used for teid/flow label. * *packet must be allocated by the calling function, and be large enough * to hold the packet header. * returns the length of the header. 0 on error. **/ static int get_default_gtp(int version, uint8_t type, void *packet) { struct gtp0_header *gtp0_default = (struct gtp0_header*) packet; struct gtp1_header_long *gtp1_default = (struct gtp1_header_long*) packet; switch (version) { case 0: /* Initialise "standard" GTP0 header */ memset(gtp0_default, 0, sizeof(struct gtp0_header)); gtp0_default->flags=0x1e; gtp0_default->type=hton8(type); gtp0_default->spare1=0xff; gtp0_default->spare2=0xff; gtp0_default->spare3=0xff; gtp0_default->number=0xff; return GTP0_HEADER_SIZE; case 1: /* Initialise "standard" GTP1 header */ /* 29.060: 8.2: S=1 and PN=0 */ /* 29.060 9.3.1: For GTP-U messages Echo Request, Echo Response */ /* and Supported Extension Headers Notification, the S field shall be */ /* set to 1 */ /* Currently extension headers are not supported */ memset(gtp1_default, 0, sizeof(struct gtp1_header_long)); gtp1_default->flags=0x32; /* No extension, enable sequence, no N-PDU */ gtp1_default->type=hton8(type); return GTP1_HEADER_SIZE_LONG; default: gtp_err(LOG_ERR, __FILE__, __LINE__, "Unknown GTP packet version"); return 0; } } /** * get_seq() * Get sequence number of a packet. * Returns 0 on error **/ static uint16_t get_seq(void *pack) { union gtp_packet *packet = (union gtp_packet *) pack; if ((packet->flags & 0xe0) == 0x00) { /* Version 0 */ return ntoh16(packet->gtp0.h.seq); } else if ((packet->flags & 0xe2) == 0x22) { /* Version 1 with seq */ return ntoh16(packet->gtp1l.h.seq); } else { gtp_err(LOG_ERR, __FILE__, __LINE__, "Unknown packet flag"); return 0; } } /** * get_tid() * Get tunnel identifier of a packet. * Returns 0 on error **/ static uint64_t get_tid(void *pack) { union gtp_packet *packet = (union gtp_packet *) pack; if ((packet->flags & 0xe0) == 0x00) { /* Version 0 */ return packet->gtp0.h.tid; } return 0; } /** * get_hlen() * Get the header length of a packet. * Returns 0 on error **/ static uint16_t get_hlen(void *pack) { union gtp_packet *packet = (union gtp_packet *) pack; if ((packet->flags & 0xe0) == 0x00) { /* Version 0 */ return GTP0_HEADER_SIZE; } else if ((packet->flags & 0xe2) == 0x22) { /* Version 1 with seq */ return GTP1_HEADER_SIZE_LONG; } else if ((packet->flags & 0xe7) == 0x20) { /* Short version 1 */ return GTP1_HEADER_SIZE_SHORT; } else { gtp_err(LOG_ERR, __FILE__, __LINE__, "Unknown packet flag"); return 0; } } /** * get_tei() * Get the tunnel endpoint identifier (flow label) of a packet. * Returns 0xffffffff on error. **/ static uint32_t get_tei(void *pack) { union gtp_packet *packet = (union gtp_packet *) pack; if ((packet->flags & 0xe0) == 0x00) { /* Version 0 */ return ntoh16(packet->gtp0.h.flow); } else if ((packet->flags & 0xe0) == 0x20) { /* Version 1 */ return ntoh32(packet->gtp1l.h.tei); } else { gtp_err(LOG_ERR, __FILE__, __LINE__, "Unknown packet flag"); return 0xffffffff; } } int print_packet(void *packet, unsigned len) { int i; printf("The packet looks like this (%d bytes):\n", len); for( i=0; isin_addr), ntohs(peer->sin_port), len); buf[size-1] = 0; pos = strlen(buf); for(n=0; nflags & 0xe0) == 0x00) { /* Version 0 */ addr.sin_port = htons(GTP0_PORT); packet->gtp0.h.length = hton16(len - GTP0_HEADER_SIZE); packet->gtp0.h.seq = hton16(gsn->seq_next); if (pdp) packet->gtp0.h.tid = (pdp->imsi & 0x0fffffffffffffffull) + ((uint64_t)pdp->nsapi << 60); if (pdp && ((packet->gtp0.h.type == GTP_GPDU) || (packet->gtp0.h.type == GTP_ERROR))) packet->gtp0.h.flow=hton16(pdp->flru); else if (pdp) packet->gtp0.h.flow=hton16(pdp->flrc); fd = gsn->fd0; } else if ((packet->flags & 0xe2) == 0x22) { /* Version 1 with seq */ addr.sin_port = htons(GTP1C_PORT); packet->gtp1l.h.length = hton16(len - GTP1_HEADER_SIZE_SHORT); packet->gtp1l.h.seq = hton16(gsn->seq_next); if (pdp && ((packet->gtp1l.h.type == GTP_GPDU) || (packet->gtp1l.h.type == GTP_ERROR))) packet->gtp1l.h.tei=hton32(pdp->teid_gn); else if (pdp) packet->gtp1l.h.tei=hton32(pdp->teic_gn); fd = gsn->fd1c; } else { gtp_err(LOG_ERR, __FILE__, __LINE__, "Unknown packet flag"); return -1; } if (sendto(fd, packet, len, 0, (struct sockaddr *) &addr, sizeof(addr)) < 0) { gsn->err_sendto++; gtp_err(LOG_ERR, __FILE__, __LINE__, "Sendto(fd=%d, msg=%lx, len=%d) failed: Error = %s", fd, (unsigned long) &packet, len, strerror(errno)); return -1; } /* Use new queue structure */ if (queue_newmsg(gsn->queue_req, &qmsg, &addr, gsn->seq_next)) { gsn->err_queuefull++; gtp_err(LOG_ERR, __FILE__, __LINE__, "Retransmit queue is full"); } else { memcpy(&qmsg->p, packet, sizeof(union gtp_packet)); qmsg->l = len; qmsg->timeout = time(NULL) + 3; /* When to timeout */ qmsg->retrans = 0; /* No retransmissions so far */ qmsg->cbp = cbp; qmsg->type = ntoh8(packet->gtp0.h.type); qmsg->fd = fd; } gsn->seq_next++; /* Count up this time */ return 0; } /* gtp_conf * Remove signalling packet from retransmission queue. * return 0 on success, EOF if packet was not found */ int gtp_conf(struct gsn_t *gsn, int version, struct sockaddr_in *peer, union gtp_packet *packet, int len, uint8_t *type, void **cbp) { uint16_t seq; if ((packet->gtp0.h.flags & 0xe0) == 0x00) seq = ntoh16(packet->gtp0.h.seq); else if ((packet->gtp1l.h.flags & 0xe2) == 0x22) seq = ntoh16(packet->gtp1l.h.seq); else { gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, packet, len, "Unknown GTP packet version"); return EOF; } if (queue_freemsg_seq(gsn->queue_req, peer, seq, type, cbp)) { gsn->err_seq++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, packet, len, "Confirmation packet not found in queue"); return EOF; } return 0; } int gtp_retrans(struct gsn_t *gsn) { /* Retransmit any outstanding packets */ /* Remove from queue if maxretrans exceeded */ time_t now; struct qmsg_t *qmsg; now = time(NULL); /*printf("Retrans: New beginning %d\n", (int) now);*/ while ((!queue_getfirst(gsn->queue_req, &qmsg)) && (qmsg->timeout <= now)) { /*printf("Retrans timeout found: %d\n", (int) time(NULL));*/ if (qmsg->retrans > 3) { /* To many retrans */ if (gsn->cb_conf) gsn->cb_conf(qmsg->type, EOF, NULL, qmsg->cbp); queue_freemsg(gsn->queue_req, qmsg); } else { if (sendto(qmsg->fd, &qmsg->p, qmsg->l, 0, (struct sockaddr *) &qmsg->peer, sizeof(struct sockaddr_in)) < 0) { gsn->err_sendto++; gtp_err(LOG_ERR, __FILE__, __LINE__, "Sendto(fd0=%d, msg=%lx, len=%d) failed: Error = %s", gsn->fd0, (unsigned long) &qmsg->p, qmsg->l, strerror(errno)); } queue_back(gsn->queue_req, qmsg); qmsg->timeout = now + 3; qmsg->retrans++; } } /* Also clean up reply timeouts */ while ((!queue_getfirst(gsn->queue_resp, &qmsg)) && (qmsg->timeout < now)) { /*printf("Retrans (reply) timeout found: %d\n", (int) time(NULL));*/ queue_freemsg(gsn->queue_resp, qmsg); } return 0; } int gtp_retranstimeout(struct gsn_t *gsn, struct timeval *timeout) { time_t now, later; struct qmsg_t *qmsg; if (queue_getfirst(gsn->queue_req, &qmsg)) { timeout->tv_sec = 10; timeout->tv_usec = 0; } else { now = time(NULL); later = qmsg->timeout; timeout->tv_sec = later - now; timeout->tv_usec = 0; if (timeout->tv_sec < 0) timeout->tv_sec = 0; /* No negative allowed */ if (timeout->tv_sec > 10) timeout->tv_sec = 10; /* Max sleep for 10 sec*/ } return 0; } int gtp_resp(int version, struct gsn_t *gsn, struct pdp_t *pdp, union gtp_packet *packet, int len, struct sockaddr_in *peer, int fd, uint16_t seq, uint64_t tid) { struct qmsg_t *qmsg; if ((packet->flags & 0xe0) == 0x00) { /* Version 0 */ packet->gtp0.h.length = hton16(len - GTP0_HEADER_SIZE); packet->gtp0.h.seq = hton16(seq); packet->gtp0.h.tid = tid; if (pdp && ((packet->gtp0.h.type == GTP_GPDU) || (packet->gtp0.h.type == GTP_ERROR))) packet->gtp0.h.flow=hton16(pdp->flru); else if (pdp) packet->gtp0.h.flow=hton16(pdp->flrc); } else if ((packet->flags & 0xe2) == 0x22) { /* Version 1 with seq */ packet->gtp1l.h.length = hton16(len - GTP1_HEADER_SIZE_SHORT); packet->gtp1l.h.seq = hton16(seq); if (pdp && (fd == gsn->fd1u)) packet->gtp1l.h.tei=hton32(pdp->teid_gn); else if (pdp) packet->gtp1l.h.tei=hton32(pdp->teic_gn); } else { gtp_err(LOG_ERR, __FILE__, __LINE__, "Unknown packet flag"); return -1; } if (fcntl(fd, F_SETFL, 0)) { gtp_err(LOG_ERR, __FILE__, __LINE__, "fnctl()"); return -1; } if (sendto(fd, packet, len, 0, (struct sockaddr *) peer, sizeof(struct sockaddr_in)) < 0) { gsn->err_sendto++; gtp_err(LOG_ERR, __FILE__, __LINE__, "Sendto(fd=%d, msg=%lx, len=%d) failed: Error = %s", fd, (unsigned long) &packet, len, strerror(errno)); return -1; } /* Use new queue structure */ if (queue_newmsg(gsn->queue_resp, &qmsg, peer, seq)) { gsn->err_queuefull++; gtp_err(LOG_ERR, __FILE__, __LINE__, "Retransmit queue is full"); } else { memcpy(&qmsg->p, packet, sizeof(union gtp_packet)); qmsg->l = len; qmsg->timeout = time(NULL) + 60; /* When to timeout */ qmsg->retrans = 0; /* No retransmissions so far */ qmsg->cbp = NULL; qmsg->type = 0; qmsg->fd = fd; } return 0; } int gtp_notification(struct gsn_t *gsn, int version, union gtp_packet *packet, int len, struct sockaddr_in *peer, int fd, uint16_t seq) { struct sockaddr_in addr; memcpy(&addr, peer, sizeof(addr)); /* In GTP0 notifications are treated as replies. In GTP1 they are requests for which there is no reply */ if (fd == gsn->fd1c) addr.sin_port = htons(GTP1C_PORT); else if (fd == gsn->fd1u) addr.sin_port = htons(GTP1C_PORT); if ((packet->flags & 0xe0) == 0x00) { /* Version 0 */ packet->gtp0.h.length = hton16(len - GTP0_HEADER_SIZE); packet->gtp0.h.seq = hton16(seq); } else if ((packet->flags & 0xe2) == 0x22) { /* Version 1 with seq */ packet->gtp1l.h.length = hton16(len - GTP1_HEADER_SIZE_SHORT); packet->gtp1l.h.seq = hton16(seq); } else { gtp_err(LOG_ERR, __FILE__, __LINE__, "Unknown packet flag"); return -1; } if (fcntl(fd, F_SETFL, 0)) { gtp_err(LOG_ERR, __FILE__, __LINE__, "fnctl()"); return -1; } if (sendto(fd, packet, len, 0, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) < 0) { gsn->err_sendto++; gtp_err(LOG_ERR, __FILE__, __LINE__, "Sendto(fd=%d, msg=%lx, len=%d) failed: Error = %s", fd, (unsigned long) &packet, len, strerror(errno)); return -1; } return 0; } int gtp_dublicate(struct gsn_t *gsn, int version, struct sockaddr_in *peer, uint16_t seq) { struct qmsg_t *qmsg; if(queue_seqget(gsn->queue_resp, &qmsg, peer, seq)) { return EOF; /* Notfound */ } if (fcntl(qmsg->fd, F_SETFL, 0)) { gtp_err(LOG_ERR, __FILE__, __LINE__, "fnctl()"); return -1; } if (sendto(qmsg->fd, &qmsg->p, qmsg->l, 0, (struct sockaddr *) peer, sizeof(struct sockaddr_in)) < 0) { gsn->err_sendto++; gtp_err(LOG_ERR, __FILE__, __LINE__, "Sendto(fd=%d, msg=%lx, len=%d) failed: Error = %s", qmsg->fd, (unsigned long) &qmsg->p, qmsg->l, strerror(errno)); } return 0; } /* Perform restoration and recovery error handling as described in 29.060 */ static void log_restart(struct gsn_t *gsn) { FILE *f; int i; int counter = 0; char filename[NAMESIZE]; filename[NAMESIZE-1] = 0; /* No null term. guarantee by strncpy */ strncpy(filename, gsn->statedir, NAMESIZE-1); strncat(filename, RESTART_FILE, NAMESIZE-1-sizeof(RESTART_FILE)); i = umask(022); /* We try to open file. On failure we will later try to create file */ if (!(f = fopen(filename, "r"))) { gtp_err(LOG_ERR, __FILE__, __LINE__, "State information file (%s) not found. Creating new file.", filename); } else { umask(i); fscanf(f, "%d", &counter); if (fclose(f)) { gtp_err(LOG_ERR, __FILE__, __LINE__, "fclose failed: Error = %s", strerror(errno)); } } gsn->restart_counter = (unsigned char) counter; gsn->restart_counter++; if (!(f = fopen(filename, "w"))) { gtp_err(LOG_ERR, __FILE__, __LINE__, "fopen(path=%s, mode=%s) failed: Error = %s", filename, "w", strerror(errno)); return; } umask(i); fprintf(f, "%d\n", gsn->restart_counter); if (fclose(f)) { gtp_err(LOG_ERR, __FILE__, __LINE__, "fclose failed: Error = %s", strerror(errno)); return; } } int gtp_new(struct gsn_t **gsn, char *statedir, struct in_addr *listen, int mode) { struct sockaddr_in addr; syslog(LOG_ERR, "GTP: gtp_newgsn() started"); *gsn = calloc(sizeof(struct gsn_t), 1); /* TODO */ (*gsn)->statedir = statedir; log_restart(*gsn); /* Initialise sequence number */ (*gsn)->seq_next = (*gsn)->restart_counter * 1024; /* Initialise request retransmit queue */ queue_new(&(*gsn)->queue_req); queue_new(&(*gsn)->queue_resp); /* Initialise pdp table */ pdp_init(); /* Initialise call back functions */ (*gsn)->cb_create_context_ind = 0; (*gsn)->cb_delete_context = 0; (*gsn)->cb_unsup_ind = 0; (*gsn)->cb_conf = 0; (*gsn)->cb_data_ind = 0; /* Store function parameters */ (*gsn)->gsnc = *listen; (*gsn)->gsnu = *listen; (*gsn)->mode = mode; /* Create GTP version 0 socket */ if (((*gsn)->fd0 = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) { (*gsn)->err_socket++; gtp_err(LOG_ERR, __FILE__, __LINE__, "socket(domain=%d, type=%d, protocol=%d) failed: Error = %s", AF_INET, SOCK_DGRAM, 0, strerror(errno)); return -1; } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr = *listen; /* Same IP for user traffic and signalling*/ addr.sin_port = htons(GTP0_PORT); #ifdef __FreeBSD__ addr.sin_len = sizeof(addr); #endif if (bind((*gsn)->fd0, (struct sockaddr *) &addr, sizeof(addr)) < 0) { (*gsn)->err_socket++; gtp_err(LOG_ERR, __FILE__, __LINE__, "bind(fd0=%d, addr=%lx, len=%d) failed: Error = %s", (*gsn)->fd0, (unsigned long) &addr, sizeof(addr), strerror(errno)); return -1; } /* Create GTP version 1 control plane socket */ if (((*gsn)->fd1c = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) { (*gsn)->err_socket++; gtp_err(LOG_ERR, __FILE__, __LINE__, "socket(domain=%d, type=%d, protocol=%d) failed: Error = %s", AF_INET, SOCK_DGRAM, 0, strerror(errno)); return -1; } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr = *listen; /* Same IP for user traffic and signalling*/ addr.sin_port = htons(GTP1C_PORT); #ifdef __FreeBSD__ addr.sin_len = sizeof(addr); #endif if (bind((*gsn)->fd1c, (struct sockaddr *) &addr, sizeof(addr)) < 0) { (*gsn)->err_socket++; gtp_err(LOG_ERR, __FILE__, __LINE__, "bind(fd1c=%d, addr=%lx, len=%d) failed: Error = %s", (*gsn)->fd1c, (unsigned long) &addr, sizeof(addr), strerror(errno)); return -1; } /* Create GTP version 1 user plane socket */ if (((*gsn)->fd1u = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) { (*gsn)->err_socket++; gtp_err(LOG_ERR, __FILE__, __LINE__, "socket(domain=%d, type=%d, protocol=%d) failed: Error = %s", AF_INET, SOCK_DGRAM, 0, strerror(errno)); return -1; } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr = *listen; /* Same IP for user traffic and signalling*/ addr.sin_port = htons(GTP1U_PORT); #ifdef __FreeBSD__ addr.sin_len = sizeof(addr); #endif if (bind((*gsn)->fd1u, (struct sockaddr *) &addr, sizeof(addr)) < 0) { (*gsn)->err_socket++; gtp_err(LOG_ERR, __FILE__, __LINE__, "bind(fd1c=%d, addr=%lx, len=%d) failed: Error = %s", (*gsn)->fd1c, (unsigned long) &addr, sizeof(addr), strerror(errno)); return -1; } return 0; } int gtp_free(struct gsn_t *gsn) { /* Clean up retransmit queues */ queue_free(gsn->queue_req); queue_free(gsn->queue_resp); close(gsn->fd0); close(gsn->fd1c); close(gsn->fd1u); free(gsn); return 0; } /* *********************************************************** * Path management messages * Messages: echo and version not supported. * A path is connection between two UDP/IP endpoints * * A path is either using GTP0 or GTP1. A path can be * established by any kind of GTP message?? * Which source port to use? * GTP-C request destination port is 2123/3386 * GTP-U request destination port is 2152/3386 * T-PDU destination port is 2152/3386. * For the above messages the source port is locally allocated. * For response messages src=rx-dst and dst=rx-src. * For simplicity we should probably use 2123+2152/3386 as * src port even for the cases where src can be locally * allocated. This also means that we have to listen only to * the same ports. * For response messages we need to be able to respond to * the relevant src port even if it is locally allocated by * the peer. * * The need for path management! * We might need to keep a list of active paths. This might * be in the form of remote IP address + UDP port numbers. * (We will consider a path astablished if we have a context * with the node in question) *************************************************************/ /* Send off an echo request */ int gtp_echo_req(struct gsn_t *gsn, int version, void *cbp, struct in_addr *inetaddr) { union gtp_packet packet; int length = get_default_gtp(version, GTP_ECHO_REQ, &packet); return gtp_req(gsn, version, NULL, &packet, length, inetaddr, cbp); } /* Send off an echo reply */ int gtp_echo_resp(struct gsn_t *gsn, int version, struct sockaddr_in *peer, int fd, void *pack, unsigned len) { union gtp_packet packet; int length = get_default_gtp(version, GTP_ECHO_RSP, &packet); gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_RECOVERY, gsn->restart_counter); return gtp_resp(version, gsn, NULL, &packet, length, peer, fd, get_seq(pack), get_tid(pack)); } /* Handle a received echo request */ int gtp_echo_ind(struct gsn_t *gsn, int version, struct sockaddr_in *peer, int fd, void *pack, unsigned len) { /* Check if it was a dublicate request */ if(!gtp_dublicate(gsn, 0, peer, get_seq(pack))) return 0; /* Send off reply to request */ return gtp_echo_resp(gsn, version, peer, fd, pack, len); } /* Handle a received echo reply */ int gtp_echo_conf(struct gsn_t *gsn, int version, struct sockaddr_in *peer, void *pack, unsigned len) { union gtpie_member *ie[GTPIE_SIZE]; unsigned char recovery; void *cbp = NULL; uint8_t type = 0; int hlen = get_hlen(pack); /* Remove packet from queue */ if (gtp_conf(gsn, version, peer, pack, len, &type, &cbp)) return EOF; /* Extract information elements into a pointer array */ if (gtpie_decaps(ie, version, pack+hlen, len-hlen)) { gsn->invalid++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Invalid message format"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, NULL, cbp); return EOF; } if (gtpie_gettv1(ie, GTPIE_RECOVERY, 0, &recovery)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, NULL, cbp); return EOF; } /* Echo reply packages does not have a cause information element */ /* Instead we return the recovery number in the callback function */ if (gsn->cb_conf) gsn->cb_conf(type, recovery, NULL, cbp); return 0; } /* Send off a Version Not Supported message */ /* This message is somewhat special in that it actually is a * response to some other message with unsupported GTP version * For this reason it has parameters like a response, and does * its own message transmission. No signalling queue is used * The reply is sent to the peer IP and peer UDP. This means that * the peer will be receiving a GTP0 message on a GTP1 port! * In practice however this will never happen as a GTP0 GSN will * only listen to the GTP0 port, and therefore will never receive * anything else than GTP0 */ int gtp_unsup_req(struct gsn_t *gsn, int version, struct sockaddr_in *peer, int fd, void *pack, unsigned len) { union gtp_packet packet; /* GTP 1 is the highest supported protocol */ int length = get_default_gtp(1, GTP_NOT_SUPPORTED, &packet); return gtp_notification(gsn, version, &packet, length, peer, fd, 0); } /* Handle a Version Not Supported message */ int gtp_unsup_ind(struct gsn_t *gsn, struct sockaddr_in *peer, void *pack, unsigned len) { if (gsn->cb_unsup_ind) gsn->cb_unsup_ind(peer); return 0; } /* Send off an Supported Extension Headers Notification */ int gtp_extheader_req(struct gsn_t *gsn, int version, struct sockaddr_in *peer, int fd, void *pack, unsigned len) { union gtp_packet packet; int length = get_default_gtp(version, GTP_SUPP_EXT_HEADER, &packet); uint8_t pdcp_pdu = GTP_EXT_PDCP_PDU; if (version < 1) return 0; /* We report back that we support only PDCP PDU headers */ gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_EXT_HEADER_T, sizeof(pdcp_pdu), &pdcp_pdu); return gtp_notification(gsn, version, &packet, length, peer, fd, get_seq(pack)); } /* Handle a Supported Extension Headers Notification */ int gtp_extheader_ind(struct gsn_t *gsn, struct sockaddr_in *peer, void *pack, unsigned len) { if (gsn->cb_extheader_ind) gsn->cb_extheader_ind(peer); return 0; } /* *********************************************************** * Session management messages * Messages: create, update and delete PDP context * * Information storage * Information storage for each PDP context is defined in * 23.060 section 13.3. Includes IMSI, MSISDN, APN, PDP-type, * PDP-address (IP address), sequence numbers, charging ID. * For the SGSN it also includes radio related mobility * information. *************************************************************/ /* API: Send Create PDP Context Request */ extern int gtp_create_context_req(struct gsn_t *gsn, struct pdp_t *pdp, void *cbp) { union gtp_packet packet; int length = get_default_gtp(pdp->version, GTP_CREATE_PDP_REQ, &packet); struct pdp_t *linked_pdp = NULL; /* TODO: Secondary PDP Context Activation Procedure */ /* In secondary activation procedure the PDP context is identified by tei in the header. The following fields are omitted: Selection mode, IMSI, MSISDN, End User Address, Access Point Name and Protocol Configuration Options */ if (pdp->secondary) { if (pdp_getgtp1(&linked_pdp, pdp->teic_own)) { gtp_err(LOG_ERR, __FILE__, __LINE__, "Unknown linked PDP context"); return EOF; } } if (pdp->version == 0) { gtpie_tv0(&packet, &length, GTP_MAX, GTPIE_QOS_PROFILE0, sizeof(pdp->qos_req0), pdp->qos_req0); } if (pdp->version == 1) { if (!pdp->secondary) /* Not Secondary PDP Context Activation Procedure */ gtpie_tv0(&packet, &length, GTP_MAX, GTPIE_IMSI, sizeof(pdp->imsi), (uint8_t*) &pdp->imsi); } gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_RECOVERY, gsn->restart_counter); if (!pdp->secondary) /* Not Secondary PDP Context Activation Procedure */ gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_SELECTION_MODE, pdp->selmode); if (pdp->version == 0) { gtpie_tv2(&packet, &length, GTP_MAX, GTPIE_FL_DI, pdp->fllu); gtpie_tv2(&packet, &length, GTP_MAX, GTPIE_FL_C, pdp->fllc); } if (pdp->version == 1) { gtpie_tv4(&packet, &length, GTP_MAX, GTPIE_TEI_DI, pdp->teid_own); if (!pdp->teic_confirmed) gtpie_tv4(&packet, &length, GTP_MAX, GTPIE_TEI_C, pdp->teic_own); gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_NSAPI, pdp->nsapi); if (pdp->secondary) /* Secondary PDP Context Activation Procedure */ gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_NSAPI, linked_pdp->nsapi); gtpie_tv2(&packet, &length, GTP_MAX, GTPIE_CHARGING_C, pdp->cch_pdp); } /* TODO gtpie_tv2(&packet, &length, GTP_MAX, GTPIE_TRACE_REF, pdp->traceref); gtpie_tv2(&packet, &length, GTP_MAX, GTPIE_TRACE_TYPE, pdp->tracetype); */ if (!pdp->secondary) /* Not Secondary PDP Context Activation Procedure */ gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_EUA, pdp->eua.l, pdp->eua.v); if (!pdp->secondary) /* Not Secondary PDP Context Activation Procedure */ gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_APN, pdp->apn_use.l, pdp->apn_use.v); if (!pdp->secondary) /* Not Secondary PDP Context Activation Procedure */ if (pdp->pco_req.l) gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_PCO, pdp->pco_req.l, pdp->pco_req.v); gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_GSN_ADDR, pdp->gsnlc.l, pdp->gsnlc.v); gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_GSN_ADDR, pdp->gsnlu.l, pdp->gsnlu.v); if (!pdp->secondary) /* Not Secondary PDP Context Activation Procedure */ gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_MSISDN, pdp->msisdn.l, pdp->msisdn.v); if (pdp->version == 1) gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_QOS_PROFILE, pdp->qos_req.l, pdp->qos_req.v); if ((pdp->version == 1) && pdp->tft.l) gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_TFT, pdp->tft.l, pdp->tft.v); if ((pdp->version == 1) && pdp->triggerid.l) gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_TRIGGER_ID, pdp->triggerid.l, pdp->triggerid.v); if ((pdp->version == 1) && pdp->omcid.l) gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_OMC_ID, pdp->omcid.l, pdp->omcid.v); /* TODO hisaddr0 */ gtp_req(gsn, pdp->version, pdp, &packet, length, &pdp->hisaddr0, cbp); return 0; } /* API: Application response to context indication */ int gtp_create_context_resp(struct gsn_t *gsn, struct pdp_t *pdp, int cause) { /* Now send off a reply to the peer */ gtp_create_pdp_resp(gsn, pdp->version, pdp, cause); if (cause != GTPCAUSE_ACC_REQ) { pdp_freepdp(pdp); } return 0; } /* API: Register create context indication callback */ int gtp_set_cb_create_context_ind(struct gsn_t *gsn, int (*cb_create_context_ind) (struct pdp_t* pdp)) { gsn->cb_create_context_ind = cb_create_context_ind; return 0; } /* Send Create PDP Context Response */ int gtp_create_pdp_resp(struct gsn_t *gsn, int version, struct pdp_t *pdp, uint8_t cause) { union gtp_packet packet; int length = get_default_gtp(version, GTP_CREATE_PDP_RSP, &packet); gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_CAUSE, cause); if (cause == GTPCAUSE_ACC_REQ) { if (version == 0) gtpie_tv0(&packet, &length, GTP_MAX, GTPIE_QOS_PROFILE0, sizeof(pdp->qos_neg0), pdp->qos_neg0); gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_REORDER, pdp->reorder); gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_RECOVERY, gsn->restart_counter); if (version == 0) { gtpie_tv2(&packet, &length, GTP_MAX, GTPIE_FL_DI, pdp->fllu); gtpie_tv2(&packet, &length, GTP_MAX, GTPIE_FL_C, pdp->fllc); } if (version == 1) { gtpie_tv4(&packet, &length, GTP_MAX, GTPIE_TEI_DI, pdp->teid_own); gtpie_tv4(&packet, &length, GTP_MAX, GTPIE_TEI_C, pdp->teic_own); } /* TODO: We use teic_own as charging ID */ gtpie_tv4(&packet, &length, GTP_MAX, GTPIE_CHARGING_ID, pdp->teic_own); gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_EUA, pdp->eua.l, pdp->eua.v); if (pdp->pco_neg.l) { /* Optional PCO */ gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_PCO, pdp->pco_neg.l, pdp->pco_neg.v); } gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_GSN_ADDR, pdp->gsnlc.l, pdp->gsnlc.v); gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_GSN_ADDR, pdp->gsnlu.l, pdp->gsnlu.v); if (version == 1) gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_QOS_PROFILE, pdp->qos_neg.l, pdp->qos_neg.v); /* TODO: Charging gateway address */ } return gtp_resp(version, gsn, pdp, &packet, length, &pdp->sa_peer, pdp->fd, pdp->seq, pdp->tid); } /* Handle Create PDP Context Request */ int gtp_create_pdp_ind(struct gsn_t *gsn, int version, struct sockaddr_in *peer, int fd, void *pack, unsigned len) { struct pdp_t *pdp, *pdp_old; struct pdp_t pdp_buf; union gtpie_member* ie[GTPIE_SIZE]; uint8_t recovery; uint16_t seq = get_seq(pack); int hlen = get_hlen(pack); uint8_t linked_nsapi = 0; struct pdp_t *linked_pdp = NULL; if(!gtp_dublicate(gsn, version, peer, seq)) return 0; pdp = &pdp_buf; memset(pdp, 0, sizeof(struct pdp_t)); if (version == 0) { pdp->imsi = ((union gtp_packet*)pack)->gtp0.h.tid & 0x0fffffffffffffffull; pdp->nsapi = (((union gtp_packet*)pack)->gtp0.h.tid & 0xf000000000000000ull) >> 60; } pdp->seq = seq; pdp->sa_peer = *peer; pdp->fd = fd; pdp->version = version; /* Decode information elements */ if (gtpie_decaps(ie, version, pack+hlen, len-hlen)) { gsn->invalid++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Invalid message format"); if (0 == version) return EOF; else return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_INVALID_MESSAGE); } if (version == 1) { /* Linked NSAPI (conditional) */ /* If included this is the Secondary PDP Context Activation Procedure */ /* In secondary activation IMSI is not included, so the context must be */ /* identified by the tei */ if (!gtpie_gettv1(ie, GTPIE_NSAPI, 1, &linked_nsapi)) { /* Find the primary PDP context */ if (pdp_getgtp1(&linked_pdp, get_tei(pack))) { gsn->incorrect++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Incorrect optional information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_OPT_IE_INCORRECT); } /* Check that the primary PDP context matches linked nsapi */ if (linked_pdp->nsapi != linked_nsapi) { gsn->incorrect++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Incorrect optional information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_OPT_IE_INCORRECT); } /* Copy parameters from primary context */ pdp->selmode = linked_pdp->selmode; pdp->imsi = linked_pdp->imsi; pdp->msisdn = linked_pdp->msisdn; pdp->eua = linked_pdp->eua; pdp->pco_req = linked_pdp->pco_req; pdp->apn_req = linked_pdp->apn_req; pdp->teic_gn = linked_pdp->teic_gn; pdp->secondary = 1; } } /* if (version == 1) */ if (version == 0) { if (gtpie_gettv0(ie, GTPIE_QOS_PROFILE0, 0, pdp->qos_req0, sizeof(pdp->qos_req0))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_MAN_IE_MISSING); } } if ((version == 1) && (!linked_pdp)) { /* Not Secondary PDP Context Activation Procedure */ /* IMSI (conditional) */ if (gtpie_gettv0(ie, GTPIE_IMSI, 0, &pdp->imsi, sizeof(pdp->imsi))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_MAN_IE_MISSING); } } /* Recovery (optional) */ if (!gtpie_gettv1(ie, GTPIE_RECOVERY, 0, &recovery)) { /* TODO: Handle received recovery IE */ } /* Selection mode (conditional) */ if (!linked_pdp) { /* Not Secondary PDP Context Activation Procedure */ if (gtpie_gettv0(ie, GTPIE_SELECTION_MODE, 0, &pdp->selmode, sizeof(pdp->selmode))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_MAN_IE_MISSING); } } if (version == 0) { if (gtpie_gettv2(ie, GTPIE_FL_DI, 0, &pdp->flru)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_MAN_IE_MISSING); } if (gtpie_gettv2(ie, GTPIE_FL_C, 0, &pdp->flrc)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_MAN_IE_MISSING); } } if (version == 1) { /* TEID (mandatory) */ if (gtpie_gettv4(ie, GTPIE_TEI_DI, 0, &pdp->teid_gn)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_MAN_IE_MISSING); } /* TEIC (conditional) */ if (!linked_pdp) { /* Not Secondary PDP Context Activation Procedure */ if (gtpie_gettv4(ie, GTPIE_TEI_C, 0, &pdp->teic_gn)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_MAN_IE_MISSING); } } /* NSAPI (mandatory) */ if (gtpie_gettv1(ie, GTPIE_NSAPI, 0, &pdp->nsapi)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_MAN_IE_MISSING); } } /* Charging Characteriatics (optional) */ /* Trace reference (optional) */ /* Trace type (optional) */ /* Charging Characteriatics (optional) */ if (!linked_pdp) { /* Not Secondary PDP Context Activation Procedure */ /* End User Address (conditional) */ if (gtpie_gettlv(ie, GTPIE_EUA, 0, &pdp->eua.l, &pdp->eua.v, sizeof(pdp->eua.v))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_MAN_IE_MISSING); } /* APN */ if (gtpie_gettlv(ie, GTPIE_APN, 0, &pdp->apn_req.l, &pdp->apn_req.v, sizeof(pdp->apn_req.v))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_MAN_IE_MISSING); } /* Extract protocol configuration options (optional) */ if (!gtpie_gettlv(ie, GTPIE_PCO, 0, &pdp->pco_req.l, &pdp->pco_req.v, sizeof(pdp->pco_req.v))) { } } /* SGSN address for signalling (mandatory) */ if (gtpie_gettlv(ie, GTPIE_GSN_ADDR, 0, &pdp->gsnrc.l, &pdp->gsnrc.v, sizeof(pdp->gsnrc.v))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_MAN_IE_MISSING); } /* SGSN address for user traffic (mandatory) */ if (gtpie_gettlv(ie, GTPIE_GSN_ADDR, 1, &pdp->gsnru.l, &pdp->gsnru.v, sizeof(pdp->gsnru.v))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_MAN_IE_MISSING); } if (!linked_pdp) { /* Not Secondary PDP Context Activation Procedure */ /* MSISDN (conditional) */ if (gtpie_gettlv(ie, GTPIE_MSISDN, 0, &pdp->msisdn.l, &pdp->msisdn.v, sizeof(pdp->msisdn.v))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_MAN_IE_MISSING); } } if (version == 1) { /* QoS (mandatory) */ if (gtpie_gettlv(ie, GTPIE_QOS_PROFILE, 0, &pdp->qos_req.l, &pdp->qos_req.v, sizeof(pdp->qos_req.v))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_MAN_IE_MISSING); } /* TFT (conditional) */ if (gtpie_gettlv(ie, GTPIE_TFT, 0, &pdp->tft.l, &pdp->tft.v, sizeof(pdp->tft.v))) { } /* Trigger ID */ /* OMC identity */ } /* Initialize our own IP addresses */ in_addr2gsna(&pdp->gsnlc, &gsn->gsnc); in_addr2gsna(&pdp->gsnlu, &gsn->gsnu); if (GTP_DEBUG) printf("gtp_create_pdp_ind: Before pdp_tidget\n"); if (!pdp_getimsi(&pdp_old, pdp->imsi, pdp->nsapi)) { /* Found old pdp with same tid. Now the voodoo begins! */ /* 09.60 / 29.060 allows create on existing context to "steal" */ /* the context which was allready established */ /* We check that the APN, selection mode and MSISDN is the same */ if (GTP_DEBUG) printf("gtp_create_pdp_ind: Old context found\n"); if ((pdp->apn_req.l == pdp_old->apn_req.l) && (!memcmp(pdp->apn_req.v, pdp_old->apn_req.v, pdp->apn_req.l)) && (pdp->selmode == pdp_old->selmode) && (pdp->msisdn.l == pdp_old->msisdn.l) && (!memcmp(pdp->msisdn.v, pdp_old->msisdn.v, pdp->msisdn.l))) { /* OK! We are dealing with the same APN. We will copy new * parameters to the old pdp and send off confirmation * We ignore the following information elements: * QoS: MS will get originally negotiated QoS. * End user address (EUA). MS will get old EUA anyway. * Protocol configuration option (PCO): Only application can verify */ if (GTP_DEBUG) printf("gtp_create_pdp_ind: Old context found\n"); /* Copy remote flow label */ pdp_old->flru = pdp->flru; pdp_old->flrc = pdp->flrc; /* Copy remote tei */ pdp_old->teid_gn = pdp->teid_gn; pdp_old->teic_gn = pdp->teic_gn; /* Copy peer GSN address */ pdp_old->gsnrc.l = pdp->gsnrc.l; memcpy(&pdp_old->gsnrc.v, &pdp->gsnrc.v, pdp->gsnrc.l); pdp_old->gsnru.l = pdp->gsnru.l; memcpy(&pdp_old->gsnru.v, &pdp->gsnru.v, pdp->gsnru.l); /* Copy request parameters */ pdp_old->seq = pdp->seq; pdp_old->sa_peer = pdp->sa_peer; pdp_old->fd = pdp->fd = fd; pdp_old->version = pdp->version = version; /* Switch to using the old pdp context */ pdp = pdp_old; /* Confirm to peer that things were "successful" */ return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_ACC_REQ); } else { /* This is not the same PDP context. Delete the old one. */ if (GTP_DEBUG) printf("gtp_create_pdp_ind: Deleting old context\n"); if (gsn->cb_delete_context) gsn->cb_delete_context(pdp_old); pdp_freepdp(pdp_old); if (GTP_DEBUG) printf("gtp_create_pdp_ind: Deleted...\n"); } } pdp_newpdp(&pdp, pdp->imsi, pdp->nsapi, pdp); /* Callback function to validata login */ if (gsn->cb_create_context_ind !=0) return gsn->cb_create_context_ind(pdp); else { gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "No create_context_ind callback defined"); return gtp_create_pdp_resp(gsn, version, pdp, GTPCAUSE_NOT_SUPPORTED); } } /* Handle Create PDP Context Response */ int gtp_create_pdp_conf(struct gsn_t *gsn, int version, struct sockaddr_in *peer, void *pack, unsigned len) { struct pdp_t *pdp; union gtpie_member *ie[GTPIE_SIZE]; uint8_t cause, recovery; void *cbp = NULL; uint8_t type = 0; int hlen = get_hlen(pack); /* Remove packet from queue */ if (gtp_conf(gsn, version, peer, pack, len, &type, &cbp)) return EOF; /* Find the context in question */ if (pdp_getgtp1(&pdp, get_tei(pack))) { gsn->err_unknownpdp++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Unknown PDP context"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, NULL, cbp); return EOF; } /* Register that we have received a valid teic from GGSN */ pdp->teic_confirmed = 1; /* Decode information elements */ if (gtpie_decaps(ie, version, pack+hlen, len-hlen)) { gsn->invalid++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Invalid message format"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return EOF; } /* Extract cause value (mandatory) */ if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return EOF; } /* Extract recovery (optional) */ if (!gtpie_gettv1(ie, GTPIE_RECOVERY, 0, &recovery)) { /* TODO: Handle received recovery IE */ } /* Extract protocol configuration options (optional) */ if (!gtpie_gettlv(ie, GTPIE_PCO, 0, &pdp->pco_req.l, &pdp->pco_req.v, sizeof(pdp->pco_req.v))) { } /* Check all conditional information elements */ if (GTPCAUSE_ACC_REQ == cause) { if (version == 0) { if (gtpie_gettv0(ie, GTPIE_QOS_PROFILE0, 0, &pdp->qos_neg0, sizeof(pdp->qos_neg0))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing conditional information field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return EOF; } } if (gtpie_gettv1(ie, GTPIE_REORDER, 0, &pdp->reorder)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing conditional information field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return EOF; } if (version == 0) { if (gtpie_gettv2(ie, GTPIE_FL_DI, 0, &pdp->flru)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing conditional information field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return EOF; } if (gtpie_gettv2(ie, GTPIE_FL_C, 0, &pdp->flrc)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing conditional information field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return EOF; } } if (version == 1) { if (gtpie_gettv4(ie, GTPIE_TEI_DI, 0, &pdp->teid_gn)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing conditional information field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return EOF; } if (gtpie_gettv4(ie, GTPIE_TEI_C, 0, &pdp->teic_gn)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing conditional information field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return EOF; } } if (gtpie_gettv4(ie, GTPIE_CHARGING_ID, 0, &pdp->cid)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing conditional information field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ } if (gtpie_gettlv(ie, GTPIE_EUA, 0, &pdp->eua.l, &pdp->eua.v, sizeof(pdp->eua.v))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing conditional information field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return EOF; } if (gtpie_gettlv(ie, GTPIE_GSN_ADDR, 0, &pdp->gsnrc.l, &pdp->gsnrc.v, sizeof(pdp->gsnrc.v))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing conditional information field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return EOF; } if (gtpie_gettlv(ie, GTPIE_GSN_ADDR, 1, &pdp->gsnru.l, &pdp->gsnru.v, sizeof(pdp->gsnru.v))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing conditional information field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return EOF; } if (version == 1) { if (gtpie_gettlv(ie, GTPIE_QOS_PROFILE, 0, &pdp->qos_neg.l, &pdp->qos_neg.v, sizeof(pdp->qos_neg.v))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing conditional information field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return EOF; } } } if (gsn->cb_conf) gsn->cb_conf(type, cause, pdp, cbp); return 0; } /* API: Send Update PDP Context Request */ int gtp_update_context(struct gsn_t *gsn, struct pdp_t *pdp, void *cbp, struct in_addr* inetaddr) { union gtp_packet packet; int length = get_default_gtp(pdp->version, GTP_UPDATE_PDP_REQ, &packet); if (pdp->version == 0) gtpie_tv0(&packet, &length, GTP_MAX, GTPIE_QOS_PROFILE0, sizeof(pdp->qos_req0), pdp->qos_req0); /* Include IMSI if updating with unknown teic_gn */ if ((pdp->version == 1) && (!pdp->teic_gn)) gtpie_tv0(&packet, &length, GTP_MAX, GTPIE_IMSI, sizeof(pdp->imsi), (uint8_t*) &pdp->imsi); gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_RECOVERY, gsn->restart_counter); if (pdp->version == 0) { gtpie_tv2(&packet, &length, GTP_MAX, GTPIE_FL_DI, pdp->fllu); gtpie_tv2(&packet, &length, GTP_MAX, GTPIE_FL_C, pdp->fllc); } if (pdp->version == 1) { gtpie_tv4(&packet, &length, GTP_MAX, GTPIE_TEI_DI, pdp->teid_own); if (!pdp->teic_confirmed) gtpie_tv4(&packet, &length, GTP_MAX, GTPIE_TEI_C, pdp->teic_own); } gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_NSAPI, pdp->nsapi); /* TODO gtpie_tv2(&packet, &length, GTP_MAX, GTPIE_TRACE_REF, pdp->traceref); gtpie_tv2(&packet, &length, GTP_MAX, GTPIE_TRACE_TYPE, pdp->tracetype); */ /* TODO if ggsn update message gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_EUA, pdp->eua.l, pdp->eua.v); */ gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_GSN_ADDR, pdp->gsnlc.l, pdp->gsnlc.v); gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_GSN_ADDR, pdp->gsnlu.l, pdp->gsnlu.v); if (pdp->version == 1) gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_QOS_PROFILE, pdp->qos_req.l, pdp->qos_req.v); if ((pdp->version == 1) && pdp->tft.l) gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_TFT, pdp->tft.l, pdp->tft.v); if ((pdp->version == 1) && pdp->triggerid.l) gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_TRIGGER_ID, pdp->triggerid.l, pdp->triggerid.v); if ((pdp->version == 1) && pdp->omcid.l) gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_OMC_ID, pdp->omcid.l, pdp->omcid.v); gtp_req(gsn, pdp->version, NULL, &packet, length, inetaddr, cbp); return 0; } /* Send Update PDP Context Response */ int gtp_update_pdp_resp(struct gsn_t *gsn, int version, struct sockaddr_in *peer, int fd, void *pack, unsigned len, struct pdp_t *pdp, uint8_t cause) { union gtp_packet packet; int length = get_default_gtp(version, GTP_CREATE_PDP_RSP, &packet); gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_CAUSE, cause); if (cause == GTPCAUSE_ACC_REQ) { if (version == 0) gtpie_tv0(&packet, &length, GTP_MAX, GTPIE_QOS_PROFILE0, sizeof(pdp->qos_neg0), pdp->qos_neg0); gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_RECOVERY, gsn->restart_counter); if (version == 0) { gtpie_tv2(&packet, &length, GTP_MAX, GTPIE_FL_DI, pdp->fllu); gtpie_tv2(&packet, &length, GTP_MAX, GTPIE_FL_C, pdp->fllc); } if (version == 1) { gtpie_tv4(&packet, &length, GTP_MAX, GTPIE_TEI_DI, pdp->teid_own); if (!pdp->teic_confirmed) gtpie_tv4(&packet, &length, GTP_MAX, GTPIE_TEI_C, pdp->teic_own); } /* TODO we use teid_own as charging ID address */ gtpie_tv4(&packet, &length, GTP_MAX, GTPIE_CHARGING_ID, pdp->teid_own); /* If ggsn gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_EUA, pdp->eua.l, pdp->eua.v); */ gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_GSN_ADDR, pdp->gsnlc.l, pdp->gsnlc.v); gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_GSN_ADDR, pdp->gsnlu.l, pdp->gsnlu.v); if (version == 1) gtpie_tlv(&packet, &length, GTP_MAX, GTPIE_QOS_PROFILE, pdp->qos_neg.l, pdp->qos_neg.v); /* TODO: Charging gateway address */ } return gtp_resp(version, gsn, pdp, &packet, length, peer, fd, get_seq(pack), get_tid(pack)); } /* Handle Update PDP Context Request */ int gtp_update_pdp_ind(struct gsn_t *gsn, int version, struct sockaddr_in *peer, int fd, void *pack, unsigned len) { struct pdp_t *pdp; struct pdp_t pdp_backup; union gtpie_member* ie[GTPIE_SIZE]; uint8_t recovery; uint16_t seq = get_seq(pack); int hlen = get_hlen(pack); uint64_t imsi; uint8_t nsapi; /* Is this a dublicate ? */ if(!gtp_dublicate(gsn, version, peer, seq)) { return 0; /* We allready send of response once */ } /* Decode information elements */ if (gtpie_decaps(ie, version, pack+hlen, len-hlen)) { gsn->invalid++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Invalid message format"); if (0 == version) return EOF; else return gtp_update_pdp_resp(gsn, version, peer, fd, pack, len, NULL, GTPCAUSE_INVALID_MESSAGE); } /* Finding PDP: */ /* For GTP0 we use the tunnel identifier to provide imsi and nsapi. */ /* For GTP1 we must use imsi and nsapi if imsi is present. Otherwise */ /* we have to use the tunnel endpoint identifier */ if (version == 0) { imsi = ((union gtp_packet*)pack)->gtp0.h.tid & 0x0fffffffffffffffull; nsapi = (((union gtp_packet*)pack)->gtp0.h.tid & 0xf000000000000000ull) >> 60; /* Find the context in question */ if (pdp_getimsi(&pdp, imsi, nsapi)) { gsn->err_unknownpdp++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Unknown PDP context"); return gtp_update_pdp_resp(gsn, version, peer, fd, pack, len, NULL, GTPCAUSE_NON_EXIST); } } else if (version == 1) { /* NSAPI (mandatory) */ if (gtpie_gettv1(ie, GTPIE_NSAPI, 0, &nsapi)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_update_pdp_resp(gsn, version, peer, fd, pack, len, NULL, GTPCAUSE_MAN_IE_MISSING); } /* IMSI (conditional) */ if (gtpie_gettv0(ie, GTPIE_IMSI, 0, &imsi, sizeof(imsi))) { /* Find the context in question */ if (pdp_getgtp1(&pdp, get_tei(pack))) { gsn->err_unknownpdp++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Unknown PDP context"); return gtp_update_pdp_resp(gsn, version, peer, fd, pack, len, NULL, GTPCAUSE_NON_EXIST); } } else { /* Find the context in question */ if (pdp_getimsi(&pdp, imsi, nsapi)) { gsn->err_unknownpdp++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Unknown PDP context"); return gtp_update_pdp_resp(gsn, version, peer, fd, pack, len, NULL, GTPCAUSE_NON_EXIST); } } } else { gtp_err(LOG_ERR, __FILE__, __LINE__, "Unknown version"); return EOF; } /* Make a backup copy in case anything is wrong */ memcpy(&pdp_backup, pdp, sizeof(pdp_backup)); if (version == 0) { if (gtpie_gettv0(ie, GTPIE_QOS_PROFILE0, 0, pdp->qos_req0, sizeof(pdp->qos_req0))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); memcpy(pdp, &pdp_backup, sizeof(pdp_backup)); return gtp_update_pdp_resp(gsn, version, peer, fd, pack, len, pdp, GTPCAUSE_MAN_IE_MISSING); } } /* Recovery (optional) */ if (!gtpie_gettv1(ie, GTPIE_RECOVERY, 0, &recovery)) { /* TODO: Handle received recovery IE */ } if (version == 0) { if (gtpie_gettv2(ie, GTPIE_FL_DI, 0, &pdp->flru)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); memcpy(pdp, &pdp_backup, sizeof(pdp_backup)); return gtp_update_pdp_resp(gsn, version, peer, fd, pack, len, pdp, GTPCAUSE_MAN_IE_MISSING); } if (gtpie_gettv2(ie, GTPIE_FL_C, 0, &pdp->flrc)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); memcpy(pdp, &pdp_backup, sizeof(pdp_backup)); return gtp_update_pdp_resp(gsn, version, peer, fd, pack, len, pdp, GTPCAUSE_MAN_IE_MISSING); } } if (version == 1) { /* TEID (mandatory) */ if (gtpie_gettv4(ie, GTPIE_TEI_DI, 0, &pdp->teid_gn)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); memcpy(pdp, &pdp_backup, sizeof(pdp_backup)); return gtp_update_pdp_resp(gsn, version, peer, fd, pack, len, pdp, GTPCAUSE_MAN_IE_MISSING); } /* TEIC (conditional) */ /* If TEIC is not included it means that we have allready received it */ /* TODO: From 29.060 it is not clear if TEI_C MUST be included for */ /* all updated contexts, or only for one of the linked contexts */ gtpie_gettv4(ie, GTPIE_TEI_C, 0, &pdp->teic_gn); /* NSAPI (mandatory) */ if (gtpie_gettv1(ie, GTPIE_NSAPI, 0, &pdp->nsapi)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); memcpy(pdp, &pdp_backup, sizeof(pdp_backup)); return gtp_update_pdp_resp(gsn, version, peer, fd, pack, len, pdp, GTPCAUSE_MAN_IE_MISSING); } } /* Trace reference (optional) */ /* Trace type (optional) */ /* End User Address (conditional) TODO: GGSN Initiated if (gtpie_gettlv(ie, GTPIE_EUA, 0, &pdp->eua.l, &pdp->eua.v, sizeof(pdp->eua.v))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); memcpy(pdp, &pdp_backup, sizeof(pdp_backup)); return gtp_update_pdp_resp(gsn, version, pdp, GTPCAUSE_MAN_IE_MISSING); } */ /* SGSN address for signalling (mandatory) */ /* It is weird that this is mandatory when TEIC is conditional */ if (gtpie_gettlv(ie, GTPIE_GSN_ADDR, 0, &pdp->gsnrc.l, &pdp->gsnrc.v, sizeof(pdp->gsnrc.v))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); memcpy(pdp, &pdp_backup, sizeof(pdp_backup)); return gtp_update_pdp_resp(gsn, version, peer, fd, pack, len, pdp, GTPCAUSE_MAN_IE_MISSING); } /* SGSN address for user traffic (mandatory) */ if (gtpie_gettlv(ie, GTPIE_GSN_ADDR, 1, &pdp->gsnru.l, &pdp->gsnru.v, sizeof(pdp->gsnru.v))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); memcpy(pdp, &pdp_backup, sizeof(pdp_backup)); return gtp_update_pdp_resp(gsn, version, peer, fd, pack, len, pdp, GTPCAUSE_MAN_IE_MISSING); } if (version == 1) { /* QoS (mandatory) */ if (gtpie_gettlv(ie, GTPIE_QOS_PROFILE, 0, &pdp->qos_req.l, &pdp->qos_req.v, sizeof(pdp->qos_req.v))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); memcpy(pdp, &pdp_backup, sizeof(pdp_backup)); return gtp_update_pdp_resp(gsn, version, peer, fd, pack, len, pdp, GTPCAUSE_MAN_IE_MISSING); } /* TFT (conditional) */ if (gtpie_gettlv(ie, GTPIE_TFT, 0, &pdp->tft.l, &pdp->tft.v, sizeof(pdp->tft.v))) { } /* OMC identity */ } /* Confirm to peer that things were "successful" */ return gtp_update_pdp_resp(gsn, version, peer, fd, pack, len, pdp, GTPCAUSE_ACC_REQ); } /* Handle Update PDP Context Response */ int gtp_update_pdp_conf(struct gsn_t *gsn, int version, struct sockaddr_in *peer, void *pack, unsigned len) { struct pdp_t *pdp; union gtpie_member *ie[GTPIE_SIZE]; uint8_t cause, recovery; void *cbp = NULL; uint8_t type = 0; /* Remove packet from queue */ if (gtp_conf(gsn, 0, peer, pack, len, &type, &cbp)) return EOF; /* Find the context in question */ if (pdp_getgtp0(&pdp, ntoh16(((union gtp_packet*)pack)->gtp0.h.flow))) { gsn->err_unknownpdp++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Unknown PDP context"); if (gsn->cb_conf) gsn->cb_conf(type, cause, NULL, cbp); return EOF; } /* Register that we have received a valid teic from GGSN */ pdp->teic_confirmed = 1; /* Decode information elements */ if (gtpie_decaps(ie, 0, pack+GTP0_HEADER_SIZE, len-GTP0_HEADER_SIZE)) { gsn->invalid++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Invalid message format"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return EOF; } /* Extract cause value (mandatory) */ if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return EOF; } /* Extract recovery (optional) */ if (!gtpie_gettv1(ie, GTPIE_RECOVERY, 0, &recovery)) { /* TODO: Handle received recovery IE */ } /* Check all conditional information elements */ if (GTPCAUSE_ACC_REQ != cause) { if (gsn->cb_conf) gsn->cb_conf(type, cause, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return 0; } else { /* Check for missing conditionary information elements */ if (!(gtpie_exist(ie, GTPIE_QOS_PROFILE0, 0) && gtpie_exist(ie, GTPIE_REORDER, 0) && gtpie_exist(ie, GTPIE_FL_DI, 0) && gtpie_exist(ie, GTPIE_FL_C, 0) && gtpie_exist(ie, GTPIE_CHARGING_ID, 0) && gtpie_exist(ie, GTPIE_EUA, 0) && gtpie_exist(ie, GTPIE_GSN_ADDR, 0) && gtpie_exist(ie, GTPIE_GSN_ADDR, 1))) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing conditional information field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, pdp, cbp); /* if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); */ return EOF; } /* Update pdp with new values */ gtpie_gettv0(ie, GTPIE_QOS_PROFILE0, 0, pdp->qos_neg0, sizeof(pdp->qos_neg0)); gtpie_gettv1(ie, GTPIE_REORDER, 0, &pdp->reorder); gtpie_gettv2(ie, GTPIE_FL_DI, 0, &pdp->flru); gtpie_gettv2(ie, GTPIE_FL_C, 0, &pdp->flrc); gtpie_gettv4(ie, GTPIE_CHARGING_ID, 0, &pdp->cid); gtpie_gettlv(ie, GTPIE_EUA, 0, &pdp->eua.l, &pdp->eua.v, sizeof(pdp->eua.v)); gtpie_gettlv(ie, GTPIE_GSN_ADDR, 0, &pdp->gsnrc.l, &pdp->gsnrc.v, sizeof(pdp->gsnrc.v)); gtpie_gettlv(ie, GTPIE_GSN_ADDR, 1, &pdp->gsnru.l, &pdp->gsnru.v, sizeof(pdp->gsnru.v)); if (gsn->cb_conf) gsn->cb_conf(type, cause, pdp, cbp); return 0; /* Succes */ } } /* API: Send Delete PDP Context Request */ int gtp_delete_context_req(struct gsn_t *gsn, struct pdp_t *pdp, void *cbp, int teardown) { union gtp_packet packet; int length = get_default_gtp(pdp->version, GTP_DELETE_PDP_REQ, &packet); struct in_addr addr; struct pdp_t *linked_pdp; struct pdp_t *secondary_pdp; int n; int count = 0; if (gsna2in_addr(&addr, &pdp->gsnrc)) { gsn->err_address++; gtp_err(LOG_ERR, __FILE__, __LINE__, "GSN address conversion failed"); return EOF; } if (pdp_getgtp1(&linked_pdp, pdp->teic_own)) { gtp_err(LOG_ERR, __FILE__, __LINE__, "Unknown linked PDP context"); return EOF; } if (!teardown) { for (n=0; n< PDP_MAXNSAPI; n++) if (linked_pdp->secondary_tei[n]) count++; if (count <= 1) { gtp_err(LOG_ERR, __FILE__, __LINE__, "Must use teardown for last context"); return EOF; } } if (pdp->version == 1) { gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_NSAPI, pdp->nsapi); if (teardown) gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_TEARDOWN, 0xff); } gtp_req(gsn, pdp->version, pdp, &packet, length, &addr, cbp); if (teardown) { /* Remove all contexts */ for (n=0; n< PDP_MAXNSAPI; n++) { if (linked_pdp->secondary_tei[n]) { if (pdp_getgtp1(&secondary_pdp, linked_pdp->secondary_tei[n])) { gtp_err(LOG_ERR, __FILE__, __LINE__, "Unknown secondary PDP context"); return EOF; } if (linked_pdp != secondary_pdp) { if (gsn->cb_delete_context) gsn->cb_delete_context(secondary_pdp); pdp_freepdp(secondary_pdp); } } } if (gsn->cb_delete_context) gsn->cb_delete_context(linked_pdp); pdp_freepdp(linked_pdp); } else { if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); if (pdp == linked_pdp) { linked_pdp->secondary_tei[pdp->nsapi & 0xf0] = 0; linked_pdp->nodata = 1; } else pdp_freepdp(pdp); } return 0; } /* Send Delete PDP Context Response */ int gtp_delete_pdp_resp(struct gsn_t *gsn, int version, struct sockaddr_in *peer, int fd, void *pack, unsigned len, struct pdp_t *pdp, struct pdp_t *linked_pdp, uint8_t cause, int teardown) { union gtp_packet packet; struct pdp_t *secondary_pdp; int length = get_default_gtp(version, GTP_DELETE_PDP_RSP, &packet); int n; gtpie_tv1(&packet, &length, GTP_MAX, GTPIE_CAUSE, cause); gtp_resp(version, gsn, pdp, &packet, length, peer, fd, get_seq(pack), get_tid(pack)); if (cause == GTPCAUSE_ACC_REQ) { if ((teardown) || (version == 0)) { /* Remove all contexts */ for (n=0; n< PDP_MAXNSAPI; n++) { if (linked_pdp->secondary_tei[n]) { if (pdp_getgtp1(&secondary_pdp, linked_pdp->secondary_tei[n])) { gtp_err(LOG_ERR, __FILE__, __LINE__, "Unknown secondary PDP context"); return EOF; } if (linked_pdp != secondary_pdp) { if (gsn->cb_delete_context) gsn->cb_delete_context(secondary_pdp); pdp_freepdp(secondary_pdp); } } } if (gsn->cb_delete_context) gsn->cb_delete_context(linked_pdp); pdp_freepdp(linked_pdp); } else { /* Remove only current context */ if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); if (pdp == linked_pdp) { linked_pdp->secondary_tei[pdp->nsapi & 0xf0] = 0; linked_pdp->nodata = 1; } else pdp_freepdp(pdp); } } /* if (cause == GTPCAUSE_ACC_REQ) */ return 0; } /* Handle Delete PDP Context Request */ int gtp_delete_pdp_ind(struct gsn_t *gsn, int version, struct sockaddr_in *peer, int fd, void *pack, unsigned len) { struct pdp_t *pdp = NULL; struct pdp_t *linked_pdp = NULL; union gtpie_member* ie[GTPIE_SIZE]; uint16_t seq = get_seq(pack); int hlen = get_hlen(pack); uint8_t nsapi; uint8_t teardown = 0; int n; int count = 0; /* Is this a dublicate ? */ if(!gtp_dublicate(gsn, version, peer, seq)) { return 0; /* We allready send off response once */ } /* Find the linked context in question */ if (pdp_getgtp1(&linked_pdp, get_tei(pack))) { gsn->err_unknownpdp++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Unknown PDP context"); return gtp_delete_pdp_resp(gsn, version, peer, fd, pack, len, NULL, NULL, GTPCAUSE_NON_EXIST, teardown); } /* If version 0 this is also the secondary context */ if (version == 0) pdp = linked_pdp; /* Decode information elements */ if (gtpie_decaps(ie, version, pack+hlen, len-hlen)) { gsn->invalid++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Invalid message format"); if (0 == version) return EOF; else return gtp_delete_pdp_resp(gsn, version, peer, fd, pack, len, NULL, NULL, GTPCAUSE_INVALID_MESSAGE, teardown); } if (version == 1) { /* NSAPI (mandatory) */ if (gtpie_gettv1(ie, GTPIE_NSAPI, 0, &nsapi)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); return gtp_delete_pdp_resp(gsn, version, peer, fd, pack, len, NULL, NULL, GTPCAUSE_MAN_IE_MISSING, teardown); } /* Find the context in question */ if (pdp_getgtp1(&pdp, linked_pdp->secondary_tei[nsapi & 0x0f])) { gsn->err_unknownpdp++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Unknown PDP context"); return gtp_delete_pdp_resp(gsn, version, peer, fd, pack, len, NULL, NULL, GTPCAUSE_NON_EXIST, teardown); } /* Teardown (conditional) */ gtpie_gettv1(ie, GTPIE_TEARDOWN, 0, &teardown); if (!teardown) { for (n=0; n< PDP_MAXNSAPI; n++) if (linked_pdp->secondary_tei[n]) count++; if (count <= 1) { return 0; /* 29.060 7.3.5 Ignore message */ } } } return gtp_delete_pdp_resp(gsn, version, peer, fd, pack, len, pdp, linked_pdp, GTPCAUSE_ACC_REQ, teardown); } /* Handle Delete PDP Context Response */ int gtp_delete_pdp_conf(struct gsn_t *gsn, int version, struct sockaddr_in *peer, void *pack, unsigned len) { union gtpie_member *ie[GTPIE_SIZE]; uint8_t cause; void *cbp = NULL; uint8_t type = 0; int hlen = get_hlen(pack); /* Remove packet from queue */ if (gtp_conf(gsn, version, peer, pack, len, &type, &cbp)) return EOF; /* Decode information elements */ if (gtpie_decaps(ie, version, pack+hlen, len-hlen)) { gsn->invalid++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Invalid message format"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, NULL, cbp); return EOF; } /* Extract cause value (mandatory) */ if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause)) { gsn->missing++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Missing mandatory information field"); if (gsn->cb_conf) gsn->cb_conf(type, EOF, NULL, cbp); return EOF; } /* Check the cause value (again) */ if ((GTPCAUSE_ACC_REQ != cause) && (GTPCAUSE_NON_EXIST != cause)) { gsn->err_cause++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Unexpected cause value received: %d", cause); if (gsn->cb_conf) gsn->cb_conf(type, cause, NULL, cbp); return EOF; } /* Callback function to notify application */ if (gsn->cb_conf) gsn->cb_conf(type, cause, NULL, cbp); return 0; } /* Send Error Indication (response to a GPDU message */ int gtp_error_ind_resp(struct gsn_t *gsn, int version, struct sockaddr_in *peer, int fd, void *pack, unsigned len) { union gtp_packet packet; int length = get_default_gtp(version, GTP_ERROR, &packet); return gtp_resp(version, gsn, NULL, &packet, length, peer, fd, get_seq(pack), get_tid(pack)); } /* Handle Error Indication */ int gtp_error_ind_conf(struct gsn_t *gsn, int version, struct sockaddr_in *peer, void *pack, unsigned len) { struct pdp_t *pdp; /* Find the context in question */ if (pdp_tidget(&pdp, ((union gtp_packet*)pack)->gtp0.h.tid)) { gsn->err_unknownpdp++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Unknown PDP context"); return EOF; } gsn->err_unknownpdp++; /* TODO: Change counter */ gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Received Error Indication"); if (gsn->cb_delete_context) gsn->cb_delete_context(pdp); pdp_freepdp(pdp); return 0; } int gtp_gpdu_ind(struct gsn_t *gsn, int version, struct sockaddr_in *peer, int fd, void *pack, unsigned len) { int hlen = GTP1_HEADER_SIZE_SHORT; /* Need to include code to verify packet src and dest addresses */ struct pdp_t *pdp; if (version == 0) { if (pdp_getgtp0(&pdp, ntoh16(((union gtp_packet*)pack)->gtp0.h.flow))) { gsn->err_unknownpdp++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Unknown PDP context"); return gtp_error_ind_resp(gsn, version, peer, fd, pack, len); } hlen = GTP0_HEADER_SIZE; } else if (version == 1) { if (pdp_getgtp1(&pdp, ntoh32(((union gtp_packet*)pack)->gtp1l.h.tei))) { gsn->err_unknownpdp++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Unknown PDP context"); return gtp_error_ind_resp(gsn, version, peer, fd, pack, len); } /* Is this a long or a short header ? */ if (((union gtp_packet*)pack)->gtp1l.h.flags & 0x07) hlen = GTP1_HEADER_SIZE_LONG; else hlen = GTP1_HEADER_SIZE_SHORT; } else { gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Unknown version"); } /* If the GPDU was not from the peer GSN tell him to delete context */ if (memcmp(&peer->sin_addr, pdp->gsnru.v, pdp->gsnru.l)) { /* TODO Range? */ gsn->err_unknownpdp++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, peer, pack, len, "Unknown PDP context"); return gtp_error_ind_resp(gsn, version, peer, fd, pack, len); } /* Callback function */ if (gsn->cb_data_ind !=0) return gsn->cb_data_ind(pdp, pack+hlen, len-hlen); return 0; } /* Receives GTP packet and sends off for further processing * Function will check the validity of the header. If the header * is not valid the packet is either dropped or a version not * supported is returned to the peer. * TODO: Need to decide on return values! */ int gtp_decaps0(struct gsn_t *gsn) { unsigned char buffer[PACKET_MAX]; struct sockaddr_in peer; int peerlen; int status; struct gtp0_header *pheader; int version = 0; /* GTP version should be determined from header!*/ int fd = gsn->fd0; /* TODO: Need strategy of userspace buffering and blocking */ /* Currently read is non-blocking and send is blocking. */ /* This means that the program have to wait for busy send calls...*/ while (1) { /* Loop until no more to read */ if (fcntl(gsn->fd0, F_SETFL, O_NONBLOCK)) { gtp_err(LOG_ERR, __FILE__, __LINE__, "fnctl()"); return -1; } peerlen = sizeof(peer); if ((status = recvfrom(gsn->fd0, buffer, sizeof(buffer), 0, (struct sockaddr *) &peer, &peerlen)) < 0 ) { if (errno == EAGAIN) return 0; gsn->err_readfrom++; gtp_err(LOG_ERR, __FILE__, __LINE__, "recvfrom(fd0=%d, buffer=%lx, len=%d) failed: status = %d error = %s", gsn->fd0, (unsigned long) buffer, sizeof(buffer), status, status ? strerror(errno) : "No error"); return -1; } /* Need at least 1 byte in order to check version */ if (status < (1)) { gsn->empty++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Discarding packet - too small"); continue; } pheader = (struct gtp0_header *) (buffer); /* Version should be gtp0 (or earlier) */ /* 09.60 is somewhat unclear on this issue. On gsn->fd0 we expect only */ /* GTP 0 messages. If other version message is received we reply that we */ /* only support version 0, implying that this is the only version */ /* supported on this port */ if (((pheader->flags & 0xe0) > 0x00)) { gsn->unsup++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unsupported GTP version"); gtp_unsup_req(gsn, 0, &peer, gsn->fd0, buffer, status); /* 29.60: 11.1.1 */ continue; } /* Check length of gtp0 packet */ if (status < GTP0_HEADER_SIZE) { gsn->tooshort++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "GTP0 packet too short"); continue; /* Silently discard 29.60: 11.1.2 */ } /* Check packet length field versus length of packet */ if (status != (ntoh16(pheader->length) + GTP0_HEADER_SIZE)) { gsn->tooshort++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "GTP packet length field does not match actual length"); continue; /* Silently discard */ } if ((gsn->mode == GTP_MODE_GGSN) && ((pheader->type == GTP_CREATE_PDP_RSP) || (pheader->type == GTP_UPDATE_PDP_RSP) || (pheader->type == GTP_DELETE_PDP_RSP))) { gsn->unexpect++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unexpected GTP Signalling Message"); continue; /* Silently discard 29.60: 11.1.4 */ } if ((gsn->mode == GTP_MODE_SGSN) && ((pheader->type == GTP_CREATE_PDP_REQ) || (pheader->type == GTP_UPDATE_PDP_REQ) || (pheader->type == GTP_DELETE_PDP_REQ))) { gsn->unexpect++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unexpected GTP Signalling Message"); continue; /* Silently discard 29.60: 11.1.4 */ } switch (pheader->type) { case GTP_ECHO_REQ: gtp_echo_ind(gsn, version, &peer, fd, buffer, status); break; case GTP_ECHO_RSP: gtp_echo_conf(gsn, version, &peer, buffer, status); break; case GTP_NOT_SUPPORTED: gtp_unsup_ind(gsn, &peer, buffer, status); break; case GTP_CREATE_PDP_REQ: gtp_create_pdp_ind(gsn, version, &peer, fd, buffer, status); break; case GTP_CREATE_PDP_RSP: gtp_create_pdp_conf(gsn, version, &peer, buffer, status); break; case GTP_UPDATE_PDP_REQ: gtp_update_pdp_ind(gsn, version, &peer, fd, buffer, status); break; case GTP_UPDATE_PDP_RSP: gtp_update_pdp_conf(gsn, version, &peer, buffer, status); break; case GTP_DELETE_PDP_REQ: gtp_delete_pdp_ind(gsn, version, &peer, fd, buffer, status); break; case GTP_DELETE_PDP_RSP: gtp_delete_pdp_conf(gsn, version, &peer, buffer, status); break; case GTP_ERROR: gtp_error_ind_conf(gsn, version, &peer, buffer, status); break; case GTP_GPDU: gtp_gpdu_ind(gsn, version, &peer, fd, buffer, status); break; default: gsn->unknown++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unknown GTP message type received"); break; } } } int gtp_decaps1c(struct gsn_t *gsn) { unsigned char buffer[PACKET_MAX]; struct sockaddr_in peer; int peerlen; int status; struct gtp1_header_short *pheader; int version = 1; /* TODO GTP version should be determined from header!*/ int fd = gsn->fd1c; /* TODO: Need strategy of userspace buffering and blocking */ /* Currently read is non-blocking and send is blocking. */ /* This means that the program have to wait for busy send calls...*/ while (1) { /* Loop until no more to read */ if (fcntl(fd, F_SETFL, O_NONBLOCK)) { gtp_err(LOG_ERR, __FILE__, __LINE__, "fnctl()"); return -1; } peerlen = sizeof(peer); if ((status = recvfrom(fd, buffer, sizeof(buffer), 0, (struct sockaddr *) &peer, &peerlen)) < 0 ) { if (errno == EAGAIN) return 0; gsn->err_readfrom++; gtp_err(LOG_ERR, __FILE__, __LINE__, "recvfrom(fd=%d, buffer=%lx, len=%d) failed: status = %d error = %s", fd, (unsigned long) buffer, sizeof(buffer), status, status ? strerror(errno) : "No error"); return -1; } /* Need at least 1 byte in order to check version */ if (status < (1)) { gsn->empty++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Discarding packet - too small"); continue; } pheader = (struct gtp1_header_short *) (buffer); /* Version must be no larger than GTP 1 */ if (((pheader->flags & 0xe0) > 0x20)) { gsn->unsup++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unsupported GTP version"); gtp_unsup_req(gsn, version, &peer, fd, buffer, status); /*29.60: 11.1.1*/ continue; } /* Version must be at least GTP 1 */ /* 29.060 is somewhat unclear on this issue. On gsn->fd1c we expect only */ /* GTP 1 messages. If GTP 0 message is received we silently discard */ /* the message */ if (((pheader->flags & 0xe0) < 0x20)) { gsn->unsup++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unsupported GTP version"); continue; } /* Check packet flag field */ if (((pheader->flags & 0xf7) != 0x32)) { gsn->unsup++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unsupported packet flag"); continue; } /* Check length of packet */ if (status < GTP1_HEADER_SIZE_LONG) { gsn->tooshort++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "GTP packet too short"); continue; /* Silently discard 29.60: 11.1.2 */ } /* Check packet length field versus length of packet */ if (status != (ntoh16(pheader->length) + GTP1_HEADER_SIZE_SHORT)) { gsn->tooshort++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "GTP packet length field does not match actual length"); continue; /* Silently discard */ } /* Check for extension headers */ /* TODO: We really should cycle through the headers and determine */ /* if any have the comprehension required flag set */ if (((pheader->flags & 0x04) != 0x00)) { gsn->unsup++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unsupported extension header"); gtp_extheader_req(gsn, version, &peer, fd, buffer, status); continue; } if ((gsn->mode == GTP_MODE_GGSN) && ((pheader->type == GTP_CREATE_PDP_RSP) || (pheader->type == GTP_UPDATE_PDP_RSP) || (pheader->type == GTP_DELETE_PDP_RSP))) { gsn->unexpect++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unexpected GTP Signalling Message"); continue; /* Silently discard 29.60: 11.1.4 */ } if ((gsn->mode == GTP_MODE_SGSN) && ((pheader->type == GTP_CREATE_PDP_REQ) || (pheader->type == GTP_UPDATE_PDP_REQ) || (pheader->type == GTP_DELETE_PDP_REQ))) { gsn->unexpect++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unexpected GTP Signalling Message"); continue; /* Silently discard 29.60: 11.1.4 */ } switch (pheader->type) { case GTP_ECHO_REQ: gtp_echo_ind(gsn, version, &peer, fd, buffer, status); break; case GTP_ECHO_RSP: gtp_echo_conf(gsn, version, &peer, buffer, status); break; case GTP_NOT_SUPPORTED: gtp_unsup_ind(gsn, &peer, buffer, status); break; case GTP_SUPP_EXT_HEADER: gtp_extheader_ind(gsn, &peer, buffer, status); break; case GTP_CREATE_PDP_REQ: gtp_create_pdp_ind(gsn, version, &peer, fd, buffer, status); break; case GTP_CREATE_PDP_RSP: gtp_create_pdp_conf(gsn, version, &peer, buffer, status); break; case GTP_UPDATE_PDP_REQ: gtp_update_pdp_ind(gsn, version, &peer, fd, buffer, status); break; case GTP_UPDATE_PDP_RSP: gtp_update_pdp_conf(gsn, version, &peer, buffer, status); break; case GTP_DELETE_PDP_REQ: gtp_delete_pdp_ind(gsn, version, &peer, fd, buffer, status); break; case GTP_DELETE_PDP_RSP: gtp_delete_pdp_conf(gsn, version, &peer, buffer, status); break; case GTP_ERROR: gtp_error_ind_conf(gsn, version, &peer, buffer, status); break; default: gsn->unknown++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unknown GTP message type received"); break; } } } int gtp_decaps1u(struct gsn_t *gsn) { unsigned char buffer[PACKET_MAX]; struct sockaddr_in peer; int peerlen; int status; struct gtp1_header_short *pheader; int version = 1; /* GTP version should be determined from header!*/ int fd = gsn->fd1u; /* TODO: Need strategy of userspace buffering and blocking */ /* Currently read is non-blocking and send is blocking. */ /* This means that the program have to wait for busy send calls...*/ while (1) { /* Loop until no more to read */ if (fcntl(gsn->fd1u, F_SETFL, O_NONBLOCK)) { gtp_err(LOG_ERR, __FILE__, __LINE__, "fnctl()"); return -1; } peerlen = sizeof(peer); if ((status = recvfrom(gsn->fd1u, buffer, sizeof(buffer), 0, (struct sockaddr *) &peer, &peerlen)) < 0 ) { if (errno == EAGAIN) return 0; gsn->err_readfrom++; gtp_err(LOG_ERR, __FILE__, __LINE__, "recvfrom(fd1u=%d, buffer=%lx, len=%d) failed: status = %d error = %s", gsn->fd1u, (unsigned long) buffer, sizeof(buffer), status, status ? strerror(errno) : "No error"); return -1; } /* Need at least 1 byte in order to check version */ if (status < (1)) { gsn->empty++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Discarding packet - too small"); continue; } pheader = (struct gtp1_header_short *) (buffer); /* Version must be no larger than GTP 1 */ if (((pheader->flags & 0xe0) > 0x20)) { gsn->unsup++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unsupported GTP version"); gtp_unsup_req(gsn, 1, &peer, gsn->fd1c, buffer, status);/*29.60: 11.1.1*/ continue; } /* Version must be at least GTP 1 */ /* 29.060 is somewhat unclear on this issue. On gsn->fd1c we expect only */ /* GTP 1 messages. If GTP 0 message is received we silently discard */ /* the message */ if (((pheader->flags & 0xe0) < 0x20)) { gsn->unsup++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unsupported GTP version"); continue; } /* Check packet flag field (allow both with and without sequence number)*/ if (((pheader->flags & 0xf5) != 0x30)) { gsn->unsup++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unsupported packet flag"); continue; } /* Check length of packet */ if (status < GTP1_HEADER_SIZE_SHORT) { gsn->tooshort++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "GTP packet too short"); continue; /* Silently discard 29.60: 11.1.2 */ } /* Check packet length field versus length of packet */ if (status != (ntoh16(pheader->length) + GTP1_HEADER_SIZE_SHORT)) { gsn->tooshort++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "GTP packet length field does not match actual length"); continue; /* Silently discard */ } /* Check for extension headers */ /* TODO: We really should cycle through the headers and determine */ /* if any have the comprehension required flag set */ if (((pheader->flags & 0x04) != 0x00)) { gsn->unsup++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unsupported extension header"); gtp_extheader_req(gsn, version, &peer, fd, buffer, status); continue; } switch (pheader->type) { case GTP_ECHO_REQ: gtp_echo_ind(gsn, version, &peer, fd, buffer, status); break; case GTP_ECHO_RSP: gtp_echo_conf(gsn, version, &peer, buffer, status); break; case GTP_SUPP_EXT_HEADER: gtp_extheader_ind(gsn, &peer, buffer, status); break; case GTP_ERROR: gtp_error_ind_conf(gsn, version, &peer, buffer, status); break; /* Supported header extensions */ case GTP_GPDU: gtp_gpdu_ind(gsn, version, &peer, fd, buffer, status); break; default: gsn->unknown++; gtp_errpack(LOG_ERR, __FILE__, __LINE__, &peer, buffer, status, "Unknown GTP message type received"); break; } } } int gtp_data_req(struct gsn_t *gsn, struct pdp_t* pdp, void *pack, unsigned len) { union gtp_packet packet; struct sockaddr_in addr; int fd; int length; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; #ifdef __FreeBSD__ addr.sin_len = sizeof(addr); #endif memcpy(&addr.sin_addr, pdp->gsnru.v,pdp->gsnru.l); /* TODO range check */ if (pdp->version == 0) { length = GTP0_HEADER_SIZE+len; addr.sin_port = htons(GTP0_PORT); fd = gsn->fd0; get_default_gtp(0, GTP_GPDU, &packet); packet.gtp0.h.length = hton16(len); packet.gtp0.h.seq = hton16(pdp->gtpsntx++); packet.gtp0.h.flow = hton16(pdp->flru); packet.gtp0.h.tid = (pdp->imsi & 0x0fffffffffffffff) + ((uint64_t)pdp->nsapi << 60); if (len > sizeof (union gtp_packet) - sizeof(struct gtp0_header)) { gsn->err_memcpy++; gtp_err(LOG_ERR, __FILE__, __LINE__, "Memcpy failed"); return EOF; } memcpy(packet.gtp0.p, pack, len); /* TODO Should be avoided! */ } else if (pdp->version == 1) { length = GTP1_HEADER_SIZE_LONG+len; addr.sin_port = htons(GTP1U_PORT); fd = gsn->fd1u; get_default_gtp(1, GTP_GPDU, &packet); packet.gtp1l.h.length = hton16(len-GTP1_HEADER_SIZE_SHORT+ GTP1_HEADER_SIZE_LONG); packet.gtp1l.h.seq = hton16(pdp->gtpsntx++); packet.gtp1l.h.tei = hton32(pdp->teid_gn); if (len > sizeof (union gtp_packet) - sizeof(struct gtp1_header_long)) { gsn->err_memcpy++; gtp_err(LOG_ERR, __FILE__, __LINE__, "Memcpy failed"); return EOF; } memcpy(packet.gtp1l.p, pack, len); /* TODO Should be avoided! */ } else { gtp_err(LOG_ERR, __FILE__, __LINE__, "Unknown version"); return EOF; } if (fcntl(fd, F_SETFL, 0)) { gtp_err(LOG_ERR, __FILE__, __LINE__, "fnctl()"); return -1; } if (sendto(fd, &packet, length, 0, (struct sockaddr *) &addr, sizeof(addr)) < 0) { gsn->err_sendto++; gtp_err(LOG_ERR, __FILE__, __LINE__, "Sendto(fd=%d, msg=%lx, len=%d) failed: Error = %s", fd, (unsigned long) &packet, GTP0_HEADER_SIZE+len, strerror(errno)); return EOF; } return 0; } /* *********************************************************** * Conversion functions *************************************************************/ int char2ul_t(char* src, struct ul_t dst) { dst.l = strlen(src)+1; dst.v = malloc(dst.l); dst.v[0] = dst.l - 1; memcpy(&dst.v[1], src, dst.v[0]); return 0; } /* *********************************************************** * IP address conversion functions * There exist several types of address representations: * - eua: End User Address. (29.060, 7.7.27, message type 128) * Used for signalling address to mobile station. Supports IPv4 * IPv6 x.25 etc. etc. * - gsna: GSN Address. (29.060, 7.7.32, message type 133): IP address * of GSN. If length is 4 it is IPv4. If length is 16 it is IPv6. * - in_addr: IPv4 address struct. * - sockaddr_in: Socket API representation of IP address and * port number. *************************************************************/ int ipv42eua(struct ul66_t *eua, struct in_addr *src) { eua->v[0] = 0xf1; /* IETF */ eua->v[1] = 0x21; /* IPv4 */ if (src) { eua->l = 6; memcpy(&eua->v[2], src, 4); } else { eua->l = 2; } return 0; } int eua2ipv4(struct in_addr *dst, struct ul66_t *eua) { if ((eua->l != 6) || (eua->v[0] != 0xf1) || (eua->v[1] = 0x21)) return -1; /* Not IPv4 address*/ memcpy(dst, &eua->v[2], 4); return 0; } int gsna2in_addr(struct in_addr *dst, struct ul16_t *gsna) { memset(dst, 0, sizeof(struct in_addr)); if (gsna->l != 4) return EOF; /* Return if not IPv4 */ memcpy(dst, gsna->v, gsna->l); return 0; } int in_addr2gsna(struct ul16_t *gsna, struct in_addr *src) { memset(gsna, 0, sizeof(struct ul16_t)); gsna->l = 4; memcpy(gsna->v, src, gsna->l); return 0; }