diff --git a/include/haproxy/action-t.h b/include/haproxy/action-t.h index 7fafd612a4a6..760b7d673edd 100644 --- a/include/haproxy/action-t.h +++ b/include/haproxy/action-t.h @@ -89,6 +89,10 @@ enum act_name { /* http request actions. */ ACT_HTTP_REQ_TARPIT, + /* http request for ppv2, joyent */ + ACT_HTTP_SET_PPV2_HDR, + ACT_HTTP_SET_PPV2_TLV_HDR, + /* tcp actions */ ACT_TCP_EXPECT_PX, ACT_TCP_EXPECT_CIP, diff --git a/include/haproxy/server-t.h b/include/haproxy/server-t.h index 03f6899a9bf1..435073dcf4bd 100644 --- a/include/haproxy/server-t.h +++ b/include/haproxy/server-t.h @@ -164,6 +164,7 @@ enum srv_initaddr { #define SRV_PP_V2_AUTHORITY 0x0080 /* proxy protocol version 2 with authority */ #define SRV_PP_V2_CRC32C 0x0100 /* proxy protocol version 2 with crc32c */ #define SRV_PP_V2_UNIQUE_ID 0x0200 /* proxy protocol version 2 with unique ID */ +#define SRV_PP_V2_SET_TLV 0x0400 /* proxy protocol version 2 with TLV, joyent */ /* function which act on servers need to return various errors */ #define SRV_STATUS_OK 0 /* everything is OK. */ @@ -445,6 +446,8 @@ struct server { struct sockaddr_storage addr; /* the address to connect to, doesn't include the port */ struct sockaddr_storage socks4_addr; /* the address of the SOCKS4 Proxy, including the port */ + struct list tlv_list; /* server's PPV2 TLVs, joyent */ + EXTRA_COUNTERS(extra_counters); }; diff --git a/src/connection.c b/src/connection.c index 5f7226aaeca6..368e688a35a5 100644 --- a/src/connection.c +++ b/src/connection.c @@ -2043,6 +2043,26 @@ static int make_proxy_line_v2(char *buf, int buf_len, struct server *srv, struct } } + /* copy server TLVs, joyent */ + if (strm && (srv->pp_opts & SRV_PP_V2_SET_TLV)) { + struct conn_tlv_list *node; + + list_for_each_entry(node, &srv->tlv_list, list) { + /* append TLVs from config */ + ret += make_tlv(&buf[ret], (buf_len - ret), node->type, node->len, node->value); + } + } + + /* copy TLVs from remote's, joyent */ + if (strm && remote) { + struct conn_tlv_list *node; + + list_for_each_entry(node, &remote->tlv_list, list) { + /* append remote TLVs */ + ret += make_tlv(&buf[ret], (buf_len - ret), node->type, node->len, node->value); + } + } + #ifdef USE_OPENSSL if (srv->pp_opts & SRV_PP_V2_SSL) { struct tlv_ssl *tlv; diff --git a/src/http_act.c b/src/http_act.c index d168cf5e034c..1e2a66eeaf82 100644 --- a/src/http_act.c +++ b/src/http_act.c @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -39,6 +40,7 @@ #include #include #include +#include /* Release memory allocated by most of HTTP actions. Concretly, it releases @@ -1554,6 +1556,307 @@ static enum act_parse_ret parse_http_set_header(const char **args, int *orig_arg return ACT_RET_PRS_OK; } +/* joyent */ +static int generate_ppv2_tlv_header(struct list *tlv_lists, char *buf, int max_len) { + struct conn_tlv_list *node; + int len = 0, tlv_val_len; + +// leading code: 0xff= +// tail semicolon +#define CODE_LEN 6 + + list_for_each_entry(node, tlv_lists, list) { + tlv_val_len = node->len; + if (max_len <= (len+tlv_val_len+CODE_LEN)) { + break; + } + len += snprintf(&buf[len], max_len-len, "0x%02x=", node->type); + strncpy(&buf[len], (char*)node->value, tlv_val_len); + len += tlv_val_len; + if (node->list.n != tlv_lists) { + buf[len++] = ';'; + } + } + + return len; +} + +static enum act_return http_action_set_ppv2_tlv_header(struct act_rule *rule, struct proxy *px, + struct session *sess, struct stream *s, int flags) +{ + struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp); + struct htx *htx = htxbuf(&msg->chn->buf); + enum act_return ret = ACT_RET_CONT; + struct server *srv = px->srv; + struct connection *conn = objt_conn(strm_orig(s)); + struct http_hdr_ctx ctx; + struct ist n, v; + + int max_len=1024, len=0; + char val[max_len]; + + val[0] = '\0'; + + // req only + if (rule->from == ACT_F_HTTP_RES) { + return ret; + } + + if (srv != NULL) { + len += generate_ppv2_tlv_header(&srv->tlv_list, &val[len], max_len - len); + } + + if (conn != NULL) { + len += generate_ppv2_tlv_header(&conn->tlv_list, &val[len], max_len - len); + } + + if (len < 1) { + return ret; + } + else if (max_len < len) { + val[max_len-1] = '\0'; + } + else { + val[len] = '\0'; + } + + n = rule->arg.http.str; + v = ist(val); + + /* remove all occurrences of the header */ + ctx.blk = NULL; + while (http_find_header(htx, n, &ctx, 1)) + http_remove_header(htx, &ctx); + + /* Now add header */ + if (!http_add_header(htx, n, v)) + goto fail_rewrite; + +leave: + return ret; + +fail_rewrite: + _HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites); + if (s->flags & SF_BE_ASSIGNED) + _HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites); + if (sess->listener && sess->listener->counters) + _HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites); + if (objt_server(s->target)) + _HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites); + + if (!(msg->flags & HTTP_MSGF_SOFT_RW)) { + ret = ACT_RET_ERR; + if (!(s->flags & SF_ERR_MASK)) + s->flags |= SF_ERR_PRXCOND; + } + goto leave; +} + +static int generate_ppv2_header(char *buf, int buf_len, const struct sockaddr_storage *src, const struct sockaddr_storage *dst) +{ + int ret = 0; + char * protocol; + char src_str[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)]; + char dst_str[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)]; + in_port_t src_port; + in_port_t dst_port; + + if ( !src + || !dst + || (src->ss_family != AF_INET && src->ss_family != AF_INET6) + || (dst->ss_family != AF_INET && dst->ss_family != AF_INET6)) { + + return ret; + } + + /* IPv4 for both src and dst */ + if (src->ss_family == AF_INET && dst->ss_family == AF_INET) { + protocol = "TCP4"; + if (!inet_ntop(AF_INET, &((struct sockaddr_in *)src)->sin_addr, src_str, sizeof(src_str))) + return 0; + src_port = ((struct sockaddr_in *)src)->sin_port; + if (!inet_ntop(AF_INET, &((struct sockaddr_in *)dst)->sin_addr, dst_str, sizeof(dst_str))) + return 0; + dst_port = ((struct sockaddr_in *)dst)->sin_port; + } + /* IPv6 for at least one of src and dst */ + else { + struct in6_addr tmp; + + protocol = "TCP6"; + + if (src->ss_family == AF_INET) { + /* Convert src to IPv6 */ + v4tov6(&tmp, &((struct sockaddr_in *)src)->sin_addr); + src_port = ((struct sockaddr_in *)src)->sin_port; + } + else { + tmp = ((struct sockaddr_in6 *)src)->sin6_addr; + src_port = ((struct sockaddr_in6 *)src)->sin6_port; + } + + if (!inet_ntop(AF_INET6, &tmp, src_str, sizeof(src_str))) + return 0; + + if (dst->ss_family == AF_INET) { + /* Convert dst to IPv6 */ + v4tov6(&tmp, &((struct sockaddr_in *)dst)->sin_addr); + dst_port = ((struct sockaddr_in *)dst)->sin_port; + } + else { + tmp = ((struct sockaddr_in6 *)dst)->sin6_addr; + dst_port = ((struct sockaddr_in6 *)dst)->sin6_port; + } + + if (!inet_ntop(AF_INET6, &tmp, dst_str, sizeof(dst_str))) + return 0; + } + + ret = snprintf(buf, buf_len, "address-type=%s;source-address=%s:%u;destination-address=%s:%u;", + protocol, src_str, ntohs(src_port), dst_str, ntohs(dst_port)); + + if (ret >= buf_len) + return 0; + + return ret; +} + +static enum act_return http_action_set_ppv2_header(struct act_rule *rule, struct proxy *px, + struct session *sess, struct stream *s, int flags) +{ + struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp); + struct htx *htx = htxbuf(&msg->chn->buf); + enum act_return ret = ACT_RET_CONT; + struct connection *conn = objt_conn(strm_orig(s)); + struct http_hdr_ctx ctx; + struct ist n, v; + + const struct sockaddr_storage *src = NULL; + const struct sockaddr_storage *dst = NULL; + + int max_len=1024, len=0; + char val[max_len]; + + val[0] = '\0'; + + // req only + if (rule->from == ACT_F_HTTP_RES) { + return ret; + } + + if (s) { + src = sc_src(s->scf); + dst = sc_dst(s->scf); + } + else if (conn && conn_get_src(conn) && conn_get_dst(conn)) { + src = conn_src(conn); + dst = conn_dst(conn); + } + + if (src && dst) + len = generate_ppv2_header(val, max_len, src, dst); + else + len = generate_ppv2_header(val, max_len, NULL, NULL); + + if (len < 1) { + return ret; + } + else if (max_len < len) { + val[max_len-1] = '\0'; + } + else { + val[len] = '\0'; + } + + n = rule->arg.http.str; + v = ist(val); + + /* remove all occurrences of the header */ + ctx.blk = NULL; + while (http_find_header(htx, n, &ctx, 1)) + http_remove_header(htx, &ctx); + + /* Now add header */ + if (!http_add_header(htx, n, v)) + goto fail_rewrite; + +leave: + return ret; + +fail_rewrite: + _HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites); + if (s->flags & SF_BE_ASSIGNED) + _HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites); + if (sess->listener && sess->listener->counters) + _HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites); + if (objt_server(s->target)) + _HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites); + + if (!(msg->flags & HTTP_MSGF_SOFT_RW)) { + ret = ACT_RET_ERR; + if (!(s->flags & SF_ERR_MASK)) + s->flags |= SF_ERR_PRXCOND; + } + goto leave; +} + +static enum act_parse_ret parse_http_set_ppv2_header(const char **args, int *orig_arg, struct proxy *px, + struct act_rule *rule, char **err) +{ + int cur_arg; + const char *p; + + /* set-proxy-v2-header */ + if (args[*orig_arg-1][13] == 'h') { + rule->action = ACT_HTTP_SET_PPV2_HDR; + rule->action_ptr = http_action_set_ppv2_header; + rule->release_ptr = release_http_action; + } + /* set-proxy-v2-tlv-header */ + else { + rule->action = ACT_HTTP_SET_PPV2_TLV_HDR; + rule->action_ptr = http_action_set_ppv2_tlv_header; + rule->release_ptr = release_http_action; + } + + LIST_INIT(&rule->arg.http.fmt); + + cur_arg = *orig_arg; + if (!*args[cur_arg]) { + memprintf(err, "expects exactly 1 arguments"); + return ACT_RET_PRS_ERR; + } + + rule->arg.http.str = ist(strdup(args[cur_arg])); + + free(px->conf.lfs_file); + px->conf.lfs_file = strdup(px->conf.args.file); + px->conf.lfs_line = px->conf.args.line; + + (*orig_arg) ++; + + /* some characters are totally forbidden in header names and + * may happen by accident when writing configs, causing strange + * failures in field. Better catch these ones early, nobody will + * miss them. In particular, a colon at the end (or anywhere + * after the first char) or a space/cr anywhere due to misplaced + * quotes are hard to spot. + */ + for (p = istptr(rule->arg.http.str); p < istend(rule->arg.http.str); p++) { + if (HTTP_IS_TOKEN(*p)) + continue; + if (p == istptr(rule->arg.http.str) && *p == ':') + continue; + /* we only report this as-is but it will not cause an error */ + memprintf(err, "header name '%s' contains forbidden character '%c'", istptr(rule->arg.http.str), *p); + break; + } + + return ACT_RET_PRS_OK; +} +/* joyent */ + + /* This function executes a replace-header or replace-value actions. It * builds a string in the trash from the specified format string. It finds * the action to be performed in <.action>, previously filled by function @@ -2450,6 +2753,9 @@ static struct action_kw_list http_req_actions = { { "track-sc", parse_http_track_sc, KWF_MATCH_PREFIX }, { "set-timeout", parse_http_set_timeout, 0 }, { "wait-for-body", parse_http_wait_for_body, 0 }, + { "set-proxy-v2-header", parse_http_set_ppv2_header, 0 }, /* joyent */ + { "set-proxy-v2-tlv-header", parse_http_set_ppv2_header, 0 }, /* joyent */ + { NULL, NULL } } }; diff --git a/src/server.c b/src/server.c index 3673340d155a..096f6a850ff7 100644 --- a/src/server.c +++ b/src/server.c @@ -49,6 +49,9 @@ #include #include +#define TLV_DELIM ";\n" +#define TLV_VALUE_DELIM "=" + static void srv_update_status(struct server *s, int type, int cause); static int srv_apply_lastaddr(struct server *srv, int *err_code); @@ -1304,6 +1307,93 @@ static int srv_parse_send_proxy_v2(char **args, int *cur_arg, return srv_enable_pp_flags(newsrv, SRV_PP_V2); } +static struct conn_tlv_list* parse_tlv_kv(char* str) +{ + struct conn_tlv_list *cur_node; + char *key = NULL; + char *val = NULL; + char *saveptr_int = NULL; + uint16_t l; + + key = strtok_r(str, TLV_VALUE_DELIM, &saveptr_int); + if (unlikely(key == NULL)) { + ha_warning("'%s' ignoring TLV, invalid key.\n", str); + return NULL; + } + + val = strtok_r(NULL, TLV_VALUE_DELIM, &saveptr_int); + if (unlikely(val == NULL)) { + ha_warning("'%s' ignoring TLV, invalid value.\n", str); + return NULL; + } + + l = (uint16_t)strlen(val); + if (unlikely(l > HA_PP2_MAX_ALLOC)) { + ha_warning("'%s' ignoring TLV, invalid length.\n", str); + return NULL; + } + + cur_node = (struct conn_tlv_list*)malloc(sizeof(struct conn_tlv_list) + l + 1); + if (unlikely(!cur_node)) + return NULL; + + LIST_INIT(&cur_node->list); + + cur_node->type = (uint8_t)strtol(key, NULL, 0); + strncpy((char*)cur_node->value, val, l); + cur_node->value[l] = '\0'; + cur_node->len = l; + + return cur_node; +} + +/* parse TLVs in config file */ +/* 0xe0=this-is-tlv-value;0x30=tlv-123456; */ +static int parse_tlv(struct server *newsrv, char* value) +{ + struct conn_tlv_list *node; + char *ret_ptr = NULL; + char *saveptr_ext = NULL; + int cnt=0; + + ret_ptr = strtok_r(value, TLV_DELIM, &saveptr_ext); + while(ret_ptr) { + node = parse_tlv_kv(ret_ptr); + if (node != NULL) { + LIST_APPEND(&newsrv->tlv_list, &node->list); + cnt ++; + } + ret_ptr = strtok_r(NULL, TLV_DELIM, &saveptr_ext); + } + + return cnt; +} + +/* Parse the "set-proxy-v2-tlv" server keyword, joyent */ +static int srv_parse_set_proxy_v2_tlv(char **args, int *cur_arg, + struct proxy *curproxy, struct server *newsrv, char **err) +{ + char *errmsg=NULL, *value; + + value = args[*cur_arg + 1]; + if (!*value) { + memprintf(err, "'%s' expects \n", args[*cur_arg]); + goto err; + } + + *cur_arg += 1; + parse_tlv(newsrv, value); + + srv_enable_pp_flags(newsrv, SRV_PP_V2); + srv_enable_pp_flags(newsrv, SRV_PP_V2_SET_TLV); + + return 0; + + err: + free(errmsg); + return ERR_ALERT | ERR_FATAL; +} + /* Parse the "slowstart" server keyword */ static int srv_parse_slowstart(char **args, int *cur_arg, struct proxy *curproxy, struct server *newsrv, char **err) @@ -1914,6 +2004,7 @@ static struct srv_kw_list srv_kws = { "ALL", { }, { { "resolvers", srv_parse_resolvers, 1, 1, 0 }, /* Configure the resolver to use for name resolution */ { "send-proxy", srv_parse_send_proxy, 0, 1, 1 }, /* Enforce use of PROXY V1 protocol */ { "send-proxy-v2", srv_parse_send_proxy_v2, 0, 1, 1 }, /* Enforce use of PROXY V2 protocol */ + { "set-proxy-v2-tlv", srv_parse_set_proxy_v2_tlv, 0, 0, 1 }, /* Set TLV of PROXY V2 protocol, joyent */ { "shard", srv_parse_shard, 1, 1, 1 }, /* Server shard (only in peers protocol context) */ { "slowstart", srv_parse_slowstart, 1, 1, 1 }, /* Set the warm-up timer for a previously failed server */ { "source", srv_parse_source, -1, 1, 1 }, /* Set the source address to be used to connect to the server */ @@ -2428,6 +2519,7 @@ struct server *new_server(struct proxy *proxy) LIST_APPEND(&servers_list, &srv->global_list); LIST_INIT(&srv->srv_rec_item); LIST_INIT(&srv->ip_rec_item); + LIST_INIT(&srv->tlv_list); MT_LIST_INIT(&srv->prev_deleted); event_hdl_sub_list_init(&srv->e_subs); srv->rid = 0; /* rid defaults to 0 */ @@ -2485,6 +2577,18 @@ void srv_free_params(struct server *srv) xprt_get(XPRT_SSL)->destroy_srv(srv); } +/* free tlvs belongs to svr, joyent */ +void srv_free_tlv_nodes(struct server *srv) +{ + struct conn_tlv_list *node, *back; + + list_for_each_entry_safe(node, back, &srv->tlv_list, list) { + LIST_DEL_INIT(&node->list); + free(node); + } +} + + /* Deallocate a server and its member. must be allocated. For * dynamic servers, its refcount is decremented first. The free operations are * conducted only if the refcount is nul. @@ -2525,6 +2629,8 @@ struct server *srv_drop(struct server *srv) LIST_DELETE(&srv->global_list); event_hdl_sub_list_destroy(&srv->e_subs); + srv_free_tlv_nodes(srv); + EXTRA_COUNTERS_FREE(srv->extra_counters); ha_free(&srv);