aboutsummaryrefslogtreecommitdiff
path: root/services/outside_network.c
diff options
context:
space:
mode:
Diffstat (limited to 'services/outside_network.c')
-rw-r--r--services/outside_network.c279
1 files changed, 264 insertions, 15 deletions
diff --git a/services/outside_network.c b/services/outside_network.c
index ec9e11a0e7af..92212be02f0d 100644
--- a/services/outside_network.c
+++ b/services/outside_network.c
@@ -198,21 +198,17 @@ pick_outgoing_tcp(struct waiting_tcp* w, int s)
return 1;
}
-/** use next free buffer to service a tcp query */
-static int
-outnet_tcp_take_into_use(struct waiting_tcp* w, uint8_t* pkt, size_t pkt_len)
+/** get TCP file descriptor for address, returns -1 on failure,
+ * tcp_mss is 0 or maxseg size to set for TCP packets. */
+int
+outnet_get_tcp_fd(struct sockaddr_storage* addr, socklen_t addrlen, int tcp_mss)
{
- struct pending_tcp* pend = w->outnet->tcp_free;
int s;
#ifdef SO_REUSEADDR
int on = 1;
#endif
- log_assert(pend);
- log_assert(pkt);
- log_assert(w->addrlen > 0);
- /* open socket */
#ifdef INET6
- if(addr_is_ip6(&w->addr, w->addrlen))
+ if(addr_is_ip6(addr, addrlen))
s = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
else
#endif
@@ -220,12 +216,12 @@ outnet_tcp_take_into_use(struct waiting_tcp* w, uint8_t* pkt, size_t pkt_len)
if(s == -1) {
#ifndef USE_WINSOCK
log_err_addr("outgoing tcp: socket", strerror(errno),
- &w->addr, w->addrlen);
+ addr, addrlen);
#else
log_err_addr("outgoing tcp: socket",
- wsa_strerror(WSAGetLastError()), &w->addr, w->addrlen);
+ wsa_strerror(WSAGetLastError()), addr, addrlen);
#endif
- return 0;
+ return -1;
}
#ifdef SO_REUSEADDR
@@ -235,11 +231,11 @@ outnet_tcp_take_into_use(struct waiting_tcp* w, uint8_t* pkt, size_t pkt_len)
" setsockopt(.. SO_REUSEADDR ..) failed");
}
#endif
- if (w->outnet->tcp_mss > 0) {
+
+ if(tcp_mss > 0) {
#if defined(IPPROTO_TCP) && defined(TCP_MAXSEG)
if(setsockopt(s, IPPROTO_TCP, TCP_MAXSEG,
- (void*)&w->outnet->tcp_mss,
- (socklen_t)sizeof(w->outnet->tcp_mss)) < 0) {
+ (void*)&tcp_mss, (socklen_t)sizeof(tcp_mss)) < 0) {
verbose(VERB_ALGO, "outgoing tcp:"
" setsockopt(.. TCP_MAXSEG ..) failed");
}
@@ -249,6 +245,50 @@ outnet_tcp_take_into_use(struct waiting_tcp* w, uint8_t* pkt, size_t pkt_len)
#endif /* defined(IPPROTO_TCP) && defined(TCP_MAXSEG) */
}
+ return s;
+}
+
+/** connect tcp connection to addr, 0 on failure */
+int
+outnet_tcp_connect(int s, struct sockaddr_storage* addr, socklen_t addrlen)
+{
+ if(connect(s, (struct sockaddr*)addr, addrlen) == -1) {
+#ifndef USE_WINSOCK
+#ifdef EINPROGRESS
+ if(errno != EINPROGRESS) {
+#endif
+ if(tcp_connect_errno_needs_log(
+ (struct sockaddr*)addr, addrlen))
+ log_err_addr("outgoing tcp: connect",
+ strerror(errno), addr, addrlen);
+ close(s);
+ return 0;
+#ifdef EINPROGRESS
+ }
+#endif
+#else /* USE_WINSOCK */
+ if(WSAGetLastError() != WSAEINPROGRESS &&
+ WSAGetLastError() != WSAEWOULDBLOCK) {
+ closesocket(s);
+ return 0;
+ }
+#endif
+ }
+ return 1;
+}
+
+/** use next free buffer to service a tcp query */
+static int
+outnet_tcp_take_into_use(struct waiting_tcp* w, uint8_t* pkt, size_t pkt_len)
+{
+ struct pending_tcp* pend = w->outnet->tcp_free;
+ int s;
+ log_assert(pend);
+ log_assert(pkt);
+ log_assert(w->addrlen > 0);
+ /* open socket */
+ s = outnet_get_tcp_fd(&w->addr, w->addrlen, w->outnet->tcp_mss);
+
if(!pick_outgoing_tcp(w, s))
return 0;
@@ -2101,6 +2141,215 @@ void outnet_serviced_query_stop(struct serviced_query* sq, void* cb_arg)
}
}
+/** create fd to send to this destination */
+static int
+fd_for_dest(struct outside_network* outnet, struct sockaddr_storage* to_addr,
+ socklen_t to_addrlen)
+{
+ struct sockaddr_storage* addr;
+ socklen_t addrlen;
+ int i;
+ int try;
+
+ /* select interface */
+ if(addr_is_ip6(to_addr, to_addrlen)) {
+ if(outnet->num_ip6 == 0) {
+ char to[64];
+ addr_to_str(to_addr, to_addrlen, to, sizeof(to));
+ verbose(VERB_QUERY, "need ipv6 to send, but no ipv6 outgoing interfaces, for %s", to);
+ return -1;
+ }
+ i = ub_random_max(outnet->rnd, outnet->num_ip6);
+ addr = &outnet->ip6_ifs[i].addr;
+ addrlen = outnet->ip6_ifs[i].addrlen;
+ } else {
+ if(outnet->num_ip4 == 0) {
+ char to[64];
+ addr_to_str(to_addr, to_addrlen, to, sizeof(to));
+ verbose(VERB_QUERY, "need ipv4 to send, but no ipv4 outgoing interfaces, for %s", to);
+ return -1;
+ }
+ i = ub_random_max(outnet->rnd, outnet->num_ip4);
+ addr = &outnet->ip4_ifs[i].addr;
+ addrlen = outnet->ip4_ifs[i].addrlen;
+ }
+
+ /* create fd */
+ for(try = 0; try<1000; try++) {
+ int freebind = 0;
+ int noproto = 0;
+ int inuse = 0;
+ int port = ub_random(outnet->rnd)&0xffff;
+ int fd = -1;
+ if(addr_is_ip6(to_addr, to_addrlen)) {
+ struct sockaddr_in6 sa = *(struct sockaddr_in6*)addr;
+ sa.sin6_port = (in_port_t)htons((uint16_t)port);
+ fd = create_udp_sock(AF_INET6, SOCK_DGRAM,
+ (struct sockaddr*)&sa, addrlen, 1, &inuse, &noproto,
+ 0, 0, 0, NULL, 0, freebind, 0);
+ } else {
+ struct sockaddr_in* sa = (struct sockaddr_in*)addr;
+ sa->sin_port = (in_port_t)htons((uint16_t)port);
+ fd = create_udp_sock(AF_INET, SOCK_DGRAM,
+ (struct sockaddr*)addr, addrlen, 1, &inuse, &noproto,
+ 0, 0, 0, NULL, 0, freebind, 0);
+ }
+ if(fd != -1) {
+ return fd;
+ }
+ if(!inuse) {
+ return -1;
+ }
+ }
+ /* too many tries */
+ log_err("cannot send probe, ports are in use");
+ return -1;
+}
+
+struct comm_point*
+outnet_comm_point_for_udp(struct outside_network* outnet,
+ comm_point_callback_type* cb, void* cb_arg,
+ struct sockaddr_storage* to_addr, socklen_t to_addrlen)
+{
+ struct comm_point* cp;
+ int fd = fd_for_dest(outnet, to_addr, to_addrlen);
+ if(fd == -1) {
+ return NULL;
+ }
+ cp = comm_point_create_udp(outnet->base, fd, outnet->udp_buff,
+ cb, cb_arg);
+ if(!cp) {
+ log_err("malloc failure");
+ close(fd);
+ return NULL;
+ }
+ return cp;
+}
+
+struct comm_point*
+outnet_comm_point_for_tcp(struct outside_network* outnet,
+ comm_point_callback_type* cb, void* cb_arg,
+ struct sockaddr_storage* to_addr, socklen_t to_addrlen,
+ sldns_buffer* query, int timeout)
+{
+ struct comm_point* cp;
+ int fd = outnet_get_tcp_fd(to_addr, to_addrlen, outnet->tcp_mss);
+ if(fd == -1) {
+ return 0;
+ }
+ fd_set_nonblock(fd);
+ if(!outnet_tcp_connect(fd, to_addr, to_addrlen)) {
+ /* outnet_tcp_connect has closed fd on error for us */
+ return 0;
+ }
+ cp = comm_point_create_tcp_out(outnet->base, 65552, cb, cb_arg);
+ if(!cp) {
+ log_err("malloc failure");
+ close(fd);
+ return 0;
+ }
+ cp->repinfo.addrlen = to_addrlen;
+ memcpy(&cp->repinfo.addr, to_addr, to_addrlen);
+ /* set timeout on TCP connection */
+ comm_point_start_listening(cp, fd, timeout);
+ /* copy scratch buffer to cp->buffer */
+ sldns_buffer_copy(cp->buffer, query);
+ return cp;
+}
+
+/** setup http request headers in buffer for sending query to destination */
+static int
+setup_http_request(sldns_buffer* buf, char* host, char* path)
+{
+ sldns_buffer_clear(buf);
+ sldns_buffer_printf(buf, "GET /%s HTTP/1.1\r\n", path);
+ sldns_buffer_printf(buf, "Host: %s\r\n", host);
+ sldns_buffer_printf(buf, "User-Agent: unbound/%s\r\n",
+ PACKAGE_VERSION);
+ /* We do not really do multiple queries per connection,
+ * but this header setting is also not needed.
+ * sldns_buffer_printf(buf, "Connection: close\r\n") */
+ sldns_buffer_printf(buf, "\r\n");
+ if(sldns_buffer_position(buf)+10 > sldns_buffer_capacity(buf))
+ return 0; /* somehow buffer too short, but it is about 60K
+ and the request is only a couple bytes long. */
+ sldns_buffer_flip(buf);
+ return 1;
+}
+
+struct comm_point*
+outnet_comm_point_for_http(struct outside_network* outnet,
+ comm_point_callback_type* cb, void* cb_arg,
+ struct sockaddr_storage* to_addr, socklen_t to_addrlen, int timeout,
+ int ssl, char* host, char* path)
+{
+ /* cp calls cb with err=NETEVENT_DONE when transfer is done */
+ struct comm_point* cp;
+ int fd = outnet_get_tcp_fd(to_addr, to_addrlen, outnet->tcp_mss);
+ if(fd == -1) {
+ return 0;
+ }
+ fd_set_nonblock(fd);
+ if(!outnet_tcp_connect(fd, to_addr, to_addrlen)) {
+ /* outnet_tcp_connect has closed fd on error for us */
+ return 0;
+ }
+ cp = comm_point_create_http_out(outnet->base, 65552, cb, cb_arg,
+ outnet->udp_buff);
+ if(!cp) {
+ log_err("malloc failure");
+ close(fd);
+ return 0;
+ }
+ cp->repinfo.addrlen = to_addrlen;
+ memcpy(&cp->repinfo.addr, to_addr, to_addrlen);
+
+ /* setup for SSL (if needed) */
+ if(ssl) {
+ cp->ssl = outgoing_ssl_fd(outnet->sslctx, fd);
+ if(!cp->ssl) {
+ log_err("cannot setup https");
+ comm_point_delete(cp);
+ return NULL;
+ }
+#ifdef USE_WINSOCK
+ comm_point_tcp_win_bio_cb(cp, cp->ssl);
+#endif
+ cp->ssl_shake_state = comm_ssl_shake_write;
+ /* https verification */
+#ifdef HAVE_SSL_SET1_HOST
+ if((SSL_CTX_get_verify_mode(outnet->sslctx)&SSL_VERIFY_PEER)) {
+ /* because we set SSL_VERIFY_PEER, in netevent in
+ * ssl_handshake, it'll check if the certificate
+ * verification has succeeded */
+ /* SSL_VERIFY_PEER is set on the sslctx */
+ /* and the certificates to verify with are loaded into
+ * it with SSL_load_verify_locations or
+ * SSL_CTX_set_default_verify_paths */
+ /* setting the hostname makes openssl verify the
+ * host name in the x509 certificate in the
+ * SSL connection*/
+ if(!SSL_set1_host(cp->ssl, host)) {
+ log_err("SSL_set1_host failed");
+ comm_point_delete(cp);
+ return NULL;
+ }
+ }
+#endif /* HAVE_SSL_SET1_HOST */
+ }
+
+ /* set timeout on TCP connection */
+ comm_point_start_listening(cp, fd, timeout);
+
+ /* setup http request in cp->buffer */
+ if(!setup_http_request(cp->buffer, host, path)) {
+ log_err("error setting up http request");
+ comm_point_delete(cp);
+ return NULL;
+ }
+ return cp;
+}
+
/** get memory used by waiting tcp entry (in use or not) */
static size_t
waiting_tcp_get_mem(struct waiting_tcp* w)