/* ==================================================================== * The Kannel Software License, Version 1.0 * * Copyright (c) 2001-2005 Kannel Group * Copyright (c) 1998-2001 WapIT Ltd. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Kannel Group (http://www.kannel.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Kannel" and "Kannel Group" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please * contact org@kannel.org. * * 5. Products derived from this software may not be called "Kannel", * nor may "Kannel" appear in their name, without prior written * permission of the Kannel Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Kannel Group. For more information on * the Kannel Group, please see . * * Portions of this software are based upon software originally written at * WapIT Ltd., Helsinki, Finland for the Kannel project. */ /* * fakewap.c - simulate wap clients talking directly to wap gw. * * This module can be built also in Windows where you should * add unzipped ".\wininc" to your include directories. * * The protocol: * * * A) Fakewap -> Gateway * * WTP: Invoke PDU * WSP: Connect PDU * * B) Gateway -> Fakewap * * WTP: Result PDU * WSP: ConnectReply PDU * * C) Fakewap -> Gateway * * WTP: Ack PDU * * D) Fakewap -> Gateway * * WTP: Invoke PDU * WSP: Get PDU (data: URL) * * E) Gateway -> Fakewap * * WTP: Result PDU (data: WML page) * WSP: Reply PDU * * F) Fakewap -> Gateway * * WTP: Ack PDU * * G) Fakewap -> Gateway * * WTP: Invoke PDU * WSP: Disconnect PDU * * * Packets A-C open a WAP session. Packets D-F fetch a WML page. * Packet G closes the session. * * The test terminates when all packets have been sent. * * Tid verification uses following protocol (at WTP level only): * * A) Fakewap -> Gateway * * Either WSP Connect PDU with tid_new flag set on or same PDU with a * *seriously* wrapped up tid (only WTP header affected). Seriously * means tid being out of the window: * * |----------------------------| * tid space * * |-------------| * wrapping up * tid window * * B) Gateway -> Fakewap * * Ack PDU, tid verification flag set on. * * C) Fakewap -> Gateway * * Ack PDU, tid verification flag set on (this means a positive * answer). * * Antti Saarenheimo for WapIT Ltd. */ #define MAX_SEND (0) static char usage[] = "\ Usage: fakewap [options] url ...\n\ \n\ where options are:\n\ \n\ -h help\n\ -v verbose\n\ -g hostname hostname or IP number of gateway (default: localhost)\n\ -p port port number of gateway (default: 9201)\n\ -m max maximum number of requests fakewap will make (default: 1)\n\ -i interval interval between requests (default: 1.0 seconds)\n\ -c threads number of concurrent clients simulated (default: 1)\n\ -V protoversion protocol version field, as an integer (default: 0)\n\ -T pdu-type PDU type, as an integer (default: 1)\n\ -t tcl transaction class, as an integer (default: 2)\n\ -n set tid_new flag in packets, forces gateway to flush cache\n\ (default: off)\n\ -s test separation, by concatenating ack and disconnect pdus\n\ (default: off)\n\ -d difference difference between successive tid numbers (default: 1)\n\ -F Accept failure and continue rather than exiting\n\ -w Write/print received data (experimental)\n\ \n\ The urls are fetched in random order.\n\ "; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gwlib/gwlib.h" #define GET_WTP_PDU_TYPE(hdr) (hdr[0] >> 3) static int get_wtp_pdu_type(Octstr *hdr) { return octstr_get_char(hdr, 0) >> 3; } #define WTP_PDU_INVOKE 1 #define WTP_PDU_RESULT 2 #define WTP_PDU_ACK 3 #define WTP_PDU_ABORT 4 /* ** Common parameters */ char **urls; int num_urls; Octstr *hostname = NULL; Octstr *gateway_addr = NULL; double interval = 1.0; unsigned short port = 9201; int max_send = 1; unsigned short tid_addition = 1; Mutex *mutex; int threads = 1; int num_sent = 0; time_t start_time, end_time; double totaltime = 0, besttime = 1000000L, worsttime = 0; int verbose = 0; int nofailexit = 0; int writedata = 0; int test_separation = 0; /* * PDU type, version number and transaction class are supplied by a * command line argument. WSP_Concat is a concatenation of WTP_Ack and * WSP_Disconnect PDUs. */ unsigned char WSP_Connect[] = {0x06, 0x00, 0x00, 0x00, /* WSP part */ 0x01, /* PDU type */ 0x10, /* Version 1.0 */ 0x00, /* Capability length */ 0x02, /* Headers length = 2*/ /* Capabilities */ /* Headers */ 0x80, 0x80 /* Accept: *\* */ }; unsigned char WSP_ConnectReply[] = {0x16, 0x80, 0x00, 0x02 }; unsigned char WTP_Ack[] = {0x18, 0x00, 0x00 }; unsigned char WTP_TidVe[] = {0x1C, 0x00, 0x00 }; unsigned char WTP_Abort[] = {0x20, 0x00, 0x00, 0x00 }; unsigned char WSP_Get[] = {0x0E, 0x00, 0x00, 0x02, 0x40 }; /* This used to also expect a content-type of 0x94, but that's too difficult * to check now that Kannel does full header encoding. */ unsigned char WSP_Reply[] = {0x16, 0x80, 0x00, 0x04, 0x20 }; unsigned char WSP_Disconnect[] = {0x0E, 0x00, 0x00, 0x00, 0x05 }; unsigned char WSP_Concat[] = {0x00, 0x03, 0x18, 0x00, 0x00, 0x05, 0x0E, 0x00, 0x00, 0x00, 0x05 }; /* ** In this case it does not matter what is the byte order */ #define SET_GTR( hdr ) hdr[0] |= 0x04 #define SET_TID( hdr, tid) \ hdr[1] |= (0x7f & ((tid) >> 8)); \ hdr[2] = (char)(tid) #define GET_TID( hdr ) (((hdr[1] & 0x7f) << 8) + hdr[2]) #define CONSTRUCT_EXPECTED_REPLY_HDR( dest, template, tid ) \ if (sizeof(dest) < sizeof(template)) panic(0,"buffer overflow.");\ memcpy( dest, template, sizeof(template));\ SET_TID( dest, tid ) static void set_tid(Octstr *hdr, int tid) { int c; c = octstr_get_char(hdr, 1); c |= 0x7f & (tid >> 8); octstr_set_char(hdr, 1, c); octstr_set_char(hdr, 2, (unsigned char) tid); } /* Use this only on Invoke packets, the others have no tid_new field */ static void set_tid_new(Octstr *hdr) { int c; c = octstr_get_char(hdr, 3); c |= 0x40; octstr_set_char(hdr, 3, c); } #ifndef min #define min(a,b) (a < b ? a : b) #endif /* ** if -v option has been defined, function prints the trace message and ** the first bytes in the message header */ static void print_msg( const char * trace, unsigned char * msg, int msg_len ) { int i; if (verbose) { mutex_lock( mutex ); printf( "%s (len %d): ", trace, msg_len ); for (i = 0; i < msg_len && i < 16; i++) printf( "%02X ", msg[i] ); printf( "\n"); mutex_unlock( mutex ); } } /* ** if -w option has been defined, function prints the trace message and ** the first bytes in the message header */ static void print_data( const char * trace, unsigned char * msg, int msg_len ) { int i; if (verbose || writedata) { mutex_lock( mutex ); printf( "%s (len %d): ", trace, msg_len ); for (i = 0; i < msg_len && i < msg_len; i++) printf( "%c", isprint(msg[i]) ? msg[i] : '_'); printf( "\n"); mutex_unlock( mutex ); } } /* Choose a random message from a table of messages. */ static char *choose_message(char **urls, int num_urls) { /* the following doesn't give an even distribution, but who cares */ return urls[gw_rand() % num_urls]; } /* returns next tid, given current tid. Every thread has its own * port, so has its own tid space. */ static unsigned short next_tid(unsigned short old_tid) { return (old_tid + tid_addition) % (1 << 15); } /* ** Function stores WAP/WSP variable length integer to buffer and returns ** actual len */ static int StoreVarInt( unsigned char *buf, unsigned long varInt ) { int i, len = 1, non_zero_bits = 7; /* ** Skip all zero high bits */ while ((varInt >> non_zero_bits) != 0) { non_zero_bits += 7; len++; } /* ** Read the higest bits first. */ for (i = 0; i < len; i++) { buf[i] = ((unsigned char)(varInt >> (non_zero_bits-7)) & 0x7f) | 0x80; non_zero_bits -= 7; } buf[len-1] &= 0x7f; return len; } /* ** Function length of WAP/WSP variable length integer in the buffer */ static int ReadVarIntLen( const unsigned char *buf ) { int len = 1; while (buf[len-1] & 0x80) len++; return len; } /* ** Function sends message to WAP GW */ static int wap_msg_send( int fd, unsigned char * hdr, int hdr_len, unsigned short tid, int tid_new, unsigned char * data, int data_len ) { int ret; Octstr *datagram; datagram = octstr_create(""); if (hdr != NULL) octstr_append_data(datagram, hdr, hdr_len); set_tid(datagram, tid); if (get_wtp_pdu_type(datagram) == WTP_PDU_INVOKE) { /* request ack every time */ int c; c = octstr_get_char(datagram, 3); octstr_set_char(datagram, 3, c | 0x10); if (tid_new) set_tid_new(datagram); } if (data != NULL) octstr_append_data(datagram, data, data_len); #if 0 debug("fakewap", 0, "Sending WDP datagram:"); octstr_dump(datagram, 0); #endif ret = udp_sendto(fd, datagram, gateway_addr); if (ret == -1) { error(0, "Sending to socket failed"); return -1; } if (verbose) { debug("", 0, "Sent packet:"); octstr_dump(datagram, 0); } octstr_destroy(datagram); return ret; } /* ** Function receives a wap wtl/wsp message. If the headers has been ** given, it must match with the received message. ** Return value: ** > 0 => length of received data ** == 0 => got acknowlengement or abort but not the expected data ** < 0 => error, */ static int wap_msg_recv( int fd, const char * hdr, int hdr_len, unsigned short tid, unsigned char * data, int data_len, int timeout ) { int ret; unsigned char msg[1024*64]; int msg_len = 0; int fResponderIsDead = 1; /* assume this by default */ Octstr *datagram, *dummy; /* ** Loop until we get the expected response or do timeout */ for (;;) { if (timeout != 0) { ret = read_available(fd, timeout * 1000 * 1000); if (ret <= 0) { info(0, "Timeout while receiving from socket.\n"); if(nofailexit){ continue; }else{ return fResponderIsDead ? -1 : 0; } /* continue if we got ack? */ } } ret = udp_recvfrom(fd, &datagram, &dummy); if (ret == 0) { octstr_get_many_chars(msg, datagram, 0, octstr_len(datagram)); msg_len = octstr_len(datagram); } octstr_destroy(datagram); octstr_destroy(dummy); if (ret == -1) { error(0, "recv() from socket failed"); return -1; } if (hdr != NULL) { /* ** Ignore extra header bits, WAP GWs return different values */ if (msg_len >= hdr_len && GET_WTP_PDU_TYPE(msg) == GET_WTP_PDU_TYPE(hdr) && (hdr_len <= 3 || !memcmp( msg+3, hdr+3, hdr_len-3 ))) { break; } /* ** Handle TID test, the answer is: Yes, we have an outstanding ** transaction with this tid. We must turn on TID_OK-flag, too. ** We have a separate tid verification PDU. */ else if (GET_WTP_PDU_TYPE(msg) == WTP_PDU_ACK && GET_TID(msg) == tid) { print_msg( "Received tid verification", msg, msg_len ); wap_msg_send( fd, WTP_TidVe, sizeof(WTP_TidVe), tid, 0, NULL, 0 ); } else if (GET_WTP_PDU_TYPE(msg) == WTP_PDU_ABORT) { print_msg( "Received WTP Abort", msg, msg_len ); } else if (GET_WTP_PDU_TYPE(msg) == WTP_PDU_RESULT) { break; } else { print_msg( "Received unexpected message", msg, msg_len ); } fResponderIsDead = 0; } else { hdr_len = 0; break; } } print_msg( "Received packet", msg, msg_len ); print_data( "Received data", msg, msg_len ); if (data != NULL && msg_len > hdr_len) { data_len = min( data_len, msg_len - hdr_len ); memcpy( data, msg+hdr_len, data_len); } else data_len = 0; return data_len; } static int get_next_transaction(void) { int i_this; mutex_lock( mutex ); i_this = num_sent + 1; if (max_send == MAX_SEND || num_sent < max_send) num_sent++; mutex_unlock( mutex ); return i_this; } /* ** Function (or thread) sets up a dgram socket. Then it loops: WTL/WSP ** Connect, Get a url and Disconnect until all requests are have been done. */ static void client_session( void * arg) { int fd; int ret; int url_len = 0, url_off = 0; double nowsec, lastsec, tmp, sleepTime; long uSleepTime; struct timeval now; struct timezone tz; char * url; unsigned char sid[20]; int sid_len = 0; unsigned char buf[64*1024]; unsigned char reply_hdr[32]; long timeout = 10; /* wap gw is broken if no input */ unsigned short tid = 0; unsigned short old_tid; int tid_new = 0; int connection_retries = 0; int i_this; fd = udp_client_socket(); if (fd == -1) panic(0, "Couldn't create socket."); /* ** Loop until all URLs have been requested */ for (;;) { /* ** Get start time of this request */ gettimeofday(&now, &tz); lastsec = (double) now.tv_sec + now.tv_usec / 1e6; /* ** Get next transaction number or exit if too many transactions */ i_this = get_next_transaction(); if (max_send != MAX_SEND && i_this > max_send) break; /* ** Connect, save sid from reply and finally ack the reply */ old_tid = tid; tid = next_tid(old_tid); tid_new = (tid < old_tid); /* Did we wrap? */ ret = wap_msg_send( fd, WSP_Connect, sizeof(WSP_Connect), tid, tid_new, NULL, 0 ); if (ret == -1) panic(0, "Send WSP_Connect failed"); CONSTRUCT_EXPECTED_REPLY_HDR( reply_hdr, WSP_ConnectReply, tid ); ret = wap_msg_recv( fd, reply_hdr, sizeof(WSP_ConnectReply), tid, buf, sizeof(buf), timeout ); if (ret == -1) panic(0, "Receive WSP_ConnectReply failed"); if (ret > 2) { sid_len = ReadVarIntLen(buf); memcpy( sid, buf, sid_len); } /* ** Send abort and continue if we get an unexpected reply */ if (ret == 0) { if (connection_retries++ > 3) { panic(0, "Cannot connect WAP GW!"); } wap_msg_send( fd, WTP_Abort, sizeof(WTP_Abort), tid, tid_new, NULL, 0 ); continue; } else { connection_retries = 0; } ret = wap_msg_send( fd, WTP_Ack, sizeof(WTP_Ack), tid, tid_new, NULL, 0 ); if (ret == -1) panic(0, "Send WTP_Ack failed"); /* ** Request WML page with the given URL */ old_tid = tid; tid = next_tid(old_tid); tid_new = (tid < old_tid); /* Did we wrap? */ url = choose_message(urls, num_urls); url_len = strlen(url); url_off = StoreVarInt( buf, url_len ); memcpy( buf+url_off, url, url_len ); ret = wap_msg_send( fd, WSP_Get, sizeof(WSP_Get), tid, tid_new, buf, url_len+url_off ); if (ret == -1) break; CONSTRUCT_EXPECTED_REPLY_HDR( reply_hdr, WSP_Reply, tid ); ret = wap_msg_recv( fd, reply_hdr, sizeof(WSP_Reply), tid, buf, sizeof(buf), timeout ); if (ret == -1) break; /* ** If we are testing separation, we concatenate WTP_Ack and ** WSP_Disconnect messages. */ if (test_separation){ ret = wap_msg_send(fd, WSP_Concat, sizeof(WSP_Concat), tid, tid_new, sid, sid_len); if (ret == -1) break; } else { ret = wap_msg_send( fd, WTP_Ack, sizeof(WTP_Ack), tid, tid_new, NULL, 0 ); if (ret == -1) break; /* ** Finally disconnect with the sid returned by connect reply */ ret = wap_msg_send( fd, WSP_Disconnect, sizeof(WSP_Disconnect), tid, tid_new, sid, sid_len ); if (ret == -1) break; } /* ** Get end time of the request */ gettimeofday(&now, &tz); nowsec = (double) now.tv_sec + now.tv_usec / 1e6; tmp = nowsec - lastsec; /* Duration of request */ sleepTime = interval-tmp; /* Amount of time left to sleep */ uSleepTime = sleepTime * 1e6; mutex_lock( mutex ); if (tmp < besttime) besttime = tmp; if (tmp > worsttime) worsttime = tmp; totaltime += tmp; mutex_unlock( mutex ); if (verbose == 1) { info(0, "fakewap: finished session # %d", i_this); } /* ** If we've done all the requests, then don't bother to sleep */ if (i_this >= max_send) break; if (tmp < (double)interval) { usleep( uSleepTime ); } } close(fd); /* The last end_time stays */ mutex_lock( mutex ); time(&end_time); mutex_unlock( mutex ); } static void help(void) { info(0, "\n%s", usage); } /* The main program. */ int main(int argc, char **argv) { int i, opt; double delta; int proto_version, pdu_type, tcl, tid_new; #ifdef SunOS struct sigaction alrm; alrm.sa_handler = SIG_IGN; sigaction(SIGALRM,&alrm,NULL); #endif gwlib_init(); proto_version = 0; pdu_type = 1; tcl = 2; tid_new = 0; hostname = octstr_create("localhost"); while ((opt = getopt(argc, argv, "Fhvc:g:p:P:m:i:t:V:T:t:nsd:w")) != EOF) { switch (opt) { case 'g': octstr_destroy(hostname); hostname = octstr_create(optarg); break; case 'p': port = atoi(optarg); break; case 'm': max_send = atoi(optarg); break; case 'i': interval = atof(optarg); break; case 'c': threads = atoi(optarg); break; case 'V': proto_version = atoi(optarg); break; case 'T': pdu_type = atoi(optarg); break; case 't': tcl = atoi(optarg); break; case 'n': tid_new = 1; break; case 's': test_separation = 1; break; case 'd': tid_addition = atoi(optarg); break; case 'v': verbose = 1; break; case 'h': help(); exit(0); break; case 'F': nofailexit=1; break; case 'w': writedata = 1; break; case '?': default: error(0, "Unknown option %c", opt); help(); panic(0, "Stopping."); } } time(&start_time); if (optind >= argc) panic(0, "%s", usage); if (verbose != 1) { log_set_output_level (GW_INFO); } WSP_Connect[3] += (proto_version&3)<<6; WSP_Connect[0] += (pdu_type&15)<<3; WSP_Connect[3] += tcl&3; WSP_Connect[3] += (tid_new&1)<<5; gateway_addr = udp_create_address(hostname, port); urls = argv + optind; num_urls = argc - optind; srand((unsigned int) time(NULL)); mutex = mutex_create(); info(0, "fakewap starting"); if (threads < 1) threads = 1; /* ** Start 'extra' client threads and finally execute the ** session of main thread */ for (i = 1; i < threads; i++) gwthread_create(client_session, NULL); client_session(NULL); /* Wait for the other sessions to complete */ gwthread_join_every(client_session); info(0, "fakewap complete."); info(0, "fakewap: %d client threads made total %d transactions.", threads, num_sent); delta = difftime(end_time, start_time); info( 0, "fakewap: total running time %.1f seconds", delta); info( 0, "fakewap: %.1f messages/seconds on average", num_sent / delta); info( 0, "fakewap: time of best, worst and average transaction: " "%.1f s, %.1f s, %.1f s", besttime, worsttime, totaltime / num_sent ); octstr_destroy(hostname); octstr_destroy(gateway_addr); mutex_destroy(mutex); gwlib_shutdown(); return 0; }