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