summaryrefslogtreecommitdiff
path: root/include/inspsocket.h
blob: a07c2eb6f82c2aaf4519496fa7dadee80b69e560 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
/*
 * InspIRCd -- Internet Relay Chat Daemon
 *
 *   Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
 *   Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
 *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
 *   Copyright (C) 2006-2007 Craig Edwards <craigedwards@brainbox.cc>
 *   Copyright (C) 2006 Oliver Lupton <oliverlupton@gmail.com>
 *
 * This file is part of InspIRCd.  InspIRCd is free software: you can
 * redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation, version 2.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


#pragma once

#include "timer.h"

class IOHook;

/**
 * States which a socket may be in
 */
enum BufferedSocketState
{
	/** Socket disconnected */
	I_DISCONNECTED,
	/** Socket connecting */
	I_CONNECTING,
	/** Socket fully connected */
	I_CONNECTED,
	/** Socket has an error */
	I_ERROR
};

/**
 * Error types which a socket may exhibit
 */
enum BufferedSocketError
{
	/** No error */
	I_ERR_NONE,
	/** Socket was closed by peer */
	I_ERR_DISCONNECT,
	/** Socket connect timed out */
	I_ERR_TIMEOUT,
	/** Socket could not be created */
	I_ERR_SOCKET,
	/** Socket could not connect (refused) */
	I_ERR_CONNECT,
	/** Socket could not bind to local port/ip */
	I_ERR_BIND,
	/** Socket could not write data */
	I_ERR_WRITE,
	/** No more file descriptors left to create socket! */
	I_ERR_NOMOREFDS,
	/** Some other error */
	I_ERR_OTHER
};

/* Required forward declarations */
class BufferedSocket;

/** Used to time out socket connections
 */
class CoreExport SocketTimeout : public Timer
{
 private:
	/** BufferedSocket the class is attached to
	 */
	BufferedSocket* sock;

	/** File descriptor of class this is attached to
	 */
	int sfd;

 public:
	/** Create a socket timeout class
	 * @param fd File descriptor of BufferedSocket
	 * @param thesock BufferedSocket to attach to
	 * @param secs_from_now Seconds from now to time out
	 */
	SocketTimeout(int fd, BufferedSocket* thesock, unsigned int secs_from_now)
		: Timer(secs_from_now)
		, sock(thesock)
		, sfd(fd)
	{
	}

	/** Handle tick event
	 */
	bool Tick(time_t now) CXX11_OVERRIDE;
};

/**
 * StreamSocket is a class that wraps a TCP socket and handles send
 * and receive queues, including passing them to IO hooks
 */
class CoreExport StreamSocket : public EventHandler
{
 public:
	/** Socket send queue
	 */
	class SendQueue
	{
	 public:
		/** One element of the queue, a continuous buffer
		 */
		typedef std::string Element;

		/** Sequence container of buffers in the queue
		 */
		typedef std::deque<Element> Container;

		/** Container iterator
		 */
		typedef Container::const_iterator const_iterator;

		SendQueue() : nbytes(0) { }

		/** Return whether the queue is empty
		 * @return True if the queue is empty, false otherwise
		 */
		bool empty() const { return (nbytes == 0); }

		/** Get the number of individual buffers in the queue
		 * @return Number of individual buffers in the queue
		 */
		Container::size_type size() const { return data.size(); }

		/** Get the number of queued bytes
		 * @return Size in bytes of the data in the queue
		 */
		size_t bytes() const { return nbytes; }

		/** Get the first buffer of the queue
		 * @return A reference to the first buffer in the queue
		 */
		const Element& front() const { return data.front(); }

		/** Get an iterator to the first buffer in the queue.
		 * The returned iterator cannot be used to make modifications to the queue,
		 * for that purpose the member functions push_*(), pop_front(), erase_front() and clear() can be used.
		 * @return Iterator referring to the first buffer in the queue, or end() if there are no elements.
		 */
		const_iterator begin() const { return data.begin(); }

		/** Get an iterator to the (theoretical) buffer one past the end of the queue.
		 * @return Iterator referring to one element past the end of the container
		 */
		const_iterator end() const { return data.end(); }

