/* source: xio-socket.c */ /* Copyright Gerhard Rieger and contributors (see file CHANGES) */ /* Published under the GNU General Public License V.2, see file COPYING */ /* this file contains the source for socket related functions, and the implementation of generic socket addresses */ #include "xiosysincludes.h" #if _WITH_SOCKET #include "xioopen.h" #include "xio-ascii.h" #include "xio-socket.h" #include "xio-named.h" #include "xio-unix.h" #if WITH_VSOCK #include "xio-vsock.h" #endif #if WITH_IP4 #include "xio-ip4.h" #endif /* WITH_IP4 */ #if WITH_IP6 #include "xio-ip6.h" #endif /* WITH_IP6 */ #include "xio-ip.h" #include "xio-listen.h" #include "xio-interface.h" #include "xio-ipapp.h" /*! not clean */ #include "xio-tcpwrap.h" static int xioopen_socket_connect(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, const struct addrdesc *addrdesc); static int xioopen_socket_listen(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, const struct addrdesc *addrdesc); static int xioopen_socket_sendto(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, const struct addrdesc *addrdesc); static int xioopen_socket_datagram(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, const struct addrdesc *addrdesc); static int xioopen_socket_recvfrom(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, const struct addrdesc *addrdesc); static int xioopen_socket_recv(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, const struct addrdesc *addrdesc); static int _xioopen_socket_sendto(const char *pfname, const char *type, const char *proto, const char *address, struct opt *opts, int xioflags, xiofile_t *xxfd, groups_t groups); static int xiolog_ancillary_socket(struct single *sfd, struct cmsghdr *cmsg, int *num, char *typbuff, int typlen, char *nambuff, int namlen, char *envbuff, int envlen, char *valbuff, int vallen); static int xiobind( struct single *sfd, union sockaddr_union *us, size_t uslen, struct opt *opts, int pf, bool alt, int level); #if WITH_GENERICSOCKET /* generic socket addresses */ const struct addrdesc xioaddr_socket_connect = { "SOCKET-CONNECT", 1+XIO_RDWR, xioopen_socket_connect, GROUP_FD|GROUP_SOCKET|GROUP_CHILD|GROUP_RETRY, 0, 0, 0 HELP(":::") }; #if WITH_LISTEN const struct addrdesc xioaddr_socket_listen = { "SOCKET-LISTEN", 1+XIO_RDWR, xioopen_socket_listen, GROUP_FD|GROUP_SOCKET|GROUP_LISTEN|GROUP_RANGE|GROUP_CHILD|GROUP_RETRY, 0, 0, 0 HELP(":::") }; #endif /* WITH_LISTEN */ const struct addrdesc xioaddr_socket_sendto = { "SOCKET-SENDTO", 1+XIO_RDWR, xioopen_socket_sendto, GROUP_FD|GROUP_SOCKET, 0, 0, 0 HELP("::::") }; const struct addrdesc xioaddr_socket_datagram= { "SOCKET-DATAGRAM", 1+XIO_RDWR, xioopen_socket_datagram, GROUP_FD|GROUP_SOCKET|GROUP_RANGE, 0, 0, 0 HELP("::::") }; const struct addrdesc xioaddr_socket_recvfrom= { "SOCKET-RECVFROM", 1+XIO_RDWR, xioopen_socket_recvfrom, GROUP_FD|GROUP_SOCKET|GROUP_RANGE|GROUP_CHILD, 0, 0, 0 HELP("::::") }; const struct addrdesc xioaddr_socket_recv = { "SOCKET-RECV", 1+XIO_RDONLY, xioopen_socket_recv, GROUP_FD|GROUP_SOCKET|GROUP_RANGE, 0, 0, 0 HELP("::::") }; #endif /* WITH_GENERICSOCKET */ /* the following options apply not only to generic socket addresses but to all addresses that have anything to do with sockets */ const struct optdesc opt_so_debug = { "so-debug", "debug", OPT_SO_DEBUG, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_DEBUG }; #ifdef SO_ACCEPTCONN /* AIX433 */ const struct optdesc opt_so_acceptconn={ "so-acceptconn","acceptconn",OPT_SO_ACCEPTCONN,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_ACCEPTCONN}; #endif /* SO_ACCEPTCONN */ const struct optdesc opt_so_broadcast= { "so-broadcast", "broadcast", OPT_SO_BROADCAST,GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_BROADCAST}; const struct optdesc opt_so_reuseaddr= { "so-reuseaddr", "reuseaddr", OPT_SO_REUSEADDR,GROUP_SOCKET, PH_PREBIND, TYPE_INT_NULL, OFUNC_SOCKOPT, SOL_SOCKET, SO_REUSEADDR}; const struct optdesc opt_so_keepalive= { "so-keepalive", "keepalive", OPT_SO_KEEPALIVE,GROUP_SOCKET, PH_FD, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_KEEPALIVE}; #if HAVE_STRUCT_LINGER const struct optdesc opt_so_linger = { "so-linger", "linger", OPT_SO_LINGER, GROUP_SOCKET, PH_PASTSOCKET, TYPE_LINGER,OFUNC_SOCKOPT,SOL_SOCKET, SO_LINGER }; #else /* !HAVE_STRUCT_LINGER */ const struct optdesc opt_so_linger = { "so-linger", "linger", OPT_SO_LINGER, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_LINGER }; #endif /* !HAVE_STRUCT_LINGER */ const struct optdesc opt_so_oobinline= { "so-oobinline", "oobinline", OPT_SO_OOBINLINE,GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_OOBINLINE}; const struct optdesc opt_so_sndbuf = { "so-sndbuf", "sndbuf", OPT_SO_SNDBUF, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_SNDBUF}; const struct optdesc opt_so_sndbuf_late={ "so-sndbuf-late","sndbuf-late",OPT_SO_SNDBUF_LATE,GROUP_SOCKET,PH_LATE,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_SNDBUF }; const struct optdesc opt_so_rcvbuf = { "so-rcvbuf", "rcvbuf", OPT_SO_RCVBUF, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_RCVBUF}; const struct optdesc opt_so_rcvbuf_late={"so-rcvbuf-late","rcvbuf-late",OPT_SO_RCVBUF_LATE,GROUP_SOCKET,PH_LATE,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_RCVBUF }; const struct optdesc opt_so_error = { "so-error", "error", OPT_SO_ERROR, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_ERROR}; const struct optdesc opt_so_type = { "so-type", "type", OPT_SO_TYPE, GROUP_SOCKET, PH_SOCKET, TYPE_INT, OFUNC_SPEC, SOL_SOCKET, SO_TYPE }; const struct optdesc opt_so_dontroute= { "so-dontroute", "dontroute", OPT_SO_DONTROUTE,GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_DONTROUTE }; #ifdef SO_RCVLOWAT const struct optdesc opt_so_rcvlowat = { "so-rcvlowat", "rcvlowat", OPT_SO_RCVLOWAT, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_RCVLOWAT }; #endif #ifdef SO_SNDLOWAT const struct optdesc opt_so_sndlowat = { "so-sndlowat", "sndlowat", OPT_SO_SNDLOWAT, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_SNDLOWAT }; #endif /* end of setsockopt options of UNIX98 standard */ #ifdef SO_RCVTIMEO const struct optdesc opt_so_rcvtimeo = { "so-rcvtimeo", "rcvtimeo", OPT_SO_RCVTIMEO, GROUP_SOCKET, PH_PASTSOCKET, TYPE_TIMEVAL, OFUNC_SOCKOPT, SOL_SOCKET, SO_RCVTIMEO }; #endif #ifdef SO_SNDTIMEO const struct optdesc opt_so_sndtimeo = { "so-sndtimeo", "sndtimeo", OPT_SO_SNDTIMEO, GROUP_SOCKET, PH_PASTSOCKET, TYPE_TIMEVAL, OFUNC_SOCKOPT, SOL_SOCKET, SO_SNDTIMEO }; #endif #ifdef SO_AUDIT /* AIX 4.3.3 */ const struct optdesc opt_so_audit = { "so-audit", "audit", OPT_SO_AUDIT, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_AUDIT }; #endif /* SO_AUDIT */ #ifdef SO_ATTACH_FILTER const struct optdesc opt_so_attach_filter={"so-attach-filter","attachfilter",OPT_SO_ATTACH_FILTER,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_ATTACH_FILTER}; #endif #ifdef SO_DETACH_FILTER const struct optdesc opt_so_detach_filter={"so-detach-filter","detachfilter",OPT_SO_DETACH_FILTER,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_DETACH_FILTER}; #endif #ifdef SO_BINDTODEVICE /* Linux: man 7 socket */ const struct optdesc opt_so_bindtodevice={"so-bindtodevice","if",OPT_SO_BINDTODEVICE,GROUP_SOCKET,PH_PASTSOCKET,TYPE_NAME,OFUNC_SOCKOPT,SOL_SOCKET,SO_BINDTODEVICE}; #endif #ifdef SO_BSDCOMPAT const struct optdesc opt_so_bsdcompat= { "so-bsdcompat","bsdcompat",OPT_SO_BSDCOMPAT,GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_BSDCOMPAT }; #endif #ifdef SO_CKSUMRECV const struct optdesc opt_so_cksumrecv= { "so-cksumrecv","cksumrecv",OPT_SO_CKSUMRECV,GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_CKSUMRECV }; #endif /* SO_CKSUMRECV */ #ifdef SO_TIMESTAMP const struct optdesc opt_so_timestamp= { "so-timestamp","timestamp",OPT_SO_TIMESTAMP,GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_TIMESTAMP }; #endif #ifdef SO_KERNACCEPT /* AIX 4.3.3 */ const struct optdesc opt_so_kernaccept={ "so-kernaccept","kernaccept",OPT_SO_KERNACCEPT,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_KERNACCEPT}; #endif /* SO_KERNACCEPT */ #ifdef SO_NO_CHECK const struct optdesc opt_so_no_check = { "so-no-check", "nocheck",OPT_SO_NO_CHECK, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_NO_CHECK }; #endif #ifdef SO_NOREUSEADDR /* AIX 4.3.3 */ const struct optdesc opt_so_noreuseaddr={"so-noreuseaddr","noreuseaddr",OPT_SO_NOREUSEADDR,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET, SO_NOREUSEADDR}; #endif /* SO_NOREUSEADDR */ #ifdef SO_PASSCRED const struct optdesc opt_so_passcred = { "so-passcred", "passcred", OPT_SO_PASSCRED, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_PASSCRED}; #endif #ifdef SO_PEERCRED const struct optdesc opt_so_peercred = { "so-peercred", "peercred", OPT_SO_PEERCRED, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT3,OFUNC_SOCKOPT, SOL_SOCKET, SO_PEERCRED}; #endif #ifdef SO_PRIORITY const struct optdesc opt_so_priority = { "so-priority", "priority", OPT_SO_PRIORITY, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_PRIORITY}; #endif #ifdef SO_REUSEPORT /* AIX 4.3.3, BSD, HP-UX, Linux >=3.9 */ const struct optdesc opt_so_reuseport = { "so-reuseport","reuseport",OPT_SO_REUSEPORT,GROUP_SOCKET, PH_PREBIND, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_REUSEPORT }; #endif /* defined(SO_REUSEPORT) */ #ifdef SO_SECURITY_AUTHENTICATION const struct optdesc opt_so_security_authentication={"so-security-authentication","securityauthentication",OPT_SO_SECURITY_AUTHENTICATION,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_SECURITY_AUTHENTICATION}; #endif #ifdef SO_SECURITY_ENCRYPTION_NETWORK const struct optdesc opt_so_security_encryption_network= { "so-security-encryption-network","securityencryptionnetwork",OPT_SO_SECURITY_ENCRYPTION_NETWORK,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_SECURITY_ENCRYPTION_NETWORK}; #endif #ifdef SO_SECURITY_ENCRYPTION_TRANSPORT const struct optdesc opt_so_security_encryption_transport= { "so-security-encryption-transport","securityencryptiontransport",OPT_SO_SECURITY_ENCRYPTION_TRANSPORT,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_SECURITY_ENCRYPTION_TRANSPORT}; #endif #ifdef SO_USE_IFBUFS const struct optdesc opt_so_use_ifbufs= { "so-use-ifbufs","useifbufs",OPT_SO_USE_IFBUFS,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_USE_IFBUFS}; #endif /* SO_USE_IFBUFS */ #ifdef SO_USELOOPBACK /* AIX433, Solaris, HP-UX */ const struct optdesc opt_so_useloopback= { "so-useloopback","useloopback",OPT_SO_USELOOPBACK,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT, SOL_SOCKET, SO_USELOOPBACK}; #endif /* SO_USELOOPBACK */ #ifdef SO_DGRAM_ERRIND /* Solaris */ const struct optdesc opt_so_dgram_errind= { "so-dgram-errind","dgramerrind",OPT_SO_DGRAM_ERRIND,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_DGRAM_ERRIND}; #endif /* SO_DGRAM_ERRIND */ #ifdef SO_DONTLINGER /* Solaris */ const struct optdesc opt_so_dontlinger = { "so-dontlinger", "dontlinger", OPT_SO_DONTLINGER, GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_DONTLINGER }; #endif /* the SO_PROTOTYPE is OS defined on Solaris, HP-UX; we lend this for a more general purpose */ const struct optdesc opt_so_prototype = { "so-protocol", "protocol", OPT_SO_PROTOTYPE, GROUP_SOCKET,PH_SOCKET, TYPE_INT,OFUNC_SPEC, SOL_SOCKET,SO_PROTOCOL }; #ifdef FIOSETOWN const struct optdesc opt_fiosetown = { "fiosetown", NULL, OPT_FIOSETOWN, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_IOCTL, FIOSETOWN }; #endif #ifdef SIOCSPGRP const struct optdesc opt_siocspgrp = { "siocspgrp", NULL, OPT_SIOCSPGRP, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_IOCTL, SIOCSPGRP }; #endif const struct optdesc opt_bind = { "bind", NULL, OPT_BIND, GROUP_SOCKET, PH_BIND, TYPE_STRING,OFUNC_SPEC }; const struct optdesc opt_connect_timeout = { "connect-timeout", NULL, OPT_CONNECT_TIMEOUT, GROUP_SOCKET, PH_PASTSOCKET, TYPE_TIMEVAL, OFUNC_OFFSET, XIO_OFFSETOF(para.socket.connect_timeout) }; const struct optdesc opt_protocol_family = { "protocol-family", "pf", OPT_PROTOCOL_FAMILY, GROUP_SOCKET, PH_PRESOCKET, TYPE_STRING, OFUNC_SPEC }; /* generic setsockopt() options */ const struct optdesc opt_setsockopt = { "setsockopt", "sockopt", OPT_SETSOCKOPT_BIN, GROUP_SOCKET,PH_CONNECTED, TYPE_INT_INT_BIN, OFUNC_SOCKOPT_GENERIC, 0, 0 }; const struct optdesc opt_setsockopt_int = { "setsockopt-int", "sockopt-int", OPT_SETSOCKOPT_INT, GROUP_SOCKET,PH_CONNECTED, TYPE_INT_INT_INT, OFUNC_SOCKOPT_GENERIC, 0, 0 }; const struct optdesc opt_setsockopt_bin = { "setsockopt-bin", "sockopt-bin", OPT_SETSOCKOPT_BIN, GROUP_SOCKET,PH_CONNECTED, TYPE_INT_INT_BIN, OFUNC_SOCKOPT_GENERIC, 0, 0 }; const struct optdesc opt_setsockopt_string = { "setsockopt-string", "sockopt-string", OPT_SETSOCKOPT_STRING, GROUP_SOCKET,PH_CONNECTED, TYPE_INT_INT_STRING, OFUNC_SOCKOPT_GENERIC, 0, 0 }; const struct optdesc opt_setsockopt_listen = { "setsockopt-listen", "sockopt-listen", OPT_SETSOCKOPT_LISTEN, GROUP_SOCKET,PH_PREBIND, TYPE_INT_INT_BIN, OFUNC_SOCKOPT_GENERIC, 0, 0 }; const struct optdesc opt_null_eof = { "null-eof", NULL, OPT_NULL_EOF, GROUP_SOCKET, PH_OFFSET, TYPE_BOOL, OFUNC_OFFSET, XIO_OFFSETOF(para.socket.null_eof) }; #if WITH_GENERICSOCKET static int xioopen_socket_connect( int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, const struct addrdesc *addrdesc) { struct single *sfd = &xxfd->stream; const char *pfname = argv[1]; const char *protname = argv[2]; const char *address = argv[3]; char *garbage; int pf; int proto; int socktype = SOCK_STREAM; int needbind = 0; union sockaddr_union them; socklen_t themlen; size_t themsize; union sockaddr_union us; socklen_t uslen = sizeof(us); int result; if (argc != 4) { xio_syntax(argv[0], 3, argc-1, addrdesc->syntax); return STAT_NORETRY; } pf = strtoul(pfname, &garbage, 0); if (*garbage != '\0') { Warn1("garbage in parameter: \"%s\"", garbage); } proto = strtoul(protname, &garbage, 0); if (*garbage != '\0') { Warn1("garbage in parameter: \"%s\"", garbage); } retropt_socket_pf(opts, &pf); retropt_int(opts, OPT_SO_TYPE, &socktype); /*retropt_int(opts, OPT_IP_PROTOCOL, &proto);*/ if (sfd->howtoend == END_UNSPEC) sfd->howtoend = END_SHUTDOWN; if (applyopts_single(sfd, opts, PH_INIT) < 0) return -1; applyopts(sfd, -1, opts, PH_INIT); applyopts(sfd, -1, opts, PH_EARLY); themsize = 0; if ((result = dalan(address, (uint8_t *)&them.soa.sa_data, &themsize, sizeof(them), 'i')) < 0) { Error1("data too long: \"%s\"", address); } else if (result > 0) { Error1("syntax error in \"%s\"", address); } them.soa.sa_family = pf; themlen = themsize + #if HAVE_STRUCT_SOCKADDR_SALEN sizeof(them.soa.sa_len) + #endif sizeof(them.soa.sa_family); sfd->dtype = XIOREAD_STREAM|XIOWRITE_STREAM; socket_init(0, &us); if (retropt_bind(opts, 0 /*pf*/, socktype, proto, (struct sockaddr *)&us, &uslen, 3, sfd->para.socket.ip.ai_flags) != STAT_NOACTION) { needbind = true; us.soa.sa_family = pf; } if ((result = xioopen_connect(sfd, needbind?&us:NULL, uslen, (struct sockaddr *)&them, themlen, opts, pf, socktype, proto, false)) != 0) { return result; } if ((result = _xio_openlate(sfd, opts)) < 0) { return result; } return STAT_OK; } #if WITH_LISTEN static int xioopen_socket_listen( int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, const struct addrdesc *addrdesc) { struct single *sfd = &xxfd->stream; const char *pfname = argv[1]; const char *protname = argv[2]; const char *usname = argv[3]; char *garbage; int pf; int proto; int socktype = SOCK_STREAM; union sockaddr_union us; socklen_t uslen; size_t ussize; struct opt *opts0; int result; if (argc != 4) { xio_syntax(argv[0], 3, argc-1, addrdesc->syntax); return STAT_NORETRY; } pf = strtoul(pfname, &garbage, 0); if (*garbage != '\0') { Warn1("garbage in parameter: \"%s\"", garbage); } proto = strtoul(protname, &garbage, 0); if (*garbage != '\0') { Warn1("garbage in parameter: \"%s\"", garbage); } retropt_socket_pf(opts, &pf); retropt_int(opts, OPT_SO_TYPE, &socktype); /*retropt_int(opts, OPT_IP_PROTOCOL, &proto);*/ if (sfd->howtoend == END_UNSPEC) sfd->howtoend = END_SHUTDOWN; socket_init(0, &us); ussize = 0; if ((result = dalan(usname, (uint8_t *)&us.soa.sa_data, &ussize, sizeof(us), 'i')) < 0) { Error1("data too long: \"%s\"", usname); } else if (result > 0) { Error1("syntax error in \"%s\"", usname); } uslen = ussize + sizeof(us.soa.sa_family) #if HAVE_STRUCT_SOCKADDR_SALEN + sizeof(us.soa.sa_len) #endif ; us.soa.sa_family = pf; if (applyopts_single(sfd, opts, PH_INIT) < 0) return -1; applyopts(sfd, -1, opts, PH_INIT); applyopts(sfd, -1, opts, PH_EARLY); opts0 = copyopts(opts, GROUP_ALL); if ((result = xioopen_listen(sfd, xioflags, &us.soa, uslen, opts, opts0, 0/*instead of pf*/, socktype, proto)) != STAT_OK) return result; return STAT_OK; } #endif /* WITH_LISTEN */ /* we expect the form: ...:domain:type:protocol:remote-address */ static int xioopen_socket_sendto( int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, const struct addrdesc *addrdesc) { int result; if (argc != 5) { xio_syntax(argv[0], 4, argc-1, addrdesc->syntax); return STAT_NORETRY; } if ((result = _xioopen_socket_sendto(argv[1], argv[2], argv[3], argv[4], opts, xioflags, xxfd, addrdesc->groups)) != STAT_OK) { return result; } _xio_openlate(&xxfd->stream, opts); return STAT_OK; } static int _xioopen_socket_sendto(const char *pfname, const char *type, const char *protname, const char *address, struct opt *opts, int xioflags, xiofile_t *xxfd, groups_t groups) { xiosingle_t *sfd = &xxfd->stream; char *garbage; union sockaddr_union us = {{0}}; socklen_t uslen = 0; size_t ussize; size_t themsize; int pf; int socktype = SOCK_RAW; int proto; bool needbind = false; char *bindstring = NULL; int result; pf = strtoul(pfname, &garbage, 0); if (*garbage != '\0') { Warn1("garbage in parameter: \"%s\"", garbage); } socktype = strtoul(type, &garbage, 0); if (*garbage != '\0') { Warn1("garbage in parameter: \"%s\"", garbage); } proto = strtoul(protname, &garbage, 0); if (*garbage != '\0') { Warn1("garbage in parameter: \"%s\"", garbage); } retropt_socket_pf(opts, &pf); retropt_int(opts, OPT_SO_TYPE, &socktype); /*retropt_int(opts, OPT_IP_PROTOCOL, &proto);*/ if (sfd->howtoend == END_UNSPEC) sfd->howtoend = END_SHUTDOWN; sfd->peersa.soa.sa_family = pf; themsize = 0; if ((result = dalan(address, (uint8_t *)&sfd->peersa.soa.sa_data, &themsize, sizeof(sfd->peersa), 'i')) < 0) { Error1("data too long: \"%s\"", address); } else if (result > 0) { Error1("syntax error in \"%s\"", address); } sfd->salen = themsize + sizeof(sa_family_t) #if HAVE_STRUCT_SOCKADDR_SALEN + sizeof(sfd->peersa.soa.sa_len) #endif ; #if HAVE_STRUCT_SOCKADDR_SALEN sfd->peersa.soa.sa_len = sizeof(sfd->peersa.soa.sa_len) + sizeof(sfd->peersa.soa.sa_family) + themsize; #endif if (applyopts_single(sfd, opts, PH_INIT) < 0) return -1; applyopts(sfd, -1, opts, PH_INIT); if (pf == PF_UNSPEC) { pf = sfd->peersa.soa.sa_family; } sfd->dtype = XIODATA_RECVFROM; if (retropt_string(opts, OPT_BIND, &bindstring) == 0) { ussize = 0; if ((result = dalan(bindstring, (uint8_t *)&us.soa.sa_data, &ussize, sizeof(us), 'i')) < 0) { Error1("data too long: \"%s\"", bindstring); } else if (result > 0) { Error1("syntax error in \"%s\"", bindstring); } us.soa.sa_family = pf; uslen = ussize + sizeof(sa_family_t) #if HAVE_STRUCT_SOCKADDR_SALEN + sizeof(us.soa.sa_len) #endif ; needbind = true; } return _xioopen_dgram_sendto(needbind?&us:NULL, uslen, opts, xioflags, sfd, groups, pf, socktype, proto, 0); } /* we expect the form: ...:domain:socktype:protocol:local-address */ static int xioopen_socket_recvfrom( int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, const struct addrdesc *addrdesc) { struct single *sfd = &xxfd->stream; const char *pfname = argv[1]; const char *typename = argv[2]; const char *protname = argv[3]; const char *address = argv[4]; char *garbage; union sockaddr_union *us = &sfd->para.socket.la; socklen_t uslen; size_t ussize; int pf, socktype, proto; char *rangename; int result; if (argc != 5) { xio_syntax(argv[0], 4, argc-1, addrdesc->syntax); return STAT_NORETRY; } pf = strtoul(pfname, &garbage, 0); if (*garbage != '\0') { Warn1("garbage in parameter: \"%s\"", garbage); } socktype = strtoul(typename, &garbage, 0); if (*garbage != '\0') { Warn1("garbage in parameter: \"%s\"", garbage); } proto = strtoul(protname, &garbage, 0); if (*garbage != '\0') { Warn1("garbage in parameter: \"%s\"", garbage); } retropt_socket_pf(opts, &pf); retropt_int(opts, OPT_SO_TYPE, &socktype); /*retropt_int(opts, OPT_IP_PROTOCOL, &proto);*/ if (sfd->howtoend == END_UNSPEC) sfd->howtoend = END_NONE; ussize = 0; if ((result = dalan(address, (uint8_t *)&us->soa.sa_data, &ussize, sizeof(*us), 'i')) < 0) { Error1("data too long: \"%s\"", address); } else if (result > 0) { Error1("syntax error in \"%s\"", address); } us->soa.sa_family = pf; uslen = ussize + sizeof(us->soa.sa_family) #if HAVE_STRUCT_SOCKADDR_SALEN + sizeof(us->soa.sa_len); #endif ; sfd->dtype = XIOREAD_RECV|XIOWRITE_SENDTO; if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) { if (xioparserange(rangename, 0, &sfd->para.socket.range, sfd->para.socket.ip.ai_flags) < 0) { free(rangename); return STAT_NORETRY; } sfd->para.socket.dorange = true; free(rangename); } if ((result = _xioopen_dgram_recvfrom(sfd, xioflags, &us->soa, uslen, opts, pf, socktype, proto, E_ERROR)) != STAT_OK) { return result; } _xio_openlate(sfd, opts); return STAT_OK; } /* we expect the form: ...:domain:type:protocol:local-address */ static int xioopen_socket_recv( int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, const struct addrdesc *addrdesc) { struct single *sfd = &xxfd->stream; const char *pfname = argv[1]; const char *typename = argv[2]; const char *protname = argv[3]; const char *address = argv[4]; char *garbage; union sockaddr_union us; socklen_t uslen; size_t ussize; int pf, socktype, proto; char *rangename; int result; if (argc != 5) { xio_syntax(argv[0], 4, argc-1, addrdesc->syntax); return STAT_NORETRY; } pf = strtoul(pfname, &garbage, 0); if (*garbage != '\0') { Warn1("garbage in parameter: \"%s\"", garbage); } socktype = strtoul(typename, &garbage, 0); if (*garbage != '\0') { Warn1("garbage in parameter: \"%s\"", garbage); } proto = strtoul(protname, &garbage, 0); if (*garbage != '\0') { Warn1("garbage in parameter: \"%s\"", garbage); } retropt_socket_pf(opts, &pf); retropt_int(opts, OPT_SO_TYPE, &socktype); /*retropt_int(opts, OPT_IP_PROTOCOL, &proto);*/ if (sfd->howtoend == END_UNSPEC) sfd->howtoend = END_NONE; ussize = 0; if ((result = dalan(address, (uint8_t *)&us.soa.sa_data, &ussize, sizeof(us), 'i')) < 0) { Error1("data too long: \"%s\"", address); } else if (result > 0) { Error1("syntax error in \"%s\"", address); } us.soa.sa_family = pf; uslen = ussize + sizeof(sa_family_t) #if HAVE_STRUCT_SOCKADDR_SALEN +sizeof(us.soa.sa_len) #endif ; sfd->dtype = XIOREAD_RECV; sfd->para.socket.la.soa.sa_family = pf; if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) { if (xioparserange(rangename, 0, &sfd->para.socket.range, sfd->para.socket.ip.ai_flags) < 0) { free(rangename); return STAT_NORETRY; } sfd->para.socket.dorange = true; free(rangename); } if ((result = _xioopen_dgram_recv(sfd, xioflags, &us.soa, uslen, opts, pf, socktype, proto, E_ERROR)) != STAT_OK) { return result; } _xio_openlate(sfd, opts); return STAT_OK; } /* we expect the form: ...:domain:type:protocol:remote-address */ static int xioopen_socket_datagram( int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, const struct addrdesc *addrdesc) { xiosingle_t *sfd = &xxfd->stream; const char *pfname = argv[1]; const char *typename = argv[2]; const char *protname = argv[3]; const char *address = argv[4]; char *garbage; char *rangename; size_t themsize; int pf; int result; if (argc != 5) { xio_syntax(argv[0], 4, argc-1, addrdesc->syntax); return STAT_NORETRY; } pf = strtoul(pfname, &garbage, 0); if (*garbage != '\0') { Warn1("garbage in parameter: \"%s\"", garbage); } retropt_socket_pf(opts, &pf); /*retropt_int(opts, OPT_IP_PROTOCOL, &proto);*/ if (sfd->howtoend == END_UNSPEC) sfd->howtoend = END_SHUTDOWN; sfd->peersa.soa.sa_family = pf; themsize = 0; if ((result = dalan(address, (uint8_t *)&sfd->peersa.soa.sa_data, &themsize, sizeof(sfd->peersa), 'i')) < 0) { Error1("data too long: \"%s\"", address); } else if (result > 0) { Error1("syntax error in \"%s\"", address); } sfd->salen = themsize + sizeof(sa_family_t); #if HAVE_STRUCT_SOCKADDR_SALEN sfd->peersa.soa.sa_len = sizeof(sfd->peersa.soa.sa_len) + sizeof(sfd->peersa.soa.sa_family) + themsize; #endif if ((result = _xioopen_socket_sendto(pfname, typename, protname, address, opts, xioflags, xxfd, addrdesc->groups)) != STAT_OK) { return result; } sfd->dtype = XIOREAD_RECV|XIOWRITE_SENDTO; sfd->para.socket.la.soa.sa_family = sfd->peersa.soa.sa_family; /* which reply sockets will accept - determine by range option */ if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) { if (xioparserange(rangename, 0, &sfd->para.socket.range, sfd->para.socket.ip.ai_flags) < 0) { free(rangename); return STAT_NORETRY; } sfd->para.socket.dorange = true; sfd->dtype |= XIOREAD_RECV_CHECKRANGE; free(rangename); } _xio_openlate(sfd, opts); return STAT_OK; } #endif /* WITH_GENERICSOCKET */ /* EINTR not handled specially */ int xiogetpacketinfo(struct single *sfd, int fd) { #if defined(MSG_ERRQUEUE) int _errno = errno; char peername[256]; union sockaddr_union _peername; /* union sockaddr_union _sockname; */ union sockaddr_union *pa = &_peername; /* peer address */ /* union sockaddr_union *la = &_sockname; */ /* local address */ socklen_t palen = sizeof(_peername); /* peer address size */ char ctrlbuff[1024]; /* ancillary messages */ struct msghdr msgh = {0}; msgh.msg_name = pa; msgh.msg_namelen = palen; #if HAVE_STRUCT_MSGHDR_MSGCONTROL msgh.msg_control = ctrlbuff; #endif #if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN msgh.msg_controllen = sizeof(ctrlbuff); #endif if (xiogetancillary(fd, &msgh, MSG_ERRQUEUE #ifdef MSG_TRUNC |MSG_TRUNC #endif ) >= 0 ) { palen = msgh.msg_namelen; Notice1("receiving packet from %s"/*"src"*/, sockaddr_info(&pa->soa, palen, peername, sizeof(peername))/*, sockaddr_info(&la->soa, sockname, sizeof(sockname))*/); xiodopacketinfo(sfd, &msgh, true, true); } errno = _errno; #endif /* defined(MSG_ERRQUEUE) */ return 0; } /* A subroutine that is common to all socket addresses that want to connect() a socket to a peer. Applies and consumes the following options: PH_PASTSOCKET, PH_FD, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_CONNECT, PH_CONNECTED, PH_LATE, OFUNC_OFFSET, OPT_SO_TYPE, OPT_SO_PROTOTYPE, OPT_USER, OPT_GROUP, OPT_CLOEXEC Does not fork, does not retry. Alternate (alt) bind semantics are: with IP sockets: lowport (selects randomly a free port from 640 to 1023) with UNIX and abstract sockets: uses tmpname() to find a free file system entry. returns 0 on success. */ int _xioopen_connect(struct single *sfd, union sockaddr_union *us, size_t uslen, struct sockaddr *them, size_t themlen, struct opt *opts, int pf, int socktype, int protocol, bool alt, int level) { int fcntl_flags = 0; char infobuff[256]; union sockaddr_union la; socklen_t lalen = themlen; int _errno; int result; #if WITH_UNIX if (pf == PF_UNIX && us != NULL) { applyopts_named(us->un.sun_path, opts, PH_EARLY); } #endif if ((sfd->fd = xiosocket(opts, pf, socktype, protocol, level)) < 0) { return STAT_RETRYLATER; } applyopts_offset(sfd, opts); applyopts(sfd, -1, opts, PH_PASTSOCKET); applyopts(sfd, -1, opts, PH_FD); applyopts_cloexec(sfd->fd, opts); if (xiobind(sfd, us, uslen, opts, pf, alt, level) < 0) { return -1; } applyopts(sfd, -1, opts, PH_CONNECT); if (sfd->para.socket.connect_timeout.tv_sec != 0 || sfd->para.socket.connect_timeout.tv_usec != 0) { fcntl_flags = Fcntl(sfd->fd, F_GETFL); Fcntl_l(sfd->fd, F_SETFL, fcntl_flags|O_NONBLOCK); } result = Connect(sfd->fd, them, themlen); _errno = errno; la.soa.sa_family = them->sa_family; lalen = sizeof(la); if (Getsockname(sfd->fd, &la.soa, &lalen) < 0) { Msg4(level-1, "getsockname(%d, %p, {%d}): %s", sfd->fd, &la.soa, lalen, strerror(errno)); } errno = _errno; if (result < 0) { if (errno == EINPROGRESS) { if (sfd->para.socket.connect_timeout.tv_sec != 0 || sfd->para.socket.connect_timeout.tv_usec != 0) { struct timeval timeout; struct pollfd writefd; int err; socklen_t errlen = sizeof(err); int result; Info4("connect(%d, %s, "F_Zd"): %s", sfd->fd, sockaddr_info(them, themlen, infobuff, sizeof(infobuff)), themlen, strerror(errno)); timeout = sfd->para.socket.connect_timeout; writefd.fd = sfd->fd; writefd.events = (POLLOUT|POLLERR); result = xiopoll(&writefd, 1, &timeout); if (result < 0) { Msg4(level, "xiopoll({%d,POLLOUT|POLLERR},,{"F_tv_sec"."F_tv_usec"): %s", sfd->fd, timeout.tv_sec, timeout.tv_usec, strerror(errno)); Close(sfd->fd); return STAT_RETRYLATER; } if (result == 0) { Msg2(level, "connecting to %s: %s", sockaddr_info(them, themlen, infobuff, sizeof(infobuff)), strerror(ETIMEDOUT)); Close(sfd->fd); return STAT_RETRYLATER; } if (writefd.revents & POLLERR) { #if 0 unsigned char dummy[1]; Read(sfd->fd, &dummy, 1); /* get error message */ Msg2(level, "connecting to %s: %s", sockaddr_info(them, infobuff, sizeof(infobuff)), strerror(errno)); #else Connect(sfd->fd, them, themlen); /* get error message */ Msg4(level, "connect(%d, %s, "F_Zd"): %s", sfd->fd, sockaddr_info(them, themlen, infobuff, sizeof(infobuff)), themlen, strerror(errno)); #endif Close(sfd->fd); return STAT_RETRYLATER; } /* otherwise OK or network error */ result = Getsockopt(sfd->fd, SOL_SOCKET, SO_ERROR, &err, &errlen); if (result != 0) { Msg2(level, "getsockopt(%d, SOL_SOCKET, SO_ERROR, ...): %s", sfd->fd, strerror(err)); Close(sfd->fd); return STAT_RETRYLATER; } Debug2("getsockopt(%d, SOL_SOCKET, SO_ERROR, { %d }) -> 0", sfd->fd, err); if (err != 0) { Msg4(level, "connect(%d, %s, "F_Zd"): %s", sfd->fd, sockaddr_info(them, themlen, infobuff, sizeof(infobuff)), themlen, strerror(err)); Close(sfd->fd); return STAT_RETRYLATER; } Fcntl_l(sfd->fd, F_SETFL, fcntl_flags); } else { Warn4("connect(%d, %s, "F_Zd"): %s", sfd->fd, sockaddr_info(them, themlen, infobuff, sizeof(infobuff)), themlen, strerror(errno)); } } else if (pf == PF_UNIX) { /* this is for UNIX domain sockets: a connect attempt seems to be the only way to distinguish stream and datagram sockets. And no ancillary message expected */ int _errno = errno; Info4("connect(%d, %s, "F_Zd"): %s", sfd->fd, sockaddr_info(them, themlen, infobuff, sizeof(infobuff)), themlen, strerror(errno)); /* caller must handle this condition */ Close(sfd->fd); sfd->fd = -1; errno = _errno; return STAT_RETRYLATER; } else { /* try to find details about error, especially from ICMP */ xiogetpacketinfo(sfd, sfd->fd); /* continue mainstream */ Msg4(level, "connect(%d, %s, "F_Zd"): %s", sfd->fd, sockaddr_info(them, themlen, infobuff, sizeof(infobuff)), themlen, strerror(errno)); Close(sfd->fd); return STAT_RETRYLATER; } } else { /* result >= 0 */ Notice1("successfully connected from local address %s", sockaddr_info(&la.soa, themlen, infobuff, sizeof(infobuff))); } applyopts_fchown(sfd->fd, opts); /* OPT_USER, OPT_GROUP */ applyopts(sfd, -1, opts, PH_CONNECTED); #if WITH_UNIX if (pf == PF_UNIX && us != NULL) { applyopts_named(us->un.sun_path, opts, PH_LATE); } #endif applyopts(sfd, -1, opts, PH_LATE); return STAT_OK; } /* a subroutine that is common to all socket addresses that want to connect to a peer address. might fork. applies and consumes the following option: PH_PASTSOCKET, PH_FD, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_CONNECT, PH_CONNECTED, PH_LATE, OFUNC_OFFSET, OPT_FORK, OPT_SO_TYPE, OPT_SO_PROTOTYPE, OPT_USER, OPT_GROUP, OPT_CLOEXEC returns 0 on success. */ int xioopen_connect(struct single *sfd, union sockaddr_union *us, size_t uslen, struct sockaddr *them, size_t themlen, struct opt *opts, int pf, int socktype, int protocol, bool alt) { bool dofork = false; struct opt *opts0; char infobuff[256]; int level; int result; retropt_bool(opts, OPT_FORK, &dofork); opts0 = copyopts(opts, GROUP_ALL); Notice1("opening connection to %s", sockaddr_info(them, themlen, infobuff, sizeof(infobuff))); do { /* loop over retries and forks */ #if WITH_RETRY if (sfd->forever || sfd->retry) { level = E_INFO; } else #endif /* WITH_RETRY */ level = E_ERROR; result = _xioopen_connect(sfd, us, uslen, them, themlen, opts, pf, socktype, protocol, alt, level); switch (result) { case STAT_OK: break; #if WITH_RETRY case STAT_RETRYLATER: if (sfd->forever || sfd->retry) { --sfd->retry; if (result == STAT_RETRYLATER) { Nanosleep(&sfd->intervall, NULL); } dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL); continue; } return STAT_NORETRY; #endif /* WITH_RETRY */ default: return result; } if (dofork) { xiosetchilddied(); /* set SIGCHLD handler */ } #if WITH_RETRY if (dofork) { pid_t pid; int level = E_ERROR; if (sfd->forever || sfd->retry) { level = E_WARN; /* most users won't expect a problem here, so Notice is too weak */ } while ((pid = xio_fork(false, level, sfd->shutup)) < 0) { --sfd->retry; if (sfd->forever || sfd->retry) { dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL); Nanosleep(&sfd->intervall, NULL); continue; } return STAT_RETRYLATER; } if (pid == 0) { /* child process */ break; } /* parent process */ Close(sfd->fd); /* with and without retry */ Nanosleep(&sfd->intervall, NULL); dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL); continue; /* with next socket() bind() connect() */ } else #endif /* WITH_RETRY */ { break; } #if 0 if ((result = _xio_openlate(fd, opts)) < 0) return result; #endif } while (true); return 0; } /* common to xioopen_udp_sendto, ..unix_sendto, ..rawip applies and consumes the following option: PH_PASTSOCKET, PH_FD, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_CONNECTED, PH_LATE OFUNC_OFFSET OPT_SO_TYPE, OPT_SO_PROTOTYPE, OPT_USER, OPT_GROUP, OPT_CLOEXEC */ int _xioopen_dgram_sendto(/* them is already in xfd->peersa */ union sockaddr_union *us, socklen_t uslen, struct opt *opts, int xioflags, xiosingle_t *sfd, groups_t groups, int pf, int socktype, int ipproto, bool alt) { int level = E_ERROR; union sockaddr_union la; socklen_t lalen = sizeof(la); char infobuff[256]; #if WITH_UNIX if (pf == PF_UNIX && us != NULL) { applyopts_named(us->un.sun_path, opts, PH_EARLY); } #endif if ((sfd->fd = xiosocket(opts, pf, socktype, ipproto, level)) < 0) { return STAT_RETRYLATER; } applyopts_offset(sfd, opts); applyopts_single(sfd, opts, PH_PASTSOCKET); applyopts(sfd, -1, opts, PH_PASTSOCKET); applyopts_single(sfd, opts, PH_FD); applyopts(sfd, -1, opts, PH_FD); applyopts_cloexec(sfd->fd, opts); if (xiobind(sfd, us, uslen, opts, pf, alt, level) < 0) { return -1; } /*applyopts(sfd, -1, opts, PH_CONNECT);*/ if (Getsockname(sfd->fd, &la.soa, &lalen) < 0) { Warn4("getsockname(%d, %p, {%d}): %s", sfd->fd, &la.soa, lalen, strerror(errno)); } applyopts_fchown(sfd->fd, opts); applyopts(sfd, -1, opts, PH_CONNECTED); #if WITH_UNIX if (pf == PF_UNIX && us != NULL) { applyopts_named(us->un.sun_path, opts, PH_LATE); } #endif /*0 applyopts(sfd, -1, opts, PH_LATE); */ /* sfd->dtype = DATA_RECVFROM; *//* no, the caller must set this (ev _SKIPIP) */ Notice1("successfully prepared local socket %s", sockaddr_info(&la.soa, lalen, infobuff, sizeof(infobuff))); return STAT_OK; } /* when the recvfrom address (with option fork) receives a packet it keeps this packet in the IP stacks input queue and forks a sub process. The sub process then reads this packet for processing its data. There is a problem because the parent process would find the same packet again if it calls select()/poll() before the child process has read the packet. To solve this problem we implement the following mechanism: Before forking an unnamed pipe (fifo) is created. The sub process closes the write side when it has read the packet. The parent process waits until the read side of the pipe gives EOF and only then continues to listen. */ /* waits for incoming packet, checks its source address and port. Depending on fork option, it may fork a subprocess. Returns STAT_OK if a packet was accepted; with fork option, this is already in a new subprocess! Other return values indicate a problem; this can happen in the master process or in a subprocess. This function does not retry. If you need retries, handle this is a loop in the calling function. after fork, we set the forever/retry of the child process to 0 applies and consumes the following options: PH_INIT, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_EARLY, PH_PREOPEN, PH_FD, PH_CONNECTED, PH_LATE, PH_LATE2 OPT_FORK, OPT_SO_TYPE, OPT_SO_PROTOTYPE, cloexec, OPT_RANGE, tcpwrap EINTR is not handled specially. */ int _xioopen_dgram_recvfrom(struct single *sfd, int xioflags, struct sockaddr *us, socklen_t uslen, struct opt *opts, int pf, int socktype, int proto, int level) { char *rangename; bool dofork = false; pid_t pid; /* mostly int; only used with fork */ char infobuff[256]; char lisname[256]; bool drop = false; /* true if current packet must be dropped */ int result; retropt_bool(opts, OPT_FORK, &dofork); if (dofork) { if (!(xioflags & XIO_MAYFORK)) { Error("option fork not allowed here"); return STAT_NORETRY; } sfd->flags |= XIO_DOESFORK; } if (applyopts_single(sfd, opts, PH_INIT) < 0) return STAT_NORETRY; if ((sfd->fd = xiosocket(opts, pf, socktype, proto, level)) < 0) { return STAT_RETRYLATER; } applyopts(sfd, -1, opts, PH_PASTSOCKET); /*! applyopts(sfd, -1, opts, PH_FD); */ applyopts_cloexec(sfd->fd, opts); if (xiobind(sfd, (union sockaddr_union *)us, uslen, opts, pf, 0, level) < 0) { return -1; } applyopts(sfd, -1, opts, PH_PASTBIND); #if WITH_UNIX if (pf == AF_UNIX && us != NULL) { applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_FD); applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_EARLY); applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_PREOPEN); } #endif /* WITH_UNIX */ #if WITH_IP4 /*|| WITH_IP6*/ switch (proto) { case IPPROTO_UDP: #ifdef IPPROTO_UDPLITE case IPPROTO_UDPLITE: #endif if (pf == PF_INET && ((struct sockaddr_in *)us)->sin_port == 0 || pf == PF_INET6 && ((struct sockaddr_in6 *)us)->sin6_port == 0) { struct sockaddr_storage bound; socklen_t bndlen = sizeof(bound); char sockbuff[256]; Getsockname(sfd->fd, (struct sockaddr *)&bound, &bndlen); sockaddr_info((struct sockaddr *)&bound, sizeof(struct sockaddr_storage), sockbuff, sizeof(sockbuff)); Notice1("_xioopen_dgram_recvfrom(): bound to %s", sockbuff); } } #endif /* for generic sockets, this has already been retrieved */ if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) { if (xioparserange(rangename, pf, &sfd->para.socket.range, sfd->para.socket.ip.ai_flags) < 0) { free(rangename); return STAT_NORETRY; } free(rangename); sfd->para.socket.dorange = true; } #if (WITH_TCP || WITH_UDP) && WITH_LIBWRAP xio_retropt_tcpwrap(sfd, opts); #endif /* && (WITH_TCP || WITH_UDP) && WITH_LIBWRAP */ if (xioparms.logopt == 'm') { Info("starting recvfrom loop, switching to syslog"); diag_set('y', xioparms.syslogfac); xioparms.logopt = 'y'; } else { Info("starting recvfrom loop"); } if (dofork) { xiosetchilddied(); } while (true) { /* but we only loop if fork option is set */ char peername[256]; union sockaddr_union _peername; union sockaddr_union _sockname; union sockaddr_union *pa = &_peername; /* peer address */ union sockaddr_union *la = &_sockname; /* local address */ socklen_t palen = sizeof(_peername); /* peer address size */ char ctrlbuff[1024]; /* ancillary messages */ struct msghdr msgh = {0}; int trigger[2]; /* for socketpair that indicates consumption of packet */ int rc; socket_init(pf, pa); if (drop) { char *dummy[2]; Recv(sfd->fd, dummy, sizeof(dummy), 0); drop = true; } Info("Recvfrom: Checking/waiting for next packet"); /* loop until select()/poll() returns valid */ do { struct pollfd readfd; /*? int level = E_ERROR;*/ if (us != NULL) { Notice1("receiving on %s", sockaddr_info(us, uslen, lisname, sizeof(lisname))); } else { Notice1("receiving IP protocol %u", proto); } readfd.fd = sfd->fd; readfd.events = POLLIN; if (xiopoll(&readfd, 1, NULL) > 0) { break; } if (errno == EINTR) { continue; } Msg2(level, "poll({%d,,},,-1): %s", sfd->fd, strerror(errno)); Close(sfd->fd); return STAT_RETRYLATER; } while (true); msgh.msg_name = pa; msgh.msg_namelen = palen; #if HAVE_STRUCT_MSGHDR_MSGCONTROL msgh.msg_control = ctrlbuff; #endif #if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN msgh.msg_controllen = sizeof(ctrlbuff); #endif while ((rc = xiogetancillary(sfd->fd, &msgh, MSG_PEEK #ifdef MSG_TRUNC |MSG_TRUNC #endif )) < 0 && errno == EINTR) ; if (rc < 0) return STAT_RETRYLATER; palen = msgh.msg_namelen; Notice1("receiving packet from %s"/*"src"*/, sockaddr_info(&pa->soa, palen, peername, sizeof(peername))/*, sockaddr_info(&la->soa, sockname, sizeof(sockname))*/); xiodopacketinfo(sfd, &msgh, true, true); if (xiocheckpeer(sfd, pa, la) < 0) { /* drop packet */ char buff[512]; Recv(sfd->fd, buff, sizeof(buff), 0); continue; } Info1("permitting packet from %s", sockaddr_info(&pa->soa, palen, infobuff, sizeof(infobuff))); /* set the env vars describing the local and remote sockets */ /*xiosetsockaddrenv("SOCK", la, lalen, proto);*/ xiosetsockaddrenv("PEER", pa, palen, proto); applyopts(sfd, -1, opts, PH_FD); applyopts(sfd, -1, opts, PH_CONNECTED); sfd->peersa = *(union sockaddr_union *)pa; sfd->salen = palen; if (dofork) { Info("Generating socketpair that triggers parent when packet has been consumed"); if (Socketpair(PF_UNIX, SOCK_STREAM, 0, trigger) < 0) { Error1("socketpair(PF_UNIX, SOCK_STREAM, 0, ...): %s", strerror(errno)); } if ((pid = xio_fork(false, level, sfd->shutup)) < 0) { Close(trigger[0]); Close(trigger[1]); Close(sfd->fd); return STAT_RETRYLATER; } if (pid == 0) { /* child */ Close(trigger[0]); sfd->triggerfd = trigger[1]; Fcntl_l(sfd->triggerfd, F_SETFD, FD_CLOEXEC); #if WITH_RETRY /* !? */ sfd->retry = 0; sfd->forever = 0; level = E_ERROR; #endif /* WITH_RETRY */ #if WITH_UNIX /* with UNIX sockets: only listening parent is allowed to remove the socket file */ sfd->opt_unlink_close = false; #endif /* WITH_UNIX */ break; } /* Parent */ Close(trigger[1]); { char buf[1]; while (Read(trigger[0], buf, 1) < 0 && errno == EINTR) ; } Close(trigger[0]); Info("continue listening"); } else { break; } } if ((result = _xio_openlate(sfd, opts)) != 0) return STAT_NORETRY; return STAT_OK; } /* returns STAT_* */ int _xioopen_dgram_recv(struct single *sfd, int xioflags, struct sockaddr *us, socklen_t uslen, struct opt *opts, int pf, int socktype, int proto, int level) { char *rangename; if (applyopts_single(sfd, opts, PH_INIT) < 0) return STAT_NORETRY; if ((sfd->fd = xiosocket(opts, pf, socktype, proto, level)) < 0) { return STAT_RETRYLATER; } applyopts(sfd, -1, opts, PH_PASTSOCKET); /*! applyopts(sfd, -1, opts, PH_FD); */ applyopts_cloexec(sfd->fd, opts); if (xiobind(sfd, (union sockaddr_union *)us, uslen, opts, pf, 0, level) < 0) { return -1; } #if WITH_UNIX if (pf == AF_UNIX && us != NULL) { applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_FD); applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_EARLY); applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_PREOPEN); } #endif /* WITH_UNIX */ #if WITH_IP4 /*|| WITH_IP6*/ switch (proto) { case IPPROTO_UDP: #ifdef IPPROTO_UDPLITE case IPPROTO_UDPLITE: #endif if (pf == PF_INET && ((struct sockaddr_in *)us)->sin_port == 0 || pf == PF_INET6 && ((struct sockaddr_in6 *)us)->sin6_port == 0) { struct sockaddr_storage bound; socklen_t bndlen = sizeof(bound); char sockbuff[256]; Getsockname(sfd->fd, (struct sockaddr *)&bound, &bndlen); sockaddr_info((struct sockaddr *)&bound, sizeof(struct sockaddr_storage), sockbuff, sizeof(sockbuff)); Notice1("_xioopen_dgram_recv(): bound to %s", sockbuff); } } #endif if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) { if (xioparserange(rangename, pf, &sfd->para.socket.range, sfd->para.socket.ip.ai_flags) < 0) { free(rangename); return STAT_NORETRY; } free(rangename); sfd->para.socket.dorange = true; } #if (WITH_TCP || WITH_UDP) && WITH_LIBWRAP xio_retropt_tcpwrap(sfd, opts); #endif /* && (WITH_TCP || WITH_UDP) && WITH_LIBWRAP */ if (xioparms.logopt == 'm') { Info("starting recv loop, switching to syslog"); diag_set('y', xioparms.syslogfac); xioparms.logopt = 'y'; } else { Info("starting recv loop"); } return STAT_OK; } int retropt_socket_pf(struct opt *opts, int *pf) { char *pfname; if (retropt_string(opts, OPT_PROTOCOL_FAMILY, &pfname) >= 0) { if (isdigit((unsigned char)pfname[0])) { *pf = strtoul(pfname, NULL /*!*/, 0); #if WITH_IP4 } else if (!strcasecmp("inet", pfname) || !strcasecmp("inet4", pfname) || !strcasecmp("ip4", pfname) || !strcasecmp("ipv4", pfname)) { *pf = PF_INET; #endif /* WITH_IP4 */ #if WITH_IP6 } else if (!strcasecmp("inet6", pfname) || !strcasecmp("ip6", pfname) || !strcasecmp("ipv6", pfname)) { *pf = PF_INET6; #endif /* WITH_IP6 */ } else { Error1("unknown protocol family \"%s\"", pfname); /*! Warn("falling back to INET");*/ } free(pfname); return 0; } return -1; } /* This function calls recvmsg(..., MSG_PEEK, ...) to obtain information about the arriving packet, thus it does not "consume" the packet. In msgh the msg_name pointer must refer to an (empty) sockaddr storage. Returns STAT_OK on success, or STAT_RETRYLATER when an error occurred, including EINTR. (recvmsg() retrieves just meta info, not the data) */ int xiogetancillary(int fd, struct msghdr *msgh, int flags) { char peekbuff[1]; #if HAVE_STRUCT_IOVEC struct iovec iovec; #endif #if HAVE_STRUCT_IOVEC iovec.iov_base = peekbuff; iovec.iov_len = sizeof(peekbuff); msgh->msg_iov = &iovec; msgh->msg_iovlen = 1; #endif #if HAVE_STRUCT_MSGHDR_MSGFLAGS msgh->msg_flags = 0; #endif if (Recvmsg(fd, msgh, flags) < 0) { Info1("recvmsg(): %s", strerror(errno)); return STAT_RETRYLATER; } return STAT_OK; } /* works through the ancillary messages found in the given socket header record and logs the relevant information (E_DEBUG, E_INFO). calls protocol/layer specific functions for handling the messages creates appropriate environment vars if withenv is set */ int xiodopacketinfo( struct single *sfd, struct msghdr *msgh, bool withlog, bool withenv) { #if defined(HAVE_STRUCT_CMSGHDR) && defined(CMSG_DATA) struct cmsghdr *cmsg; /* parse ancillary messages */ cmsg = CMSG_FIRSTHDR(msgh); while (cmsg != NULL) { int num = 0; /* number of data components of a ancill.msg */ int i; char typbuff[16], *typp; char nambuff[128], *namp; char valbuff[256], *valp; char envbuff[256], *envp; Info3("ancillary message in xiodopacketinfo(): len="F_Zu", level=%d, type=%d", cmsg->cmsg_len, cmsg->cmsg_level, cmsg->cmsg_type); if (withlog) { xiodump(CMSG_DATA(cmsg), cmsg->cmsg_len-((char *)CMSG_DATA(cmsg)-(char *)cmsg), valbuff, sizeof(valbuff)-1, 0); Debug4("ancillary message: len="F_cmsg_len", level=%d, type=%d, data=%s", cmsg->cmsg_len, cmsg->cmsg_level, cmsg->cmsg_type, valbuff); } /* try to get the anc.msg. contents in handy components, protocol/level dependent */ switch (cmsg->cmsg_level) { case SOL_SOCKET: xiolog_ancillary_socket(sfd, cmsg, &num, typbuff, sizeof(typbuff)-1, nambuff, sizeof(nambuff)-1, envbuff, sizeof(envbuff)-1, valbuff, sizeof(valbuff)-1); break; #if WITH_IP4 || WITH_IP6 case SOL_IP: xiolog_ancillary_ip(sfd, cmsg, &num, typbuff, sizeof(typbuff)-1, nambuff, sizeof(nambuff)-1, envbuff, sizeof(envbuff)-1, valbuff, sizeof(valbuff)-1); break; #endif /* WITH_IP4 || WITH_IP6 */ #if WITH_IP6 case SOL_IPV6: xiolog_ancillary_ip6(sfd, cmsg, &num, typbuff, sizeof(typbuff)-1, nambuff, sizeof(nambuff)-1, envbuff, sizeof(envbuff)-1, valbuff, sizeof(valbuff)-1); break; #endif /* WITH_IP6 */ #if _WITH_INTERFACE && HAVE_STRUCT_CMSGHDR && HAVE_STRUCT_TPACKET_AUXDATA case SOL_PACKET: xiolog_ancillary_packet(sfd, cmsg, &num, typbuff, sizeof(typbuff)-1, nambuff, sizeof(nambuff)-1, envbuff, sizeof(envbuff)-1, valbuff, sizeof(valbuff)-1); break; #endif /* HAVE_STRUCT_CMSGHDR && HAVE_STRUCT_TPACKET_AUXDATA */ default: num = 1; snprintf(typbuff, sizeof(typbuff)-1, "LEVEL%u", cmsg->cmsg_level); snprintf(nambuff, sizeof(nambuff)-1, "type%u", cmsg->cmsg_type); xiodump(CMSG_DATA(cmsg), cmsg->cmsg_len-((char *)CMSG_DATA(cmsg)-(char *)cmsg), valbuff, sizeof(valbuff)-1, 0); } /* here the info is in typbuff (one string), nambuff (num consecutive strings), and valbuff (num consecutive strings) */ i = 0; typp = typbuff; namp = nambuff; envp = envbuff; valp = valbuff; while (i < num) { if (withlog) { Info3("ancillary message: %s: %s=%s", typp, namp, valp); } if (withenv) { if (*envp) { xiosetenv(envp, valp, 1, NULL); } else if (!strcasecmp(typp+strlen(typp)-strlen(namp), namp)) { xiosetenv(typp, valp, 1, NULL); } else { xiosetenv2(typp, namp, valp, 1, NULL); } } if (++i == num) break; namp = strchr(namp, '\0')+1; envp = strchr(envp, '\0')+1; valp = strchr(valp, '\0')+1; } cmsg = CMSG_NXTHDR(msgh, cmsg); } return 0; #else /* !(defined(HAVE_STRUCT_CMSGHDR) && defined(CMSG_DATA)) */ return -1; #endif /* !(defined(HAVE_STRUCT_CMSGHDR) && defined(CMSG_DATA)) */ } /* check if peer address is within permitted range. return >= 0 if so. */ int xiocheckrange(union sockaddr_union *sa, struct xiorange *range) { switch (sa->soa.sa_family) { #if WITH_IP4 case PF_INET: return xiocheckrange_ip4(&sa->ip4, range); #endif /* WITH_IP4 */ #if WITH_IP6 case PF_INET6: return xiocheckrange_ip6(&sa->ip6, range); #endif /* WITH_IP6 */ #if 0 case PF_UNSPEC: { socklen_t i; for (i = 0; i < sizeof(sa->soa.sa_data); ++i) { if ((range->netmask.soa.sa_data[i] & sa->soa.sa_data[i]) != range->netaddr.soa.sa_data[i]) { return -1; } } return 0; } #endif } return -1; } int xiocheckpeer(xiosingle_t *sfd, union sockaddr_union *pa, union sockaddr_union *la) { char infobuff[256]; int result; #if WITH_IP4 if (sfd->para.socket.dorange) { if (pa == NULL) { return -1; } if (xiocheckrange(pa, &sfd->para.socket.range) < 0) { char infobuff[256]; Warn1("refusing connection from %s due to range option", sockaddr_info(&pa->soa, 0, infobuff, sizeof(infobuff))); return -1; } Info1("permitting connection from %s due to range option", sockaddr_info(&pa->soa, 0, infobuff, sizeof(infobuff))); } #endif /* WITH_IP4 */ #if WITH_TCP || WITH_UDP if (sfd->para.socket.ip.dosourceport) { if (pa == NULL) { return -1; } #if WITH_IP4 if (pa->soa.sa_family == AF_INET && ntohs(((struct sockaddr_in *)pa)->sin_port) != sfd->para.socket.ip.sourceport) { Warn1("refusing connection from %s due to wrong sourceport", sockaddr_info(&pa->soa, 0, infobuff, sizeof(infobuff))); return -1; } #endif /* WITH_IP4 */ #if WITH_IP6 if (pa->soa.sa_family == AF_INET6 && ntohs(((struct sockaddr_in6 *)pa)->sin6_port) != sfd->para.socket.ip.sourceport) { Warn1("refusing connection from %s due to wrong sourceport", sockaddr_info(&pa->soa, 0, infobuff, sizeof(infobuff))); return -1; } #endif /* WITH_IP6 */ Info1("permitting connection from %s due to sourceport option", sockaddr_info(&pa->soa, 0, infobuff, sizeof(infobuff))); } else if (sfd->para.socket.ip.lowport) { if (pa == NULL) { return -1; } if (pa->soa.sa_family == AF_INET && ntohs(((struct sockaddr_in *)pa)->sin_port) >= IPPORT_RESERVED) { Warn1("refusing connection from %s due to lowport option", sockaddr_info(&pa->soa, 0, infobuff, sizeof(infobuff))); return -1; } #if WITH_IP6 else if (pa->soa.sa_family == AF_INET6 && ntohs(((struct sockaddr_in6 *)pa)->sin6_port) >= IPPORT_RESERVED) { Warn1("refusing connection from %s due to lowport option", sockaddr_info(&pa->soa, 0, infobuff, sizeof(infobuff))); return -1; } #endif /* WITH_IP6 */ Info1("permitting connection from %s due to lowport option", sockaddr_info(&pa->soa, 0, infobuff, sizeof(infobuff))); } #endif /* WITH_TCP || WITH_UDP */ #if (WITH_TCP || WITH_UDP) && WITH_LIBWRAP result = xio_tcpwrap_check(sfd, la, pa); if (result < 0) { char infobuff[256]; Warn1("refusing connection from %s due to tcpwrapper option", sockaddr_info(&pa->soa, 0, infobuff, sizeof(infobuff))); return -1; } else if (result > 0) { Info1("permitting connection from %s due to tcpwrapper option", sockaddr_info(&pa->soa, 0, infobuff, sizeof(infobuff))); } #endif /* (WITH_TCP || WITH_UDP) && WITH_LIBWRAP */ return 0; /* permitted */ } #if HAVE_STRUCT_CMSGHDR /* converts the ancillary message in *cmsg into a form useable for further processing. knows the specifics of common message types. returns the number of resulting syntax elements in *num returns a sequence of \0 terminated type strings in *typbuff returns a sequence of \0 terminated name strings in *nambuff returns a sequence of \0 terminated value strings in *valbuff the respective len parameters specify the available space in the buffers returns STAT_OK or other STAT_* */ static int xiolog_ancillary_socket( struct single *sfd, struct cmsghdr *cmsg, int *num, char *typbuff, int typlen, char *nambuff, int namlen, char *envbuff, int envlen, char *valbuff, int vallen) { const char *cmsgtype, *cmsgname, *cmsgenvn; size_t msglen; struct timeval *tv; int rc = STAT_OK; #if defined(CMSG_DATA) msglen = cmsg->cmsg_len-((char *)CMSG_DATA(cmsg)-(char *)cmsg); switch (cmsg->cmsg_type) { #ifdef SO_PASSCRED case SO_PASSCRED: /* this is really a UNIX/LOCAL message */ /*! needs implementation */ #endif /* SO_PASSCRED */ #ifdef SO_RIGHTS case SO_RIGHTS: /* this is really a UNIX/LOCAL message */ /*! needs implementation */ #endif default: /* binary data */ snprintf(typbuff, typlen, "SOCKET.%u", cmsg->cmsg_type); nambuff[0] = '\0'; strncat(nambuff, "data", namlen-1); xiodump(CMSG_DATA(cmsg), msglen, valbuff, vallen, 0); return STAT_OK; #ifdef SO_TIMESTAMP # ifdef SCM_TIMESTAMP case SCM_TIMESTAMP: # else case SO_TIMESTAMP: # endif tv = (struct timeval *)CMSG_DATA(cmsg); cmsgtype = #ifdef SCM_TIMESTAMP "SCM_TIMESTAMP" /* FreeBSD */ #else "SO_TIMESTAMP" /* Linux */ #endif ; cmsgname = "timestamp"; cmsgenvn = "TIMESTAMP"; { time_t t = tv->tv_sec; ctime_r(&t, valbuff); } snprintf(strchr(valbuff, '\0')-1/*del \n*/, vallen-strlen(valbuff)+1, ", %06ld usecs", (long)tv->tv_usec); break; #endif /* defined(SO_TIMESTAMP) */ ; } /* when we come here we provide a single parameter with type in cmsgtype, name in cmsgname, and value already in valbuff */ *num = 1; if (strlen(cmsgtype) >= typlen) rc = STAT_WARNING; typbuff[0] = '\0'; strncat(typbuff, cmsgtype, typlen-1); if (strlen(cmsgname) >= namlen) rc = STAT_WARNING; nambuff[0] = '\0'; strncat(nambuff, cmsgname, namlen-1); if (strlen(cmsgenvn) >= envlen) rc = STAT_WARNING; envbuff[0] = '\0'; strncat(envbuff, cmsgenvn, envlen-1); return rc; #else /* !defined(CMSG_DATA) */ return STAT_NORETRY; #endif /* !defined(CMSG_DATA) */ } #endif /* HAVE_STRUCT_CMSGHDR */ /* return the name of the interface with given index or NULL if is fails The system call requires an arbitrary socket; the calling program may provide one in parameter ins to avoid creation of a dummy socket. ins must be <0 if it does not specify a socket fd. */ char *xiogetifname(int ind, char *val, int ins) { #if !HAVE_PROTOTYPE_LIB_if_indextoname int s; struct ifreq ifr; if (ins >= 0) { s = ins; } else { if ((s = Socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)) < 0) { Error1("socket(PF_INET, SOCK_DGRAM, IPPROTO_IP): %s", strerror(errno)); return NULL; } } #if HAVE_STRUCT_IFREQ_IFR_INDEX ifr.ifr_index = ind; #elif HAVE_STRUCT_IFREQ_IFR_IFINDEX ifr.ifr_ifindex = ind; #endif #ifdef SIOCGIFNAME if(Ioctl(s, SIOCGIFNAME, &ifr) < 0) { #if HAVE_STRUCT_IFREQ_IFR_INDEX Info3("ioctl(%d, SIOCGIFNAME, {..., ifr_index=%d, ...}: %s", s, ifr.ifr_index, strerror(errno)); #elif HAVE_STRUCT_IFREQ_IFR_IFINDEX Info3("ioctl(%d, SIOCGIFNAME, {..., ifr_ifindex=%d, ...}: %s", s, ifr.ifr_ifindex, strerror(errno)); #endif if (ins < 0) Close(s); return NULL; } #endif /* SIOCGIFNAME */ if (ins < 0) Close(s); strcpy(val, ifr.ifr_name); return val; #else /* HAVE_PROTOTYPE_LIB_if_indextoname */ return if_indextoname(ind, val); #endif /* HAVE_PROTOTYPE_LIB_if_indextoname */ } /* parses a network specification consisting of an address and a mask. */ int xioparsenetwork( const char *rangename, int pf, struct xiorange *range, const int ai_flags[2]) { size_t addrlen = 0, masklen = 0; int result; switch (pf) { #if WITH_IP4 case PF_INET: return xioparsenetwork_ip4(rangename, range, ai_flags); break; #endif /* WITH_IP4 */ #if WITH_IP6 case PF_INET6: return xioparsenetwork_ip6(rangename, range, ai_flags); break; #endif /* WITH_IP6 */ case PF_UNSPEC: { char *addrname; const char *maskname; if ((maskname = strchr(rangename, ':')) == NULL) { Error1("syntax error in range \"%s\" of unspecified address family: use :", rangename); return STAT_NORETRY; } ++maskname; /* skip ':' */ if ((addrname = Malloc(maskname-rangename)) == NULL) { return STAT_NORETRY; } strncpy(addrname, rangename, maskname-rangename-1); /* ok */ addrname[maskname-rangename-1] = '\0'; result = dalan(addrname, (uint8_t *)&range->netaddr.soa.sa_data, &addrlen, sizeof(range->netaddr)-(size_t)(&((struct sockaddr *)0)->sa_data) /* data length */, 'i'); if (result < 0) { Error1("data too long: \"%s\"", addrname); free(addrname); return STAT_NORETRY; } else if (result > 0) { Error1("syntax error in \"%s\"", addrname); free(addrname); return STAT_NORETRY; } free(addrname); result = dalan(maskname, (uint8_t *)&range->netmask.soa.sa_data, &masklen, sizeof(range->netaddr)-(size_t)(&((struct sockaddr *)0)->sa_data) /* data length */, 'i'); if (result < 0) { Error1("data too long: \"%s\"", maskname); return STAT_NORETRY; } else if (result > 0) { Error1("syntax error in \"%s\"", maskname); return STAT_NORETRY; } if (addrlen != masklen) { Error2("network address is "F_Zu" bytes long, mask is "F_Zu" bytes long", addrlen, masklen); /* recover by padding the shorter component with 0 */ memset((char *)&range->netaddr.soa.sa_data+addrlen, 0, MAX(0, addrlen-masklen)); memset((char *)&range->netmask.soa.sa_data+masklen, 0, MAX(0, masklen-addrlen)); } } break; default: Error1("range option not supported with address family %d", pf); return STAT_NORETRY; } return STAT_OK; } /* parses a string of form address/bits or address:mask, and fills the fields of the range union. The addr component is masked with mask. */ int xioparserange( const char *rangename, int pf, struct xiorange *range, const int ai_flags[2]) { int i; if (xioparsenetwork(rangename, pf, range, ai_flags) < 0) { Error2("failed to parse or resolve range \"%s\" (pf=%d)", rangename, pf); return -1; } /* we have parsed the address and mask; now we make sure that the stored address has 0 where mask is 0, to simplify comparisions */ switch (pf) { #if WITH_IP4 case PF_INET: range->netaddr.ip4.sin_addr.s_addr &= range->netmask.ip4.sin_addr.s_addr; break; #endif /* WITH_IP4 */ #if WITH_IP6 case PF_INET6: return xiorange_ip6andmask(range); break; #endif /* WITH_IP6 */ case PF_UNSPEC: for (i = 0; i < sizeof(range->netaddr); ++i) { ((char *)&range->netaddr)[i] &= ((char *)&range->netmask)[i]; } break; default: Error1("range option not supported with address family %d", pf); return STAT_NORETRY; } return 0; } /* set environment variables describing (part of) a socket address, e.g. SOCAT_SOCKADDR. lr (local/remote) specifies a string like "SOCK" or "PEER". proto should correspond to the third parameter of socket(2) and is used to determine the presence of port information. */ int xiosetsockaddrenv(const char *lr, union sockaddr_union *sau, socklen_t salen, int proto) { # define XIOSOCKADDRENVLEN 256 char namebuff[XIOSOCKADDRENVLEN]; char valuebuff[XIOSOCKADDRENVLEN]; int idx = 0, result; strcpy(namebuff, lr); switch (sau->soa.sa_family) { #if WITH_UNIX case PF_UNIX: result = xiosetsockaddrenv_unix(idx, strchr(namebuff, '\0'), XIOSOCKADDRENVLEN-strlen(lr), valuebuff, XIOSOCKADDRENVLEN, &sau->un, salen, proto); xiosetenv(namebuff, valuebuff, 1, NULL); break; #endif /* WITH_UNIX */ #if WITH_IP4 case PF_INET: do { result = xiosetsockaddrenv_ip4(idx, strchr(namebuff, '\0'), XIOSOCKADDRENVLEN-strlen(lr), valuebuff, XIOSOCKADDRENVLEN, &sau->ip4, proto); xiosetenv(namebuff, valuebuff, 1, NULL); namebuff[strlen(lr)] = '\0'; ++idx; } while (result > 0); break; #endif /* WITH_IP4 */ #if WITH_IP6 case PF_INET6: strcpy(namebuff, lr); do { result = xiosetsockaddrenv_ip6(idx, strchr(namebuff, '\0'), XIOSOCKADDRENVLEN-strlen(lr), valuebuff, XIOSOCKADDRENVLEN, &sau->ip6, proto); xiosetenv(namebuff, valuebuff, 1, NULL); namebuff[strlen(lr)] = '\0'; ++idx; } while (result > 0); break; #endif /* WITH_IP6 */ #if WITH_VSOCK case PF_VSOCK: strcpy(namebuff, lr); do { result = xiosetsockaddrenv_vsock(idx, strchr(namebuff, '\0'), XIOSOCKADDRENVLEN-strlen(lr), valuebuff, XIOSOCKADDRENVLEN, &sau->vm, proto); xiosetenv(namebuff, valuebuff, 1, NULL); namebuff[strlen(lr)] = '\0'; ++idx; } while (result > 0); break; #endif /* WITH_VSOCK */ #if LATER case PF_PACKET: result = xiosetsockaddrenv_packet(lr, (void *)sau, proto); break; #endif default: result = -1; break; } return result; # undef XIOSOCKADDRENVLEN } #endif /* _WITH_SOCKET */ /* these do sockets internally */ /* retrieves options so-type and so-prototype from opts, calls socket, and ev. generates an appropriate error message. returns 0 on success or -1 if an error occurred. */ int xiosocket(struct opt *opts, int pf, int socktype, int proto, int msglevel) { int result; retropt_int(opts, OPT_SO_TYPE, &socktype); retropt_int(opts, OPT_SO_PROTOTYPE, &proto); applyopts(NULL, -1, opts, PH_PRESOCKET); result = Socket(pf, socktype, proto); if (result < 0) { int _errno = errno; Msg4(msglevel, "socket(%d, %d, %d): %s", pf, socktype, proto, strerror(errno)); errno = _errno; return -1; } return result; } /* retrieves options so-type and so-prototype from opts, calls socketpair, and ev. generates an appropriate error message. returns 0 on success or -1 if an error occurred. */ int xiosocketpair(struct opt *opts, int pf, int socktype, int proto, int sv[2]) { int result; retropt_int(opts, OPT_SO_TYPE, &socktype); retropt_int(opts, OPT_SO_PROTOTYPE, &proto); result = Socketpair(pf, socktype, proto, sv); if (result < 0) { Error5("socketpair(%d, %d, %d, %p): %s", pf, socktype, proto, sv, strerror(errno)); return -1; } return result; } /* Binds a socket to a socket address. Handles IP (internet protocol), UNIX domain, Linux abstract UNIX domain. The bind address us may be NULL in which case no bind() happens, except with alt (on option unix-bind-tempname (bind-tempname)). Alternate (atl) bind semantics are: with IP sockets: lowport (selects randomly a free port from 640 to 1023) with UNIX and abstract sockets: uses a method similar to tmpname() to find a free file system entry. */ int xiobind( struct single *sfd, union sockaddr_union *us, size_t uslen, struct opt *opts, int pf, bool alt, int level) { char infobuff[256]; int result; if (false /* for canonical reasons */) { ; #if WITH_UNIX } else if (pf == PF_UNIX) { if (alt && us != NULL) { bool abstract = false; char *usrname = NULL, *sockname; #if WITH_ABSTRACT_UNIXSOCKET abstract = (us->un.sun_path[0] == '\0'); #endif if (uslen == ((char *)&us->un.sun_path-(char *)us)) { usrname = NULL; } else { #if WITH_ABSTRACT_UNIXSOCKET if (abstract) usrname = strndup(us->un.sun_path+1, sizeof(us->un.sun_path)-1); else #endif usrname = strndup(us->un.sun_path, sizeof(us->un.sun_path)); if (usrname == NULL) { int _errno = errno; Error2("strndup(\"%s\", "F_Zu"): out of memory", us->un.sun_path, sizeof(us->un.sun_path)); errno = _errno; return -1; } } do { /* loop over tempnam bind() attempts */ sockname = xio_tempnam(usrname, abstract); if (sockname == NULL) { Error2("tempnam(\"%s\"): %s", usrname, strerror(errno)); free(usrname); return -1; } strncpy(us->un.sun_path+(abstract?1:0), sockname, sizeof(us->un.sun_path)); uslen = sizeof(&((struct sockaddr_un *)0)->sun_path) + Min(strlen(sockname), sizeof(us->un.sun_path)); /*?*/ free(sockname); if (Bind(sfd->fd, (struct sockaddr *)us, uslen) < 0) { Msg4(errno==EADDRINUSE?E_INFO:level, "bind(%d, {%s}, "F_Zd"): %s", sfd->fd, sockaddr_info((struct sockaddr *)us, uslen, infobuff, sizeof(infobuff)), uslen, strerror(errno)); if (errno != EADDRINUSE) { free(usrname); Close(sfd->fd); return STAT_RETRYLATER; } } else { break; /* could bind to path, good, continue past loop */ } } while (true); free(usrname); applyopts_named(us->un.sun_path, opts, PH_PREOPEN); } else if (us != NULL) { if (Bind(sfd->fd, &us->soa, uslen) < 0) { Msg4(level, "bind(%d, {%s}, "F_Zd"): %s", sfd->fd, sockaddr_info(&us->soa, uslen, infobuff, sizeof(infobuff)), uslen, strerror(errno)); Close(sfd->fd); return STAT_RETRYLATER; } applyopts_named(us->un.sun_path, opts, PH_PREOPEN); } applyopts(sfd, sfd->fd, opts, PH_PREBIND); applyopts(sfd, sfd->fd, opts, PH_BIND); #endif /* WITH_UNIX */ #if WITH_TCP || WITH_UDP } else if (alt) { union sockaddr_union sin, *sinp; unsigned short *port, i, N; div_t dv; applyopts(sfd, sfd->fd, opts, PH_PREBIND); applyopts(sfd, sfd->fd, opts, PH_BIND); /* prepare sockaddr for bind probing */ if (us) { sinp = us; } else { if (pf == AF_INET) { socket_in_init(&sin.ip4); #if WITH_IP6 } else { socket_in6_init(&sin.ip6); #endif } sinp = &sin; } if (pf == AF_INET) { port = &sin.ip4.sin_port; #if WITH_IP6 } else if (pf == AF_INET6) { port = &sin.ip6.sin6_port; #endif } else { port = 0; /* just to make compiler happy */ } /* combine random+step variant to quickly find a free port when only few are in use, and certainly find a free port in defined time even if there are almost all in use */ /* dirt 1: having tcp/udp code in socket function */ /* dirt 2: using a time related system call for init of random */ { /* generate a random port, with millisecond random init */ #if 0 struct timeb tb; ftime(&tb); srandom(tb.time*1000+tb.millitm); #else struct timeval tv; struct timezone tz; tz.tz_minuteswest = 0; tz.tz_dsttime = 0; if ((result = Gettimeofday(&tv, &tz)) < 0) { Warn2("gettimeofday(%p, {0,0}): %s", &tv, strerror(errno)); } srandom(tv.tv_sec*1000000+tv.tv_usec); #endif } /* Note: IPPORT_RESERVED is from includes, 1024 */ dv = div(random(), IPPORT_RESERVED-XIO_IPPORT_LOWER); i = N = XIO_IPPORT_LOWER + dv.rem; do { /* loop over lowport bind() attempts */ *port = htons(i); if (Bind(sfd->fd, &sinp->soa, sizeof(*sinp)) < 0) { Msg4(errno==EADDRINUSE?E_INFO:level, "bind(%d, {%s}, "F_Zd"): %s", sfd->fd, sockaddr_info(&sinp->soa, sizeof(*sinp), infobuff, sizeof(infobuff)), sizeof(*sinp), strerror(errno)); if (errno != EADDRINUSE) { Close(sfd->fd); return STAT_RETRYLATER; } } else { break; /* could bind to port, good, continue past loop */ } --i; if (i < XIO_IPPORT_LOWER) i = IPPORT_RESERVED-1; if (i == N) { Msg(level, "no low port available"); /*errno = EADDRINUSE; still assigned */ Close(sfd->fd); return STAT_RETRYLATER; } } while (i != N); #endif /* WITH_TCP || WITH_UDP */ } else { applyopts(sfd, sfd->fd, opts, PH_PREBIND); if (us) { applyopts(sfd, sfd->fd, opts, PH_BIND); if (Bind(sfd->fd, &us->soa, uslen) < 0) { Msg4(level, "bind(%d, {%s}, "F_Zd"): %s", sfd->fd, sockaddr_info(&us->soa, uslen, infobuff, sizeof(infobuff)), uslen, strerror(errno)); Close(sfd->fd); return STAT_RETRYLATER; } } } applyopts(sfd, -1, opts, PH_PASTBIND); return 0; } /* Handles the SO_REUSEADDR socket option for TCP LISTEN addresses depending on Socat option so-reuseaddr: Option not applied: set it to 1 Option applied with a value: set it to the value Option applied eith empty value "so-reuseaddr=": do not call setsockopt() for SO_REUSEADDR Return 0 on success, or -1 with errno when an error occurred. */ int xiosock_reuseaddr(int fd, int ipproto, struct opt *opts) { union integral val; union integral notnull; int _errno; val.u_int = 0; notnull.u_bool = false; if (ipproto == IPPROTO_TCP) { val.u_int = 1; notnull.u_bool = true; } retropt_2integrals(opts, OPT_SO_REUSEADDR, &val, ¬null); if (notnull.u_bool) { if (Setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val.u_int, sizeof(int)) != 0) { _errno = errno; Error4("setsockopt(%d, SOL_SOCKET, SO_REUSEADDR, { %d }, "F_Zu"): %s", fd, val.u_int, sizeof(val.u_int), strerror(errno)); errno = _errno; return -1; } } return 0; }