mirror of
https://github.com/isc-projects/bind9.git
synced 2026-06-08 22:22:05 -04:00
Commit socket code so far -- nowhere near done, but I don't wanna loose work.
This commit is contained in:
parent
35921f41cf
commit
6d05b41aae
3 changed files with 1199 additions and 0 deletions
153
bin/tests/sock_test.c
Normal file
153
bin/tests/sock_test.c
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
|
||||
#include "attribute.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <isc/assertions.h>
|
||||
#include <isc/memcluster.h>
|
||||
#include <isc/task.h>
|
||||
#include <isc/thread.h>
|
||||
#include <isc/result.h>
|
||||
#include <isc/socket.h>
|
||||
|
||||
isc_memctx_t mctx = NULL;
|
||||
|
||||
static isc_boolean_t
|
||||
my_callback(isc_task_t task, isc_event_t event)
|
||||
{
|
||||
char *name = event->arg;
|
||||
|
||||
printf("task %s (%p)\n", name, task);
|
||||
fflush(stdout);
|
||||
isc_event_free(&event);
|
||||
|
||||
return (ISC_FALSE);
|
||||
}
|
||||
|
||||
static isc_boolean_t
|
||||
my_shutdown(isc_task_t task, isc_event_t event)
|
||||
{
|
||||
char *name = event->arg;
|
||||
|
||||
printf("shutdown %s (%p)\n", name, task);
|
||||
fflush(stdout);
|
||||
isc_event_free(&event);
|
||||
|
||||
return (ISC_TRUE);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
isc_taskmgr_t manager = NULL;
|
||||
isc_task_t t1 = NULL, t2 = NULL;
|
||||
isc_task_t t3 = NULL, t4 = NULL;
|
||||
isc_event_t event;
|
||||
unsigned int workers;
|
||||
isc_socketmgr_t socketmgr;
|
||||
isc_socket_t so1, so2;
|
||||
|
||||
if (argc > 1)
|
||||
workers = atoi(argv[1]);
|
||||
else
|
||||
workers = 2;
|
||||
printf("%d workers\n", workers);
|
||||
|
||||
INSIST(isc_memctx_create(0, 0, &mctx) == ISC_R_SUCCESS);
|
||||
|
||||
INSIST(isc_taskmgr_create(mctx, workers, 0, &manager) ==
|
||||
ISC_R_SUCCESS);
|
||||
|
||||
INSIST(isc_task_create(manager, my_shutdown, "1", 0, &t1) ==
|
||||
ISC_R_SUCCESS);
|
||||
INSIST(isc_task_create(manager, my_shutdown, "2", 0, &t2) ==
|
||||
ISC_R_SUCCESS);
|
||||
INSIST(isc_task_create(manager, my_shutdown, "3", 0, &t3) ==
|
||||
ISC_R_SUCCESS);
|
||||
INSIST(isc_task_create(manager, my_shutdown, "4", 0, &t4) ==
|
||||
ISC_R_SUCCESS);
|
||||
|
||||
printf("task 1 = %p\n", t1);
|
||||
printf("task 2 = %p\n", t2);
|
||||
printf("task 3 = %p\n", t3);
|
||||
printf("task 4 = %p\n", t4);
|
||||
|
||||
socketmgr = NULL;
|
||||
INSIST(isc_socketmgr_create(mctx, &socketmgr) == ISC_R_SUCCESS);
|
||||
so1 = NULL;
|
||||
INSIST(isc_socket_create(socketmgr, isc_socket_udp,
|
||||
&so1) == ISC_R_SUCCESS);
|
||||
so2 = NULL;
|
||||
INSIST(isc_socket_create(socketmgr, isc_socket_udp,
|
||||
&so2) == ISC_R_SUCCESS);
|
||||
|
||||
sleep(2);
|
||||
|
||||
event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1",
|
||||
sizeof *event);
|
||||
isc_task_send(t1, &event);
|
||||
event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1",
|
||||
sizeof *event);
|
||||
isc_task_send(t1, &event);
|
||||
event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1",
|
||||
sizeof *event);
|
||||
isc_task_send(t1, &event);
|
||||
event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1",
|
||||
sizeof *event);
|
||||
isc_task_send(t1, &event);
|
||||
event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1",
|
||||
sizeof *event);
|
||||
isc_task_send(t1, &event);
|
||||
event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1",
|
||||
sizeof *event);
|
||||
isc_task_send(t1, &event);
|
||||
event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1",
|
||||
sizeof *event);
|
||||
isc_task_send(t1, &event);
|
||||
event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1",
|
||||
sizeof *event);
|
||||
isc_task_send(t1, &event);
|
||||
event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "1",
|
||||
sizeof *event);
|
||||
isc_task_send(t1, &event);
|
||||
event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "2",
|
||||
sizeof *event);
|
||||
isc_task_send(t2, &event);
|
||||
event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "3",
|
||||
sizeof *event);
|
||||
isc_task_send(t3, &event);
|
||||
event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "4",
|
||||
sizeof *event);
|
||||
isc_task_send(t4, &event);
|
||||
event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "2",
|
||||
sizeof *event);
|
||||
isc_task_send(t2, &event);
|
||||
event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "3",
|
||||
sizeof *event);
|
||||
isc_task_send(t3, &event);
|
||||
event = isc_event_allocate(mctx, (void *)main, 1, my_callback, "4",
|
||||
sizeof *event);
|
||||
isc_task_send(t4, &event);
|
||||
isc_task_purge(t3, NULL, 0);
|
||||
|
||||
isc_task_detach(&t1);
|
||||
isc_task_detach(&t2);
|
||||
isc_task_detach(&t3);
|
||||
isc_task_detach(&t4);
|
||||
|
||||
sleep(10);
|
||||
printf("destroy\n");
|
||||
isc_socket_detach(&so1);
|
||||
isc_socket_detach(&so2);
|
||||
|
||||
isc_socketmgr_destroy(&socketmgr);
|
||||
isc_taskmgr_destroy(&manager);
|
||||
printf("destroyed\n");
|
||||
|
||||
isc_mem_stats(mctx, stdout);
|
||||
isc_memctx_destroy(&mctx);
|
||||
|
||||
return (0);
|
||||
}
|
||||
522
lib/isc/include/isc/socket.h
Normal file
522
lib/isc/include/isc/socket.h
Normal file
|
|
@ -0,0 +1,522 @@
|
|||
|
||||
#ifndef ISC_SOCKET_H
|
||||
#define ISC_SOCKET_H 1
|
||||
|
||||
/*****
|
||||
***** Module Info
|
||||
*****/
|
||||
|
||||
/*
|
||||
* Sockets
|
||||
*
|
||||
* Provides TCP and UDP sockets for network I/O. The sockets are event
|
||||
* sources in the task system.
|
||||
*
|
||||
* When I/O completes, a completion event for the socket is posted to the
|
||||
* event queue of the task which requested the I/O.
|
||||
*
|
||||
* MP:
|
||||
* The module ensures appropriate synchronization of data structures it
|
||||
* creates and manipulates.
|
||||
*
|
||||
* Clients of this module must not be holding a socket's task's lock when
|
||||
* making a call that affects that socket. Failure to follow this rule
|
||||
* can result in deadlock.
|
||||
*
|
||||
* The caller must ensure that isc_socketmgr_destroy() is called only
|
||||
* once for a given manager.
|
||||
*
|
||||
* Reliability:
|
||||
* No anticipated impact.
|
||||
*
|
||||
* Resources:
|
||||
* <TBS>
|
||||
*
|
||||
* Security:
|
||||
* No anticipated impact.
|
||||
*
|
||||
* Standards:
|
||||
* None.
|
||||
*/
|
||||
|
||||
/***
|
||||
*** Imports
|
||||
***/
|
||||
|
||||
#include <isc/boolean.h>
|
||||
#include <isc/result.h>
|
||||
|
||||
#include <isc/task.h>
|
||||
#include <isc/region.h>
|
||||
#include <isc/memcluster.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
/***
|
||||
*** Types
|
||||
***/
|
||||
|
||||
typedef struct isc_socket *isc_socket_t;
|
||||
typedef struct isc_socketmgr *isc_socketmgr_t;
|
||||
|
||||
/*
|
||||
* XXX Export this as isc/sockaddr.h
|
||||
*/
|
||||
typedef struct isc_sockaddr {
|
||||
/*
|
||||
* XXX Must be big enough for all sockaddr types we care about.
|
||||
*/
|
||||
union foo {
|
||||
struct sockaddr_in sin;
|
||||
};
|
||||
} *isc_sockaddr_t;
|
||||
|
||||
typedef struct isc_sockevent {
|
||||
struct isc_event common; /* Sender is the socket. */
|
||||
isc_result_t result; /* OK, EOF, whatever else */
|
||||
unsigned int n; /* bytes read or written */
|
||||
struct isc_sockaddr address; /* source address */
|
||||
int length; /* length of address */
|
||||
} *isc_sockevent_t;
|
||||
|
||||
#define ISC_SOCKEVENT_ANYEVENT (0)
|
||||
#define ISC_SOCKEVENT_RECVDONE (EVENT_CLASS_SOCKET + 1)
|
||||
#define ISC_SOCKEVENT_SENDDONE (EVENT_CLASS_SOCKET + 2)
|
||||
#define ISC_SOCKEVENT_NEWCONN (EVENT_CLASS_SOCKET + 3)
|
||||
#define ISC_SOCKEVENT_CONNECTED (EVENT_CLASS_SOCKET + 4)
|
||||
|
||||
typedef enum {
|
||||
isc_socket_udp,
|
||||
isc_socket_tcp
|
||||
} isc_sockettype_t;
|
||||
|
||||
typedef enum {
|
||||
isc_sockshut_reading,
|
||||
isc_sockshut_writing,
|
||||
isc_sockshut_all
|
||||
} isc_socketshutdown_t;
|
||||
|
||||
/***
|
||||
*** Socket and Socket Manager Functions
|
||||
***
|
||||
*** Note: all Ensures conditions apply only if the result is success for
|
||||
*** those functions which return an isc_result.
|
||||
***/
|
||||
|
||||
isc_result_t
|
||||
isc_socket_create(isc_socketmgr_t manager,
|
||||
isc_sockettype_t type,
|
||||
isc_socket_t *socketp);
|
||||
/*
|
||||
* Create a new 'type' socket managed by 'manager'.
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* 'manager' is a valid manager
|
||||
*
|
||||
* 'socketp' is a valid pointer, and *socketp == NULL
|
||||
*
|
||||
* Ensures:
|
||||
*
|
||||
* '*socketp' is attached to the newly created socket
|
||||
*
|
||||
* Returns:
|
||||
*
|
||||
* Success
|
||||
* No memory
|
||||
* Unexpected error
|
||||
*/
|
||||
|
||||
|
||||
void
|
||||
isc_socket_shutdown(isc_socket_t socket, isc_task_t task,
|
||||
isc_socketshutdown_t how);
|
||||
/*
|
||||
* Shutdown 'socket' according to 'how'.
|
||||
*
|
||||
* Note: if 'task' is NULL, then the shutdown applies to all tasks using the
|
||||
* socket.
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* 'socket' is a valid socket.
|
||||
*
|
||||
* 'task' is NULL or is a valid task.
|
||||
*
|
||||
* Ensures:
|
||||
*
|
||||
* If 'how' is 'isc_sockshut_reading' or 'isc_sockshut_all' then
|
||||
*
|
||||
* Any pending read completion events for the task are
|
||||
* removed from the task's event queue.
|
||||
*
|
||||
* No further read completion events will be delivered to the
|
||||
* task.
|
||||
*
|
||||
* No further read requests may be made.
|
||||
*
|
||||
* If 'how' is 'isc_sockshut_writing' or 'isc_sockshut_all' then
|
||||
*
|
||||
* Any pending write completion events for the task are
|
||||
* removed from the task's event queue.
|
||||
*
|
||||
* No further write completion events will be delivered to the
|
||||
* task.
|
||||
*
|
||||
* No further write requests may be made.
|
||||
*
|
||||
* If 'socket' is a TCP socket, then when the last currently
|
||||
* pending write completes, TCP FIN will sent to the remote peer.
|
||||
*/
|
||||
|
||||
void
|
||||
isc_socket_attach(isc_socket_t socket, isc_socket_t *socketp);
|
||||
/*
|
||||
* Attach *socketp to socket.
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* 'socket' is a valid socket.
|
||||
*
|
||||
* 'socketp' points to a NULL socket.
|
||||
*
|
||||
* Ensures:
|
||||
*
|
||||
* *socketp is attached to socket.
|
||||
*/
|
||||
|
||||
void
|
||||
isc_socket_detach(isc_socket_t *socketp);
|
||||
/*
|
||||
* Detach *socketp from its socket.
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* Detaching the last reference may cause any still-pending I/O to be
|
||||
* cancelled.
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* 'socketp' points to a valid socket.
|
||||
*
|
||||
* Ensures:
|
||||
*
|
||||
* *socketp is NULL.
|
||||
*
|
||||
* If '*socketp' is the last reference to the socket,
|
||||
* then:
|
||||
*
|
||||
* The socket will be shutdown (both reading and writing)
|
||||
* for all tasks.
|
||||
*
|
||||
* All resources used by the socket have been freed
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
net_socket_bind(isc_socket_t socket, struct isc_sockaddr *addressp,
|
||||
int length);
|
||||
/*
|
||||
* Bind 'socket' to '*addressp'.
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* 'socket' is a valid socket
|
||||
*
|
||||
* 'addressp' points to a valid isc_sockaddr.
|
||||
*
|
||||
* 'length' is approprate for the isc_sockaddr type.
|
||||
*
|
||||
* Returns:
|
||||
*
|
||||
* Success
|
||||
* Address not available
|
||||
* Address in use
|
||||
* Permission denied
|
||||
* Unexpected error
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
isc_socket_listen(isc_socket_t socket, int backlog,
|
||||
isc_task_t task, isc_taskaction_t action, void *arg);
|
||||
/*
|
||||
* Listen on 'socket'. Every time a new connection request arrives,
|
||||
* a NEWCONN event with action 'action' and arg 'arg' will be posted
|
||||
* to the event queue for 'task'.
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* 'backlog' is as in the UNIX system call listen().
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* 'socket' is a valid TCP socket.
|
||||
*
|
||||
* 'task' is a valid task
|
||||
*
|
||||
* 'action' is a valid action
|
||||
*
|
||||
* Returns:
|
||||
*
|
||||
* Success
|
||||
* Unexpected error
|
||||
*/
|
||||
|
||||
void
|
||||
isc_socket_hold(isc_socket_t socket);
|
||||
/*
|
||||
* Put a TCP listener socket on hold. No NEWCONN events will be posted
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* While 'on hold', new connection requests will be queued or dropped
|
||||
* by the operating system.
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* 'socket' is a valid TCP socket
|
||||
*
|
||||
* Some task is listening on the socket.
|
||||
*
|
||||
*/
|
||||
|
||||
void
|
||||
isc_socket_unhold(isc_socket_t socket);
|
||||
/*
|
||||
* Restore normal NEWCONN event posting.
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* 'socket' is a valid TCP socket
|
||||
*
|
||||
* Some task is listening on the socket.
|
||||
*
|
||||
* 'socket' is holding.
|
||||
*
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
isc_socket_accept(isc_socket_t s1, isc_socket_t *s2p);
|
||||
/*
|
||||
* Accept a connection from 's1', creating a new socket for the connection
|
||||
* and attaching '*s2p' to it.
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* 'socket' is a valid TCP socket.
|
||||
*
|
||||
* s2p is a valid pointer, and *s2p == NULL;
|
||||
*
|
||||
* Ensures:
|
||||
*
|
||||
* *s2p is attached to the newly created socket.
|
||||
*
|
||||
* Returns:
|
||||
*
|
||||
* Success
|
||||
* No memory
|
||||
* No pending connection requests
|
||||
* Unexpected error
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
isc_socket_connect(isc_socket_t socket, struct isc_sockaddr *addressp,
|
||||
int length, isc_task_t task, isc_taskaction_t action,
|
||||
void *arg);
|
||||
/*
|
||||
* Connect 'socket' to peer with address *saddr. When the connection
|
||||
* succeeds, or when an error occurs, a CONNECTED event with action 'action'
|
||||
* and arg 'arg' will be posted to the event queue for 'task'.
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* 'socket' is a valid TCP socket
|
||||
*
|
||||
* 'addressp' points to a valid isc_sockaddr
|
||||
*
|
||||
* 'length' is approprate for the isc_sockaddr type
|
||||
*
|
||||
* 'task' is a valid task
|
||||
*
|
||||
* 'action' is a valid action
|
||||
*
|
||||
* Returns:
|
||||
*
|
||||
* Success
|
||||
* No memory
|
||||
* Address not available
|
||||
* Address in use
|
||||
* Host unreachable
|
||||
* Network unreachable
|
||||
* Connection refused
|
||||
* Unexpected error
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
isc_socket_getpeername(isc_socket_t socket, struct isc_sockaddr *addressp,
|
||||
int *lengthp);
|
||||
/*
|
||||
* Get the name of the peer connected to 'socket'.
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* 'socket' is a valid TCP socket.
|
||||
*
|
||||
* 'addressp' points to '*lengthp' bytes.
|
||||
*
|
||||
* Returns:
|
||||
*
|
||||
* Success
|
||||
* Address buffer too small
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
isc_socket_getsockname(isc_socket_t socket, struct isc_sockaddr *addressp,
|
||||
int *lengthp);
|
||||
/*
|
||||
* Get the name of 'socket'.
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* 'socket' is a valid socket.
|
||||
*
|
||||
* 'addressp' points to '*lengthp' bytes.
|
||||
*
|
||||
* Returns:
|
||||
*
|
||||
* Success
|
||||
* Address buffer too small
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
isc_socket_recv(isc_socket_t socket, isc_region_t region,
|
||||
isc_boolean_t partial,
|
||||
isc_task_t task, isc_taskaction_t action, void *arg);
|
||||
/*
|
||||
* Receive from 'socket', storing the results in region.
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* Let 'length' refer to the length of 'region'.
|
||||
*
|
||||
* If 'partial' is true, then at most 'length' bytes will be read.
|
||||
* Otherwise the read will not complete until exactly 'length' bytes
|
||||
* have been read.
|
||||
*
|
||||
* The read will complete when the desired number of bytes have been
|
||||
* read, if end-of-input occurs, or if an error occurs. A read done
|
||||
* event with the given 'action' and 'arg' will be posted to the
|
||||
* event queue of 'task'.
|
||||
*
|
||||
* The caller may neither read from nor write to 'region' until it
|
||||
* has received the read completion event.
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* 'socket' is a valid socket
|
||||
*
|
||||
* 'region' is a valid region
|
||||
*
|
||||
* 'task' is a valid task
|
||||
*
|
||||
* action != NULL and is a valid action
|
||||
*
|
||||
* Returns:
|
||||
*
|
||||
* Success
|
||||
* No memory
|
||||
* Unexpected error
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
isc_socket_send(isc_socket_t socket, isc_region_t region,
|
||||
isc_task_t task, isc_taskaction_t action, void *arg);
|
||||
/*
|
||||
* Send the contents of 'region' to the socket's peer.
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* Shutting down the requestor's task *may* result in any
|
||||
* still pending writes being dropped.
|
||||
*
|
||||
* If 'action' is NULL, then no completion event will be posted.
|
||||
*
|
||||
* The caller may neither read from nor write to 'region' until it
|
||||
* has received the write completion event, or all references to the
|
||||
* socket have been detached.
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* 'socket' is a valid socket
|
||||
*
|
||||
* 'region' is a valid region
|
||||
*
|
||||
* 'task' is a valid task
|
||||
*
|
||||
* action == NULL or is a valid action
|
||||
*
|
||||
* Returns:
|
||||
*
|
||||
* Success
|
||||
* No memory
|
||||
* Unexpected error
|
||||
*/
|
||||
|
||||
/* XXX this is some of how to do a read
|
||||
* generate new net_request
|
||||
* generate new read-result net_event
|
||||
* attach to requestor
|
||||
* lock socket
|
||||
* queue request
|
||||
* unlock socket
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
isc_socketmgr_create(isc_memctx_t mctx, isc_socketmgr_t *managerp);
|
||||
/*
|
||||
* Create a socket manager.
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* All memory will be allocated in memory context 'mctx'.
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* 'mctx' is a valid memory context.
|
||||
*
|
||||
* 'managerp' points to a NULL isc_socketmgr_t.
|
||||
*
|
||||
* Ensures:
|
||||
*
|
||||
* '*managerp' is a valid isc_socketmgr_t.
|
||||
*
|
||||
* Returns:
|
||||
*
|
||||
* Success
|
||||
* No memory
|
||||
* Unexpected error
|
||||
*/
|
||||
|
||||
void
|
||||
isc_socketmgr_destroy(isc_socketmgr_t *);
|
||||
/*
|
||||
* Destroy a socket manager.
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* This routine blocks until there are no sockets left in the manager,
|
||||
* so if the caller holds any socket references using the manager, it
|
||||
* must detach them before calling isc_socketmgr_destroy() or it will
|
||||
* block forever.
|
||||
*
|
||||
* Requires:
|
||||
*
|
||||
* '*managerp' is a valid isc_socketmgr_t.
|
||||
*
|
||||
* Ensures:
|
||||
*
|
||||
* *managerp == NULL
|
||||
*
|
||||
* All resources used by the manager have been freed.
|
||||
*/
|
||||
|
||||
#endif /* ISC_SOCKET_H */
|
||||
524
lib/isc/unix/socket.c
Normal file
524
lib/isc/unix/socket.c
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
|
||||
#include "attribute.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <isc/assertions.h>
|
||||
#include <isc/unexpect.h>
|
||||
#include <isc/thread.h>
|
||||
#include <isc/mutex.h>
|
||||
#include <isc/condition.h>
|
||||
#include <isc/socket.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#define WINAPI /* we're not windows */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* We use macros instead of calling the routines directly because
|
||||
* the capital letters make the locking stand out.
|
||||
*
|
||||
* We INSIST that they succeed since there's no way for us to continue
|
||||
* if they fail.
|
||||
*/
|
||||
|
||||
#define LOCK(lp) \
|
||||
INSIST(isc_mutex_lock((lp)) == ISC_R_SUCCESS);
|
||||
#define UNLOCK(lp) \
|
||||
INSIST(isc_mutex_unlock((lp)) == ISC_R_SUCCESS);
|
||||
#define BROADCAST(cvp) \
|
||||
INSIST(isc_condition_broadcast((cvp)) == ISC_R_SUCCESS);
|
||||
#define SIGNAL(cvp) \
|
||||
INSIST(isc_condition_signal((cvp)) == ISC_R_SUCCESS);
|
||||
#define WAIT(cvp, lp) \
|
||||
INSIST(isc_condition_wait((cvp), (lp)) == ISC_R_SUCCESS);
|
||||
#define WAITUNTIL(cvp, lp, tp) \
|
||||
isc_condition_waituntil((cvp), (lp), (tp))
|
||||
|
||||
/*
|
||||
* Debugging
|
||||
*/
|
||||
#if 1
|
||||
#define XTRACE(a) fprintf(stderr, a)
|
||||
#define XENTER(a) fprintf(stderr, "ENTER %s\n", (a))
|
||||
#define XEXIT(a) fprintf(stderr, "EXIT %s\n", (a))
|
||||
#else
|
||||
#define XTRACE(a)
|
||||
#define XENTER(a)
|
||||
#define XEXIT(a)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* A socket request. These are allocated XXX
|
||||
*/
|
||||
struct isc_socket_req {
|
||||
isc_task_t task;
|
||||
};
|
||||
|
||||
|
||||
#define SOCKET_MAGIC 0x494f696fU /* IOio */
|
||||
#define VALID_SOCKET(t) ((t) != NULL && \
|
||||
(t)->magic == SOCKET_MAGIC)
|
||||
struct isc_socket {
|
||||
/* Not locked. */
|
||||
unsigned int magic;
|
||||
isc_socketmgr_t manager;
|
||||
isc_mutex_t lock;
|
||||
/* Locked by socket lock. */
|
||||
unsigned int references;
|
||||
int fd;
|
||||
LIST(struct isc_socket_req) read_reqs;
|
||||
LIST(struct isc_socket_req) write_reqs;
|
||||
/* Locked by manager lock. */
|
||||
isc_sockettype_t type;
|
||||
LINK(struct isc_socket) link;
|
||||
};
|
||||
|
||||
#define SOCKET_MANAGER_MAGIC 0x494f6d67U /* IOmg */
|
||||
#define VALID_MANAGER(m) ((m) != NULL && \
|
||||
(m)->magic == SOCKET_MANAGER_MAGIC)
|
||||
|
||||
struct isc_socketmgr {
|
||||
/* Not locked. */
|
||||
unsigned int magic;
|
||||
isc_memctx_t mctx;
|
||||
isc_mutex_t lock;
|
||||
/* Locked by manager lock. */
|
||||
isc_boolean_t done;
|
||||
LIST(struct isc_socket) sockets;
|
||||
unsigned int nscheduled;
|
||||
isc_thread_t thread;
|
||||
int pipe_fds[2]; /* XXX lock needed? */
|
||||
fd_set read_fds; /* XXX This sucks... */
|
||||
fd_set write_fds;
|
||||
};
|
||||
|
||||
#define SELECT_POKE_SHUTDOWN (1)
|
||||
#define SELECT_POKE_REFRESH (2)
|
||||
|
||||
typedef unsigned long select_msg_t;
|
||||
|
||||
/*
|
||||
* poke the select loop when there is something for us to do.
|
||||
*/
|
||||
static void
|
||||
select_poke(isc_socketmgr_t mgr, select_msg_t msg)
|
||||
{
|
||||
write(mgr->pipe_fds[1], &msg, sizeof(select_msg_t));
|
||||
}
|
||||
|
||||
/*
|
||||
* read a message on the internal fd.
|
||||
*/
|
||||
static select_msg_t
|
||||
select_readmsg(isc_socketmgr_t mgr)
|
||||
{
|
||||
select_msg_t msg;
|
||||
|
||||
read(mgr->pipe_fds[0], &msg, sizeof(select_msg_t));
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set a socket up for reading or writing. This is a low level, internal
|
||||
* routine.
|
||||
*
|
||||
* Caller must ensure locking.
|
||||
*/
|
||||
static inline isc_result_t
|
||||
schedule(isc_socket_t sock)
|
||||
{
|
||||
isc_result_t result;
|
||||
isc_socketmgr_t manager;
|
||||
|
||||
/*
|
||||
* do stuff here to arange to track I/O on this socket.
|
||||
*/
|
||||
|
||||
return (ISC_R_SUCCESS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove either read, write, or both from a socket.
|
||||
*
|
||||
* Caller must ensure locking.
|
||||
*/
|
||||
static inline void
|
||||
deschedule(isc_socket_t sock)
|
||||
{
|
||||
isc_boolean_t need_wakeup = ISC_FALSE;
|
||||
isc_socketmgr_t manager;
|
||||
|
||||
manager = sock->manager;
|
||||
}
|
||||
|
||||
/*
|
||||
* Kill.
|
||||
*
|
||||
* Caller must ensure locking.
|
||||
*/
|
||||
static void
|
||||
destroy(isc_socket_t sock)
|
||||
{
|
||||
isc_socketmgr_t manager = sock->manager;
|
||||
|
||||
LOCK(&manager->lock);
|
||||
|
||||
/*
|
||||
* XXX
|
||||
* This is going to be tricky... Run through the list of all
|
||||
* tasks attached to this socket and purge events in their
|
||||
* queues.
|
||||
*/
|
||||
deschedule(sock);
|
||||
UNLINK(manager->sockets, sock, link);
|
||||
|
||||
UNLOCK(&manager->lock);
|
||||
|
||||
(void)isc_mutex_destroy(&sock->lock);
|
||||
sock->magic = 0;
|
||||
isc_mem_put(manager->mctx, sock, sizeof *sock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new 'type' socket managed by 'manager'. The sockets
|
||||
* parameters are specified by 'expires' and 'interval'. Events
|
||||
* will be posted to 'task' and when dispatched 'action' will be
|
||||
* called with 'arg' as the arg value. The new socket is returned
|
||||
* in 'socketp'.
|
||||
*/
|
||||
isc_result_t
|
||||
isc_socket_create(isc_socketmgr_t manager, isc_sockettype_t type,
|
||||
isc_socket_t *socketp)
|
||||
{
|
||||
isc_socket_t sock;
|
||||
isc_result_t result;
|
||||
|
||||
REQUIRE(VALID_MANAGER(manager));
|
||||
REQUIRE(socketp != NULL && *socketp == NULL);
|
||||
|
||||
XENTER("isc_socket_create");
|
||||
|
||||
sock = isc_mem_get(manager->mctx, sizeof *sock);
|
||||
if (sock == NULL)
|
||||
return (ISC_R_NOMEMORY);
|
||||
|
||||
sock->magic = SOCKET_MAGIC;
|
||||
sock->manager = manager;
|
||||
sock->references = 1;
|
||||
sock->type = type;
|
||||
|
||||
/*
|
||||
* set up list of readers and writers to be initially empty
|
||||
*/
|
||||
INIT_LIST(sock->read_reqs);
|
||||
INIT_LIST(sock->write_reqs);
|
||||
|
||||
/*
|
||||
* Create the associated socket XXX
|
||||
*/
|
||||
switch (type) {
|
||||
case isc_socket_udp:
|
||||
sock->fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
break;
|
||||
case isc_socket_tcp:
|
||||
sock->fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
break;
|
||||
}
|
||||
if (sock->fd < 0) {
|
||||
isc_mem_put(manager->mctx, sock, sizeof *sock);
|
||||
|
||||
switch (errno) {
|
||||
case EMFILE:
|
||||
case ENFILE:
|
||||
case ENOBUFS:
|
||||
return (ISC_R_NORESOURCES);
|
||||
break;
|
||||
default:
|
||||
UNEXPECTED_ERROR(__FILE__, __LINE__,
|
||||
"socket() failed: %s",
|
||||
strerror(errno));
|
||||
return (ISC_R_UNEXPECTED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* initialize the lock
|
||||
*/
|
||||
if (isc_mutex_init(&sock->lock) != ISC_R_SUCCESS) {
|
||||
isc_mem_put(manager->mctx, sock, sizeof *sock);
|
||||
close(sock->fd);
|
||||
UNEXPECTED_ERROR(__FILE__, __LINE__,
|
||||
"isc_mutex_init() failed");
|
||||
return (ISC_R_UNEXPECTED);
|
||||
}
|
||||
|
||||
LOCK(&manager->lock);
|
||||
|
||||
/*
|
||||
* Note we don't have to lock the socket like we normally would because
|
||||
* there are no external references to it yet.
|
||||
*/
|
||||
|
||||
APPEND(manager->sockets, sock, link);
|
||||
result = schedule(sock);
|
||||
|
||||
UNLOCK(&manager->lock);
|
||||
|
||||
if (result == ISC_R_SUCCESS)
|
||||
*socketp = sock;
|
||||
|
||||
XEXIT("isc_socket_create");
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Attach to a socket. Caller must explicitly detach when it is done.
|
||||
*/
|
||||
void
|
||||
isc_socket_attach(isc_socket_t sock, isc_socket_t *socketp)
|
||||
{
|
||||
REQUIRE(VALID_SOCKET(sock));
|
||||
REQUIRE(socketp != NULL && *socketp == NULL);
|
||||
|
||||
LOCK(&sock->lock);
|
||||
sock->references++;
|
||||
UNLOCK(&sock->lock);
|
||||
|
||||
*socketp = sock;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dereference a socket. If this is the last reference to it, clean things
|
||||
* up by destroying the socket.
|
||||
*/
|
||||
void
|
||||
isc_socket_detach(isc_socket_t *socketp)
|
||||
{
|
||||
isc_socket_t sock;
|
||||
isc_boolean_t free_socket = ISC_FALSE;
|
||||
|
||||
REQUIRE(socketp != NULL);
|
||||
sock = *socketp;
|
||||
REQUIRE(VALID_SOCKET(sock));
|
||||
|
||||
XENTER("isc_socket_detach");
|
||||
|
||||
LOCK(&sock->lock);
|
||||
REQUIRE(sock->references > 0);
|
||||
sock->references--;
|
||||
if (sock->references == 0)
|
||||
free_socket = ISC_TRUE;
|
||||
UNLOCK(&sock->lock);
|
||||
|
||||
if (free_socket)
|
||||
destroy(sock);
|
||||
|
||||
XEXIT("isc_socket_detach");
|
||||
|
||||
*socketp = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
dispatch(isc_socketmgr_t manager, isc_socket_t sock)
|
||||
{
|
||||
isc_boolean_t done = ISC_FALSE;
|
||||
isc_boolean_t post_event;
|
||||
isc_boolean_t need_schedule;
|
||||
isc_event_t event;
|
||||
isc_eventtype_t type = 0;
|
||||
isc_result_t result;
|
||||
|
||||
while (manager->nscheduled > 0 && !done) {
|
||||
/*
|
||||
* Do what here? XXX
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the task that will loop forever, always in a select or poll call.
|
||||
* When select returns something to do, track down what thread gets to do
|
||||
* this I/O and post the event to it.
|
||||
*/
|
||||
static isc_threadresult_t
|
||||
WINAPI
|
||||
run(void *uap)
|
||||
{
|
||||
isc_socketmgr_t manager = uap;
|
||||
isc_boolean_t done;
|
||||
int ctlfd;
|
||||
int cc;
|
||||
fd_set readfds;
|
||||
fd_set writefds;
|
||||
select_msg_t msg;
|
||||
|
||||
/*
|
||||
* Get the control fd here. This will never change.
|
||||
*/
|
||||
LOCK(&manager->lock);
|
||||
ctlfd = manager->pipe_fds[0];
|
||||
|
||||
done = ISC_FALSE;
|
||||
while (!done) {
|
||||
readfds = manager->read_fds;
|
||||
writefds = manager->write_fds;
|
||||
|
||||
UNLOCK(&manager->lock);
|
||||
|
||||
/*
|
||||
* call select/poll. This will block. XXX flesh out
|
||||
*/
|
||||
cc = select(FD_SETSIZE, &readfds, &writefds, NULL, NULL);
|
||||
if (cc < 0) {
|
||||
if (errno != EINTR)
|
||||
UNEXPECTED_ERROR(__FILE__, __LINE__,
|
||||
"select returned error (%s)",
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
LOCK(&manager->lock);
|
||||
|
||||
/*
|
||||
* Process reads on internal, control fd.
|
||||
*/
|
||||
if (FD_ISSET(ctlfd, &readfds)) {
|
||||
msg = select_readmsg(manager);
|
||||
|
||||
/*
|
||||
* handle shutdown message. No other type is handled
|
||||
* here, as REFRESH tells us to reread our fd_sets,
|
||||
* which we always do at the top of the loop.
|
||||
*/
|
||||
if (msg == SELECT_POKE_SHUTDOWN)
|
||||
done = ISC_TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process read/writes on other fds here
|
||||
*/
|
||||
}
|
||||
|
||||
UNLOCK(&manager->lock);
|
||||
return ((isc_threadresult_t)0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new socket manager.
|
||||
*/
|
||||
isc_result_t
|
||||
isc_socketmgr_create(isc_memctx_t mctx, isc_socketmgr_t *managerp)
|
||||
{
|
||||
isc_socketmgr_t manager;
|
||||
|
||||
REQUIRE(managerp != NULL && *managerp == NULL);
|
||||
|
||||
XENTER("isc_socketmgr_create");
|
||||
|
||||
manager = isc_mem_get(mctx, sizeof *manager);
|
||||
if (manager == NULL)
|
||||
return (ISC_R_NOMEMORY);
|
||||
|
||||
manager->magic = SOCKET_MANAGER_MAGIC;
|
||||
manager->mctx = mctx;
|
||||
manager->done = ISC_FALSE;
|
||||
INIT_LIST(manager->sockets);
|
||||
manager->nscheduled = 0;
|
||||
if (isc_mutex_init(&manager->lock) != ISC_R_SUCCESS) {
|
||||
isc_mem_put(mctx, manager, sizeof *manager);
|
||||
UNEXPECTED_ERROR(__FILE__, __LINE__,
|
||||
"isc_mutex_init() failed");
|
||||
return (ISC_R_UNEXPECTED);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create the special fds that will be used to wake up the
|
||||
* select/poll loop when something internal needs to be done.
|
||||
*/
|
||||
if (pipe(manager->pipe_fds) != 0) {
|
||||
(void)isc_mutex_destroy(&manager->lock);
|
||||
isc_mem_put(mctx, manager, sizeof *manager);
|
||||
UNEXPECTED_ERROR(__FILE__, __LINE__,
|
||||
"pipe() failed: %s",
|
||||
strerror(errno)); /* XXX */
|
||||
|
||||
return (ISC_R_UNEXPECTED);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up initial state for the select loop
|
||||
*/
|
||||
FD_ZERO(&manager->read_fds);
|
||||
FD_ZERO(&manager->write_fds);
|
||||
FD_SET(manager->pipe_fds[0], &manager->read_fds);
|
||||
|
||||
/*
|
||||
* Start up the select/poll thread.
|
||||
*/
|
||||
if (isc_thread_create(run, manager, &manager->thread) !=
|
||||
ISC_R_SUCCESS) {
|
||||
(void)isc_mutex_destroy(&manager->lock);
|
||||
isc_mem_put(mctx, manager, sizeof *manager);
|
||||
UNEXPECTED_ERROR(__FILE__, __LINE__,
|
||||
"isc_thread_create() failed");
|
||||
return (ISC_R_UNEXPECTED);
|
||||
}
|
||||
|
||||
*managerp = manager;
|
||||
|
||||
XEXIT("isc_socketmgr_create (normal)");
|
||||
return (ISC_R_SUCCESS);
|
||||
}
|
||||
|
||||
void
|
||||
isc_socketmgr_destroy(isc_socketmgr_t *managerp)
|
||||
{
|
||||
isc_socketmgr_t manager;
|
||||
|
||||
/*
|
||||
* Destroy a socket manager.
|
||||
*/
|
||||
|
||||
REQUIRE(managerp != NULL);
|
||||
manager = *managerp;
|
||||
REQUIRE(VALID_MANAGER(manager));
|
||||
|
||||
LOCK(&manager->lock);
|
||||
REQUIRE(EMPTY(manager->sockets));
|
||||
manager->done = ISC_TRUE;
|
||||
UNLOCK(&manager->lock);
|
||||
|
||||
/*
|
||||
* Here, poke our select/poll thread. Do this by closing the write
|
||||
* half of the pipe, which will send EOF to the read half.
|
||||
*/
|
||||
select_poke(manager, SELECT_POKE_SHUTDOWN);
|
||||
|
||||
/*
|
||||
* Wait for thread to exit.
|
||||
*/
|
||||
if (isc_thread_join(manager->thread, NULL) != ISC_R_SUCCESS)
|
||||
UNEXPECTED_ERROR(__FILE__, __LINE__,
|
||||
"isc_thread_join() failed");
|
||||
|
||||
/*
|
||||
* Clean up.
|
||||
*/
|
||||
close(manager->pipe_fds[0]);
|
||||
close(manager->pipe_fds[1]);
|
||||
(void)isc_mutex_destroy(&manager->lock);
|
||||
manager->magic = 0;
|
||||
isc_mem_put(manager->mctx, manager, sizeof *manager);
|
||||
|
||||
*managerp = NULL;
|
||||
}
|
||||
Loading…
Reference in a new issue