Line data Source code
1 : #define _GNU_SOURCE
2 : #include "fd_ipecho_server.h"
3 :
4 : #include "../../util/fd_util.h"
5 : #include "../../util/net/fd_ip4.h"
6 :
7 : #include <errno.h>
8 : #include <unistd.h>
9 : #include <sys/poll.h>
10 : #include <sys/socket.h>
11 : #include <netinet/in.h>
12 :
13 0 : #define STATE_READING (0)
14 0 : #define STATE_WRITING (1)
15 :
16 0 : #define CLOSE_OK ( 0)
17 0 : #define CLOSE_EXPECTED_EOF (-1)
18 0 : #define CLOSE_PEER_RESET (-2)
19 0 : #define CLOSE_LARGE_REQUEST (-3)
20 0 : #define CLOSE_BAD_HEADER (-4)
21 0 : #define CLOSE_BAD_TRAILER (-5)
22 0 : #define CLOSE_BAD_LENGTH (-6)
23 0 : #define CLOSE_EVICTED (-7)
24 :
25 : struct fd_ipecho_server_connection {
26 : int state;
27 :
28 : uint ipv4;
29 :
30 : ushort parent;
31 :
32 : ulong request_bytes_read;
33 : uchar request_bytes[ 22UL ];
34 : ulong response_bytes_written;
35 : uchar response_bytes[ 27UL ];
36 : };
37 :
38 : typedef struct fd_ipecho_server_connection fd_ipecho_server_connection_t;
39 :
40 : #define POOL_NAME conn_pool
41 0 : #define POOL_T fd_ipecho_server_connection_t
42 : #define POOL_IDX_T ushort
43 0 : #define POOL_NEXT parent
44 : #include "../../util/tmpl/fd_pool.c"
45 :
46 : struct fd_ipecho_server {
47 : ushort shred_version;
48 :
49 : ulong evict_idx;
50 : ulong max_connection_cnt;
51 :
52 : fd_ipecho_server_connection_t * pool;
53 : struct pollfd * pollfds;
54 :
55 : fd_ipecho_server_metrics_t metrics[ 1 ];
56 :
57 : ulong magic;
58 : };
59 :
60 : FD_FN_CONST ulong
61 0 : fd_ipecho_server_align( void ) {
62 0 : return 128UL;
63 0 : }
64 :
65 : FD_FN_CONST ulong
66 0 : fd_ipecho_server_footprint( ulong max_connection_cnt ) {
67 0 : ulong l = FD_LAYOUT_INIT;
68 0 : l = FD_LAYOUT_APPEND( l, fd_ipecho_server_align(), sizeof(fd_ipecho_server_t) );
69 0 : l = FD_LAYOUT_APPEND( l, conn_pool_align(), conn_pool_footprint( max_connection_cnt ) );
70 0 : l = FD_LAYOUT_APPEND( l, alignof(struct pollfd), (1UL+max_connection_cnt)*sizeof(struct pollfd) );
71 0 : return FD_LAYOUT_FINI( l, fd_ipecho_server_align() );
72 0 : }
73 :
74 : void *
75 : fd_ipecho_server_new( void * shmem,
76 0 : ulong max_connection_cnt ) {
77 0 : if( FD_UNLIKELY( !shmem ) ) {
78 0 : FD_LOG_WARNING(( "NULL shmem" ));
79 0 : return NULL;
80 0 : }
81 :
82 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_ipecho_server_align() ) ) ) {
83 0 : FD_LOG_WARNING(( "misaligned shmem" ));
84 0 : return NULL;
85 0 : }
86 :
87 0 : FD_SCRATCH_ALLOC_INIT( l, shmem );
88 0 : fd_ipecho_server_t * server = FD_SCRATCH_ALLOC_APPEND( l, fd_ipecho_server_align(), sizeof(fd_ipecho_server_t) );
89 0 : void * pool = FD_SCRATCH_ALLOC_APPEND( l, conn_pool_align(), conn_pool_footprint( max_connection_cnt ) );
90 0 : server->pollfds = FD_SCRATCH_ALLOC_APPEND( l, alignof(struct pollfd), (1UL+max_connection_cnt)*sizeof(struct pollfd) );
91 :
92 0 : server->pool = conn_pool_join( conn_pool_new( pool, max_connection_cnt ) );
93 0 : FD_TEST( server->pool );
94 :
95 0 : for( ulong i=0UL; i<max_connection_cnt; i++ ) {
96 0 : server->pollfds[ i ].fd = -1;
97 0 : server->pollfds[ i ].events = POLLIN | POLLOUT;
98 0 : }
99 :
100 0 : server->evict_idx = 0UL;
101 0 : server->max_connection_cnt = max_connection_cnt;
102 :
103 0 : memset( &server->metrics, 0, sizeof(server->metrics) );
104 :
105 0 : FD_COMPILER_MFENCE();
106 0 : FD_VOLATILE( server->magic ) = FD_IPECHO_SERVER_MAGIC;
107 0 : FD_COMPILER_MFENCE();
108 :
109 0 : return server;
110 0 : }
111 :
112 : fd_ipecho_server_t *
113 0 : fd_ipecho_server_join( void * shipe ) {
114 0 : if( FD_UNLIKELY( !shipe ) ) {
115 0 : FD_LOG_WARNING(( "NULL shipe" ));
116 0 : return NULL;
117 0 : }
118 :
119 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shipe, fd_ipecho_server_align() ) ) ) {
120 0 : FD_LOG_WARNING(( "misaligned shipe" ));
121 0 : return NULL;
122 0 : }
123 :
124 0 : fd_ipecho_server_t * server = (fd_ipecho_server_t *)shipe;
125 :
126 0 : if( FD_UNLIKELY( server->magic!=FD_IPECHO_SERVER_MAGIC ) ) {
127 0 : FD_LOG_WARNING(( "bad magic" ));
128 0 : return NULL;
129 0 : }
130 :
131 0 : return server;
132 0 : }
133 :
134 : void
135 : fd_ipecho_server_init( fd_ipecho_server_t * server,
136 : uint address,
137 : ushort port,
138 0 : ushort shred_version ) {
139 0 : server->shred_version = shred_version;
140 :
141 0 : int sockfd = socket( AF_INET, SOCK_STREAM|SOCK_NONBLOCK, 0 );
142 0 : if( FD_UNLIKELY( -1==sockfd ) ) FD_LOG_ERR(( "socket() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
143 :
144 0 : int optval = 1;
145 0 : if( FD_UNLIKELY( -1==setsockopt( sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof( optval ) ) ) )
146 0 : FD_LOG_ERR(( "setsockopt failed (%i-%s)", errno, strerror( errno ) ));
147 :
148 0 : struct sockaddr_in addr = {
149 0 : .sin_family = AF_INET,
150 0 : .sin_port = fd_ushort_bswap( port ),
151 0 : .sin_addr.s_addr = address,
152 0 : };
153 :
154 0 : if( FD_UNLIKELY( -1==bind( sockfd, fd_type_pun( &addr ), sizeof( addr ) ) ) ) {
155 0 : FD_LOG_ERR(( "bind(%i,AF_INET," FD_IP4_ADDR_FMT ":%u) failed (%i-%s)",
156 0 : sockfd, FD_IP4_ADDR_FMT_ARGS( address ), port,
157 0 : errno, fd_io_strerror( errno ) ));
158 0 : }
159 0 : if( FD_UNLIKELY( -1==listen( sockfd, (int)server->max_connection_cnt ) ) ) FD_LOG_ERR(( "listen() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
160 :
161 0 : server->pollfds[ server->max_connection_cnt ] = (struct pollfd){ .fd = sockfd, .events = POLLIN, .revents = 0 };
162 0 : }
163 :
164 : static inline int
165 0 : is_expected_network_error( int err ) {
166 0 : return
167 0 : err==ENETDOWN ||
168 0 : err==EPROTO ||
169 0 : err==ENOPROTOOPT ||
170 0 : err==EHOSTDOWN ||
171 0 : err==ENONET ||
172 0 : err==EHOSTUNREACH ||
173 0 : err==EOPNOTSUPP ||
174 0 : err==ENETUNREACH ||
175 0 : err==ETIMEDOUT ||
176 0 : err==ENETRESET ||
177 0 : err==ECONNABORTED ||
178 0 : err==ECONNRESET ||
179 0 : err==EPIPE;
180 0 : }
181 :
182 : static void
183 : close_conn( fd_ipecho_server_t * server,
184 : ulong conn_idx,
185 0 : int reason ) {
186 0 : (void)reason;
187 0 : FD_TEST( server->pollfds[ conn_idx ].fd!=-1 );
188 :
189 0 : if( FD_UNLIKELY( -1==close( server->pollfds[ conn_idx ].fd ) ) ) FD_LOG_ERR(( "close failed (%i-%s)", errno, strerror( errno ) ));
190 0 : server->pollfds[ conn_idx ].fd = -1;
191 0 : conn_pool_ele_release( server->pool, &server->pool[ conn_idx ] );
192 :
193 0 : FD_TEST( server->metrics->connection_cnt );
194 0 : server->metrics->connection_cnt--;
195 0 : if( FD_UNLIKELY( reason==CLOSE_OK ) ) server->metrics->connections_closed_ok++;
196 0 : else server->metrics->connections_closed_error++;
197 0 : }
198 :
199 : static void
200 0 : accept_conns( fd_ipecho_server_t * server ) {
201 0 : for(;;) {
202 0 : struct sockaddr_in addr;
203 0 : socklen_t addr_len = sizeof(addr);
204 0 : int fd = accept4( server->pollfds[ server->max_connection_cnt ].fd, fd_type_pun( &addr ), &addr_len, SOCK_NONBLOCK|SOCK_CLOEXEC );
205 :
206 0 : if( FD_UNLIKELY( -1==fd ) ) {
207 0 : if( FD_LIKELY( EAGAIN==errno ) ) break;
208 0 : else if( FD_LIKELY( is_expected_network_error( errno ) ) ) continue;
209 0 : else FD_LOG_ERR(( "accept4() failed (%i-%s)", errno, strerror( errno ) ));
210 0 : }
211 :
212 0 : if( FD_UNLIKELY( !conn_pool_free( server->pool ) ) ) {
213 0 : close_conn( server, server->evict_idx, CLOSE_EVICTED );
214 0 : server->evict_idx = (server->evict_idx+1UL) % server->max_connection_cnt;
215 0 : }
216 0 : ulong conn_id = conn_pool_idx_acquire( server->pool );
217 :
218 0 : server->pollfds[ conn_id ].fd = fd;
219 0 : server->pool[ conn_id ].ipv4 = addr.sin_addr.s_addr;
220 0 : server->pool[ conn_id ].state = STATE_READING;
221 0 : server->pool[ conn_id ].request_bytes_read = 0UL;
222 0 : server->pool[ conn_id ].response_bytes_written = 0UL;
223 :
224 0 : server->metrics->connection_cnt++;
225 0 : }
226 0 : }
227 :
228 : static void
229 : read_conn( fd_ipecho_server_t * server,
230 0 : ulong conn_idx ) {
231 0 : fd_ipecho_server_connection_t * conn = &server->pool[ conn_idx ];
232 :
233 0 : if( FD_UNLIKELY( conn->state!=STATE_READING ) ) {
234 0 : close_conn( server, conn_idx, CLOSE_EXPECTED_EOF );
235 0 : return;
236 0 : }
237 :
238 0 : long sz = read( server->pollfds[ conn_idx ].fd, conn->request_bytes+conn->request_bytes_read, sizeof(conn->request_bytes)-conn->request_bytes_read );
239 0 : if( FD_UNLIKELY( -1==sz && errno==EAGAIN ) ) return; /* No data to read, continue. */
240 0 : else if( -1==sz && is_expected_network_error( errno ) ) {
241 0 : close_conn( server, conn_idx, CLOSE_PEER_RESET );
242 0 : return;
243 0 : }
244 0 : else if( FD_UNLIKELY( -1==sz ) ) FD_LOG_ERR(( "read failed (%i-%s)", errno, strerror( errno ) )); /* Unexpected programmer error, abort */
245 :
246 0 : if( FD_UNLIKELY( !sz && conn->request_bytes_read!=21UL ) ) {
247 0 : close_conn( server, conn_idx, CLOSE_BAD_LENGTH );
248 0 : return;
249 0 : }
250 :
251 : /* New data was read... process it */
252 0 : server->metrics->bytes_read += (ulong)sz;
253 0 : conn->request_bytes_read += (ulong)sz;
254 0 : if( FD_UNLIKELY( conn->request_bytes_read==sizeof(conn->request_bytes) ) ) {
255 0 : close_conn( server, conn_idx, CLOSE_LARGE_REQUEST );
256 0 : return;
257 0 : }
258 :
259 0 : if( FD_UNLIKELY( memcmp( conn->request_bytes, "\0\0\0\0", 4UL ) ) ) {
260 0 : close_conn( server, conn_idx, CLOSE_BAD_HEADER );
261 0 : return;
262 0 : }
263 :
264 0 : if( FD_UNLIKELY( conn->request_bytes[ 20UL ]!='\n' ) ) {
265 0 : close_conn( server, conn_idx, CLOSE_BAD_TRAILER );
266 0 : return;
267 0 : }
268 :
269 0 : uchar response[ 27UL ] = {
270 0 : 0, 0, 0, 0, /* Magic */
271 0 : 0, 0, 0, 0, /* IP address variant */
272 0 : 0, 0, 0, 0, /* IP address */
273 0 : 1, /* Shred version option variant */
274 0 : 0, 0, /* Shred version */
275 0 : 0, /* [...] 12 bytes of trailing garbage, as in Agave */
276 0 : };
277 :
278 0 : FD_STORE( uint, response+8UL, conn->ipv4 );
279 0 : FD_STORE( ushort, response+13UL, server->shred_version );
280 :
281 : /* Now have a complete request ... buffer response */
282 0 : conn->state = STATE_WRITING;
283 0 : conn->response_bytes_written = 0UL;
284 0 : memcpy( conn->response_bytes, response, sizeof(response) );
285 0 : }
286 :
287 : static void
288 : write_conn( fd_ipecho_server_t * server,
289 0 : ulong conn_idx ) {
290 0 : fd_ipecho_server_connection_t * conn = &server->pool[ conn_idx ];
291 :
292 0 : if( FD_LIKELY( conn->state==STATE_READING ) ) return;
293 :
294 0 : long sz = send( server->pollfds[ conn_idx ].fd, conn->response_bytes+conn->response_bytes_written, sizeof(conn->response_bytes)-conn->response_bytes_written, MSG_NOSIGNAL );
295 0 : if( FD_UNLIKELY( -1==sz && errno==EAGAIN ) ) return; /* No data was written, continue. */
296 0 : if( FD_UNLIKELY( -1==sz && is_expected_network_error( errno ) ) ) {
297 0 : close_conn( server, conn_idx, CLOSE_PEER_RESET );
298 0 : return;
299 0 : }
300 0 : if( FD_UNLIKELY( -1==sz ) ) FD_LOG_ERR(( "write failed (%i-%s)", errno, strerror( errno ) )); /* Unexpected programmer error, abort */
301 :
302 0 : server->metrics->bytes_written += (ulong)sz;
303 0 : conn->response_bytes_written += (ulong)sz;
304 0 : if( FD_UNLIKELY( conn->response_bytes_written<sizeof(conn->response_bytes) ) ) return;
305 :
306 0 : close_conn( server, conn_idx, CLOSE_OK );
307 0 : }
308 :
309 : void
310 : fd_ipecho_server_poll( fd_ipecho_server_t * server,
311 : int * charge_busy,
312 0 : int timeout_ms ) {
313 0 : int nfds = fd_syscall_poll( server->pollfds, (uint)( server->max_connection_cnt+1UL ), timeout_ms );
314 0 : if( FD_UNLIKELY( 0==nfds ) ) return;
315 0 : else if( FD_UNLIKELY( -1==nfds && errno==EINTR ) ) return;
316 0 : else if( FD_UNLIKELY( -1==nfds ) ) FD_LOG_ERR(( "poll() failed (%i-%s)", errno, strerror( errno ) ));
317 :
318 0 : *charge_busy = 1;
319 :
320 0 : for( ulong i=0UL; i<server->max_connection_cnt+1UL; i++ ) {
321 0 : if( FD_UNLIKELY( -1==server->pollfds[ i ].fd ) ) continue;
322 0 : if( FD_UNLIKELY( i==server->max_connection_cnt ) ) {
323 0 : accept_conns( server );
324 0 : } else {
325 0 : if( FD_LIKELY( server->pollfds[ i ].revents & POLLIN ) ) read_conn( server, i );
326 0 : if( FD_UNLIKELY( -1==server->pollfds[ i ].fd ) ) continue;
327 0 : if( FD_LIKELY( server->pollfds[ i ].revents & POLLOUT ) ) write_conn( server, i );
328 : /* No need to handle POLLHUP, read() will return 0 soon enough. */
329 0 : }
330 0 : }
331 0 : }
332 :
333 : fd_ipecho_server_metrics_t *
334 0 : fd_ipecho_server_metrics( fd_ipecho_server_t * server ) {
335 0 : return server->metrics;
336 0 : }
|