-
Notifications
You must be signed in to change notification settings - Fork 29
/
select_group.h
178 lines (147 loc) · 8.65 KB
/
select_group.h
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
/* select_group.h
* By Ron Bowes
* Created August, 2008
*
* (See LICENSE.txt)
*
* This module implements a simple interface to the select() function that works across Windows, Linux,
* BSD, and Mac. Any (reasonable) number of sockets can be added and when any one of them has data,
* callbacks are used to notify the main program.
*
* This library is single-threaded (except on Windows.. I'll get to that). I've compiled and tested it on
* Linux, FreeBSD, Mac, and Windows, and it works beautifully on all of them. On any of those platforms it
* can select just fine on stream sockets, datagram sockets, listeners, and pipes (including stdin on Windows).
*
* Windows support for pipes is a special case. Because Windows can't select() on a pipe or HANDLE, I had
* to implement some special code. Basically, it polls -- instead of adding pipes to the select(), it times
* out the select after a set amount of time (right now, it's 100ms). That means that every 100ms, select()
* returns and checks if any input is waiting on the pipes. Not the greatest solution, but it isn't the
* greatest OS for networking stuff.
*
* Even worse, stdin is a special case. stdin can be read through a pipe, so the polling code works great --
* except that Windows won't echo types characters unless stdin is being actively read. Rather than
* introducing a 100ms-delay to everything types, that would probably make me even more crazy, I created
* the Windows-specific function get_stdin_handle(). The first time it's called, it creates a thread
* that reads from stdin and writes to a pipe. That pipe can be added to select() and everything else
* works the same. It's an ugly hack, I know, but when writing Ncat (http://nmap.org/ncat) David Fifield
* came up with the same solution. Apparently, it's the best we've got.
*/
#ifndef __SELECT_GROUP_H__
#define __SELECT_GROUP_H__
#include <stdlib.h>
#ifdef WIN32
#include <winsock2.h>
#else
#endif
#include "types.h"
/* The maximum number of possible sockets (huge number, but I want to prevent overflows). Note that this is
* sort of a range, because the number of sockets are doubled each time. So it's between 32768 and 65536. */
#define SOCKET_LIST_MAX_SOCKETS (65536/2)
/* The time, in milliseconds, between select() timing out and polling for pipe data (on Windows). */
#ifdef WIN32
#define TIMEOUT_INTERVAL 100
#endif
/* Different types of sockets, which will affect different aspects. */
typedef enum
{
SOCKET_TYPE_STREAM, /* No special treatment (anything can technically use this one). */
SOCKET_TYPE_DATAGRAM, /* Uses recvfrom() and passes along the socket address). */
SOCKET_TYPE_LISTEN, /* Listening implies a stream. */
#ifdef WIN32
SOCKET_TYPE_PIPE /* For use on Windows, only, is handled separately. */
#endif
} SOCKET_TYPE_t;
/* Possible return values from callback functions. */
typedef enum
{
SELECT_OK, /* Everything went well. */
SELECT_REMOVE, /* Remove the socket from the list. */
SELECT_CLOSE_REMOVE, /* Close the socket and remove it from the list. */
} SELECT_RESPONSE_t;
/* Define callback function types. I have to make the first parameter 'void*' because the struct hasn't been defined
* yet, and the struct requires these typedefs to be in place. */
/* 'addr' will only be filled in for datagram requests. */
typedef SELECT_RESPONSE_t(select_recv)(void *group, int s, uint8_t *data, size_t length, char *addr, uint16_t port, void *param);
typedef SELECT_RESPONSE_t(select_listen)(void *group, int s, void *param);
typedef SELECT_RESPONSE_t(select_error)(void *group, int s, int err, void *param);
typedef SELECT_RESPONSE_t(select_closed)(void *group, int s, void *param);
typedef SELECT_RESPONSE_t(select_timeout)(void *group, int s, void *param);
/* This struct is for internal use. */
typedef struct
{
int s; /* The socket. */
#ifdef WIN32
HANDLE pipe; /* A pipe (used for Windows' named pipes. */
#endif
SOCKET_TYPE_t type; /* Datagram, stream, etc. */
select_recv *recv_callback; /* The function to call when data arrives. */
select_listen *listen_callback; /* The function to call when a connection arrives. */
select_error *error_callback; /* The function to call when there's an error. */
select_closed *closed_callback; /* The function to call when the connection is closed. */
select_timeout *timeout_callback; /* The function to call when the timeout time expires. */
size_t waiting_for; /* The number of bytes being waited on. If set to 0, will trigger on all incoming data. */
uint8_t *buffer; /* The buffer that holds the current bytes. */
size_t buffered; /* The number of bytes currently stored in the buffer. */
NBBOOL active; /* Set to 'false' when the socket is 'deleted'. It's easier than physically removing it from
* the list, so until I implement something heavy weight this will work. */
void *param; /* Used to store a piece of arbitrary data that's sent to the callbacks. */
} select_t;
/* This is the primary struct for this module. */
typedef struct
{
select_t **select_list; /* A list of the select_t objects. */
size_t current_size; /* The current number of "select_t"s in the list. */
size_t maximum_size; /* The maximum number of "select_t"s in the list before realloc() has to expand it. */
#ifdef WIN32
uint32_t elapsed_time; /* The number of milliseconds that have elapsed; used for timeouts. */
#endif
int biggest_socket; /* The handle to the highest-numbered socket in the list (required for select() call). */
} select_group_t;
/* Allocate memory for a select group */
select_group_t *select_group_create();
/* Destroy and cleanup the group. */
void select_group_destroy(select_group_t *group);
/* Add a socket to the group. */
void select_group_add_socket(select_group_t *group, int s, SOCKET_TYPE_t type, void *param);
#ifdef WIN32
/* Add a pipe to the group. The 'identifier' is treated as a socket and is used in place of a socket
* to look up the pipe. */
void select_group_add_pipe(select_group_t *group, int identifier, HANDLE pipe, void *param);
#endif
/* Set the recv() callback. This will return with as much data as comes in, or with the number of bytes set by
* set_group_wait_for_bytes(), if that's set. Returns the old callback, if set. */
select_recv *select_set_recv(select_group_t *group, int s, select_recv *callback);
/* Set the listen() callback for incoming connections. It's up to the callback to perform the accept() to
* get the new socket. */
select_listen *select_set_listen(select_group_t *group, int s, select_listen *callback);
/* Set the error callback, for socket errors. If SELECT_OK is returned, it assumes the error's been handled
* and will continue to select() on the socket. In almost every case, SELECT_OK is the wrong thing to return.
* If this isn't defined, the socket is automatically closed/removed. */
select_error *select_set_error(select_group_t *group, int s, select_error *callback);
/* Set the closed callback. This is called when the connection is gracefully terminated. Like with errors,
* SELECT_OK is probably not what you want. If this isn't handled, the socket is automatically removed from
* the list. */
select_closed *select_set_closed(select_group_t *group, int s, select_closed *callback);
/* Set the timeout callback, for when the time specified in select_group_do_select() elapses. */
select_timeout *select_set_timeout(select_group_t *group, int s, select_timeout *callback);
/* Remove a socket from the group. Returns non-zero if successful. */
NBBOOL select_group_remove_socket(select_group_t *group, int s);
/* Remove a socket from the group, and close it. */
NBBOOL select_group_remove_and_close_socket(select_group_t *group, int s);
/* Perform the select() call across the various sockets. with the given timeout in milliseconds.
* Note that the timeout (and therefore the timeout callback) only fires if _every_ socket is idle.
* If timeout_ms < 0, it will block indefinitely (till data arrives on any socket). Because of polling,
* on Windows, timeout_ms actually has a resolution defined by TIMEOUT_INTERVAL. */
void select_group_do_select(select_group_t *group, int timeout_ms);
/* Wait for the given number of bytes to arrive on the socket, rather than any number of bytes. This doesn't
* work for datagram sockets.
* Note: any data already queued up will be whacked. */
NBBOOL select_group_wait_for_bytes(select_group_t *group, int s, size_t bytes);
/* Check how many active sockets are left. */
size_t select_group_get_active_count(select_group_t *group);
#ifdef WIN32
/* Get a handle to stdin. This handle can be added to a select_group as a pipe. Behind the scenes,
* it uses a thread. Don't ask. */
HANDLE get_stdin_handle();
#endif
#endif