		/** Remove the first buffer in the queue
		 */
		void pop_front()
		{
			nbytes -= data.front().length();
			data.pop_front();
		}

		/** Remove bytes from the beginning of the first buffer
		 * @param n Number of bytes to remove
		 */
		void erase_front(Element::size_type n)
		{
			nbytes -= n;
			data.front().erase(0, n);
		}

		/** Insert a new buffer at the beginning of the queue
		 * @param newdata Data to add
		 */
		void push_front(const Element& newdata)
		{
			data.push_front(newdata);
			nbytes += newdata.length();
		}

		/** Insert a new buffer at the end of the queue
		 * @param newdata Data to add
		 */
		void push_back(const Element& newdata)
		{
			data.push_back(newdata);
			nbytes += newdata.length();
		}

		/** Clear the queue
		 */
		void clear()
		{
			data.clear();
			nbytes = 0;
		}

		void moveall(SendQueue& other)
		{
			nbytes += other.bytes();
			data.insert(data.end(), other.data.begin(), other.data.end());
			other.clear();
		}

	 private:
	 	/** Private send queue. Note that individual strings may be shared.
		 */
		Container data;

		/** Length, in bytes, of the sendq
		 */
		size_t nbytes;
	};

	/** The type of socket this IOHook represents. */
	enum Type
	{
		SS_UNKNOWN,
		SS_USER
	};

 private:
	/** Whether this socket should close once its sendq is empty */
	bool closeonempty;

	/** Whether the socket is currently closing or not, used to avoid repeatedly closing a closed socket */
	bool closing;

	/** The IOHook that handles raw I/O for this socket, or NULL */
	IOHook* iohook;

	/** Send queue of the socket
	 */
	SendQueue sendq;

	/** Error - if nonempty, the socket is dead, and this is the reason. */
	std::string error;

	/** Check if the socket has an error set, if yes, call OnError
	 * @param err Error to pass to OnError()
	 */
	void CheckError(BufferedSocketError err);

	/** Read data from the socket into the recvq, if successful call OnDataReady()
	 */
	void DoRead();

	/** Send as much data contained in a SendQueue object as possible.
	 * All data which successfully sent will be removed from the SendQueue.
	 * @param sq SendQueue to flush
	 */
	void FlushSendQ(SendQueue& sq);

	/** Read incoming data into a receive queue.
	 * @param rq Receive queue to put incoming data into
	 * @return < 0 on error or close, 0 if no new data is ready (but the socket is still connected), > 0 if data was read from the socket and put into the recvq
	 */
	int ReadToRecvQ(std::string& rq);

	/** Read data from a hook chain recursively, starting at 'hook'.
	 * If 'hook' is NULL, the recvq is filled with data from SocketEngine::Recv(), otherwise it is filled with data from the
	 * next hook in the chain.
	 * @param hook Next IOHook in the chain, can be NULL
	 * @param rq Receive queue to put incoming data into
	 * @return < 0 on error or close, 0 if no new data is ready (but the socket is still connected), > 0 if data was read from
	 the socket and put into the recvq
	 */
	int HookChainRead(IOHook* hook, std::string& rq);

 protected:
	std::string recvq;
 public:
	const Type type;
	StreamSocket(Type sstype = SS_UNKNOWN)
		: closeonempty(false)
		, closing(false)
		, iohook(NULL)
		, type(sstype)
	{
	}
	IOHook* GetIOHook() const;
	void AddIOHook(IOHook* hook);
	void DelIOHook();

	/** Flush the send queue
	 */
	void DoWrite();

	/** Called by the socket engine on a read event
	 */
	void OnEventHandlerRead() CXX11_OVERRIDE;

	/** Called by the socket engine on a write event
	 */
	void OnEventHandlerWrite() CXX11_OVERRIDE;

	/** Called by the socket engine on error
	 * @param errcode Error
	 */
	void OnEventHandlerError(int errcode) CXX11_OVERRIDE;

	/** Sets the error message for this socket. Once set, the socket is dead. */
	void SetError(const std::string& err) { if (error.empty()) error = err; }

	/** Gets the error message for this socket. */
	const std::string& getError() const { return error; }

