diff options
author | Laurent Bercot <ska-skaware@skarnet.org> | 2014-12-15 23:08:59 +0000 |
---|---|---|
committer | Laurent Bercot <ska-skaware@skarnet.org> | 2014-12-15 23:08:59 +0000 |
commit | e0fc82203d677a6f1e808e9a1a46176c109d89be (patch) | |
tree | e9609209b755e3f7a8480aea86601ffe9d4ca540 /src/conn-tools/s6-tcpclient.c | |
download | s6-networking-e0fc82203d677a6f1e808e9a1a46176c109d89be.tar.xz |
Initial commit
Diffstat (limited to 'src/conn-tools/s6-tcpclient.c')
-rw-r--r-- | src/conn-tools/s6-tcpclient.c | 377 |
1 files changed, 377 insertions, 0 deletions
diff --git a/src/conn-tools/s6-tcpclient.c b/src/conn-tools/s6-tcpclient.c new file mode 100644 index 0000000..4686636 --- /dev/null +++ b/src/conn-tools/s6-tcpclient.c @@ -0,0 +1,377 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/uint16.h> +#include <skalibs/uint.h> +#include <skalibs/bytestr.h> +#include <skalibs/sgetopt.h> +#include <skalibs/fmtscan.h> +#include <skalibs/strerr2.h> +#include <skalibs/stralloc.h> +#include <skalibs/genalloc.h> +#include <skalibs/tai.h> +#include <skalibs/djbunix.h> +#include <skalibs/skamisc.h> +#include <skalibs/socket.h> +#include <skalibs/ip46.h> +#include <s6-dns/s6dns.h> +#include <s6-networking/ident.h> + +#ifdef SKALIBS_IPV6_ENABLED +# define USAGE "s6-tcpclient [ -q | -Q | -v ] [ -4 | -6 ] [ -d | -D ] [ -r | -R ] [ -h | -H ] [ -n | -N ] [ -t timeoutinfo ] [ -l localname ] [ -T timeoutconn ] [ -i localip ] [ -p localport ] host port prog..." +# define TFLAGS_DEFAULT { 0, 0, { 2, 58 }, IP46_ZERO, 0, 1, 0, 0, 1, 0, 1, 1 } +# define OPTSTRING "qQv46dDrRhHnNt:l:T:i:p:" +#else +# define USAGE "s6-tcpclient [ -q | -Q | -v ] [ -d | -D ] [ -r | -R ] [ -h | -H ] [ -n | -N ] [ -t timeoutinfo ] [ -l localname ] [ -T timeoutconn ] [ -i localip ] [ -p localport ] host port prog..." +# define TFLAGS_DEFAULT { 0, 0, { 2, 58 }, IP46_ZERO, 0, 1, 1, 0, 1, 1 } +# define OPTSTRING "qQvdDrRhHnNt:l:T:i:p:" +#endif + +#define usage() strerr_dieusage(100, USAGE) +#define dienomem() strerr_diefu1sys(111, "allocate") + +#define MAXIP 16 + +typedef struct tflags_s tflags, *tflags_ref ; +struct tflags_s +{ + char const *localname ; + unsigned int timeout ; + unsigned int timeoutconn[2] ; + ip46_t localip ; + uint16 localport ; + unsigned int verbosity : 2 ; +#ifdef SKALIBS_IPV6_ENABLED + unsigned int ip4 : 1 ; + unsigned int ip6 : 1 ; +#endif + unsigned int delay : 1 ; + unsigned int remoteinfo : 1 ; + unsigned int remotehost : 1 ; + unsigned int qualif : 1 ; +} ; + +static tain_t deadline ; + +int main (int argc, char const *const *argv) +{ + int s ; + tflags flags = TFLAGS_DEFAULT ; + uint16 remoteport ; + PROG = "s6-tcpclient" ; + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + register int opt = subgetopt_r(argc, argv, OPTSTRING, &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'q' : if (flags.verbosity) flags.verbosity-- ; break ; + case 'Q' : flags.verbosity = 1 ; break ; + case 'v' : flags.verbosity++ ; break ; +#ifdef SKALIBS_IPV6_ENABLED + case '4' : flags.ip4 = 1 ; break ; + case '6' : flags.ip6 = 1 ; break ; +#endif + case 'd' : flags.delay = 1 ; break ; + case 'D' : flags.delay = 0 ; break ; + case 'r' : flags.remoteinfo = 1 ; break ; + case 'R' : flags.remoteinfo = 0 ; break ; + case 'h' : flags.remotehost = 1 ; break ; + case 'H' : flags.remotehost = 0 ; break ; + case 'n' : flags.qualif = 1 ; break ; + case 'N' : flags.qualif = 0 ; break ; + case 't' : if (!uint0_scan(l.arg, &flags.timeout)) usage() ; break ; + case 'l' : flags.localname = l.arg ; break ; + case 'T' : + { + unsigned int n = uint_scan(l.arg, &flags.timeoutconn[0]) ; + if (!n) usage() ; + if (!l.arg[n]) + { + flags.timeoutconn[1] = 0 ; + break ; + } + if (l.arg[n] != '+') usage() ; + if (!uint0_scan(l.arg + n + 1, &flags.timeoutconn[1])) usage() ; + break ; + } + case 'i' : if (!ip46_scan(l.arg, &flags.localip)) usage() ; break ; + case 'p' : if (!uint160_scan(l.arg, &flags.localport)) usage() ; break ; + default : usage() ; + } + } + argc -= l.ind ; argv += l.ind ; + } + if (argc < 3) usage() ; +#ifdef SKALIBS_IPV6_ENABLED + if (!flags.ip6) flags.ip4 = 1 ; +#endif + if (!uint160_scan(argv[1], &remoteport)) + strerr_dief2x(100, "invalid port number: ", argv[1]) ; + tain_now_g() ; + if (flags.timeout) tain_addsec_g(&deadline, flags.timeout) ; + else tain_add_g(&deadline, &tain_infinite_relative) ; + + { + ip46_t ip[2][MAXIP] ; + unsigned int j = 0 ; + unsigned int n[2] = { 0, 0 } ; + register unsigned int i = 0 ; + if (!**argv || ((**argv == '0') && !argv[0][1])) + { +#ifdef SKALIBS_IPV6_ENABLED + ip46_from_ip6(&ip[0][n[0]++], IP6_LOCAL) ; +#endif + ip46_from_ip4(&ip[0][n[0]++], IP4_LOCAL) ; + } + else + { + if (!flags.remotehost) + { +#ifdef SKALIBS_IPV6_ENABLED + if (flags.ip6 && !flags.ip4) + { + char ip6[MAXIP << 4] ; + register unsigned int i = 0 ; + if (!ip6_scanlist(ip6, MAXIP, argv[0], &n[0])) usage() ; + for (; i < n[0] ; i++) ip46_from_ip6(&ip[0][i], ip6 + (i << 4)) ; + } + else if (!flags.ip6) + { + char ip4[MAXIP << 2] ; + register unsigned int i = 0 ; + if (!ip4_scanlist(ip4, MAXIP, argv[0], &n[0])) usage() ; + for (; i < n[0] ; i++) ip46_from_ip4(&ip[0][i], ip4 + (i << 2)) ; + } + else +#endif + if (!ip46_scanlist(ip[0], MAXIP, argv[0], &n[0])) usage() ; + } + else + { +#ifdef SKALIBS_IPV6_ENABLED + if (flags.ip6 && flags.ip4) + { + if (!ip46_scanlist(ip[0], MAXIP, argv[0], &n[0])) + { + genalloc ips = STRALLOC_ZERO ; + if (s6dns_resolve_aaaaa_g(&ips, argv[0], str_len(argv[0]), flags.qualif, &deadline) <= 0) + strerr_diefu4x(111, "resolve ", argv[0], ": ", s6dns_constants_error_str(errno)) ; + n[0] = genalloc_len(ip46_t, &ips) ; + if (n[0] >= MAXIP) n[0] = MAXIP ; + for (; i < n[0] ; i++) ip[0][i] = genalloc_s(ip46_t, &ips)[i] ; + genalloc_free(ip46_t, &ips) ; + } + } + else if (flags.ip6) + { + char ip6[MAXIP << 4] ; + if (ip6_scanlist(ip6, MAXIP, argv[0], &n[0])) + { + register unsigned int i = 0 ; + for (; i < n[0] ; i++) ip46_from_ip6(&ip[0][i], ip6 + (i << 4)) ; + } + else + { + stralloc ip6s = STRALLOC_ZERO ; + if (s6dns_resolve_aaaa_g(&ip6s, argv[0], str_len(argv[0]), flags.qualif, &deadline) <= 0) + strerr_diefu4x(111, "resolve ", argv[0], ": ", s6dns_constants_error_str(errno)) ; + n[0] = ip6s.len >> 4 ; + if (n[0] >= MAXIP) n[0] = MAXIP ; + for (; i < n[0] ; i++) ip46_from_ip6(&ip[0][i], ip6s.s + (i << 4)) ; + stralloc_free(&ip6s) ; + } + } + else +#endif + { + char ip4[MAXIP << 2] ; + if (ip4_scanlist(ip4, MAXIP, argv[0], &n[0])) + { + register unsigned int i = 0 ; + for (; i < n[0] ; i++) ip46_from_ip4(&ip[0][i], ip4 + (i << 2)) ; + } + else + { + stralloc ip4s = STRALLOC_ZERO ; + if (s6dns_resolve_a_g(&ip4s, argv[0], str_len(argv[0]), flags.qualif, &deadline) <= 0) + strerr_diefu4x(111, "resolve ", argv[0], ": ", s6dns_constants_error_str(errno)) ; + n[0] = ip4s.len >> 2 ; + if (n[0] >= MAXIP) n[0] = MAXIP ; + for (; i < n[0] ; i++) ip46_from_ip4(&ip[0][i], ip4s.s + (i << 2)) ; + stralloc_free(&ip4s) ; + } + } + } + if (!n[0]) strerr_dief2x(100, "no IP address for ", argv[0]) ; + } + + if (n[0] == 1) + { + flags.timeoutconn[0] += flags.timeoutconn[1] ; + flags.timeoutconn[1] = 0 ; + } + + for (; j < 2 ; j++) + { + unsigned int i = 0 ; + for (; i < n[j] ; i++) + { + tain_t localdeadline ; + s = socket_tcp46(ip46_is6(&flags.localip)) ; + if (s < 0) strerr_diefu1sys(111, "create socket") ; + if (socket_bind46(s, &flags.localip, flags.localport) < 0) + strerr_diefu1sys(111, "bind socket") ; + tain_addsec_g(&localdeadline, flags.timeoutconn[j]) ; + if (tain_less(&deadline, &localdeadline)) localdeadline = deadline ; + if (socket_deadlineconnstamp46_g(s, &ip[j][i], remoteport, &localdeadline)) goto connected ; + fd_close(s) ; + if (!j && flags.timeoutconn[1]) ip[1][n[1]++] = ip[0][i] ; + else + { + char fmtip[IP46_FMT] ; + char fmtport[UINT16_FMT] ; + fmtip[ip46_fmt(fmtip, &ip[j][i])] = 0 ; + fmtport[uint16_fmt(fmtport, remoteport)] = 0 ; + strerr_warnwu4sys("connect to ", fmtip, " port ", fmtport) ; + } + } + } + strerr_diefu2x(111, "connect to ", "a suitable IP address") ; + } + + connected: + + if (ndelay_off(s) == -1) + strerr_diefu1sys(111, "ndelay_off") ; + if (!flags.delay) socket_tcpnodelay(s) ; + if (socket_local46(s, &flags.localip, &flags.localport) == -1) + strerr_diefu2sys(111, "get local", " address and port") ; + + { + ip46_t remoteip ; + char fmtip[IP46_FMT] ; + char fmtport[UINT16_FMT] ; + + if (socket_remote46(s, &remoteip, &remoteport) == -1) + strerr_diefu2sys(111, "get remote", " address and port") ; + fmtip[ip46_fmt(fmtip, &remoteip)] = 0 ; + fmtport[uint16_fmt(fmtport, remoteport)] = 0 ; + if (flags.verbosity >= 2) + strerr_warni4x("connected to ", fmtip, " port ", fmtport) ; + if (!pathexec_env("PROTO", "TCP") + || !pathexec_env("TCPREMOTEIP", fmtip) + || !pathexec_env("TCPREMOTEPORT", fmtport)) dienomem() ; + + fmtip[ip46_fmt(fmtip, &flags.localip)] = 0 ; + fmtport[uint16_fmt(fmtport, flags.localport)] = 0 ; + if (!pathexec_env("TCPLOCALIP", fmtip) + || !pathexec_env("TCPLOCALPORT", fmtport)) dienomem() ; + + if (flags.localname) + { + if (!pathexec_env("TCPLOCALHOST", flags.localname)) dienomem() ; + } + + /* DNS resolution for TCPLOCALHOST and TCPREMOTEHOST */ + + if (!flags.localname || flags.remotehost) + { + s6dns_resolve_t blob[2] ; + s6dns_dpag_t data[2] = { S6DNS_DPAG_ZERO, S6DNS_DPAG_ZERO } ; + if (!flags.localname) + { + s6dns_domain_arpafromip46(&blob[0].q, &flags.localip) ; + s6dns_domain_encode(&blob[0].q) ; + blob[0].qtype = S6DNS_T_PTR ; + blob[0].deadline = deadline ; + blob[0].parsefunc = &s6dns_message_parse_answer_domain ; + blob[0].data = &data[0] ; + blob[0].options = S6DNS_O_RECURSIVE ; + data[0].rtype = S6DNS_T_PTR ; + } + if (flags.remotehost) + { + s6dns_domain_arpafromip46(&blob[1].q, &remoteip) ; + s6dns_domain_encode(&blob[1].q) ; + blob[1].qtype = S6DNS_T_PTR ; + blob[1].deadline = deadline ; + blob[1].parsefunc = &s6dns_message_parse_answer_domain ; + blob[1].data = &data[1] ; + blob[1].options = S6DNS_O_RECURSIVE ; + data[1].rtype = S6DNS_T_PTR ; + } + { + tain_t infinite = TAIN_INFINITE ; + if (!s6dns_resolven_parse_g(blob + !!flags.localname, !flags.localname + !!flags.remotehost, &infinite)) + strerr_diefu2x(111, "resolve IP addresses: ", s6dns_constants_error_str(errno)) ; + } + if (!flags.localname) + { + if (blob[0].status) + { + if (!pathexec_env("TCPLOCALHOST", 0)) dienomem() ; + } + else + { + char s[256] ; + register unsigned int len = 0 ; + if (genalloc_len(s6dns_domain_t, &data[0].ds)) + len = s6dns_domain_tostring(s, 255, genalloc_s(s6dns_domain_t, &data[0].ds)) ; + genalloc_free(s6dns_domain_t, &data[0].ds) ; + s[len] = 0 ; + if (!pathexec_env("TCPLOCALHOST", s)) dienomem() ; + } + } + if (flags.remotehost) + { + if (blob[1].status) + { + if (!pathexec_env("TCPREMOTEHOST", 0)) dienomem() ; + } + else + { + char s[256] ; + register unsigned int len = 0 ; + if (genalloc_len(s6dns_domain_t, &data[1].ds)) + len = s6dns_domain_tostring(s, 255, genalloc_s(s6dns_domain_t, &data[1].ds)) ; + genalloc_free(s6dns_domain_t, &data[1].ds) ; + s[len] = 0 ; + if (!pathexec_env("TCPREMOTEHOST", s)) dienomem() ; + } + } + } + + + /* TCPREMOTEINFO */ + /* + Yes, I should have made all the network queries in parallel, + not only the DNS ones, but the IDENT one too. Well, that was + too much work for an obsolete protocol. Sue me. + */ + { + char idbuf[S6NET_IDENT_ID_SIZE] ; + if (flags.remoteinfo) + { + register int r = s6net_ident_client_g(idbuf, S6NET_IDENT_ID_SIZE, &remoteip, remoteport, &flags.localip, flags.localport, &deadline) ; + if (r <= 0) + { + if (flags.verbosity) + { + if (r < 0) strerr_warnwu1sys("s6net_ident_client") ; + else strerr_warnw2x("ident server replied: ", s6net_ident_error_str(errno)) ; + } + if (!pathexec_env("TCPREMOTEINFO", "")) dienomem() ; + } + else if (!pathexec_env("TCPREMOTEINFO", idbuf)) dienomem() ; + } + } + } + + if (fd_move(6, s) < 0) strerr_diefu2sys(111, "set up fd ", "6") ; + if (fd_copy(7, 6) < 0) strerr_diefu2sys(111, "set up fd ", "7") ; + pathexec(argv+2) ; + strerr_dieexec(111, argv[2]) ; +} |