/* sim_sock.c: OS-dependent socket routines | |
Copyright (c) 2001-2010, Robert M Supnik | |
Permission is hereby granted, free of charge, to any person obtaining a | |
copy of this software and associated documentation files (the "Software"), | |
to deal in the Software without restriction, including without limitation | |
the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
and/or sell copies of the Software, and to permit persons to whom the | |
Software is furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in | |
all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
Except as contained in this notice, the name of Robert M Supnik shall not be | |
used in advertising or otherwise to promote the sale, use or other dealings | |
in this Software without prior written authorization from Robert M Supnik. | |
15-Oct-12 MP Added definitions needed to detect possible tcp | |
connect failures | |
25-Sep-12 MP Reworked for RFC3493 interfaces supporting IPv6 and IPv4 | |
22-Jun-10 RMS Fixed types in sim_accept_conn (from Mark Pizzolato) | |
19-Nov-05 RMS Added conditional for OpenBSD (from Federico G. Schwindt) | |
16-Aug-05 RMS Fixed spurious SIGPIPE signal error in Unix | |
14-Apr-05 RMS Added WSAEINPROGRESS test (from Tim Riker) | |
09-Jan-04 RMS Fixed typing problem in Alpha Unix (found by Tim Chapman) | |
17-Apr-03 RMS Fixed non-implemented version of sim_close_sock | |
(found by Mark Pizzolato) | |
17-Dec-02 RMS Added sim_connect_socket, sim_create_socket | |
08-Oct-02 RMS Revised for .NET compatibility | |
22-Aug-02 RMS Changed calling sequence for sim_accept_conn | |
22-May-02 RMS Added OS2 EMX support from Holger Veit | |
06-Feb-02 RMS Added VMS support from Robert Alan Byer | |
16-Sep-01 RMS Added Macintosh support from Peter Schorn | |
02-Sep-01 RMS Fixed UNIX bugs found by Mirian Lennox and Tom Markson | |
*/ | |
#include "sim_sock.h" | |
#include <signal.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#if defined(AF_INET6) && defined(_WIN32) | |
#include <ws2tcpip.h> | |
#endif | |
#ifdef HAVE_DLOPEN | |
#include <dlfcn.h> | |
#endif | |
#ifndef WSAAPI | |
#define WSAAPI | |
#endif | |
#if defined(SHUT_RDWR) && !defined(SD_BOTH) | |
#define SD_BOTH SHUT_RDWR | |
#endif | |
#ifndef NI_MAXHOST | |
#define NI_MAXHOST 1025 | |
#endif | |
/* OS dependent routines | |
sim_master_sock create master socket | |
sim_connect_sock connect a socket to a remote destination | |
sim_connect_sock_ex connect a socket to a remote destination | |
sim_accept_conn accept connection | |
sim_read_sock read from socket | |
sim_write_sock write from socket | |
sim_close_sock close socket | |
sim_setnonblock set socket non-blocking | |
*/ | |
/* First, all the non-implemented versions */ | |
#if defined (__OS2__) && !defined (__EMX__) | |
void sim_init_sock (void) | |
{ | |
} | |
void sim_cleanup_sock (void) | |
{ | |
} | |
SOCKET sim_master_sock_ex (const char *hostport, int *parse_status, int opt_flags) | |
{ | |
return INVALID_SOCKET; | |
} | |
SOCKET sim_connect_sock_ex (const char *sourcehostport, const char *hostport, const char *default_host, const char *default_port, int opt_flags) | |
{ | |
return INVALID_SOCKET; | |
} | |
SOCKET sim_accept_conn (SOCKET master, char **connectaddr); | |
{ | |
return INVALID_SOCKET; | |
} | |
int sim_read_sock (SOCKET sock, char *buf, int nbytes) | |
{ | |
return -1; | |
} | |
int sim_write_sock (SOCKET sock, char *msg, int nbytes) | |
{ | |
return 0; | |
} | |
void sim_close_sock (SOCKET sock) | |
{ | |
return; | |
} | |
#else /* endif unimpl */ | |
/* UNIX, Win32, Macintosh, VMS, OS2 (Berkeley socket) routines */ | |
static struct sock_errors { | |
int value; | |
char *text; | |
} sock_errors[] = { | |
{WSAEWOULDBLOCK, "Operation would block"}, | |
{WSAENAMETOOLONG, "File name too long"}, | |
{WSAEINPROGRESS, "Operation now in progress "}, | |
{WSAETIMEDOUT, "Connection timed out"}, | |
{WSAEISCONN, "Transport endpoint is already connected"}, | |
{WSAECONNRESET, "Connection reset by peer"}, | |
{WSAECONNREFUSED, "Connection refused"}, | |
{WSAECONNABORTED, "Connection aborted"}, | |
{WSAEHOSTUNREACH, "No route to host"}, | |
{WSAEADDRINUSE, "Address already in use"}, | |
#if defined (WSAEAFNOSUPPORT) | |
{WSAEAFNOSUPPORT, "Address family not supported by protocol"}, | |
#endif | |
{WSAEACCES, "Permission denied"}, | |
{0, NULL} | |
}; | |
char *sim_get_err_sock (const char *emsg) | |
{ | |
int err = WSAGetLastError (); | |
int i; | |
static char err_buf[512]; | |
for (i=0; (sock_errors[i].text) && (sock_errors[i].value != err); i++) | |
; | |
if (sock_errors[i].value == err) | |
sprintf (err_buf, "Sockets: %s error %d - %s\n", emsg, err, sock_errors[i].text); | |
else | |
#if defined(_WIN32) | |
sprintf (err_buf, "Sockets: %s error %d\n", emsg, err); | |
#else | |
sprintf (err_buf, "Sockets: %s error %d - %s\n", emsg, err, strerror(err)); | |
#endif | |
return err_buf; | |
} | |
SOCKET sim_err_sock (SOCKET s, const char *emsg) | |
{ | |
sim_printf ("%s", sim_get_err_sock (emsg)); | |
if (s != INVALID_SOCKET) { | |
int err = WSAGetLastError (); | |
sim_close_sock (s); | |
WSASetLastError (err); /* Retain Original socket error value */ | |
} | |
return INVALID_SOCKET; | |
} | |
typedef void (WSAAPI *freeaddrinfo_func) (struct addrinfo *ai); | |
static freeaddrinfo_func p_freeaddrinfo; | |
typedef int (WSAAPI *getaddrinfo_func) (const char *hostname, | |
const char *service, | |
const struct addrinfo *hints, | |
struct addrinfo **res); | |
static getaddrinfo_func p_getaddrinfo; | |
#if defined(VMS) | |
typedef size_t socklen_t; | |
#if !defined(EAI_OVERFLOW) | |
#define EAI_OVERFLOW EAI_FAIL | |
#endif | |
#endif | |
#if defined(__hpux) | |
#if !defined(EAI_OVERFLOW) | |
#define EAI_OVERFLOW EAI_FAIL | |
#endif | |
#endif | |
typedef int (WSAAPI *getnameinfo_func) (const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags); | |
static getnameinfo_func p_getnameinfo; | |
static void WSAAPI s_freeaddrinfo (struct addrinfo *ai) | |
{ | |
struct addrinfo *a, *an; | |
for (a=ai; a != NULL; a=an) { | |
an = a->ai_next; | |
free (a->ai_canonname); | |
free (a->ai_addr); | |
free (a); | |
} | |
} | |
static int WSAAPI s_getaddrinfo (const char *hostname, | |
const char *service, | |
const struct addrinfo *hints, | |
struct addrinfo **res) | |
{ | |
struct hostent *he; | |
struct servent *se = NULL; | |
struct sockaddr_in *sin; | |
struct addrinfo *result = NULL; | |
struct addrinfo *ai, *lai = NULL; | |
struct addrinfo dhints; | |
struct in_addr ipaddr; | |
struct in_addr *fixed[2]; | |
struct in_addr **ips = NULL; | |
struct in_addr **ip; | |
const char *cname = NULL; | |
int port = 0; | |
// Validate parameters | |
if ((hostname == NULL) && (service == NULL)) | |
return EAI_NONAME; | |
if (hints) { | |
if ((hints->ai_family != PF_INET) && (hints->ai_family != PF_UNSPEC)) | |
return EAI_FAMILY; | |
switch (hints->ai_socktype) | |
{ | |
default: | |
return EAI_SOCKTYPE; | |
case SOCK_DGRAM: | |
case SOCK_STREAM: | |
case 0: | |
break; | |
} | |
} | |
else { | |
hints = &dhints; | |
memset(&dhints, 0, sizeof(dhints)); | |
dhints.ai_family = PF_UNSPEC; | |
} | |
if (service) { | |
char *c; | |
port = strtoul(service, &c, 10); | |
if ((port == 0) || (*c != '\0')) { | |
switch (hints->ai_socktype) | |
{ | |
case SOCK_DGRAM: | |
se = getservbyname(service, "udp"); | |
break; | |
case SOCK_STREAM: | |
case 0: | |
se = getservbyname(service, "tcp"); | |
break; | |
} | |
if (NULL == se) | |
return EAI_SERVICE; | |
port = se->s_port; | |
} | |
} | |
if (hostname) { | |
if ((0xffffffff != (ipaddr.s_addr = inet_addr(hostname))) || | |
(0 == strcmp("255.255.255.255", hostname))) { | |
fixed[0] = &ipaddr; | |
fixed[1] = NULL; | |
} | |
else { | |
if ((0xffffffff != (ipaddr.s_addr = inet_addr(hostname))) || | |
(0 == strcmp("255.255.255.255", hostname))) { | |
fixed[0] = &ipaddr; | |
fixed[1] = NULL; | |
if ((hints->ai_flags & AI_CANONNAME) && !(hints->ai_flags & AI_NUMERICHOST)) { | |
he = gethostbyaddr((char *)&ipaddr, 4, AF_INET); | |
if (NULL != he) | |
cname = he->h_name; | |
else | |
cname = hostname; | |
} | |
ips = fixed; | |
} | |
else { | |
if (hints->ai_flags & AI_NUMERICHOST) | |
return EAI_NONAME; | |
he = gethostbyname(hostname); | |
if (he) { | |
ips = (struct in_addr **)he->h_addr_list; | |
if (hints->ai_flags & AI_CANONNAME) | |
cname = he->h_name; | |
} | |
else { | |
switch (h_errno) | |
{ | |
case HOST_NOT_FOUND: | |
case NO_DATA: | |
return EAI_NONAME; | |
case TRY_AGAIN: | |
return EAI_AGAIN; | |
default: | |
return EAI_FAIL; | |
} | |
} | |
} | |
} | |
} | |
else { | |
if (hints->ai_flags & AI_PASSIVE) | |
ipaddr.s_addr = htonl(INADDR_ANY); | |
else | |
ipaddr.s_addr = htonl(INADDR_LOOPBACK); | |
fixed[0] = &ipaddr; | |
fixed[1] = NULL; | |
ips = fixed; | |
} | |
for (ip=ips; (ip != NULL) && (*ip != NULL); ++ip) { | |
ai = (struct addrinfo *)calloc(1, sizeof(*ai)); | |
if (NULL == ai) { | |
s_freeaddrinfo(result); | |
return EAI_MEMORY; | |
} | |
ai->ai_family = PF_INET; | |
ai->ai_socktype = hints->ai_socktype; | |
ai->ai_protocol = hints->ai_protocol; | |
ai->ai_addr = NULL; | |
ai->ai_addrlen = sizeof(struct sockaddr_in); | |
ai->ai_canonname = NULL; | |
ai->ai_next = NULL; | |
ai->ai_addr = (struct sockaddr *)calloc(1, sizeof(struct sockaddr_in)); | |
if (NULL == ai->ai_addr) { | |
free(ai); | |
s_freeaddrinfo(result); | |
return EAI_MEMORY; | |
} | |
sin = (struct sockaddr_in *)ai->ai_addr; | |
sin->sin_family = PF_INET; | |
sin->sin_port = (unsigned short)port; | |
memcpy(&sin->sin_addr, *ip, sizeof(sin->sin_addr)); | |
if (NULL == result) | |
result = ai; | |
else | |
lai->ai_next = ai; | |
lai = ai; | |
} | |
if (cname) { | |
result->ai_canonname = (char *)calloc(1, strlen(cname)+1); | |
if (NULL == result->ai_canonname) { | |
s_freeaddrinfo(result); | |
return EAI_MEMORY; | |
} | |
strcpy(result->ai_canonname, cname); | |
} | |
*res = result; | |
return 0; | |
} | |
#ifndef EAI_OVERFLOW | |
#define EAI_OVERFLOW WSAENAMETOOLONG | |
#endif | |
static int WSAAPI s_getnameinfo (const struct sockaddr *sa, socklen_t salen, | |
char *host, size_t hostlen, | |
char *serv, size_t servlen, | |
int flags) | |
{ | |
struct hostent *he; | |
struct servent *se = NULL; | |
struct sockaddr_in *sin = (struct sockaddr_in *)sa; | |
if (sin->sin_family != PF_INET) | |
return EAI_FAMILY; | |
if ((NULL == host) && (NULL == serv)) | |
return EAI_NONAME; | |
if ((serv) && (servlen > 0)) { | |
if (flags & NI_NUMERICSERV) | |
se = NULL; | |
else | |
if (flags & NI_DGRAM) | |
se = getservbyport(sin->sin_port, "udp"); | |
else | |
se = getservbyport(sin->sin_port, "tcp"); | |
if (se) { | |
if (servlen <= strlen(se->s_name)) | |
return EAI_OVERFLOW; | |
strcpy(serv, se->s_name); | |
} | |
else { | |
char buf[16]; | |
sprintf(buf, "%d", ntohs(sin->sin_port)); | |
if (servlen <= strlen(buf)) | |
return EAI_OVERFLOW; | |
strcpy(serv, buf); | |
} | |
} | |
if ((host) && (hostlen > 0)) { | |
if (flags & NI_NUMERICHOST) | |
he = NULL; | |
else | |
he = gethostbyaddr((char *)&sin->sin_addr, 4, AF_INET); | |
if (he) { | |
if (hostlen < strlen(he->h_name)+1) | |
return EAI_OVERFLOW; | |
strcpy(host, he->h_name); | |
} | |
else { | |
if (flags & NI_NAMEREQD) | |
return EAI_NONAME; | |
if (hostlen < strlen(inet_ntoa(sin->sin_addr))+1) | |
return EAI_OVERFLOW; | |
strcpy(host, inet_ntoa(sin->sin_addr)); | |
} | |
} | |
return 0; | |
} | |
#if defined(_WIN32) || defined(__CYGWIN__) | |
#if !defined(IPV6_V6ONLY) /* Older XP environments may not define IPV6_V6ONLY */ | |
#define IPV6_V6ONLY 27 /* Treat wildcard bind as AF_INET6-only. */ | |
#endif | |
/* Dynamic DLL load variables */ | |
#ifdef _WIN32 | |
static HINSTANCE hLib = 0; /* handle to DLL */ | |
#else | |
static void *hLib = NULL; /* handle to Library */ | |
#endif | |
static int lib_loaded = 0; /* 0=not loaded, 1=loaded, 2=library load failed, 3=Func load failed */ | |
static char* lib_name = "Ws2_32.dll"; | |
/* load function pointer from DLL */ | |
typedef int (*_func)(); | |
static void load_function(char* function, _func* func_ptr) { | |
#ifdef _WIN32 | |
*func_ptr = (_func)GetProcAddress(hLib, function); | |
#else | |
*func_ptr = (_func)dlsym(hLib, function); | |
#endif | |
if (*func_ptr == 0) { | |
char* msg = "Sockets: Failed to find function '%s' in %s\r\n"; | |
sim_printf (msg, function, lib_name); | |
lib_loaded = 3; | |
} | |
} | |
/* load Ws2_32.dll as required */ | |
int load_ws2(void) { | |
switch(lib_loaded) { | |
case 0: /* not loaded */ | |
/* attempt to load DLL */ | |
#ifdef _WIN32 | |
hLib = LoadLibraryA(lib_name); | |
#else | |
hLib = dlopen(lib_name, RTLD_NOW); | |
#endif | |
if (hLib == 0) { | |
/* failed to load DLL */ | |
char* msg = "Sockets: Failed to load %s\r\n"; | |
sim_printf (msg, lib_name); | |
lib_loaded = 2; | |
break; | |
} else { | |
/* library loaded OK */ | |
lib_loaded = 1; | |
} | |
/* load required functions; sets dll_load=3 on error */ | |
load_function("getaddrinfo", (_func *) &p_getaddrinfo); | |
load_function("getnameinfo", (_func *) &p_getnameinfo); | |
load_function("freeaddrinfo", (_func *) &p_freeaddrinfo); | |
if (lib_loaded != 1) { | |
/* unsuccessful load, connect stubs */ | |
p_getaddrinfo = (getaddrinfo_func)s_getaddrinfo; | |
p_getnameinfo = (getnameinfo_func)s_getnameinfo; | |
p_freeaddrinfo = (freeaddrinfo_func)s_freeaddrinfo; | |
} | |
break; | |
default: /* loaded or failed */ | |
break; | |
} | |
return (lib_loaded == 1) ? 1 : 0; | |
} | |
#endif | |
/* OS independent routines | |
sim_parse_addr parse a hostname/ipaddress from port and apply defaults and | |
optionally validate an address match | |
*/ | |
/* sim_parse_addr host:port | |
Presumption is that the input, if it doesn't contain a ':' character is a port specifier. | |
If the host field contains one or more colon characters (i.e. it is an IPv6 address), | |
the IPv6 address MUST be enclosed in square bracket characters (i.e. Domain Literal format) | |
Inputs: | |
cptr = pointer to input string | |
default_host | |
= optional pointer to default host if none specified | |
host_len = length of host buffer | |
default_port | |
= optional pointer to default port if none specified | |
port_len = length of port buffer | |
validate_addr = optional name/addr which is checked to be equivalent | |
to the host result of parsing the other input. This | |
address would usually be returned by sim_accept_conn. | |
Outputs: | |
host = pointer to buffer for IP address (may be NULL), 0 = none | |
port = pointer to buffer for IP port (may be NULL), 0 = none | |
result = status (0 on complete success or -1 if | |
parsing can't happen due to bad syntax, a value is | |
out of range, a result can't fit into a result buffer, | |
a service name doesn't exist, or a validation name | |
doesn't match the parsed host) | |
*/ | |
int sim_parse_addr (const char *cptr, char *host, size_t host_len, const char *default_host, char *port, size_t port_len, const char *default_port, const char *validate_addr) | |
{ | |
char gbuf[CBUFSIZE]; | |
char *hostp, *portp; | |
char *endc; | |
unsigned long portval; | |
if ((host != NULL) && (host_len != 0)) | |
memset (host, 0, host_len); | |
if ((port != NULL) && (port_len != 0)) | |
memset (port, 0, port_len); | |
if ((cptr == NULL) || (*cptr == 0)) { | |
if (((default_host == NULL) || (*default_host == 0)) || ((default_port == NULL) || (*default_port == 0))) | |
return -1; | |
if ((host == NULL) || (port == NULL)) | |
return -1; /* no place */ | |
if ((strlen(default_host) >= host_len) || (strlen(default_port) >= port_len)) | |
return -1; /* no room */ | |
strcpy (host, default_host); | |
strcpy (port, default_port); | |
return 0; | |
} | |
gbuf[sizeof(gbuf)-1] = '\0'; | |
strncpy (gbuf, cptr, sizeof(gbuf)-1); | |
hostp = gbuf; /* default addr */ | |
portp = NULL; | |
if ((portp = strrchr (gbuf, ':')) && /* x:y? split */ | |
(NULL == strchr (portp, ']'))) { | |
*portp++ = 0; | |
if (*portp == '\0') | |
portp = (char *)default_port; | |
} | |
else { /* No colon in input */ | |
portp = gbuf; /* Input is the port specifier */ | |
hostp = (char *)default_host; /* host is defaulted if provided */ | |
} | |
if (portp != NULL) { | |
portval = strtoul(portp, &endc, 10); | |
if ((*endc == '\0') && ((portval == 0) || (portval > 65535))) | |
return -1; /* numeric value too big */ | |
if (*endc != '\0') { | |
struct servent *se = getservbyname(portp, "tcp"); | |
if (se == NULL) | |
return -1; /* invalid service name */ | |
} | |
} | |
if (port) /* port wanted? */ | |
if (portp != NULL) { | |
if (strlen(portp) >= port_len) | |
return -1; /* no room */ | |
else | |
strcpy (port, portp); | |
} | |
if (hostp != NULL) { | |
if (']' == hostp[strlen(hostp)-1]) { | |
if ('[' != hostp[0]) | |
return -1; /* invalid domain literal */ | |
/* host may be the const default_host so move to temp buffer before modifying */ | |
strncpy(gbuf, hostp+1, sizeof(gbuf)-1); /* remove brackets from domain literal host */ | |
hostp = gbuf; | |
hostp[strlen(hostp)-1] = '\0'; | |
} | |
} | |
if (host) { /* host wanted? */ | |
if (hostp != NULL) { | |
if (strlen(hostp) >= host_len) | |
return -1; /* no room */ | |
else | |
if (('\0' != hostp[0]) || (default_host == NULL)) | |
strcpy (host, hostp); | |
else | |
if (strlen(default_host) >= host_len) | |
return -1; /* no room */ | |
else | |
strcpy (host, default_host); | |
} | |
else { | |
if (default_host) { | |
if (strlen(default_host) >= host_len) | |
return -1; /* no room */ | |
else | |
strcpy (host, default_host); | |
} | |
} | |
} | |
if (validate_addr) { | |
struct addrinfo *ai_host, *ai_validate, *ai, *aiv; | |
int status; | |
if (hostp == NULL) | |
return -1; | |
if (p_getaddrinfo(hostp, NULL, NULL, &ai_host)) | |
return -1; | |
if (p_getaddrinfo(validate_addr, NULL, NULL, &ai_validate)) { | |
p_freeaddrinfo (ai_host); | |
return -1; | |
} | |
status = -1; | |
for (ai = ai_host; ai != NULL; ai = ai->ai_next) { | |
for (aiv = ai_validate; aiv != NULL; aiv = aiv->ai_next) { | |
if ((ai->ai_addrlen == aiv->ai_addrlen) && | |
(ai->ai_family == aiv->ai_family) && | |
(0 == memcmp (ai->ai_addr, aiv->ai_addr, ai->ai_addrlen))) { | |
status = 0; | |
break; | |
} | |
} | |
} | |
if (status != 0) { | |
/* be generous and allow successful validations against variations of localhost addresses */ | |
if (((0 == strcmp("127.0.0.1", hostp)) && | |
(0 == strcmp("::1", validate_addr))) || | |
((0 == strcmp("127.0.0.1", validate_addr)) && | |
(0 == strcmp("::1", hostp)))) | |
status = 0; | |
} | |
p_freeaddrinfo (ai_host); | |
p_freeaddrinfo (ai_validate); | |
return status; | |
} | |
return 0; | |
} | |
/* sim_parse_addr_ex localport:host:port | |
Presumption is that the input, if it doesn't contain a ':' character is a port specifier. | |
If the host field contains one or more colon characters (i.e. it is an IPv6 address), | |
the IPv6 address MUST be enclosed in square bracket characters (i.e. Domain Literal format) | |
llll:w.x.y.z:rrrr | |
llll:name.domain.com:rrrr | |
llll::rrrr | |
rrrr | |
w.x.y.z:rrrr | |
[w.x.y.z]:rrrr | |
name.domain.com:rrrr | |
Inputs: | |
cptr = pointer to input string | |
default_host | |
= optional pointer to default host if none specified | |
host_len = length of host buffer | |
default_port | |
= optional pointer to default port if none specified | |
port_len = length of port buffer | |
Outputs: | |
host = pointer to buffer for IP address (may be NULL), 0 = none | |
port = pointer to buffer for IP port (may be NULL), 0 = none | |
localport | |
= pointer to buffer for local IP port (may be NULL), 0 = none | |
result = status (SCPE_OK on complete success or SCPE_ARG if | |
parsing can't happen due to bad syntax, a value is | |
out of range, a result can't fit into a result buffer, | |
a service name doesn't exist, or a validation name | |
doesn't match the parsed host) | |
*/ | |
int sim_parse_addr_ex (const char *cptr, char *host, size_t hostlen, const char *default_host, char *port, size_t port_len, char *localport, size_t localport_len, const char *default_port) | |
{ | |
char *hostp; | |
if ((localport != NULL) && (localport_len != 0)) | |
memset (localport, 0, localport_len); | |
hostp = strchr (cptr, ':'); | |
if ((hostp != NULL) && ((hostp[1] == '[') || (NULL != strchr (hostp+1, ':')))) { | |
if ((localport != NULL) && (localport_len != 0)) { | |
localport_len -= 1; | |
if (localport_len > (size_t)(hostp-cptr)) | |
localport_len = (size_t)(hostp-cptr); | |
memcpy (localport, cptr, localport_len); | |
} | |
return sim_parse_addr (hostp+1, host, hostlen, default_host, port, port_len, default_port, NULL); | |
} | |
return sim_parse_addr (cptr, host, hostlen, default_host, port, port_len, default_port, NULL); | |
} | |
void sim_init_sock (void) | |
{ | |
#if defined (_WIN32) | |
int err; | |
WORD wVersionRequested; | |
WSADATA wsaData; | |
wVersionRequested = MAKEWORD (2, 2); | |
err = WSAStartup (wVersionRequested, &wsaData); /* start Winsock */ | |
if (err != 0) | |
sim_printf ("Winsock: startup error %d\n", err); | |
#if defined(AF_INET6) | |
load_ws2 (); | |
#endif /* endif AF_INET6 */ | |
#else /* Use native addrinfo APIs */ | |
#if defined(AF_INET6) | |
p_getaddrinfo = (getaddrinfo_func)getaddrinfo; | |
p_getnameinfo = (getnameinfo_func)getnameinfo; | |
p_freeaddrinfo = (freeaddrinfo_func)freeaddrinfo; | |
#else | |
/* Native APIs not available, connect stubs */ | |
p_getaddrinfo = (getaddrinfo_func)s_getaddrinfo; | |
p_getnameinfo = (getnameinfo_func)s_getnameinfo; | |
p_freeaddrinfo = (freeaddrinfo_func)s_freeaddrinfo; | |
#endif /* endif AF_INET6 */ | |
#endif /* endif _WIN32 */ | |
#if defined (SIGPIPE) | |
signal (SIGPIPE, SIG_IGN); /* no pipe signals */ | |
#endif | |
} | |
void sim_cleanup_sock (void) | |
{ | |
#if defined (_WIN32) | |
WSACleanup (); | |
#endif | |
} | |
#if defined (_WIN32) /* Windows */ | |
static int sim_setnonblock (SOCKET sock) | |
{ | |
unsigned long non_block = 1; | |
return ioctlsocket (sock, FIONBIO, &non_block); /* set nonblocking */ | |
} | |
#elif defined (VMS) /* VMS */ | |
static int sim_setnonblock (SOCKET sock) | |
{ | |
int non_block = 1; | |
return ioctl (sock, FIONBIO, &non_block); /* set nonblocking */ | |
} | |
#else /* Mac, Unix, OS/2 */ | |
static int sim_setnonblock (SOCKET sock) | |
{ | |
int fl, sta; | |
fl = fcntl (sock, F_GETFL,0); /* get flags */ | |
if (fl == -1) | |
return SOCKET_ERROR; | |
sta = fcntl (sock, F_SETFL, fl | O_NONBLOCK); /* set nonblock */ | |
if (sta == -1) | |
return SOCKET_ERROR; | |
#if !defined (macintosh) && !defined (__EMX__) && \ | |
!defined (__HAIKU__) /* Unix only */ | |
sta = fcntl (sock, F_SETOWN, getpid()); /* set ownership */ | |
if (sta == -1) | |
return SOCKET_ERROR; | |
#endif | |
return 0; | |
} | |
#endif /* endif !Win32 && !VMS */ | |
static int sim_setnodelay (SOCKET sock) | |
{ | |
int nodelay = 1; | |
int sta; | |
/* disable Nagle algorithm */ | |
sta = setsockopt (sock, IPPROTO_TCP, TCP_NODELAY, (char *)&nodelay, sizeof(nodelay)); | |
if (sta == -1) | |
return SOCKET_ERROR; | |
#if defined(TCP_NODELAYACK) | |
/* disable delayed ack algorithm */ | |
sta = setsockopt (sock, IPPROTO_TCP, TCP_NODELAYACK, (char *)&nodelay, sizeof(nodelay)); | |
if (sta == -1) | |
return SOCKET_ERROR; | |
#endif | |
#if defined(TCP_QUICKACK) | |
/* disable delayed ack algorithm */ | |
sta = setsockopt (sock, IPPROTO_TCP, TCP_QUICKACK, (char *)&nodelay, sizeof(nodelay)); | |
if (sta == -1) | |
return SOCKET_ERROR; | |
#endif | |
return sta; | |
} | |
static SOCKET sim_create_sock (int af, int opt_flags) | |
{ | |
SOCKET newsock; | |
int err; | |
newsock = socket (af, ((opt_flags & SIM_SOCK_OPT_DATAGRAM) ? SOCK_DGRAM : SOCK_STREAM), 0);/* create socket */ | |
if (newsock == INVALID_SOCKET) { /* socket error? */ | |
err = WSAGetLastError (); | |
#if defined(WSAEAFNOSUPPORT) | |
if (err == WSAEAFNOSUPPORT) /* expected error, just return */ | |
return newsock; | |
#endif | |
return sim_err_sock (newsock, "socket"); /* report error and return */ | |
} | |
return newsock; | |
} | |
/* | |
Some platforms and/or network stacks have varying support for listening on | |
an IPv6 socket and receiving connections from both IPv4 and IPv6 client | |
connections. This is known as IPv4-Mapped. Some platforms claim such | |
support (i.e. some Windows versions), but it doesn't work in all cases. | |
*/ | |
SOCKET sim_master_sock_ex (const char *hostport, int *parse_status, int opt_flags) | |
{ | |
SOCKET newsock = INVALID_SOCKET; | |
int sta; | |
char host[CBUFSIZE], port[CBUFSIZE]; | |
int r; | |
struct addrinfo hints; | |
struct addrinfo *result = NULL, *preferred; | |
r = sim_parse_addr (hostport, host, sizeof(host), NULL, port, sizeof(port), NULL, NULL); | |
if (parse_status) | |
*parse_status = r; | |
if (r) | |
return newsock; | |
memset(&hints, 0, sizeof(hints)); | |
hints.ai_flags = AI_PASSIVE; | |
hints.ai_family = AF_UNSPEC; | |
hints.ai_protocol = IPPROTO_TCP; | |
hints.ai_socktype = SOCK_STREAM; | |
if (p_getaddrinfo(host[0] ? host : NULL, port[0] ? port : NULL, &hints, &result)) { | |
if (parse_status) | |
*parse_status = -1; | |
return newsock; | |
} | |
preferred = result; | |
#ifdef IPV6_V6ONLY | |
/* | |
When we can create a dual stack socket, be sure to find the IPv6 addrinfo | |
to bind to. | |
*/ | |
for (; preferred != NULL; preferred = preferred->ai_next) { | |
if (preferred->ai_family == AF_INET6) | |
break; | |
} | |
if (preferred == NULL) | |
preferred = result; | |
#endif | |
retry: | |
newsock = sim_create_sock (preferred->ai_family, 0); /* create socket */ | |
if (newsock == INVALID_SOCKET) { /* socket error? */ | |
#ifndef IPV6_V6ONLY | |
if (preferred->ai_next) { | |
preferred = preferred->ai_next; | |
goto retry; | |
} | |
#else | |
if ((preferred->ai_family == AF_INET6) && | |
(preferred != result)) { | |
preferred = result; | |
goto retry; | |
} | |
#endif | |
p_freeaddrinfo(result); | |
return newsock; | |
} | |
#ifdef IPV6_V6ONLY | |
if (preferred->ai_family == AF_INET6) { | |
int off = 0; | |
sta = setsockopt (newsock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&off, sizeof(off)); | |
} | |
#endif | |
if (opt_flags & SIM_SOCK_OPT_REUSEADDR) { | |
int on = 1; | |
sta = setsockopt (newsock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)); | |
} | |
#if defined (SO_EXCLUSIVEADDRUSE) | |
else { | |
int on = 1; | |
sta = setsockopt (newsock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *)&on, sizeof(on)); | |
} | |
#endif | |
sta = bind (newsock, preferred->ai_addr, preferred->ai_addrlen); | |
p_freeaddrinfo(result); | |
if (sta == SOCKET_ERROR) /* bind error? */ | |
return sim_err_sock (newsock, "bind"); | |
if (!(opt_flags & SIM_SOCK_OPT_BLOCKING)) { | |
sta = sim_setnonblock (newsock); /* set nonblocking */ | |
if (sta == SOCKET_ERROR) /* fcntl error? */ | |
return sim_err_sock (newsock, "fcntl"); | |
} | |
sta = listen (newsock, 1); /* listen on socket */ | |
if (sta == SOCKET_ERROR) /* listen error? */ | |
return sim_err_sock (newsock, "listen"); | |
return newsock; /* got it! */ | |
} | |
SOCKET sim_connect_sock_ex (const char *sourcehostport, const char *hostport, const char *default_host, const char *default_port, int opt_flags) | |
{ | |
SOCKET newsock = INVALID_SOCKET; | |
int sta; | |
char host[CBUFSIZE], port[CBUFSIZE]; | |
struct addrinfo hints; | |
struct addrinfo *result = NULL, *source = NULL; | |
if (sim_parse_addr (hostport, host, sizeof(host), default_host, port, sizeof(port), default_port, NULL)) | |
return INVALID_SOCKET; | |
memset(&hints, 0, sizeof(hints)); | |
hints.ai_family = AF_UNSPEC; | |
hints.ai_protocol = ((opt_flags & SIM_SOCK_OPT_DATAGRAM) ? IPPROTO_UDP : IPPROTO_TCP); | |
hints.ai_socktype = ((opt_flags & SIM_SOCK_OPT_DATAGRAM) ? SOCK_DGRAM : SOCK_STREAM); | |
if (p_getaddrinfo(host[0] ? host : NULL, port[0] ? port : NULL, &hints, &result)) | |
return INVALID_SOCKET; | |
if (sourcehostport) { | |
/* Validate the local/source side address which we'll bind to */ | |
if (sim_parse_addr (sourcehostport, host, sizeof(host), NULL, port, sizeof(port), NULL, NULL)) { | |
p_freeaddrinfo (result); | |
return INVALID_SOCKET; | |
} | |
memset(&hints, 0, sizeof(hints)); | |
hints.ai_flags = AI_PASSIVE; | |
hints.ai_family = result->ai_family; /* Same family as connect destination */ | |
hints.ai_protocol = ((opt_flags & SIM_SOCK_OPT_DATAGRAM) ? IPPROTO_UDP : IPPROTO_TCP); | |
hints.ai_socktype = ((opt_flags & SIM_SOCK_OPT_DATAGRAM) ? SOCK_DGRAM : SOCK_STREAM); | |
if (p_getaddrinfo(host[0] ? host : NULL, port[0] ? port : NULL, &hints, &source)) { | |
p_freeaddrinfo (result); | |
return INVALID_SOCKET; | |
} | |
newsock = sim_create_sock (result->ai_family, opt_flags & SIM_SOCK_OPT_DATAGRAM);/* create socket */ | |
if (newsock == INVALID_SOCKET) { /* socket error? */ | |
p_freeaddrinfo (result); | |
p_freeaddrinfo (source); | |
return newsock; | |
} | |
sta = bind (newsock, source->ai_addr, source->ai_addrlen); | |
p_freeaddrinfo(source); | |
source = NULL; | |
if (sta == SOCKET_ERROR) { /* bind error? */ | |
p_freeaddrinfo (result); | |
return sim_err_sock (newsock, "bind"); | |
} | |
} | |
if (newsock == INVALID_SOCKET) { /* socket error? */ | |
newsock = sim_create_sock (result->ai_family, opt_flags & SIM_SOCK_OPT_DATAGRAM);/* create socket */ | |
if (newsock == INVALID_SOCKET) { /* socket error? */ | |
p_freeaddrinfo (result); | |
return newsock; | |
} | |
} | |
if (!(opt_flags & SIM_SOCK_OPT_BLOCKING)) { | |
sta = sim_setnonblock (newsock); /* set nonblocking */ | |
if (sta == SOCKET_ERROR) { /* fcntl error? */ | |
p_freeaddrinfo (result); | |
return sim_err_sock (newsock, "fcntl"); | |
} | |
} | |
if ((!(opt_flags & SIM_SOCK_OPT_DATAGRAM)) && (opt_flags & SIM_SOCK_OPT_NODELAY)) { | |
sta = sim_setnodelay (newsock); /* set nodelay */ | |
if (sta == SOCKET_ERROR) { /* setsock error? */ | |
p_freeaddrinfo (result); | |
return sim_err_sock (newsock, "setnodelay"); | |
} | |
} | |
if (!(opt_flags & SIM_SOCK_OPT_DATAGRAM)) { | |
int keepalive = 1; | |
/* enable TCP Keep Alives */ | |
sta = setsockopt (newsock, SOL_SOCKET, SO_KEEPALIVE, (char *)&keepalive, sizeof(keepalive)); | |
if (sta == -1) | |
return sim_err_sock (newsock, "setsockopt KEEPALIVE"); | |
} | |
sta = connect (newsock, result->ai_addr, result->ai_addrlen); | |
p_freeaddrinfo (result); | |
if (sta == SOCKET_ERROR) { | |
if (opt_flags & SIM_SOCK_OPT_BLOCKING) { | |
if ((WSAGetLastError () == WSAETIMEDOUT) || /* expected errors after a connect failure */ | |
(WSAGetLastError () == WSAEHOSTUNREACH) || | |
(WSAGetLastError () == WSAECONNREFUSED) || | |
(WSAGetLastError () == WSAECONNABORTED) || | |
(WSAGetLastError () == WSAECONNRESET)) { | |
sim_close_sock (newsock); | |
newsock = INVALID_SOCKET; | |
} | |
else | |
return sim_err_sock (newsock, "connect"); | |
} | |
else /* Non Blocking case won't return errors until some future read */ | |
if ((WSAGetLastError () != WSAEWOULDBLOCK) && | |
(WSAGetLastError () != WSAEINPROGRESS)) | |
return sim_err_sock (newsock, "connect"); | |
} | |
return newsock; /* got it! */ | |
} | |
SOCKET sim_accept_conn_ex (SOCKET master, char **connectaddr, int opt_flags) | |
{ | |
int sta = 0, err; | |
int keepalive = 1; | |
#if defined (macintosh) || defined (__linux) || defined (__linux__) || \ | |
defined (__APPLE__) || defined (__OpenBSD__) || \ | |
defined(__NetBSD__) || defined(__FreeBSD__) || \ | |
(defined(__hpux) && defined(_XOPEN_SOURCE_EXTENDED)) || \ | |
defined (__HAIKU__) | |
socklen_t size; | |
#elif defined (_WIN32) || defined (__EMX__) || \ | |
(defined (__ALPHA) && defined (__unix__)) || \ | |
defined (__hpux) | |
int size; | |
#else | |
size_t size; | |
#endif | |
SOCKET newsock; | |
struct sockaddr_storage clientname; | |
if (master == 0) /* not attached? */ | |
return INVALID_SOCKET; | |
size = sizeof (clientname); | |
memset (&clientname, 0, sizeof(clientname)); | |
newsock = accept (master, (struct sockaddr *) &clientname, &size); | |
if (newsock == INVALID_SOCKET) { /* error? */ | |
err = WSAGetLastError (); | |
if (err != WSAEWOULDBLOCK) | |
sim_err_sock(newsock, "accept"); | |
return INVALID_SOCKET; | |
} | |
if (connectaddr != NULL) { | |
*connectaddr = (char *)calloc(1, NI_MAXHOST+1); | |
#ifdef AF_INET6 | |
p_getnameinfo((struct sockaddr *)&clientname, size, *connectaddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); | |
if (0 == memcmp("::ffff:", *connectaddr, 7)) /* is this a IPv4-mapped IPv6 address? */ | |
memmove(*connectaddr, 7+*connectaddr, /* prefer bare IPv4 address */ | |
strlen(*connectaddr) - 7 + 1); /* length to include terminating \0 */ | |
#else | |
strcpy(*connectaddr, inet_ntoa(((struct sockaddr_in *)&connectaddr)->s_addr)); | |
#endif | |
} | |
if (!(opt_flags & SIM_SOCK_OPT_BLOCKING)) { | |
sta = sim_setnonblock (newsock); /* set nonblocking */ | |
if (sta == SOCKET_ERROR) /* fcntl error? */ | |
return sim_err_sock (newsock, "fcntl"); | |
} | |
if ((opt_flags & SIM_SOCK_OPT_NODELAY)) { | |
sta = sim_setnodelay (newsock); /* set nonblocking */ | |
if (sta == SOCKET_ERROR) /* setsockopt error? */ | |
return sim_err_sock (newsock, "setnodelay"); | |
} | |
/* enable TCP Keep Alives */ | |
sta = setsockopt (newsock, SOL_SOCKET, SO_KEEPALIVE, (char *)&keepalive, sizeof(keepalive)); | |
if (sta == -1) | |
return sim_err_sock (newsock, "setsockopt KEEPALIVE"); | |
return newsock; | |
} | |
int sim_check_conn (SOCKET sock, int rd) | |
{ | |
fd_set rw_set, er_set; | |
fd_set *rw_p = &rw_set; | |
fd_set *er_p = &er_set; | |
struct timeval tz; | |
struct sockaddr_storage peername; | |
#if defined (macintosh) || defined (__linux) || defined (__linux__) || \ | |
defined (__APPLE__) || defined (__OpenBSD__) || \ | |
defined(__NetBSD__) || defined(__FreeBSD__) || \ | |
(defined(__hpux) && defined(_XOPEN_SOURCE_EXTENDED)) || \ | |
defined (__HAIKU__) | |
socklen_t peernamesize = (socklen_t)sizeof(peername); | |
#elif defined (_WIN32) || defined (__EMX__) || \ | |
(defined (__ALPHA) && defined (__unix__)) || \ | |
defined (__hpux) | |
int peernamesize = (int)sizeof(peername); | |
#else | |
size_t peernamesize = sizeof(peername); | |
#endif | |
timerclear (&tz); | |
FD_ZERO (rw_p); | |
FD_ZERO (er_p); | |
FD_SET (sock, rw_p); | |
FD_SET (sock, er_p); | |
if (rd) | |
select ((int) sock + 1, rw_p, NULL, er_p, &tz); | |
else select ((int) sock + 1, NULL, rw_p, er_p, &tz); | |
if (FD_ISSET (sock, er_p)) | |
return -1; | |
if (FD_ISSET (sock, rw_p)) { | |
if (0 == getpeername (sock, (struct sockaddr *)&peername, &peernamesize)) | |
return 1; | |
else | |
return -1; | |
} | |
return 0; | |
} | |
static int _sim_getaddrname (struct sockaddr *addr, size_t addrsize, char *hostnamebuf, char *portnamebuf) | |
{ | |
#if defined (macintosh) || defined (__linux) || defined (__linux__) || \ | |
defined (__APPLE__) || defined (__OpenBSD__) || \ | |
defined(__NetBSD__) || defined(__FreeBSD__) || \ | |
(defined(__hpux) && defined(_XOPEN_SOURCE_EXTENDED)) || \ | |
defined (__HAIKU__) | |
socklen_t size = (socklen_t)addrsize; | |
#elif defined (_WIN32) || defined (__EMX__) || \ | |
(defined (__ALPHA) && defined (__unix__)) || \ | |
defined (__hpux) | |
int size = (int)addrsize; | |
#else | |
size_t size = addrsize; | |
#endif | |
int ret = 0; | |
#ifdef AF_INET6 | |
*hostnamebuf = '\0'; | |
*portnamebuf = '\0'; | |
ret = p_getnameinfo(addr, size, hostnamebuf, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); | |
if (0 == memcmp("::ffff:", hostnamebuf, 7)) /* is this a IPv4-mapped IPv6 address? */ | |
memmove(hostnamebuf, 7+hostnamebuf, /* prefer bare IPv4 address */ | |
strlen(hostnamebuf) + 7 - 1); /* length to include terminating \0 */ | |
if (!ret) | |
ret = p_getnameinfo(addr, size, NULL, 0, portnamebuf, NI_MAXSERV, NI_NUMERICSERV); | |
#else | |
strcpy(hostnamebuf, inet_ntoa(((struct sockaddr_in *)addr)->s_addr)); | |
sprintf(portnamebuf, "%d", (int)ntohs(((struct sockaddr_in *)addr)->s_port))); | |
#endif | |
return ret; | |
} | |
int sim_getnames_sock (SOCKET sock, char **socknamebuf, char **peernamebuf) | |
{ | |
struct sockaddr_storage sockname, peername; | |
#if defined (macintosh) || defined (__linux) || defined (__linux__) || \ | |
defined (__APPLE__) || defined (__OpenBSD__) || \ | |
defined(__NetBSD__) || defined(__FreeBSD__) || \ | |
(defined(__hpux) && defined(_XOPEN_SOURCE_EXTENDED)) || \ | |
defined (__HAIKU__) | |
socklen_t socknamesize = (socklen_t)sizeof(sockname); | |
socklen_t peernamesize = (socklen_t)sizeof(peername); | |
#elif defined (_WIN32) || defined (__EMX__) || \ | |
(defined (__ALPHA) && defined (__unix__)) || \ | |
defined (__hpux) | |
int socknamesize = (int)sizeof(sockname); | |
int peernamesize = (int)sizeof(peername); | |
#else | |
size_t socknamesize = sizeof(sockname); | |
size_t peernamesize = sizeof(peername); | |
#endif | |
char hostbuf[NI_MAXHOST+1]; | |
char portbuf[NI_MAXSERV+1]; | |
if (socknamebuf) | |
*socknamebuf = (char *)calloc(1, NI_MAXHOST+NI_MAXSERV+4); | |
if (peernamebuf) | |
*peernamebuf = (char *)calloc(1, NI_MAXHOST+NI_MAXSERV+4); | |
getsockname (sock, (struct sockaddr *)&sockname, &socknamesize); | |
getpeername (sock, (struct sockaddr *)&peername, &peernamesize); | |
if (socknamebuf != NULL) { | |
_sim_getaddrname ((struct sockaddr *)&sockname, (size_t)socknamesize, hostbuf, portbuf); | |
sprintf(*socknamebuf, "[%s]:%s", hostbuf, portbuf); | |
} | |
if (peernamebuf != NULL) { | |
_sim_getaddrname ((struct sockaddr *)&peername, (size_t)peernamesize, hostbuf, portbuf); | |
sprintf(*peernamebuf, "[%s]:%s", hostbuf, portbuf); | |
} | |
return 0; | |
} | |
int sim_read_sock (SOCKET sock, char *buf, int nbytes) | |
{ | |
int rbytes, err; | |
rbytes = recv (sock, buf, nbytes, 0); | |
if (rbytes == 0) /* disconnect */ | |
return -1; | |
if (rbytes == SOCKET_ERROR) { | |
err = WSAGetLastError (); | |
if (err == WSAEWOULDBLOCK) /* no data */ | |
return 0; | |
#if defined(EAGAIN) | |
if (err == EAGAIN) /* no data */ | |
return 0; | |
#endif | |
if ((err != WSAETIMEDOUT) && /* expected errors after a connect failure */ | |
(err != WSAEHOSTUNREACH) && | |
(err != WSAECONNREFUSED) && | |
(err != WSAECONNABORTED) && | |
(err != WSAECONNRESET)) | |
sim_err_sock (INVALID_SOCKET, "read"); | |
return -1; | |
} | |
return rbytes; | |
} | |
int sim_write_sock (SOCKET sock, const char *msg, int nbytes) | |
{ | |
int err, sbytes = send (sock, msg, nbytes, 0); | |
if (sbytes == SOCKET_ERROR) { | |
err = WSAGetLastError (); | |
if (err == WSAEWOULDBLOCK) /* no data */ | |
return 0; | |
#if defined(EAGAIN) | |
if (err == EAGAIN) /* no data */ | |
return 0; | |
#endif | |
} | |
return sbytes; | |
} | |
void sim_close_sock (SOCKET sock) | |
{ | |
shutdown(sock, SD_BOTH); | |
closesocket (sock); | |
} | |
#endif /* end else !implemented */ |