/* IRCfs - IRC FileServ for *nix. * Copyright (C) 2002 Nick 'Zaf' Clifford * For licensing details, refer to the LICENSE file in the source * code directory. */ #include "select.h" #include "socket.h" #include "module.h" #include #include #include #include #include #include #include "circbuf.h" enum socket_state { socket_state_disconnected, socket_state_bound, socket_state_listening, socket_state_connecting, socket_state_connected, socket_state_disconnecting, socket_state_error, socket_state_destroying }; struct socket_event { socket_callback_t func; void *appdata; int calling; }; struct socket { POOL pool; int fd; struct circbuf *read_buffer; struct circbuf *write_buffer; unsigned short local_port, remote_port; struct in_addr local_ip, remote_ip; int err; enum socket_state state; struct socket_event events[socket_num_events]; }; MODULE_NAME("socket"); MODULE_INIT(socket_init); MODULE_DESTROY(socket_unload); MODULE_DEPENDS("select"); MODULE_REGISTER(socket); static void socket_select_callback(int fd, enum select_mode, struct socket *s); static int socket_connected(struct socket *s); static int socket_raise_event(struct socket *s, enum socket_event_type event); static int socket_write_buffered(struct socket *s, const char *buffer, size_t len); static char tempbuf[1024]; static POOL pool; static DEBUG socket_debug; int socket_init() { socket_debug = debug_register("socket"); //debug_off(socket_debug); pool = pool_new(NULL); return 0; } int socket_unload() { return -1; } int socket_is_valid(struct socket *s) { if (s->state == socket_state_error) return -1; return 0; } void socket_destroy(struct socket *s) { enum socket_state old; ASSERT(s); info(socket_debug,"Socket(%d)->destroy()",s->fd); old = s->state; s->state = socket_state_destroying; if (old != socket_state_destroying) { socket_raise_event(s, socket_event_close); } unregister_wait_item(s->fd, select_mode_any); if (s->fd != -1) close(s->fd); s->fd = -1; pool_destroy(s->pool); return; } struct socket *socket_create(enum socket_type type) { POOL p; struct socket *s; struct protoent *pe; int protonum=0; int itrue, ilen,r; itrue = 1; ilen = sizeof(int); debug(socket_debug,"Creating socket"); pe = getprotobyname("IP"); if (pe == NULL) { error(socket_debug,"Failed to call getprotoent('IP'). Using 0 instead"); } else { protonum=pe->p_proto; } errno = 0; /* getprotobyname sets errno */ p = pool_new(pool); s = XMALLOC(p, struct socket); s->pool = p; s->fd = socket(PF_INET, SOCK_STREAM, protonum); if (s->fd == -1) { error(socket_debug,"Failed to allocate socket."); pool_destroy(s->pool); return NULL; } r = fcntl(s->fd, F_SETFL, O_NONBLOCK); if (r == -1) { error(socket_debug,"Failed to set socket non blocking"); pool_destroy(s->pool); return NULL; } ilen = sizeof(int); r = setsockopt(s->fd, SOL_SOCKET, SO_KEEPALIVE, &itrue, ilen); if (r == -1) { error(socket_debug,"Failed to set socket for keep alive"); pool_destroy(s->pool); return NULL; } itrue = 1; r = setsockopt(s->fd, SOL_SOCKET, SO_REUSEADDR, &itrue, ilen); if (r == -1) { error(socket_debug,"Failed to set socket for reuse"); pool_destroy(s->pool); return NULL; } s->state = socket_state_disconnected; register_wait_item(s->fd, select_mode_error, (select_wait_func) socket_select_callback, s); debug(socket_debug,"Have created new socket(%d)",s->fd); return s; } int socket_set_callback(struct socket *s, enum socket_event_type type, socket_callback_t func, void *appdata) { ASSERT(s); s->events[type].func = func; s->events[type].appdata = appdata; if (type == socket_event_read && s->state != socket_state_listening) { if (func == NULL) { unregister_wait_item(s->fd,select_mode_read); } else { register_wait_item(s->fd, select_mode_read, (select_wait_func)socket_select_callback, s); } } else if (type == socket_event_write && s->state != socket_state_connecting) { if (func == NULL) { unregister_wait_item(s->fd,select_mode_write); } else { register_wait_item(s->fd, select_mode_write, (select_wait_func)socket_select_callback, s); } } return 0; } socket_callback_t socket_get_callback(struct socket *s, enum socket_event_type type) { ASSERT(s); return s->events[type].func; } int socket_set_local_port(struct socket *s, unsigned short port) { ASSERT(s); if (s->state != socket_state_disconnected) { s->err = socket_err_readonly; return -1; } s->local_port = port; return 0; }; unsigned short socket_get_local_port(struct socket *s) { ASSERT(s); return (s->local_port); } int socket_set_local_addr(struct socket *s, const char *addr) { struct in_addr ip; ASSERT(s); if (s->state != socket_state_disconnected) { s->err = socket_err_readonly; return -1; } if (inet_aton(addr, &ip) == -1) { s->err = socket_err_invalid; return -1; } memcpy(&s->local_ip,&ip,sizeof(struct in_addr)); return 0; } int socket_get_local_addr(struct socket *s, char *buff, size_t bufflen) { char *cp; ASSERT(s); cp = inet_ntoa(s->local_ip); strncpy(buff,cp,bufflen); return 0; } int socket_set_remote_port(struct socket *s, unsigned short port) { ASSERT(s); if (s->state != socket_state_disconnected) { s->err = socket_err_readonly; return -1; } s->remote_port = port; return 0; } unsigned short socket_get_remote_port(struct socket *s) { ASSERT(s); return s->remote_port; } int socket_set_remote_addr(struct socket *s, const char *addr) { struct in_addr ip; ASSERT(s); if (s->state != socket_state_disconnected) { s->err = socket_err_readonly; return -1; } if (inet_aton(addr, &ip) == -1) { s->err = socket_err_invalid; return -1; } memcpy(&s->remote_ip,&ip,sizeof(struct in_addr)); return 0; } int socket_get_remote_addr(struct socket *s, char *buff, size_t bufflen) { char *cp; ASSERT(s); cp = inet_ntoa(s->remote_ip); strncpy(buff,cp,bufflen); return 0; } /* Binds the socket to the local port and address, and/or the * remote port & address. * If the port and/or address is invalid/inuse, then this is when * the error occurs */ int socket_bind(struct socket *s) { int r; struct sockaddr_in sin; socklen_t len; ASSERT(s); sin.sin_family = AF_INET; sin.sin_port = htons(s->local_port); memcpy(&sin.sin_addr,&s->local_ip,sizeof(struct in_addr)); r = bind(s->fd, (struct sockaddr *) &sin, sizeof(struct sockaddr_in)); if (r == -1) { switch(errno) { case EACCES: s->err = socket_err_denied; break; default: s->err = socket_err_unknown; break; } error(socket_debug,"bind() failed."); return -1; } if (getsockname(s->fd, (struct sockaddr *) &sin, &len) == -1) { error(socket_debug,"Can't get peer addr"); s->err = socket_err_unknown; s->state = socket_state_error; socket_raise_event(s, socket_event_error); return -1; } s->local_port = ntohs(sin.sin_port); s->local_ip.s_addr = sin.sin_addr.s_addr; s->state = socket_state_bound; return 0; } int socket_set_buffer_size(struct socket *s, size_t bytes) { ASSERT(s); if (s->read_buffer == NULL) { s->read_buffer = circbuf_create(s->pool,bytes); if (s->read_buffer == NULL) return -1; } else { if (circbuf_resize(s->read_buffer,bytes) == -1) return -1; } if (s->write_buffer == NULL) { s->write_buffer = circbuf_create(s->pool,bytes); if (s->write_buffer == NULL) { circbuf_destroy(s->read_buffer); s->read_buffer = NULL; return -1; } } else { if (circbuf_resize(s->write_buffer,bytes) == -1) { circbuf_destroy(s->read_buffer); s->read_buffer = NULL; return -1; } } return 0; } size_t socket_get_buffer_size(struct socket *s) { ASSERT(s); if (s->read_buffer == NULL || s->write_buffer == NULL) return 0; return circbuf_size(s->read_buffer); } int socket_connect_to(struct socket *s, const char *addr, unsigned short port) { ASSERT(s); if (s->state != socket_state_disconnected && s->state != socket_state_bound) { s->err = socket_err_readonly; return -1; } socket_set_remote_port(s,port); socket_set_remote_addr(s,addr); return socket_connect(s); } int socket_connect(struct socket *s) { int r; struct sockaddr_in sin; ASSERT(s); if (s->state != socket_state_disconnected && s->state != socket_state_bound) { s->err = socket_err_readonly; return -1; } sin.sin_family = AF_INET; sin.sin_port = htons(s->remote_port); memcpy(&sin.sin_addr,&s->remote_ip,sizeof(struct in_addr)); r = connect(s->fd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)); if (r == -1 && errno == EINPROGRESS) { info(socket_debug,"Connecting to %s:%d, in progress", inet_ntoa(s->remote_ip), s->remote_port); errno = 0; s->state = socket_state_connecting; register_wait_item(s->fd, select_mode_write, (select_wait_func) socket_select_callback, s); return 0; } else if (r == -1) { switch(errno) { case ECONNREFUSED: s->err = socket_err_refused; break; case ETIMEDOUT: s->err = socket_err_timeout; break; case ENETUNREACH: s->err = socket_err_unreachable; break; case EADDRINUSE: s->err = socket_err_inuse; break; default: s->err = socket_err_unknown; break; } error(socket_debug,"Connect to %s:%d, failed. s->err = %d", inet_ntoa(s->remote_ip), s->remote_port, s->err); return -1; } info(socket_debug,"Connecting to %s:%d, immediate", inet_ntoa(s->remote_ip), s->remote_port); /* We connected immediatly */ return socket_connected(s); } int socket_raise_event(struct socket *s, enum socket_event_type event) { ASSERT(s); debug(socket_debug,"Socket(%d) raised event %d:%s", s->fd,event,socket_event_str(event)); if (s && s->events[event].func && s->events[event].calling == 0) { s->events[event].calling = 1; debug(socket_debug,"Socket(%d) calling for event", s->fd); s->events[event].func(s,event,s->events[event].appdata); s->events[event].calling = 0; return 0; } return -1; } /* Called when we have connected, either by select or connect */ int socket_connected(struct socket *s) { struct sockaddr_in sin; int i,ilen; socklen_t len; ASSERT(s); info(socket_debug,"Socket Connected"); ilen = sizeof(int); /* Are we connected? */ i = 0; if (getsockopt(s->fd, SOL_SOCKET, SO_ERROR, &i, &ilen) == -1) { error(socket_debug,"Can't get SO_ERROR"); s->err = socket_err_unknown; s->state = socket_state_error; socket_raise_event(s, socket_event_error); return -1; } if (i != 0) { error(socket_debug,"Socket(%d) connect failed. err=%s", s->fd,strerror(i)); switch(i) { case ECONNREFUSED: s->err = socket_err_refused; break; case ETIMEDOUT: s->err = socket_err_timeout; break; case ENETUNREACH: s->err = socket_err_unreachable; break; case EADDRINUSE: s->err = socket_err_inuse; break; default: s->err = socket_err_unknown; break; } s->state = socket_state_error; socket_raise_event(s, socket_event_error); return -1; } len = sizeof(struct sockaddr_in); if (getpeername(s->fd, (struct sockaddr *) &sin, &len) == -1) { error(socket_debug,"Can't get peer addr"); s->err = socket_err_unknown; s->state = socket_state_error; socket_raise_event(s, socket_event_error); return -1; } s->remote_port = ntohs(sin.sin_port); s->remote_ip.s_addr = sin.sin_addr.s_addr; s->state = socket_state_connected; if (s->events[socket_event_write].func == NULL) { unregister_wait_item(s->fd, select_mode_write); } socket_raise_event(s, socket_event_connect); return 0; } void socket_select_callback(int fd, enum select_mode mode, struct socket *s) { int i,ilen; ASSERT(s); ASSERT(s->fd == fd); debug(socket_debug,"Got event for socket(%d), %s", fd,mode == select_mode_read ? "read" : mode == select_mode_write ? "write" : mode == select_mode_error ? "error" : "" ); if (mode == select_mode_read) { switch(s->state) { case socket_state_listening: socket_raise_event(s,socket_event_connect); break; default: if (socket_raise_event(s,socket_event_read) == -1) { /* Why are we waiting then? */ debug(socket_debug,"Socket(%d) " "Got read event, but " "don't have use for it", s->fd); unregister_wait_item(s->fd, socket_event_read); } } } else if (mode == select_mode_write) { switch(s->state) { case socket_state_connecting: socket_connected(s); break; default: if (socket_raise_event(s,socket_event_write) == -1) { debug(socket_debug,"Socket(%d) " "Got write event, but don't" "have use for it", s->fd); unregister_wait_item(s->fd,socket_event_write); } break; } } else if (mode == select_mode_error) { ilen = sizeof(int); i = 0; if (getsockopt(s->fd, SOL_SOCKET, SO_ERROR, &i, &ilen) == -1) { error(socket_debug,"Can't get SO_ERROR"); s->err = socket_err_unknown; s->state = socket_state_error; socket_raise_event(s, socket_event_error); return; } error(socket_debug,"Socket(%d) error. err=%s", s->fd,strerror(i)); switch(i) { case ECONNREFUSED: s->err = socket_err_refused; break; case ETIMEDOUT: s->err = socket_err_timeout; break; case ENETUNREACH: s->err = socket_err_unreachable; break; case EADDRINUSE: s->err = socket_err_inuse; break; default: s->err = socket_err_unknown; break; } s->state = socket_state_error; socket_raise_event(s, socket_event_error); return; } } /* Listen on specified port */ int socket_listen(struct socket *s) { ASSERT(s); if (s->state != socket_state_bound) { s->err = socket_err_noaddr; return -1; } if (listen(s->fd,5) == -1) { if (errno == EADDRINUSE) { s->err = socket_err_inuse; } else { s->err = socket_err_unknown; } return -1; } s->state = socket_state_listening; register_wait_item(s->fd, select_mode_read, (select_wait_func) socket_select_callback, s); return 0; } struct socket *socket_accept(struct socket *s) { int fd; struct sockaddr_in sin; struct socket *ns; socklen_t len; int itrue,ilen,r; POOL p; ASSERT(s); if (s->state != socket_state_listening) { s->err = socket_err_invalid; return NULL; } len = sizeof(struct sockaddr_in); fd = accept(s->fd, (struct sockaddr *) &sin, &len); if (fd == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { s->err = socket_err_pending; errno = 0; return NULL; } else if (fd == -1) { error(socket_debug,"accept returned -1"); return NULL; } debug(socket_debug,"Have new socket(%d)",fd); p = pool_new(pool); ns = XMALLOC(p, struct socket); ns->pool = p; ns->fd = fd; r = fcntl(ns->fd, F_SETFL, O_NONBLOCK); if (r == -1) { error(socket_debug,"Failed to set socket non blocking"); pool_destroy(ns->pool); return NULL; } itrue = 1; ilen = sizeof(int); r = setsockopt(ns->fd, SOL_SOCKET, SO_KEEPALIVE, &itrue, ilen); if (r == -1) { error(socket_debug,"Failed to set socket for keep alive"); pool_destroy(ns->pool); return NULL; } ns->remote_port = ntohs(sin.sin_port); ns->remote_ip.s_addr = sin.sin_addr.s_addr; if (getsockname(ns->fd, (struct sockaddr *) &sin, &len) == -1) { error(socket_debug,"Can't get local addr"); pool_destroy(ns->pool); return NULL; } ns->local_port = ntohs(sin.sin_port); ns->local_ip.s_addr = sin.sin_addr.s_addr; register_wait_item(s->fd, select_mode_error, (select_wait_func) socket_select_callback, s); ns->state = socket_state_connected; return ns; } int socket_printf(struct socket *s, const char *format, ...) { int r; va_list va; va_start(va,format); r = socket_vprintf(s,format, va); va_end(va); return r; } int socket_vprintf(struct socket *s, const char *format, va_list va) { static char writebuf[1024]; vsnprintf(writebuf,1023,format,va); writebuf[1023]=0; return socket_write(s,writebuf, strlen(writebuf)); } int socket_write(struct socket *s, const char *buffer, size_t len) { int r; ASSERT(s); if (s->write_buffer) return socket_write_buffered(s,buffer,len); if (s->state != socket_state_connected) { s->err = socket_err_notconnected; return -1; } if (len == 0) { return 0; } r = send(s->fd, buffer,len,MSG_NOSIGNAL); if (r == -1 && errno == EAGAIN) { errno = 0; return 0; } else if (r == -1) { error(socket_debug,"write to socket(%d) returned error %s", s->fd, strerror(errno)); s->err = socket_err_unknown; s->state = socket_state_error; socket_raise_event(s, socket_event_error); return -1; } else if (r == -1 && errno == EPIPE) { debug(socket_debug,"Socket(%d) Closed", s->fd); s->err = socket_err_closed; s->state = socket_state_disconnected; socket_raise_event(s, socket_event_close); return 0; } return r; } int socket_write_buffered(struct socket *s, const char *buffer, size_t len) { size_t l; int r; ASSERT(s); if (s->write_buffer == NULL) { s->err = socket_err_invalid; return -1; } /* Send curret buffer */ l = circbuf_bytes(s->write_buffer); while(l > 0) { l = circbuf_peek(s->write_buffer, tempbuf, sizeof(tempbuf) - 1 ); r = send(s->fd,tempbuf,l,MSG_NOSIGNAL); if (r == -1 && errno == EAGAIN) { errno = 0; if (circbuf_add(s->write_buffer, buffer,len) == -1) { s->err = socket_err_full; return -1; } return 0; } else if (r == -1) { error(socket_debug,"write to socket(%d) " "returned error %s", s->fd, strerror(errno)); s->err = socket_err_unknown; s->state = socket_state_error; socket_raise_event(s, socket_event_error); return -1; } else if (r == -1 && errno == EPIPE) { debug(socket_debug,"Socket(%d) Closed", s->fd); s->err = socket_err_closed; s->state = socket_state_disconnected; socket_raise_event(s, socket_event_close); return 0; } circbuf_pop(s->write_buffer, r); l = circbuf_bytes(s->write_buffer); } /* Send current data */ r = send(s->fd,buffer,len,MSG_NOSIGNAL); if (r == -1 && errno == EAGAIN) { errno = 0; if (circbuf_add(s->write_buffer, buffer,len) == -1) { s->err = socket_err_full; return -1; } return 0; } else if (r == -1) { error(socket_debug,"write to socket(%d) returned error %s", s->fd, strerror(errno)); s->err = socket_err_unknown; s->state = socket_state_error; socket_raise_event(s, socket_event_error); return -1; } else if (r == -1 && errno == EPIPE) { debug(socket_debug,"Socket(%d) Closed", s->fd); s->err = socket_err_closed; s->state = socket_state_disconnected; socket_raise_event(s, socket_event_close); return 0; } if (r != len && circbuf_add(s->write_buffer, buffer + r,len - r) == -1) { s->err = socket_err_full; return -1; } return r; } int socket_write_str(struct socket *s, const char *str) { size_t len; ASSERT(s); len = strlen(str); return socket_write(s,str,len); } int socket_read(struct socket *s, char *buffer, size_t len) { int r; ASSERT(s); if (s->state != socket_state_connected) { warn(socket_debug,"Socket(%d) tried to read while not " "connected", s->fd); s->err = socket_err_notconnected; return -1; } if (s->read_buffer && circbuf_bytes(s->read_buffer) > 0) { return circbuf_get(s->read_buffer,buffer,len); } r = read(s->fd, buffer,len); if (r == -1 && errno == EAGAIN) { errno = 0; return 0; } else if (r == -1) { error(socket_debug,"read of socket(%d) returned error %s", s->fd, strerror(errno)); s->err = socket_err_unknown; return -1; } else if (r == 0) { debug(socket_debug,"Socket(%d)->read, EOF", s->fd); s->err = socket_err_closed; s->state = socket_state_disconnected; socket_raise_event(s, socket_event_close); return 0; } return r; } int socket_readline(struct socket *s, char *buffer, size_t bufflen) { int r; size_t len; ASSERT(s); if (s->read_buffer == NULL) { error(socket_debug,"Socket(%d) readline called without " "setting up read buffer", s->fd); s->err = socket_err_invalid; return -1; } r = circbuf_find_ch(s->read_buffer,'\n'); if (r != -1 && r > bufflen-1) { /* Users buffer is too small */ warn(socket_debug,"Socket(%d) users buffer too small", s->fd); r = circbuf_get(s->read_buffer,buffer,bufflen-1); if (r != -1) buffer[r] = 0; debug(socket_debug,"Socket(%d) readline returns %d",s->fd,r); return r; } else if (r != -1) { /* Found it */ r = circbuf_get_till(s->read_buffer, buffer,bufflen-1,'\n'); if (r != -1) { buffer[r] = 0; debug(socket_debug,"Socket(%d) readline returns %d foundit1",s->fd,r); return r; } debug(socket_debug,"Socket(%d) found line, but get_till " "returned -1 blah", s->fd); } while(1) { len = circbuf_remaining(s->read_buffer); if (len < 10) { break; } if (len > sizeof(tempbuf) - 1) { len = sizeof(tempbuf) -1; } r = read(s->fd,tempbuf,len); if (r < 1) { break; } if (circbuf_add(s->read_buffer,tempbuf,r) == -1) { debug(socket_debug,"Socket(%d) circbuf_add failed for %d bytes",s->fd, r); break; } } /* Ok, so we've done some reading (maybe). What can we return? */ if (circbuf_remaining(s->read_buffer) == 0) { r = circbuf_get(s->read_buffer,buffer,bufflen-1); if (r != -1) buffer[r] = 0; errno = 0; debug(socket_debug,"Socket(%d) readline returns %d remain=0",s->fd,r); return r; } len = circbuf_find_ch(s->read_buffer,'\n'); if (len != -1 && len > bufflen) { /* Users buffer is too small */ r = circbuf_get(s->read_buffer,buffer,bufflen-1); if (r != -1) buffer[r] = 0; errno = 0; warn(socket_debug,"Socket(%d) users buffer too small", s->fd); debug(socket_debug,"Socket(%d) readline returns %d",s->fd,r); return r; } else if (len != -1) { /* Found it */ len = circbuf_get_till(s->read_buffer, buffer,bufflen-1,'\n'); if (len != -1) { buffer[len] = 0; errno = 0; debug(socket_debug,"Socket(%d) readline returns %d foundit2",s->fd,len); return len; } debug(socket_debug,"Socket(%d) found line, but get_till " "returned -1", s->fd); } if (r == 0) { /* EOF */ debug(socket_debug,"Socket(%d)->readline, EOF", s->fd); s->err = socket_err_closed; s->state = socket_state_disconnected; socket_raise_event(s, socket_event_close); debug(socket_debug,"Socket(%d) readline returns %d EOF",s->fd,0); return 0; } if (r == -1 && errno != EAGAIN) { debug(socket_debug,"Socket(%d)->readline returned error %s", s->fd, strerror(errno)); s->err = socket_err_unknown; debug(socket_debug,"Socket(%d) readline returns %d",s->fd,-1); return -1; } /* No data */ debug(socket_debug,"Socket(%d) readline returns %d no data",s->fd,0); errno = 0; return 0; } enum socket_err socket_get_error(struct socket *s) { ASSERT(s); return s->err; } const char *socket_event_str(enum socket_event_type type) { switch(type) { case socket_event_read: return "read"; case socket_event_write: return "write"; case socket_event_connect: return "connect"; case socket_event_error: return "error"; case socket_event_close: return "close"; default: return "unknown"; } }