+HEADER #include <netdb.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <unistd.h>
+#include <errno.h>
+
+HEADER extern const struct addrinfo libjh_tcp_hints;
+HEADER #define JH_TCP_HINTS (&libjh_tcp_hints)
+const struct addrinfo libjh_tcp_hints = {
+ .ai_flags = AI_ADDRCONFIG,
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ .ai_protocol = 0
+};
+
+// negative return value: interpret as a getaddrinfo() error (non-gai errors become EAI_SYSTEM)
+// >=0 return value: interpret as resulting fd
+PUBLIC_FN int netopen(const char *node, const char *service, const struct addrinfo *hints) {
+ struct addrinfo *addrs;
+ int gai_res = getaddrinfo(node, service, hints, &addrs);
+ if (gai_res) return gai_res;
+
+ int s = socket(addrs[0].ai_family, addrs[0].ai_socktype, addrs[0].ai_protocol);
+ if (s == -1) goto err_socket;
+
+ if (connect(s, addrs[0].ai_addr, addrs[0].ai_addrlen)) goto err_connect;
+
+ freeaddrinfo(addrs);
+ return s;
+
+err_connect:;
+ int errno_ = errno;
+ close(s);
+ errno = errno_;
+err_socket:
+ freeaddrinfo(addrs);
+ return EAI_SYSTEM;
+}
+
+// err points to where the error from netopen() should be stored
+PUBLIC_FN FILE *fnetopen(const char *node, const char *service, const struct addrinfo *hints, int *err) {
+ int rval = netopen(node, service, hints);
+ if (rval < 0) goto err;
+
+ FILE *res = fdopen(rval, "r+");
+ if (res) return res;
+
+ int errno_ = errno;
+ close(rval);
+ errno = errno_;
+ rval = EAI_SYSTEM;
+
+err:
+ if (err) *err = rval;
+ return NULL;
+}
\ No newline at end of file