c-libp2p/net/socket.c

320 lines
7.9 KiB
C

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include "libp2p/utils/logger.h"
#include "libp2p/net/p2pnet.h"
/**
* associate an IP address with an port to a socket.
* @param s the socket file descriptor
* @param ip an array of four bytes IP address in binary format
* @returns 0 on sucess or -1 on error setting errno apropriated.
**/
int socket_bind4(int s, uint32_t ip, uint16_t port)
{
struct sockaddr_in sa;
bzero(&sa, sizeof sa);
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = ip;
return bind(s, (struct sockaddr *) &sa, sizeof sa);
}
/**
* Same as socket_bind4(), but set SO_REUSEADDR before
* @param s the socket file descriptor
* @param ip the ip address to use
* @param port the port to use
* @returns something...
*/
int socket_bind4_reuse(int s, uint32_t ip, uint16_t port)
{
int opt = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
return socket_bind4(s, ip, port);
}
int socket_read_select4(int socket_fd, int num_seconds) {
fd_set rfds;
struct timeval tv;
FD_ZERO(&rfds);
FD_SET(socket_fd, &rfds);
tv.tv_sec = num_seconds;
tv.tv_usec = 0;
return select(socket_fd +1, &rfds, NULL, NULL, &tv);
}
/* Accept a connection in a socket and return ip and port of
* remote connection at pointers passed as parameters.
*/
int socket_accept4(int s, uint32_t *ip, uint16_t *port)
{
struct sockaddr_in sa;
socklen_t dummy = sizeof sa;
int fd;
fd = accept(s, (struct sockaddr *) &sa, &dummy);
if (fd == -1) return -1;
*ip = sa.sin_addr.s_addr;
*port = ntohs(sa.sin_port);
return fd;
}
/**
* retrieve local ip and port information from a socket.
* @param s the file descriptor
* @param ip the IP address
* @param port the port
* @returns 0 on success, -1 on error
*/
int socket_local4(int s, uint32_t *ip, uint16_t *port)
{
struct sockaddr_in sa;
socklen_t dummy = sizeof sa;
if (getsockname(s, (struct sockaddr *) &sa, &dummy) == -1)
return -1;
*ip = sa.sin_addr.s_addr;
*port = ntohs(sa.sin_port);
return 0;
}
/***
* start a client connection.
* @param s the socket number
* @param ip the ip address
* @param port the port number
* @return 0 on success, otherwise -1
*/
int socket_connect4(int s, uint32_t ip, uint16_t port) {
return socket_connect4_with_timeout(s, ip, port, 10);
}
/***
* start a client connection.
* @param s the socket number
* @param ip the ip address
* @param port the port number
* @param timeout_secs the number of seconds before timeout
* @return 0 on success, otherwise -1
*/
int socket_connect4_with_timeout(int s, uint32_t ip, uint16_t port, int timeout_secs)
{
struct sockaddr_in sa;
long args; // fctl args with O_NONBLOCK
long orig_args; // fctl args;
memset(&sa, 0, sizeof sa);
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = ip;
// set to non blocking
orig_args = fcntl(s, F_GETFL, NULL);
if (orig_args < 0) {
// unable to get flags
libp2p_logger_error("socket", "Unable to get socket flags on connect.\n");
return -1;
}
args = orig_args;
args |= O_NONBLOCK;
if (args != orig_args) {
libp2p_logger_debug("socket", "Setting socket to non-blocking on connect.\n");
if (fcntl(s, F_SETFL, args) < 0) {
// unable to set flags
return -1;
}
} else {
libp2p_logger_debug("socket", "Socket already non-blocking during connect.\n");
}
// connect
int retVal = connect(s, (struct sockaddr *) &sa, sizeof sa);
int tried_secs = 0;
while (retVal == -1 && errno == EINPROGRESS && tried_secs < timeout_secs) {
libp2p_logger_debug("socket", "Socket connect unsuccessful. Waiting to try again.\n");
// wait for timeout
sleep(1);
tried_secs++;
retVal = connect(s, (struct sockaddr *) &sa, sizeof sa);
if (retVal == -1 && errno == EALREADY) {
libp2p_logger_debug("socket", "Socket connect completed.\n");
retVal = 0;
break;
}
}
if (retVal == -1) {
libp2p_logger_debug("socket", "Socket connect failed with error %d.\n", errno);
}
if (args != orig_args) {
// set back to blocking
libp2p_logger_debug("socket", "Setting socket back to blocking.\n");
args = fcntl(s, F_GETFL, NULL);
args &= (~O_NONBLOCK);
fcntl(s, F_SETFL, args);
}
return retVal;
}
/**
* bind and listen to a socket.
* @param s socket file descriptor
* @param localip the ip address
* @param localport the port
* @returns the socket file descriptor
*/
int socket_listen(int s, uint32_t *localip, uint16_t *localport)
{
if (socket_bind4_reuse(s, *localip, *localport) == -1) {
close(s);
return -1;
}
if (socket_local4(s, localip, localport) == -1) {
close(s);
return -1;
}
if (listen(s, 1) == -1) {
close(s);
return -1;
}
return s;
}
/***
* Reads data from a socket, used instead of recv so if a protocol needs
* to use something else before or after it can be done here instead of
* outside the lib.
*
* @param s the socket
* @param buf what to send
* @param len the length of buf
* @param flags network flags
* @param num_secs the number of seconds before a timeout
* @returns number of bytes, 0, or negative number on error (i.e. EAGAIN or EWOULDBLOCK)
*/
ssize_t socket_read(int s, char *buf, size_t len, int flags, int num_secs)
{
struct timeval tv;
tv.tv_sec = num_secs;
tv.tv_usec = 0;
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(struct timeval));
return recv(s, buf, len, flags);
}
/* Same reason as socket_read, but to send data instead of receive.
*/
ssize_t socket_write(int s, const char *buf, size_t len, int flags)
{
return send(s, buf, len, flags);
}
int socket_open4() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
return sockfd;
}
/**
* Used to send the size of the next transmission for "framed" transmissions. NOTE: This will send in big endian format
* @param s the socket descriptor
* @param size the size to send
* @param flags socket flags
* @returns number of bytes sent
*/
ssize_t socket_write_size(int s, unsigned long size, int flags) {
// determine if we're big or little endian
int big_endian = 1;
if (*(char*)&big_endian == 1) {
big_endian = 0;
}
// convert to int32_t
int32_t conv = htonl(size);
// swap bytes if this machine is little endian
if (!big_endian) {
uint32_t b0, b1, b2, b3;
b0 = (conv & 0x000000ff) << 24u;
b1 = (conv & 0x0000ff00) << 8u;
b2 = (conv & 0x00ff0000) >> 8u;
b3 = (conv & 0xff000000) >> 24u;
conv = b0 | b1 | b2 | b3;
}
// send to socket
char* data = (char*)&conv;
int left = sizeof(conv);
ssize_t rc;
int retries_left = 100;
do {
rc = send(s, data, left, flags);
if (rc < 0) {
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
retries_left--;
if (retries_left <= 0)
break;
} else {
return -1;
}
} else {
data += rc;
left -= rc;
}
} while (left > 0);
return sizeof(conv) - left;
//return send(s, size, 4, flags);
}
/**
* convert a hostname into an ip address
* @param hostname the name of the host. i.e. www.jmjatlanta.com
* @returns the ip address as an uint32_t
*/
uint32_t hostname_to_ip(const char* hostname)
{
struct sockaddr_in sa;
int result = inet_pton(AF_INET, hostname, &(sa.sin_addr));
if (result != 0) {
// an ip address was passed in instead of a hostname
return sa.sin_addr.s_addr;
} else {
// it is probably an actual host name and not just an ip address
struct hostent *he;
struct in_addr **addr_list;
if ( (he = gethostbyname( hostname ) ) == NULL)
{
// get the host info
herror("gethostbyname");
return 1;
}
addr_list = (struct in_addr **) he->h_addr_list;
if ((*addr_list) == NULL)
return 0;
return addr_list[0]->s_addr;
}
}