1 /*--------------------------------------------------------------------------
2  Copyright 1999, Dan Kegel http://www.kegel.com/
3  See the file COPYING
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program; if not, write to the Free Software
17  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 --------------------------------------------------------------------------*/
19 
20 /*--------------------------------------------------------------------------
21  Module to simulate FTP users.
22 --------------------------------------------------------------------------*/
23 #include "dprint.h"
24 #include "robouser.h"
25 #include "Platoon.h"
26 #include <arpa/inet.h>
27 #include <netinet/in.h>
28 #include <poll.h>
29 #include <assert.h>
30 #include <errno.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 
36 #define STATUS_DISCONNECTED 999
37 
38 /* return whether the given status code indicates a given FTP command status */
39 #define STATUS_OK(s)  (((s) >= 200) && ((s) <= 299))
40 
41 /***************** Static Variable Initializations ************************/
42 int robouser_t::s_hertz = eclock_hertz();   /* hope it doesn't change during the run */
43 
44 /***************** Private Member functions *******************************/
45 
46 /*----------------------------------------------------------------------
47  Jump to the given state.
48  Adjust counts.
49 ----------------------------------------------------------------------*/
gotoState(state_t newstate)50 void robouser_t::gotoState(state_t newstate)
51 {
52 	m_platoon->countStateChange(m_user, m_state, newstate);
53 	m_state = newstate;
54 }
55 
56 /*----------------------------------------------------------------------
57  Pick a random file from the set we know are on the server.
58  For now, a bogus implementation.
59  Later on, we will probably expand m_filename as a template.
60 ----------------------------------------------------------------------*/
pick_random_file(char * fname)61 void robouser_t::pick_random_file(char *fname)
62 {
63 	strcpy(fname, m_platoon->getFilename());
64 }
65 
66 /*----------------------------------------------------------------------
67  Start an already-allocated robouser on its tiny-brained way.
68  usernum is the index of this object in an outer container.
69 
70  Returns 0 on success, Unix error code on failure.
71 ----------------------------------------------------------------------*/
start(int usernum)72 int robouser_t::start(int usernum)
73 {
74 	struct sockaddr_in *local_addr = 0;
75 
76 	m_user = usernum;
77 	DPRINT(("robouser%d::start:\n", m_user));
78 
79 	/* For very large tests, need to use different local IP address for
80 	 * each client.  Port part of this address must be zero, so
81 	 * operating system will assign an ephemeral port.
82 	 */
83 	if (m_platoon->getLocalAddrs())
84 		local_addr = m_platoon->getLocalAddrs() +
85 			m_user % m_platoon->getNLocalAddrs();
86 
87 	m_fcp.init(this, m_platoon->getSked(), m_platoon->getMaxBytesPerSec(), m_platoon->getPoller(), local_addr);
88 	m_startedAt = 0;
89 	m_bytesFetched = 0;
90 	m_reads = 0;
91 	/* schedule first event for now */
92 	m_platoon->getSked()->addClient(this, eclock());
93 
94 	gotoState(CONNECT);
95 
96 	return 0;
97 }
98 
99 /*--------------------------------------------------------------------------
100  Shut down one user.
101 --------------------------------------------------------------------------*/
stop()102 void robouser_t::stop()
103 {
104 	DPRINT(("robouser%d::stop: skedIndex %d\n", m_user, skedIndex));
105 	/* Don't do anything here - too dangerous - might recurse. */
106 	gotoState(STOPPED);
107 
108 	/* Insert ourselves in the list of dead users */
109 	m_platoon->addToDeadlist(this);
110 	DPRINT(("robouser%d::stop: done\n", m_user));
111 }
112 
113 /***************** Callback functions ************************************/
114 
115 /*----------------------------------------------------------------------
116  When the time specified by addClient() has elapsed, Sked calls this method.
117 ----------------------------------------------------------------------*/
skedCallback(clock_t now)118 void robouser_t::skedCallback(clock_t now)
119 {
120 	ftpCmdDone(0, -1, NULL);
121 	(void) now; /* will use now later */
122 }
123 
124 /*----------------------------------------------------------------------
125  Callback function.
126  Called internally by ftp_client_pipe_t::handle_io().
127  The operating system has told us that our data file descriptor
128  is ready for I/O.  This function performs the desired I/O.
129 ----------------------------------------------------------------------*/
handle_data_io(int fd,short revents,clock_t now)130 int robouser_t::handle_data_io(int fd, short revents, clock_t now)
131 {
132 	char buf[16384];
133 
134 	/*---------------------------------------------------------------------
135 	 It's an object-oriented callback function; a torturous path
136 	 of interface inheritance and interface pointers arranges for
137 	 this method to be called (for the right object even!) when there's
138 	 I/O to be done on the data channel.
139 
140 	 Function must issue a single read or write on the fd (as appropriate
141 	 for the call that triggered the transfer) with as large a buffer as
142 	 is practical.
143 	 If read reads zero bytes, the transfer is over.
144 	 When the transfer is over, this routine must return '0' without
145 	 closing the file.
146 	 If the transfer is not over, this routine must return the
147 	 number of bytes tranferred during this call.
148 	 Returns negative Unix error code if error or no data available.
149 	---------------------------------------------------------------------*/
150 
151 	/* bogus implementation for the moment */
152 	(void)revents;
153 	assert(m_platoon->getBytesPerRead() <= (int) sizeof(buf));
154 	DPRINT(("robouser%d::handle_data_io: fd %d, revents %x; calling read\n",
155 		m_user, fd, revents));
156 	int nread = read(fd, buf, m_platoon->getBytesPerRead());
157 	m_reads++;
158 	if (nread == 0) {
159 		DPRINT(("robouser%d::handle_data_io: fd %d, revents %x; transfer over\n", m_user, fd, revents));
160 		return 0;
161 	}
162 	if (nread > 0) {
163 		if (m_bytesFetched == 0) {
164 			/* Start measuring bandwidth with first packet received.
165 			 * This avoids dying when fetching short files due to
166 			 * confusing transfer startup time with bandwidth.
167 			 * There should be a separate measurement of startup time.
168 			 */
169 			m_startedAt = eclock();
170 		}
171 		m_bytesFetched += nread;
172 		m_platoon->incBytesFetched(nread);
173 		m_platoon->incNReads();
174 		if ((int)m_bytesFetched > (10 * m_platoon->getBytesPerRead())) {
175 			/* Don't kill users early until ten packets have come in. */
176 			float bytesPerSec;
177 			bytesPerSec = (1.0 * m_bytesFetched * s_hertz) / (now - m_startedAt);
178 			DPRINT(("robouser%d::handle_data_io: fd %d, revents %x; read %d bytes, %d bytes per second\n", m_user, fd, revents, nread, (int)bytesPerSec));
179 			if (bytesPerSec < m_platoon->getMinBytesPerSec()) {
180 				DPRINT(("robouser%d::handle_data_io: Test failed early (%d bytes), too slow (%d bytes/sec)\n", m_user, m_bytesFetched, (int) bytesPerSec));
181 				printf("User%d: Test failed early (%d bytes), too slow (%d bytes/sec)\n", m_user, m_bytesFetched, (int) bytesPerSec);
182 
183 				stop();
184 				return 0;
185 			}
186 		} else {
187 			DPRINT(("robouser%d::handle_data_io: fd %d, revents %x; read %d bytes\n", m_user, fd, revents, nread));
188 		}
189 	} else if (errno == EWOULDBLOCK) {
190 		/* Operating system thought there was data ready on this fd,
191 		 * but there wasn't.  This can happen especially when using Linux's
192 		 * sigio/F_SETSIG model of I/O.  It's not harmful; just ignore it.
193 		 */
194 		DPRINT(("robouser_t::handle_data_io: fd %d: EWOULDBLOCK on read\n",fd));
195 		return -EWOULDBLOCK;
196 	} else {
197 		/* Any other error is probably fatal to this connection. */
198 		int err = errno;
199 		DPRINT(("robouser_t::handle_data_io: read failed, errno %d\n", err));
200 		return -err;
201 	}
202 	m_platoon->reap();
203 	return nread;
204 }
205 
206 /*----------------------------------------------------------------------
207  Callback function.  When any command (cd, ls, get, put, ...) finishes,
208  ftp_client_pipe_t::handle_io() calls this function called to alert us.
209 ----------------------------------------------------------------------*/
ftpCmdDone(int xerr,int status,const char * buf)210 void robouser_t::ftpCmdDone(int xerr, int status, const char *buf)
211 {
212 	int err = -1;
213 
214 	(void) buf;
215 
216 	DPRINT(("robouser%d::ftpCmdDone: xerr %d, status %d\n", m_user, xerr, status));
217 	if (xerr) {
218 		EDPRINT(("robouser%d::ftpCmdDone: xerr %d, aborting\n", m_user, xerr));
219 		stop();
220 		return;
221 	}
222 
223 	/*------------------------------------------------------------------
224 	 If xerr is zero, the local part of the command succeeded; the
225 	 server's numerical response is in status.
226 	 If xerr is nonzero, the local part of the command failed (perhaps
227 	 a data connection could not be established), and we should
228 	 give up on this session (by calling stop() and starting over).
229 
230 	 Allowed to call ftp_client_pipe methods.
231 	------------------------------------------------------------------*/
232 
233 	/* Loop until internal state transitions are finished.
234 	 * A state that wants to immediately jump to another state sets m_state
235 	 * and breaks out of the switch, bringing it back to the loop.
236 	 * A state that wants to send a command to the server and wait for it to
237 	 * finish will submit the command and then return to break out of the loop.
238 	 * This function will be called again when the server responds to the
239 	 * command or the connection is lost.
240 	 * A state that wants to wait a certain amount of time calls
241 	 * m_sked->addClient() and returns to break out of the loop.
242 	 */
243 	for (;;) {
244 		DPRINT(("robouser%d::ftpCmdDone: m_state %d\n", m_user, m_state));
245 		switch (m_state) {
246 
247 		case CONNECT:
248 			/* not connected yet - connect him */
249 			err = m_fcp.connect(m_platoon->getServername(), m_platoon->getPort());
250 			if (err) {
251 				EDPRINT(("robouser%d::ftpCmdDone:CONNECT: connect returns %d\n", m_user, err));
252 				stop();
253 				return;
254 			}
255 
256 			err = m_fcp.login(m_platoon->getUsername(), m_platoon->getPasswd());
257 			if (err) {
258 				EDPRINT(("robouser%d::ftpCmdDone:CONNECT: login returns %d\n",m_user, err));
259 				stop();
260 				return;
261 			}
262 			gotoState(CONNECTING);
263 			return;
264 
265 		case CONNECTING:
266 			if (!STATUS_OK(status)) {
267 				EDPRINT(("robouser%d::ftpCmdDone:CONNECTING: status %d, aborting\n",m_user, status));
268 				stop();
269 				return;
270 			}
271 			DPRINT(("robouser%d::ftpCmdDone:CONNECTING: connect succeeded, jumping to state %d\n", m_user, GET));
272 			gotoState(START_TYPE);
273 			break;
274 
275 		case START_TYPE: {
276 			DPRINT(("robouser%d::ftpCmdDone:START_TYPE: setting TYPE I\n", m_user));
277 			err = m_fcp.type("I");
278 			if (err) {
279 				EDPRINT(("robouser%d::ftpCmdDone:START_TYPE: type() returns error %d\n", m_user, err));
280 				stop();
281 				return;
282 			}
283 			gotoState(FINISH_TYPE);
284 			return;
285 		}
286 
287 		case FINISH_TYPE: {
288 			// see if the command had an error
289 			if (xerr || !STATUS_OK(status)) {
290 				EDPRINT(("robouser%d::ftpCmdDone:FINISH_TYPE: xerr %d, status %d, aborting\n",m_user, xerr, status));
291 				stop();
292 				return;
293 			}
294 
295 			gotoState(GET);	/* start fetching files */
296 			break;
297 		}
298 
299 		case GET: {
300 			char fetchee_name[1024];
301 			pick_random_file(fetchee_name);
302 			DPRINT(("robouser%d::ftpCmdDone:GET: fetching file %s\n", m_user, fetchee_name));
303 			err = m_fcp.get(fetchee_name, true);	/* FIXME */
304 			if (err) {
305 				EDPRINT(("robouser%d::ftpCmdDone:GET: get() returns error %d\n", m_user, err));
306 				stop();
307 				return;
308 			}
309 			m_startedAt = eclock();	/* note: reset when first packet comes in */
310 			m_bytesFetched = 0;
311 			m_reads = 0;
312 			gotoState(GETTING);
313 			return;
314 		}
315 
316 		case GETTING: {
317 			// see if the command had an error
318 			if (xerr || !STATUS_OK(status)) {
319 				EDPRINT(("robouser%d::ftpCmdDone:GETTING: xerr %d, status %d, aborting\n",m_user, xerr, status));
320 				stop();
321 				return;
322 			}
323 
324 			clock_t now = eclock();
325 			clock_t total = now - m_startedAt;
326 			if (total > 0) {
327 				float bytesPerSec;
328 				bytesPerSec = (1.0 * m_bytesFetched * s_hertz) / total;
329 				// FIXME: should verify correct # of bytes fetched
330 				if (m_platoon->getVerbosity()) {
331 					printf("User%d: fetching %d bytes took %f seconds, %d bytes per second\n", m_user, m_bytesFetched, total * 1.0/s_hertz, (int)bytesPerSec);
332 				}
333 				if (bytesPerSec < m_platoon->getMinBytesPerSec()) {
334 					DPRINT(("robouser%d::ftpCmdDone: Test failed at end (%d bytes), too slow (%d bytes/sec)\n", m_user, m_bytesFetched, (int) bytesPerSec));
335 					printf("User%d: Test failed at end (%d bytes), too slow (%d bytes/sec)\n", m_user, m_bytesFetched, (int) bytesPerSec);
336 					stop();
337 					return;
338 				}
339 			} else {
340 				printf("User%d: fetching %d bytes took 0 seconds\n", m_user, m_bytesFetched);
341 			}
342 			gotoState(GET);	/* loop back */
343 			break;
344 		}
345 
346 		case STOPPED:
347 			/* should never be reached */
348 			return;
349 
350 		default:
351 			return;
352 		}
353 	}
354 	/* notreached */
355 	return;
356 }
357 
358