	/** Called when new data is present in recvq */
	virtual void OnDataReady() = 0;
	/** Called when the socket gets an error from socket engine or IO hook */
	virtual void OnError(BufferedSocketError e) = 0;

	/** Called when the endpoint addresses are changed.
	 * @param local The new local endpoint.
	 * @param remote The new remote endpoint.
	 * @return true if the connection is still open, false if it has been closed
	 */
	virtual bool OnSetEndPoint(const irc::sockets::sockaddrs& local, const irc::sockets::sockaddrs& remote);

	/** Send the given data out the socket, either now or when writes unblock
	 */
	void WriteData(const std::string& data);
	/** Convenience function: read a line from the socket
	 * @param line The line read
	 * @param delim The line delimiter
	 * @return true if a line was read
	 */
	bool GetNextLine(std::string& line, char delim = '\n');
	/** Useful for implementing sendq exceeded */
	size_t getSendQSize() const;

	SendQueue& GetSendQ() { return sendq; }

	/**
	 * Close the socket, remove from socket engine, etc
	 */
	virtual void Close();

	/** If writeblock is true then only close the socket if all data has been sent. Otherwise, close immediately. */
	void Close(bool writeblock);

	/** This ensures that close is called prior to destructor */
	CullResult cull() CXX11_OVERRIDE;

	/** Get the IOHook of a module attached to this socket
	 * @param mod Module whose IOHook to return
	 * @return IOHook belonging to the module or NULL if the module haven't attached an IOHook to this socket
	 */
	IOHook* GetModHook(Module* mod) const;
};
/**
 * BufferedSocket is an extendable socket class which modules
 * can use for TCP socket support. It is fully integrated
 * into InspIRCds socket loop and attaches its sockets to
 * the core's instance of the SocketEngine class, meaning
 * that all use is fully asynchronous.
 *
 * To use BufferedSocket, you must inherit a class from it.
 */
class CoreExport BufferedSocket : public StreamSocket
{
 public:
	/** Timeout object or NULL
	 */
	SocketTimeout* Timeout;

	/**
	 * The state for this socket, either
	 * listening, connecting, connected
	 * or error.
	 */
	BufferedSocketState state;

	BufferedSocket();
	/**
	 * This constructor is used to associate
	 * an existing connecting with an BufferedSocket
	 * class. The given file descriptor must be
	 * valid, and when initialized, the BufferedSocket
	 * will be placed in CONNECTED state.
	 */
	BufferedSocket(int newfd);

	/** Begin connection to the given address
	 * This will create a socket, register with socket engine, and start the asynchronous
	 * connection process. If an error is detected at this point (such as out of file descriptors),
	 * OnError will be called; otherwise, the state will become CONNECTING.
	 * @param dest Remote endpoint to connect to.
	 * @param bind Local endpoint to connect from.
	 * @param maxtime Time to wait for connection
	 */
	void DoConnect(const irc::sockets::sockaddrs& dest, const irc::sockets::sockaddrs& bind, unsigned int maxtime);

	/** This method is called when an outbound connection on your socket is
	 * completed.
	 */
	virtual void OnConnected();

	/** When there is data waiting to be read on a socket, the OnDataReady()
	 * method is called.
	 */
	void OnDataReady() CXX11_OVERRIDE = 0;

	/**
	 * When an outbound connection fails, and the attempt times out, you
	 * will receive this event.  The method will trigger once maxtime
	 * seconds are reached (as given in the constructor) just before the
	 * socket's descriptor is closed.  A failed DNS lookup may cause this
	 * event if the DNS server is not responding, as well as a failed
	 * connect() call, because DNS lookups are nonblocking as implemented by
	 * this class.
	 */
	virtual void OnTimeout();

	virtual ~BufferedSocket();
 protected:
	void OnEventHandlerWrite() CXX11_OVERRIDE;
	BufferedSocketError BeginConnect(const irc::sockets::sockaddrs& dest, const irc::sockets::sockaddrs& bind, unsigned int timeout);
};

inline IOHook* StreamSocket::GetIOHook() const { return iohook; }
inline void StreamSocket::DelIOHook() { iohook = NULL; }