/* This callback is used to send a valid PROXY protocol line to a socket being * established. It returns 0 if it fails in a fatal way or needs to poll to go * further, otherwise it returns non-zero and removes itself from the connection's * flags (the bit is provided in <flag> by the caller). It is designed to be * called by the connection handler and relies on it to commit polling changes. * Note that it can emit a PROXY line by relying on the other end's address * when the connection is attached to a stream interface, or by resolving the * local address otherwise (also called a LOCAL line). */ int conn_si_send_proxy(struct connection *conn, unsigned int flag) { /* we might have been called just after an asynchronous shutw */ if (conn->flags & CO_FL_SOCK_WR_SH) goto out_error; if (!conn_ctrl_ready(conn)) goto out_error; if (!fd_send_ready(conn->t.sock.fd)) goto out_wait; /* If we have a PROXY line to send, we'll use this to validate the * connection, in which case the connection is validated only once * we've sent the whole proxy line. Otherwise we use connect(). */ while (conn->send_proxy_ofs) { int ret; /* The target server expects a PROXY line to be sent first. * If the send_proxy_ofs is negative, it corresponds to the * offset to start sending from then end of the proxy string * (which is recomputed every time since it's constant). If * it is positive, it means we have to send from the start. * We can only send a "normal" PROXY line when the connection * is attached to a stream interface. Otherwise we can only * send a LOCAL line (eg: for use with health checks). */ if (conn->data == &si_conn_cb) { struct stream_interface *si = conn->owner; struct connection *remote = objt_conn(si->ob->prod->end); ret = make_proxy_line(trash.str, trash.size, objt_server(conn->target), remote); } else { /* The target server expects a LOCAL line to be sent first. Retrieving * local or remote addresses may fail until the connection is established. */ conn_get_from_addr(conn); if (!(conn->flags & CO_FL_ADDR_FROM_SET)) goto out_wait; conn_get_to_addr(conn); if (!(conn->flags & CO_FL_ADDR_TO_SET)) goto out_wait; ret = make_proxy_line(trash.str, trash.size, objt_server(conn->target), conn); } if (!ret) goto out_error; if (conn->send_proxy_ofs > 0) conn->send_proxy_ofs = -ret; /* first call */ /* we have to send trash from (ret+sp for -sp bytes). If the * data layer has a pending write, we'll also set MSG_MORE. */ ret = send(conn->t.sock.fd, trash.str + ret + conn->send_proxy_ofs, -conn->send_proxy_ofs, (conn->flags & CO_FL_DATA_WR_ENA) ? MSG_MORE : 0); if (ret == 0) goto out_wait; if (ret < 0) { if (errno == EAGAIN || errno == ENOTCONN) goto out_wait; if (errno == EINTR) continue; conn->flags |= CO_FL_SOCK_RD_SH | CO_FL_SOCK_WR_SH; goto out_error; } conn->send_proxy_ofs += ret; /* becomes zero once complete */ if (conn->send_proxy_ofs != 0) goto out_wait; /* OK we've sent the whole line, we're connected */ break; } /* The connection is ready now, simply return and let the connection * handler notify upper layers if needed. */ if (conn->flags & CO_FL_WAIT_L4_CONN) conn->flags &= ~CO_FL_WAIT_L4_CONN; conn->flags &= ~flag; return 1; out_error: /* Write error on the file descriptor */ conn->flags |= CO_FL_ERROR; return 0; out_wait: __conn_sock_stop_recv(conn); fd_cant_send(conn->t.sock.fd); return 0; }
/* Finish a stream accept() for a proxy (TCP or HTTP). It returns a negative * value in case of a critical failure which must cause the listener to be * disabled, a positive or null value in case of success. */ int frontend_accept(struct stream *s) { struct session *sess = s->sess; struct connection *conn = objt_conn(sess->origin); struct listener *l = sess->listener; struct proxy *fe = sess->fe; if (unlikely(fe->nb_req_cap > 0)) { if ((s->req_cap = pool_alloc2(fe->req_cap_pool)) == NULL) goto out_return; /* no memory */ memset(s->req_cap, 0, fe->nb_req_cap * sizeof(void *)); } if (unlikely(fe->nb_rsp_cap > 0)) { if ((s->res_cap = pool_alloc2(fe->rsp_cap_pool)) == NULL) goto out_free_reqcap; /* no memory */ memset(s->res_cap, 0, fe->nb_rsp_cap * sizeof(void *)); } if (fe->http_needed) { /* we have to allocate header indexes only if we know * that we may make use of them. This of course includes * (mode == PR_MODE_HTTP). */ if (unlikely(!http_alloc_txn(s))) goto out_free_rspcap; /* no memory */ /* and now initialize the HTTP transaction state */ http_init_txn(s); } if ((fe->mode == PR_MODE_TCP || fe->mode == PR_MODE_HTTP) && (!LIST_ISEMPTY(&fe->logsrvs))) { if (likely(!LIST_ISEMPTY(&fe->logformat))) { /* we have the client ip */ if (s->logs.logwait & LW_CLIP) if (!(s->logs.logwait &= ~(LW_CLIP|LW_INIT))) s->do_log(s); } else if (conn) { char pn[INET6_ADDRSTRLEN], sn[INET6_ADDRSTRLEN]; conn_get_from_addr(conn); conn_get_to_addr(conn); switch (addr_to_str(&conn->addr.from, pn, sizeof(pn))) { case AF_INET: case AF_INET6: addr_to_str(&conn->addr.to, sn, sizeof(sn)); send_log(fe, LOG_INFO, "Connect from %s:%d to %s:%d (%s/%s)\n", pn, get_host_port(&conn->addr.from), sn, get_host_port(&conn->addr.to), fe->id, (fe->mode == PR_MODE_HTTP) ? "HTTP" : "TCP"); break; case AF_UNIX: /* UNIX socket, only the destination is known */ send_log(fe, LOG_INFO, "Connect to unix:%d (%s/%s)\n", l->luid, fe->id, (fe->mode == PR_MODE_HTTP) ? "HTTP" : "TCP"); break; } } } if (unlikely((global.mode & MODE_DEBUG) && conn && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)))) { char pn[INET6_ADDRSTRLEN]; conn_get_from_addr(conn); switch (addr_to_str(&conn->addr.from, pn, sizeof(pn))) { case AF_INET: case AF_INET6: chunk_printf(&trash, "%08x:%s.accept(%04x)=%04x from [%s:%d]\n", s->uniq_id, fe->id, (unsigned short)l->fd, (unsigned short)conn->t.sock.fd, pn, get_host_port(&conn->addr.from)); break; case AF_UNIX: /* UNIX socket, only the destination is known */ chunk_printf(&trash, "%08x:%s.accept(%04x)=%04x from [unix:%d]\n", s->uniq_id, fe->id, (unsigned short)l->fd, (unsigned short)conn->t.sock.fd, l->luid); break; } shut_your_big_mouth_gcc(write(1, trash.str, trash.len)); } if (fe->mode == PR_MODE_HTTP) s->req.flags |= CF_READ_DONTWAIT; /* one read is usually enough */ /* everything's OK, let's go on */ return 1; /* Error unrolling */ out_free_rspcap: pool_free2(fe->rsp_cap_pool, s->res_cap); out_free_reqcap: pool_free2(fe->req_cap_pool, s->req_cap); out_return: return -1; }
/* Finish a session accept() for a proxy (TCP or HTTP). It returns a negative * value in case of a critical failure which must cause the listener to be * disabled, a positive value in case of success, or zero if it is a success * but the session must be closed ASAP (eg: monitoring). */ int frontend_accept(struct session *s) { int cfd = s->si[0].conn->t.sock.fd; tv_zero(&s->logs.tv_request); s->logs.t_queue = -1; s->logs.t_connect = -1; s->logs.t_data = -1; s->logs.t_close = 0; s->logs.bytes_in = s->logs.bytes_out = 0; s->logs.prx_queue_size = 0; /* we get the number of pending conns before us */ s->logs.srv_queue_size = 0; /* we will get this number soon */ /* FIXME: the logs are horribly complicated now, because they are * defined in <p>, <p>, and later <be> and <be>. */ s->do_log = sess_log; /* default error reporting function, may be changed by analysers */ s->srv_error = default_srv_error; /* Adjust some socket options */ if (s->listener->addr.ss_family == AF_INET || s->listener->addr.ss_family == AF_INET6) { if (setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, (char *) &one, sizeof(one)) == -1) goto out_return; if (s->fe->options & PR_O_TCP_CLI_KA) setsockopt(cfd, SOL_SOCKET, SO_KEEPALIVE, (char *) &one, sizeof(one)); if (s->fe->options & PR_O_TCP_NOLING) setsockopt(cfd, SOL_SOCKET, SO_LINGER, (struct linger *) &nolinger, sizeof(struct linger)); #if defined(TCP_MAXSEG) if (s->listener->maxseg < 0) { /* we just want to reduce the current MSS by that value */ int mss; socklen_t mss_len = sizeof(mss); if (getsockopt(cfd, IPPROTO_TCP, TCP_MAXSEG, &mss, &mss_len) == 0) { mss += s->listener->maxseg; /* remember, it's < 0 */ setsockopt(cfd, IPPROTO_TCP, TCP_MAXSEG, &mss, sizeof(mss)); } } #endif } if (global.tune.client_sndbuf) setsockopt(cfd, SOL_SOCKET, SO_SNDBUF, &global.tune.client_sndbuf, sizeof(global.tune.client_sndbuf)); if (global.tune.client_rcvbuf) setsockopt(cfd, SOL_SOCKET, SO_RCVBUF, &global.tune.client_rcvbuf, sizeof(global.tune.client_rcvbuf)); if (s->fe->mode == PR_MODE_HTTP) { /* the captures are only used in HTTP frontends */ if (unlikely(s->fe->nb_req_cap > 0 && (s->txn.req.cap = pool_alloc2(s->fe->req_cap_pool)) == NULL)) goto out_return; /* no memory */ if (unlikely(s->fe->nb_rsp_cap > 0 && (s->txn.rsp.cap = pool_alloc2(s->fe->rsp_cap_pool)) == NULL)) goto out_free_reqcap; /* no memory */ } if (s->fe->http_needed) { /* we have to allocate header indexes only if we know * that we may make use of them. This of course includes * (mode == PR_MODE_HTTP). */ s->txn.hdr_idx.size = global.tune.max_http_hdr; if (unlikely((s->txn.hdr_idx.v = pool_alloc2(pool2_hdr_idx)) == NULL)) goto out_free_rspcap; /* no memory */ /* and now initialize the HTTP transaction state */ http_init_txn(s); } if ((s->fe->mode == PR_MODE_TCP || s->fe->mode == PR_MODE_HTTP) && (!LIST_ISEMPTY(&s->fe->logsrvs))) { if (likely(!LIST_ISEMPTY(&s->fe->logformat))) { /* we have the client ip */ if (s->logs.logwait & LW_CLIP) if (!(s->logs.logwait &= ~(LW_CLIP|LW_INIT))) s->do_log(s); } else { char pn[INET6_ADDRSTRLEN], sn[INET6_ADDRSTRLEN]; conn_get_from_addr(s->req->prod->conn); conn_get_to_addr(s->req->prod->conn); switch (addr_to_str(&s->req->prod->conn->addr.from, pn, sizeof(pn))) { case AF_INET: case AF_INET6: addr_to_str(&s->req->prod->conn->addr.to, sn, sizeof(sn)); send_log(s->fe, LOG_INFO, "Connect from %s:%d to %s:%d (%s/%s)\n", pn, get_host_port(&s->req->prod->conn->addr.from), sn, get_host_port(&s->req->prod->conn->addr.to), s->fe->id, (s->fe->mode == PR_MODE_HTTP) ? "HTTP" : "TCP"); break; case AF_UNIX: /* UNIX socket, only the destination is known */ send_log(s->fe, LOG_INFO, "Connect to unix:%d (%s/%s)\n", s->listener->luid, s->fe->id, (s->fe->mode == PR_MODE_HTTP) ? "HTTP" : "TCP"); break; } } } if (unlikely((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)))) { char pn[INET6_ADDRSTRLEN]; conn_get_from_addr(s->req->prod->conn); switch (addr_to_str(&s->req->prod->conn->addr.from, pn, sizeof(pn))) { case AF_INET: case AF_INET6: chunk_printf(&trash, "%08x:%s.accept(%04x)=%04x from [%s:%d]\n", s->uniq_id, s->fe->id, (unsigned short)s->listener->fd, (unsigned short)cfd, pn, get_host_port(&s->req->prod->conn->addr.from)); break; case AF_UNIX: /* UNIX socket, only the destination is known */ chunk_printf(&trash, "%08x:%s.accept(%04x)=%04x from [unix:%d]\n", s->uniq_id, s->fe->id, (unsigned short)s->listener->fd, (unsigned short)cfd, s->listener->luid); break; } if (write(1, trash.str, trash.len) < 0) /* shut gcc warning */; } if (s->fe->mode == PR_MODE_HTTP) s->req->flags |= CF_READ_DONTWAIT; /* one read is usually enough */ /* note: this should not happen anymore since there's always at least the switching rules */ if (!s->req->analysers) { channel_auto_connect(s->req); /* don't wait to establish connection */ channel_auto_close(s->req); /* let the producer forward close requests */ } s->req->rto = s->fe->timeout.client; s->rep->wto = s->fe->timeout.client; /* everything's OK, let's go on */ return 1; /* Error unrolling */ out_free_rspcap: pool_free2(s->fe->rsp_cap_pool, s->txn.rsp.cap); out_free_reqcap: pool_free2(s->fe->req_cap_pool, s->txn.req.cap); out_return: return -1; }