/* If an explicit source binding is specified on the server and/or backend, and * this source makes use of the transparent proxy, then it is extracted now and * assigned to the session's pending connection. This function assumes that an * outgoing connection has already been assigned to s->si[1].end. */ static void assign_tproxy_address(struct session *s) { #if defined(CONFIG_HAP_CTTPROXY) || defined(CONFIG_HAP_TRANSPARENT) struct server *srv = objt_server(s->target); struct conn_src *src; struct connection *cli_conn; struct connection *srv_conn = objt_conn(s->si[1].end); if (srv && srv->conn_src.opts & CO_SRC_BIND) src = &srv->conn_src; else if (s->be->conn_src.opts & CO_SRC_BIND) src = &s->be->conn_src; else return; switch (src->opts & CO_SRC_TPROXY_MASK) { case CO_SRC_TPROXY_ADDR: srv_conn->addr.from = src->tproxy_addr; break; case CO_SRC_TPROXY_CLI: case CO_SRC_TPROXY_CIP: /* FIXME: what can we do if the client connects in IPv6 or unix socket ? */ cli_conn = objt_conn(s->si[0].end); if (cli_conn) srv_conn->addr.from = cli_conn->addr.from; else memset(&srv_conn->addr.from, 0, sizeof(srv_conn->addr.from)); break; case CO_SRC_TPROXY_DYN: if (src->bind_hdr_occ) { char *vptr; int vlen; int rewind; /* bind to the IP in a header */ ((struct sockaddr_in *)&srv_conn->addr.from)->sin_family = AF_INET; ((struct sockaddr_in *)&srv_conn->addr.from)->sin_port = 0; ((struct sockaddr_in *)&srv_conn->addr.from)->sin_addr.s_addr = 0; b_rew(s->req.buf, rewind = http_hdr_rewind(&s->txn.req)); if (http_get_hdr(&s->txn.req, src->bind_hdr_name, src->bind_hdr_len, &s->txn.hdr_idx, src->bind_hdr_occ, NULL, &vptr, &vlen)) { ((struct sockaddr_in *)&srv_conn->addr.from)->sin_addr.s_addr = htonl(inetaddr_host_lim(vptr, vptr + vlen)); } b_adv(s->req.buf, rewind); } break; default: memset(&srv_conn->addr.from, 0, sizeof(srv_conn->addr.from)); } #endif }
/* * This function assigns a server address to a session, and sets SN_ADDR_SET. * The address is taken from the currently assigned server, or from the * dispatch or transparent address. * * It may return : * SRV_STATUS_OK if everything is OK. * SRV_STATUS_INTERNAL for other unrecoverable errors. * * Upon successful return, the session flag SN_ADDR_SET is set. This flag is * not cleared, so it's to the caller to clear it if required. * * The caller is responsible for having already assigned a connection * to si->end. * */ int assign_server_address(struct session *s) { struct connection *cli_conn = objt_conn(s->si[0].end); struct connection *srv_conn = objt_conn(s->si[1].end); #ifdef DEBUG_FULL fprintf(stderr,"assign_server_address : s=%p\n",s); #endif if ((s->flags & SN_DIRECT) || (s->be->lbprm.algo & BE_LB_KIND)) { /* A server is necessarily known for this session */ if (!(s->flags & SN_ASSIGNED)) return SRV_STATUS_INTERNAL; srv_conn->addr.to = objt_server(s->target)->addr; if (!is_addr(&srv_conn->addr.to) && cli_conn) { /* if the server has no address, we use the same address * the client asked, which is handy for remapping ports * locally on multiple addresses at once. Nothing is done * for AF_UNIX addresses. */ conn_get_to_addr(cli_conn); if (cli_conn->addr.to.ss_family == AF_INET) { ((struct sockaddr_in *)&srv_conn->addr.to)->sin_addr = ((struct sockaddr_in *)&cli_conn->addr.to)->sin_addr; } else if (cli_conn->addr.to.ss_family == AF_INET6) { ((struct sockaddr_in6 *)&srv_conn->addr.to)->sin6_addr = ((struct sockaddr_in6 *)&cli_conn->addr.to)->sin6_addr; } } /* if this server remaps proxied ports, we'll use * the port the client connected to with an offset. */ if ((objt_server(s->target)->flags & SRV_F_MAPPORTS) && cli_conn) { int base_port; conn_get_to_addr(cli_conn); /* First, retrieve the port from the incoming connection */ base_port = get_host_port(&cli_conn->addr.to); /* Second, assign the outgoing connection's port */ base_port += get_host_port(&srv_conn->addr.to); set_host_port(&srv_conn->addr.to, base_port); } } else if (s->be->options & PR_O_DISPATCH) { /* connect to the defined dispatch addr */ srv_conn->addr.to = s->be->dispatch_addr; } else if ((s->be->options & PR_O_TRANSP) && cli_conn) { /* in transparent mode, use the original dest addr if no dispatch specified */ conn_get_to_addr(cli_conn); if (cli_conn->addr.to.ss_family == AF_INET || cli_conn->addr.to.ss_family == AF_INET6) srv_conn->addr.to = cli_conn->addr.to; } else if (s->be->options & PR_O_HTTP_PROXY) { /* If HTTP PROXY option is set, then server is already assigned * during incoming client request parsing. */ } else { /* no server and no LB algorithm ! */ return SRV_STATUS_INTERNAL; } /* Copy network namespace from client connection */ srv_conn->proxy_netns = cli_conn ? cli_conn->proxy_netns : NULL; s->flags |= SN_ADDR_SET; return SRV_STATUS_OK; }
int assign_server(struct session *s) { struct connection *conn; struct server *conn_slot; struct server *srv, *prev_srv; int err; DPRINTF(stderr,"assign_server : s=%p\n",s); err = SRV_STATUS_INTERNAL; if (unlikely(s->pend_pos || s->flags & SN_ASSIGNED)) goto out_err; prev_srv = objt_server(s->target); conn_slot = s->srv_conn; /* We have to release any connection slot before applying any LB algo, * otherwise we may erroneously end up with no available slot. */ if (conn_slot) sess_change_server(s, NULL); /* We will now try to find the good server and store it into <objt_server(s->target)>. * Note that <objt_server(s->target)> may be NULL in case of dispatch or proxy mode, * as well as if no server is available (check error code). */ srv = NULL; s->target = NULL; conn = objt_conn(s->si[1].end); if (conn && (conn->flags & CO_FL_CONNECTED) && objt_server(conn->target) && __objt_server(conn->target)->proxy == s->be && ((s->txn.flags & TX_PREFER_LAST) || ((s->be->options & PR_O_PREF_LAST) && (!s->be->max_ka_queue || server_has_room(__objt_server(conn->target)) || (__objt_server(conn->target)->nbpend + 1) < s->be->max_ka_queue))) && srv_is_usable(__objt_server(conn->target))) { /* This session was relying on a server in a previous request * and the proxy has "option prefer-last-server" set, so * let's try to reuse the same server. */ srv = __objt_server(conn->target); s->target = &srv->obj_type; } else if (s->be->lbprm.algo & BE_LB_KIND) { /* we must check if we have at least one server available */ if (!s->be->lbprm.tot_weight) { err = SRV_STATUS_NOSRV; goto out; } /* First check whether we need to fetch some data or simply call * the LB lookup function. Only the hashing functions will need * some input data in fact, and will support multiple algorithms. */ switch (s->be->lbprm.algo & BE_LB_LKUP) { case BE_LB_LKUP_RRTREE: srv = fwrr_get_next_server(s->be, prev_srv); break; case BE_LB_LKUP_FSTREE: srv = fas_get_next_server(s->be, prev_srv); break; case BE_LB_LKUP_LCTREE: srv = fwlc_get_next_server(s->be, prev_srv); break; case BE_LB_LKUP_CHTREE: case BE_LB_LKUP_MAP: if ((s->be->lbprm.algo & BE_LB_KIND) == BE_LB_KIND_RR) { if (s->be->lbprm.algo & BE_LB_LKUP_CHTREE) srv = chash_get_next_server(s->be, prev_srv); else srv = map_get_server_rr(s->be, prev_srv); break; } else if ((s->be->lbprm.algo & BE_LB_KIND) != BE_LB_KIND_HI) { /* unknown balancing algorithm */ err = SRV_STATUS_INTERNAL; goto out; } switch (s->be->lbprm.algo & BE_LB_PARM) { case BE_LB_HASH_SRC: conn = objt_conn(s->si[0].end); if (conn && conn->addr.from.ss_family == AF_INET) { srv = get_server_sh(s->be, (void *)&((struct sockaddr_in *)&conn->addr.from)->sin_addr, 4); } else if (conn && conn->addr.from.ss_family == AF_INET6) { srv = get_server_sh(s->be, (void *)&((struct sockaddr_in6 *)&conn->addr.from)->sin6_addr, 16); } else { /* unknown IP family */ err = SRV_STATUS_INTERNAL; goto out; } break; case BE_LB_HASH_URI: /* URI hashing */ if (s->txn.req.msg_state < HTTP_MSG_BODY) break; srv = get_server_uh(s->be, b_ptr(s->req.buf, -http_uri_rewind(&s->txn.req)), s->txn.req.sl.rq.u_l); break; case BE_LB_HASH_PRM: /* URL Parameter hashing */ if (s->txn.req.msg_state < HTTP_MSG_BODY) break; srv = get_server_ph(s->be, b_ptr(s->req.buf, -http_uri_rewind(&s->txn.req)), s->txn.req.sl.rq.u_l); if (!srv && s->txn.meth == HTTP_METH_POST) srv = get_server_ph_post(s); break; case BE_LB_HASH_HDR: /* Header Parameter hashing */ if (s->txn.req.msg_state < HTTP_MSG_BODY) break; srv = get_server_hh(s); break; case BE_LB_HASH_RDP: /* RDP Cookie hashing */ srv = get_server_rch(s); break; default: /* unknown balancing algorithm */ err = SRV_STATUS_INTERNAL; goto out; } /* If the hashing parameter was not found, let's fall * back to round robin on the map. */ if (!srv) { if (s->be->lbprm.algo & BE_LB_LKUP_CHTREE) srv = chash_get_next_server(s->be, prev_srv); else srv = map_get_server_rr(s->be, prev_srv); } /* end of map-based LB */ break; default: /* unknown balancing algorithm */ err = SRV_STATUS_INTERNAL; goto out; } if (!srv) { err = SRV_STATUS_FULL; goto out; } else if (srv != prev_srv) { s->be->be_counters.cum_lbconn++; srv->counters.cum_lbconn++; } s->target = &srv->obj_type; } else if (s->be->options & (PR_O_DISPATCH | PR_O_TRANSP)) { s->target = &s->be->obj_type; } else if ((s->be->options & PR_O_HTTP_PROXY) && (conn = objt_conn(s->si[1].end)) && is_addr(&conn->addr.to)) { /* in proxy mode, we need a valid destination address */ s->target = &s->be->obj_type; } else { err = SRV_STATUS_NOSRV; goto out; } s->flags |= SN_ASSIGNED; err = SRV_STATUS_OK; out: /* Either we take back our connection slot, or we offer it to someone * else if we don't need it anymore. */ if (conn_slot) { if (conn_slot == srv) { sess_change_server(s, srv); } else { if (may_dequeue_tasks(conn_slot, s->be)) process_srv_queue(conn_slot); } } out_err: return err; }
/* * This function initiates a connection to the server assigned to this session * (s->target, s->si[1].addr.to). It will assign a server if none * is assigned yet. * It can return one of : * - SN_ERR_NONE if everything's OK * - SN_ERR_SRVTO if there are no more servers * - SN_ERR_SRVCL if the connection was refused by the server * - SN_ERR_PRXCOND if the connection has been limited by the proxy (maxconn) * - SN_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...) * - SN_ERR_INTERNAL for any other purely internal errors * Additionnally, in the case of SN_ERR_RESOURCE, an emergency log will be emitted. * The server-facing stream interface is expected to hold a pre-allocated connection * in s->si[1].conn. */ int connect_server(struct session *s) { struct connection *cli_conn; struct connection *srv_conn; struct server *srv; int reuse = 0; int err; srv_conn = objt_conn(s->si[1].end); if (srv_conn) reuse = s->target == srv_conn->target; if (reuse) { /* Disable connection reuse if a dynamic source is used. * As long as we don't share connections between servers, * we don't need to disable connection reuse on no-idempotent * requests nor when PROXY protocol is used. */ srv = objt_server(s->target); if (srv && srv->conn_src.opts & CO_SRC_BIND) { if ((srv->conn_src.opts & CO_SRC_TPROXY_MASK) == CO_SRC_TPROXY_DYN) reuse = 0; } else if (s->be->conn_src.opts & CO_SRC_BIND) { if ((s->be->conn_src.opts & CO_SRC_TPROXY_MASK) == CO_SRC_TPROXY_DYN) reuse = 0; } } srv_conn = si_alloc_conn(&s->si[1], reuse); if (!srv_conn) return SN_ERR_RESOURCE; if (!(s->flags & SN_ADDR_SET)) { err = assign_server_address(s); if (err != SRV_STATUS_OK) return SN_ERR_INTERNAL; } if (!conn_xprt_ready(srv_conn)) { /* the target was only on the session, assign it to the SI now */ srv_conn->target = s->target; /* set the correct protocol on the output stream interface */ if (objt_server(s->target)) { conn_prepare(srv_conn, protocol_by_family(srv_conn->addr.to.ss_family), objt_server(s->target)->xprt); } else if (obj_type(s->target) == OBJ_TYPE_PROXY) { /* proxies exclusively run on raw_sock right now */ conn_prepare(srv_conn, protocol_by_family(srv_conn->addr.to.ss_family), &raw_sock); if (!objt_conn(s->si[1].end) || !objt_conn(s->si[1].end)->ctrl) return SN_ERR_INTERNAL; } else return SN_ERR_INTERNAL; /* how did we get there ? */ /* process the case where the server requires the PROXY protocol to be sent */ srv_conn->send_proxy_ofs = 0; if (objt_server(s->target) && objt_server(s->target)->pp_opts) { srv_conn->send_proxy_ofs = 1; /* must compute size */ cli_conn = objt_conn(s->si[0].end); if (cli_conn) conn_get_to_addr(cli_conn); } si_attach_conn(&s->si[1], srv_conn); assign_tproxy_address(s); } else { /* the connection is being reused, just re-attach it */ si_attach_conn(&s->si[1], srv_conn); s->flags |= SN_SRV_REUSED; } /* flag for logging source ip/port */ if (s->fe->options2 & PR_O2_SRC_ADDR) s->si[1].flags |= SI_FL_SRC_ADDR; /* disable lingering */ if (s->be->options & PR_O_TCP_NOLING) s->si[1].flags |= SI_FL_NOLINGER; err = si_connect(&s->si[1]); if (err != SN_ERR_NONE) return err; /* set connect timeout */ s->si[1].exp = tick_add_ifset(now_ms, s->be->timeout.connect); srv = objt_server(s->target); if (srv) { s->flags |= SN_CURR_SESS; srv->cur_sess++; if (srv->cur_sess > srv->counters.cur_sess_max) srv->counters.cur_sess_max = srv->cur_sess; if (s->be->lbprm.server_take_conn) s->be->lbprm.server_take_conn(srv); } return SN_ERR_NONE; /* connection is OK */ }
/* 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; }
/* 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; }