CVE-2026-107929: local interface connection reuse mixup
What the report claims
CURLOPT_INTERFACE with documented prefixes is not part of the connection reuse key, causing later transfers to reuse connections bound to a different source address.
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2026-107929 local interface connection reuse mixup
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The code supports the reported flaw. A maintainer should review its impact and decide what to do next.
Why this finding?
- The code confirms that CURLOPT_INTERFACE with prefixes (if!, host!, ifhost!) is parsed into three separate strings (STRING_DEVICE, STRING_INTERFACE, STRING_BINDHOST) but only STRING_DEVICE is stored in the connection struct (conn->localdev) and compared during connection reuse.
- bindlocal() reads all three strings when initially binding a socket, but connection reuse skips rebinding, so the second request's different source binding is silently ignored.
- The unprefixed form works correctly because it lands entirely in STRING_DEVICE, which is already in the reuse key.
- This allows a multi-tenant application sharing a libcurl connection cache to have one tenant's traffic egress from another tenant's source IP, bypassing source-IP ACLs.
- The flaw exists in both the tested release (8.22.0) and current master (efd42dfb).
How this analysis ran 6 claims · 17 citations verified
Checked against curl tested commit efd42dfbbf65 (efd42dfbbf65). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Review a draft reply to the reporter
Edit and verify before sending.
Thank you for the detailed report and reproduction. The code confirms the flaw: CURLOPT_INTERFACE with prefixes (if!, host!, ifhost!) is parsed into three strings but only STRING_DEVICE enters the connection reuse key (lib/url.c:1139-1142, lib/url.c:624-628). On reuse, bindlocal() is not called, so the second request's different binding is ignored. This is a valid security issue for applications using per-tenant source bindings with a shared connection cache. We will prepare a fix that includes STRING_INTERFACE and STRING_BINDHOST in the reuse key. Your patch approach of rebuilding the full spelling into localdev is sound; please open a PR when ready.
Inspect all claims and source code 6 claims · 8 code excerpts
Report claims
CURLOPT_INTERFACE with documented prefixes (`if!`, `host!`, `ifhost!`) is not included in the connection reuse key
Read the reasoning
The code confirms that CURLOPT_INTERFACE with prefixes (if!, host!, ifhost!) is parsed into three strings (STRING_DEVICE, STRING_INTERFACE, STRING_BINDHOST) but only STRING_DEVICE is included in the connection reuse key via conn->localdev. The match function compares only localdev, localport, and localportrange.
The reuse key is built in `allocate_conn()` and `url_match_connect_config()`
Read the reasoning
The reuse key is indeed built in allocate_conn (storing only STRING_DEVICE) and checked in url_match_connect_config (comparing only localdev).
Setting CURLOPT_INTERFACE with a prefix such as `host!127.0.0.2` and then making a second request that asks for a different source address
Read the reasoning
Setting CURLOPT_INTERFACE with a prefix triggers Curl_parse_interface which separates the input into device, interface, and host components, stored in three different string slots.
The second transfer reuses the earlier connection and sends traffic from the earlier source address, ignoring the new source binding
Read the reasoning
Because the reuse key omits STRING_INTERFACE and STRING_BINDHOST, a second request with a different prefixed interface matches the existing connection. The socket is not rebound on reuse, so the original source address is used.
Use curl with `--interface "host!127.0.0.2"` followed by `--interface "host!127.0.0.3"` against a keep-alive HTTP server and observe the local IP reported
Read the reasoning
The reproduction steps directly exercise the code path: two requests with different host! prefixes against a keep-alive server will match in url_match_connect_config because only STRING_DEVICE (empty for prefixed forms) is compared.
A request configured for identity B can be sent over a socket bound to identity A, bypassing source‑IP ACLs
Read the reasoning
An application using per-tenant source bindings with a shared connection cache will have requests for tenant B reuse tenant A's connection, sending traffic from A's source IP and bypassing source-IP ACLs.
Deterministic checks that failed
- line none of Curl_parse_interface, setstropt_interface, bindlocal, allocate_conn appear near line 1208 at efd42dfbbf65; it reads: 'Curl_hsts_applies(data->hsts, data->state.origin)) {'
Read the original report (excerpt)
Source code
| 563 | /* |
| 564 | * Curl_parse_interface() |
| 565 | * |
| 566 | * This is used to parse interface argument in the following formats. |
| 567 | * In all the examples, `host` can be an IP address or a hostname. |
| 568 | * |
| 569 | * <iface_or_host> - can be either an interface name or a host. |
| 570 | * if!<iface> - interface name. |
| 571 | * host!<host> - hostname. |
| 572 | * ifhost!<iface>!<host> - interface name and hostname. |
| 573 | * |
| 574 | * Parameters: |
| 575 | * |
| 576 | * input [in] - input string. |
| 577 | * len [in] - length of the input string. |
| 578 | * dev [in/out] - address where a pointer to newly allocated memory |
| 579 | * holding the interface-or-host will be stored upon |
| 580 | * completion. |
| 581 | * iface [in/out] - address where a pointer to newly allocated memory |
| 582 | * holding the interface will be stored upon completion. |
| 583 | * host [in/out] - address where a pointer to newly allocated memory |
| 584 | * holding the host will be stored upon completion. |
| 585 | * |
| 586 | * Returns CURLE_OK on success. |
| 587 | */ |
| 588 | CURLcode Curl_parse_interface(const char *input, |
| C3: Curl_parse_interface definition shows prefix parsing logic | |
| 589 | char **dev, char **iface, char **host) |
| 590 | { |
| 591 | static const char if_prefix[] = "if!"; |
| 592 | static const char host_prefix[] = "host!"; |
| 593 | static const char if_host_prefix[] = "ifhost!"; |
| 594 | size_t len; |
| 595 | |
| 596 | DEBUGASSERT(dev); |
| 597 | DEBUGASSERT(iface); |
| 598 | DEBUGASSERT(host); |
| 599 | |
| 600 | len = strlen(input); |
| 601 | if(len > 512) |
| 602 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 603 | |
| 604 | if(!strncmp(if_prefix, input, strlen(if_prefix))) { |
| 605 | input += strlen(if_prefix); |
| 606 | if(!*input) |
| 607 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 608 | *iface = curlx_memdup0(input, len - strlen(if_prefix)); |
| 609 | return *iface ? CURLE_OK : CURLE_OUT_OF_MEMORY; |
| 610 | } |
| 611 | else if(!strncmp(host_prefix, input, strlen(host_prefix))) { |
| 612 | input += strlen(host_prefix); |
| 613 | if(!*input) |
| 614 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 615 | *host = curlx_memdup0(input, len - strlen(host_prefix)); |
| 616 | return *host ? CURLE_OK : CURLE_OUT_OF_MEMORY; |
| 617 | } |
| 618 | else if(!strncmp(if_host_prefix, input, strlen(if_host_prefix))) { |
| 619 | const char *host_part; |
| 620 | input += strlen(if_host_prefix); |
| 621 | len -= strlen(if_host_prefix); |
| 622 | host_part = memchr(input, '!', len); |
| 623 | if(!host_part || !*(host_part + 1)) |
| 624 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 625 | *iface = curlx_memdup0(input, host_part - input); |
| 626 | if(!*iface) |
| 627 | return CURLE_OUT_OF_MEMORY; |
| 628 | ++host_part; |
| 629 | *host = curlx_memdup0(host_part, len - (host_part - input)); |
| 630 | if(!*host) { |
| 631 | curlx_safefree(*iface); |
| 632 | return CURLE_OUT_OF_MEMORY; |
| 633 | } |
| 634 | return CURLE_OK; |
| 635 | } |
| 636 | |
| 637 | if(!*input) |
| 638 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 639 | *dev = curlx_memdup0(input, len); |
| 640 | return *dev ? CURLE_OK : CURLE_OUT_OF_MEMORY; |
| 641 | } |
| 130 | DEBUGASSERT(passwdp); |
| 131 | |
| 132 | /* Parse the login details if specified. If not, then we treat NULL as a |
| 133 | hint to clear the existing data */ |
| 134 | if(option) { |
| 135 | size_t len = strlen(option); |
| 136 | CURLcode result; |
| 137 | if(len > CURL_MAX_INPUT_LENGTH) |
| 138 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 139 | |
| 140 | result = Curl_parse_login_details(option, len, &user, &passwd, NULL); |
| 141 | if(result) |
| 142 | return result; |
| 143 | } |
| 144 | |
| 145 | curlx_free(*userp); |
| 146 | *userp = user; |
| 147 | |
| 148 | curlx_strzero(*passwdp); |
| 149 | curlx_free(*passwdp); |
| 150 | *passwdp = passwd; |
| 151 | |
| 152 | return CURLE_OK; |
| 153 | } |
| 154 | |
| 155 | static CURLcode setstropt_interface(struct Curl_easy *data, char *option) |
| 156 | { |
| 157 | char *dev = NULL; |
| 158 | char *iface = NULL; |
| 159 | char *host = NULL; |
| 160 | CURLcode result; |
| 161 | |
| 162 | if(option) { |
| 163 | /* Parse the interface details if set, otherwise clear them all */ |
| 164 | result = Curl_parse_interface(option, &dev, &iface, &host); |
| C3: Curl_parse_interface splits the option into dev, iface, host | |
| 165 | if(result) |
| 166 | return result; |
| 167 | } |
| 168 | |
| 169 | result = CURL_EASY_STR_SETN(data, STRING_DEVICE, dev); |
| C1: Only STRING_DEVICE is stored from the parsed interface option | |
| 170 | dev = NULL; |
| 171 | if(!result) { |
| 172 | result = CURL_EASY_STR_SETN(data, STRING_INTERFACE, iface); |
| C1: STRING_INTERFACE is set but not used in connection reuse key | |
| 173 | iface = NULL; |
| 174 | } |
| 175 | if(!result) { |
| 176 | result = CURL_EASY_STR_SETN(data, STRING_BINDHOST, host); |
| C1: STRING_BINDHOST is set but not used in connection reuse key | |
| 177 | host = NULL; |
| 178 | } |
| 179 | curlx_free(dev); |
| 180 | curlx_free(iface); |
| 181 | curlx_free(host); |
| 182 | return result; |
| 183 | } |
| 184 | |
| 185 | #ifdef USE_SSL |
| 186 | #define C_SSLVERSION_VALUE(x) ((x) & 0xffff) |
| 187 | #define C_SSLVERSION_MAX_VALUE(x) ((unsigned long)(x) & 0xffff0000) |
| 188 | #endif |
| 189 | |
| 190 | static CURLcode protocol2num(const char *str, curl_prot_t *val) |
| 191 | { |
| 192 | /* |
| 193 | * We are asked to cherry-pick protocols, so play it safe and disallow all |
| 194 | * protocols to start with, and re-add the wanted ones back in. |
| 195 | */ |
| 196 | *val = 0; |
| 197 | |
| 198 | if(!str) |
| 199 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 200 | |
| 201 | if(curl_strequal(str, "all")) { |
| 202 | *val = ~(curl_prot_t)0; |
| 203 | return CURLE_OK; |
| 204 | } |
| 205 | |
| 206 | do { |
| 207 | const char *token = str; |
| 208 | size_t tlen; |
| 636 | |
| 637 | if(!*input) |
| 638 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 639 | *dev = curlx_memdup0(input, len); |
| 640 | return *dev ? CURLE_OK : CURLE_OUT_OF_MEMORY; |
| 641 | } |
| 642 | |
| 643 | #ifndef CURL_DISABLE_BINDLOCAL |
| 644 | static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, |
| 645 | curl_socket_t sockfd, int af, unsigned int scope, |
| 646 | uint8_t transport) |
| 647 | { |
| 648 | struct Curl_sockaddr_storage sa; |
| 649 | struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */ |
| 650 | curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */ |
| 651 | struct sockaddr_in *si4 = (struct sockaddr_in *)&sa; |
| 652 | #ifdef USE_IPV6 |
| 653 | struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa; |
| 654 | #endif |
| 655 | |
| 656 | struct Curl_dns_entry *h = NULL; |
| 657 | unsigned short port = data->set.localport; /* use this port number, 0 for |
| 658 | "random" */ |
| 659 | /* how many port numbers to try to bind to, increasing one at a time */ |
| 660 | int portnum = data->set.localportrange; |
| 661 | const char *dev = CURL_EASY_STR(data, STRING_DEVICE); |
| C4: bindlocal reads all three strings but is not called on reuse | |
| C6: bindlocal uses the request's binding parameters but is skipped on reuse | |
| 662 | const char *iface_input = CURL_EASY_STR(data, STRING_INTERFACE); |
| 663 | const char *host_input = CURL_EASY_STR(data, STRING_BINDHOST); |
| C4: STRING_BINDHOST used only during initial bind | |
| 664 | const char *iface = iface_input ? iface_input : dev; |
| 665 | const char *host = host_input ? host_input : dev; |
| 666 | int sockerr; |
| 667 | #ifdef IP_BIND_ADDRESS_NO_PORT |
| 668 | int on = 1; |
| 669 | #endif |
| 670 | #ifndef USE_IPV6 |
| 671 | (void)scope; |
| 672 | #endif |
| 673 | |
| 674 | /************************************************************* |
| 675 | * Select device to bind socket to |
| 676 | *************************************************************/ |
| 677 | if(!iface && !host && !port) |
| 678 | /* no local kind of binding was requested */ |
| 679 | return CURLE_OK; |
| 680 | else if(iface && (strlen(iface) >= 255)) |
| 681 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 682 | |
| 683 | memset(&sa, 0, sizeof(struct Curl_sockaddr_storage)); |
| 684 | |
| 685 | if(iface || host) { |
| 686 | char myhost[256] = ""; |
| 687 | int done = 0; /* -1 for error, 1 for address found */ |
| 688 | if2ip_result_t if2ip_result = IF2IP_NOT_FOUND; |
| 689 | |
| 690 | #ifdef SO_BINDTODEVICE |
| 691 | /* |
| 692 | * This binds the local socket to a particular interface. This will |
| 693 | * force even requests to other local interfaces to go out the external |
| 694 | * interface. Only bind to the interface when specified as interface, |
| 695 | * not as a hostname or ip address. |
| 696 | * |
| 697 | * The interface might be a VRF, eg: vrf-blue, which means it cannot be |
| 698 | * converted to an IP address and would fail Curl_if2ip. Try to |
| 699 | * use it straight away. |
| 700 | */ |
| 701 | if(iface && |
| 702 | setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, |
| 703 | iface, (curl_socklen_t)strlen(iface) + 1) == 0 && |
| 704 | /* This is often "errno 1, error: Operation not permitted" if you are |
| 705 | * not running as root or another suitable privileged user. If it |
| 706 | * succeeds it means the parameter was a valid interface and not an IP |
| 707 | * address. Return immediately. |
| 708 | */ |
| 709 | !host_input) { |
| 710 | infof(data, "socket successfully bound to interface '%s'", iface); |
| 711 | return CURLE_OK; |
| 712 | } |
| 713 | #endif |
| 714 | if(!host_input) { |
| 715 | /* Discover IP from input device, then bind to it */ |
| 716 | if2ip_result = Curl_if2ip(af, |
| 717 | #ifdef USE_IPV6 |
| 718 | scope, conn->scope_id, |
| 719 | #endif |
| 720 | iface, myhost, sizeof(myhost)); |
| 721 | } |
| 722 | switch(if2ip_result) { |
| 723 | case IF2IP_NOT_FOUND: |
| 724 | if(iface_input && !host_input) { |
| 725 | /* Do not fall back to treating it as a hostname */ |
| 726 | char buffer[STRERROR_LEN]; |
| 727 | data->state.os_errno = sockerr = SOCKERRNO; |
| 728 | failf(data, "Could not bind to interface '%s' with errno %d: %s", |
| 729 | iface, sockerr, curlx_strerror(sockerr, buffer, sizeof(buffer))); |
| 730 | return CURLE_INTERFACE_FAILED; |
| 731 | } |
| 732 | break; |
| 733 | case IF2IP_AF_NOT_SUPPORTED: |
| 734 | /* Signal the caller to try another address family if available */ |
| 735 | return CURLE_UNSUPPORTED_PROTOCOL; |
| 736 | case IF2IP_FOUND: |
| 737 | /* |
| 738 | * We now have the numerical IP address in the 'myhost' buffer |
| 739 | */ |
| 740 | host = myhost; |
| 741 | infof(data, "Local Interface %s is ip %s using address family %d", |
| 742 | iface, host, af); |
| 743 | done = 1; |
| 744 | break; |
| 745 | } |
| 746 | if(!iface_input || host_input) { |
| 747 | /* |
| 748 | * This was not an interface, resolve the name as a hostname |
| 749 | * or IP number |
| 750 | * |
| 751 | * Temporarily force name resolution to use only the address type |
| 752 | * of the connection. The resolve functions should really be changed |
| 753 | * to take a type parameter instead. |
| 754 | */ |
| 755 | uint8_t dns_queries = (af == AF_INET) ? |
| 756 | CURL_DNSQ_A : (CURL_DNSQ_A | CURL_DNSQ_AAAA); |
| 757 | #ifdef USE_IPV6 |
| 758 | if(af == AF_INET6) |
| 759 | dns_queries = CURL_DNSQ_AAAA; |
| 760 | #endif |
| 761 | |
| 762 | (void)Curl_resolv_blocking(data, dns_queries, host, 80, transport, &h); |
| 763 | if(h) { |
| 764 | int h_af = h->addr->ai_family; |
| 765 | /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ |
| 766 | Curl_printable_address(h->addr, myhost, sizeof(myhost)); |
| 767 | infof(data, "Name '%s' family %d resolved to '%s' family %d", |
| 768 | host, af, myhost, h_af); |
| 769 | Curl_dns_entry_unlink(data, &h); /* this will NULL, potential free h */ |
| 770 | if(af != h_af) { |
| 771 | /* bad IP version combo, signal the caller to try another address |
| 772 | family if available */ |
| 773 | return CURLE_UNSUPPORTED_PROTOCOL; |
| 774 | } |
| 775 | done = 1; |
| 776 | } |
| 777 | else { |
| 778 | /* |
| 779 | * provided dev was no interface (or interfaces are not supported |
| 780 | * e.g. Solaris) no ip address and no domain we fail here |
| 781 | */ |
| 782 | done = -1; |
| 783 | } |
| 784 | } |
| 785 | |
| 786 | if(done > 0) { |
| 787 | #ifdef USE_IPV6 |
| 788 | /* IPv6 address */ |
| 789 | if(af == AF_INET6) { |
| 790 | #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
| 791 | char *scope_ptr = strchr(myhost, '%'); |
| 792 | if(scope_ptr) |
| 793 | *(scope_ptr++) = '\0'; |
| 794 | #endif |
| 795 | if(curlx_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0) { |
| 796 | si6->sin6_family = AF_INET6; |
| 797 | si6->sin6_port = htons(port); |
| 798 | #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
| 799 | if(scope_ptr) { |
| 800 | /* The "myhost" string either comes from Curl_if2ip or from |
| 801 | Curl_printable_address. The latter returns only numeric scope |
| 802 | IDs and the former returns none at all. Making the scope ID, |
| 803 | if present, known to be numeric */ |
| 804 | curl_off_t scope_id; |
| 594 | * reuse connections that have TLS. */ |
| 595 | BIT(wait_pipe); |
| 596 | BIT(seen_pending_conn); |
| 597 | BIT(seen_single_use_conn); |
| 598 | BIT(seen_multiplex_conn); |
| 599 | }; |
| 600 | |
| 601 | static bool url_match_connect_config(struct connectdata *conn, |
| 602 | struct url_conn_match *m) |
| 603 | { |
| 604 | /* connect-only or to-be-closed connections will not be reused */ |
| 605 | if(conn->bits.connect_only || conn->bits.close || conn->bits.no_reuse) |
| 606 | return FALSE; |
| 607 | |
| 608 | /* ip_version must match */ |
| 609 | if(m->data->set.ipver != CURL_IPRESOLVE_WHATEVER && |
| 610 | m->data->set.ipver != conn->ip_version) |
| 611 | return FALSE; |
| 612 | |
| 613 | if((m->needle->localdev || m->needle->localport) && |
| 614 | /* If we are bound to a specific local end (IP+port), we must not reuse a |
| 615 | random other one, although if we did not ask for a particular one we |
| 616 | can reuse one that was bound. |
| 617 | |
| 618 | This comparison is a bit rough and too strict. Since the input |
| 619 | parameters can be specified in numerous ways and still end up the same |
| 620 | it would take a lot of processing to make it really accurate. Instead, |
| 621 | this matching will assume that reuses of bound connections will most |
| 622 | likely also reuse the exact same binding parameters and missing out a |
| 623 | few edge cases should not hurt anyone much. */ |
| 624 | ((conn->localport != m->needle->localport) || |
| C1: url_match_connect_config compares only localdev, localport, localportrange | |
| C2: url_match_connect_config uses only localdev for local binding comparison | |
| C4: Connection match ignores STRING_INTERFACE and STRING_BINDHOST | |
| C5: The match logic that causes the misbehavior | |
| C6: Connection reuse ignores the per-request binding parameters | |
| 625 | (conn->localportrange != m->needle->localportrange) || |
| 626 | (m->needle->localdev && |
| 627 | (!conn->localdev || strcmp(conn->localdev, m->needle->localdev))))) |
| C1: No comparison of STRING_INTERFACE or STRING_BINDHOST values | |
| 628 | return FALSE; |
| 629 | |
| 630 | if(!m->needle->via_peer != !conn->via_peer) |
| 631 | /* do not mix connections that use the "connect to host" feature and |
| 632 | * connections that do not use this feature */ |
| 633 | return FALSE; |
| 634 | |
| 635 | return TRUE; |
| 636 | } |
| 637 | |
| 638 | static bool url_match_fully_connected(struct connectdata *conn, |
| 639 | struct url_conn_match *m) |
| 640 | { |
| 641 | if(!Curl_conn_is_connected(conn, FIRSTSOCKET) || |
| 642 | conn->bits.upgrade_in_progress) { |
| 643 | /* Not yet connected, or a protocol upgrade is in progress. The later |
| 644 | * happens for HTTP/2 Upgrade: requests that need a response. */ |
| 645 | if(m->may_multiplex) { |
| 646 | m->seen_pending_conn = TRUE; |
| 647 | /* Do not pick a connection that has not connected yet */ |
| 648 | infof(m->data, "Connection #%" FMT_OFF_T |
| 649 | " is not open enough, cannot reuse", conn->connection_id); |
| 650 | } |
| 651 | /* Do not pick a connection that has not connected yet */ |
| 652 | return FALSE; |
| 653 | } |
| 1114 | static struct connectdata *allocate_conn(struct Curl_easy *data) |
| 1115 | { |
| 1116 | struct connectdata *conn = curlx_calloc(1, sizeof(struct connectdata)); |
| 1117 | if(!conn) |
| 1118 | return NULL; |
| 1119 | |
| 1120 | /* and we setup a few fields in case we end up actually using this struct */ |
| 1121 | conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ |
| 1122 | conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ |
| 1123 | conn->recv_idx = 0; /* default for receiving transfer data */ |
| 1124 | conn->send_idx = 0; /* default for sending transfer data */ |
| 1125 | conn->connection_id = -1; /* no ID */ |
| 1126 | conn->attached_xfers = 0; |
| 1127 | conn->shutdown.start_ms[FIRSTSOCKET] = |
| 1128 | conn->shutdown.start_ms[SECONDARYSOCKET] = -1; |
| 1129 | |
| 1130 | #ifndef CURL_DISABLE_FTP |
| 1131 | conn->bits.ftp_use_epsv = data->set.ftp_use_epsv; |
| 1132 | conn->bits.ftp_use_eprt = data->set.ftp_use_eprt; |
| 1133 | #endif |
| 1134 | conn->ip_version = data->set.ipver; |
| 1135 | conn->bits.connect_only = (bool)data->set.connect_only; |
| 1136 | conn->transport_wanted = TRNSPRT_TCP; /* most of them are TCP streams */ |
| 1137 | |
| 1138 | /* Store the local bind parameters that will be used for this connection */ |
| 1139 | if(CURL_EASY_STR(data, STRING_DEVICE)) { |
| C1: allocate_conn copies only STRING_DEVICE to conn->localdev | |
| C2: allocate_conn builds the connection struct with only STRING_DEVICE | |
| 1140 | conn->localdev = curlx_strdup(CURL_EASY_STR(data, STRING_DEVICE)); |
| 1141 | if(!conn->localdev) |
| 1142 | goto error; |
| C1: No copy of STRING_INTERFACE or STRING_BINDHOST to connection struct | |
| 1143 | } |
| 1144 | #ifndef CURL_DISABLE_BINDLOCAL |
| 1145 | conn->localportrange = data->set.localportrange; |
| 1146 | conn->localport = data->set.localport; |
| 1147 | #endif |
| 1148 | |
| 1149 | /* the close socket stuff needs to be copied to the connection struct as |
| 1150 | it may live on without (this specific) Curl_easy */ |
| 1151 | conn->fclosesocket = data->set.fclosesocket; |
| 1152 | conn->closesocket_client = data->set.closesocket_client; |
| 1153 | #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) |
| 1154 | conn->gssapi_delegation = data->set.gssapi_delegation; |
| 1155 | #endif |
| 1156 | return conn; |
| 1157 | error: |
| 1158 | |
| 1159 | curlx_free(conn->localdev); |
| 1160 | curlx_free(conn); |
| 1161 | return NULL; |
| 1162 | } |
| 1163 | |
| 1164 | static CURLcode url_set_conn_scheme(struct Curl_easy *data, |
| 1165 | struct connectdata *conn) |
| 1166 | { |
| 1167 | /* URL scheme is usable for connection when it is |
| 1168 | * - allowed |
| 1169 | * - not from a redirect or an allowed redirect protocol */ |
| 1170 | const struct Curl_scheme *scheme = conn->origin->scheme; |
| 1171 | if(scheme->run && |
| 1172 | (data->set.allowed_protocols & scheme->protocol) && |
Show 51 lines (not cited by the judge)
| 1181 | static struct connectdata *allocate_conn(struct Curl_easy *data) |
| 1182 | { |
| 1183 | struct connectdata *conn = curlx_calloc(1, sizeof(struct connectdata)); |
| 1184 | if(!conn) |
| 1185 | return NULL; |
| 1186 | |
| 1187 | /* and we setup a few fields in case we end up actually using this struct */ |
| 1188 | |
| 1189 | conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ |
| 1190 | conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ |
| 1191 | conn->recv_idx = 0; /* default for receiving transfer data */ |
| 1192 | conn->send_idx = 0; /* default for sending transfer data */ |
| 1193 | conn->connection_id = -1; /* no ID */ |
| 1194 | conn->attached_xfers = 0; |
| 1195 | |
| 1196 | /* Remember time this connection started */ |
| 1197 | conn->lastused = conn->lastupkeep = conn->created = *Curl_pgrs_now(data); |
| 1198 | |
| 1199 | #ifndef CURL_DISABLE_FTP |
| 1200 | conn->bits.ftp_use_epsv = data->set.ftp_use_epsv; |
| 1201 | conn->bits.ftp_use_eprt = data->set.ftp_use_eprt; |
| 1202 | #endif |
| 1203 | conn->ip_version = data->set.ipver; |
| 1204 | conn->bits.connect_only = (bool)data->set.connect_only; |
| 1205 | conn->transport_wanted = TRNSPRT_TCP; /* most of them are TCP streams */ |
| 1206 | |
| 1207 | /* Store the local bind parameters that will be used for this connection */ |
| 1208 | if(CURL_EASY_STR(data, STRING_DEVICE)) { |
| 1209 | conn->localdev = curlx_strdup(CURL_EASY_STR(data, STRING_DEVICE)); |
| 1210 | if(!conn->localdev) |
| 1211 | goto error; |
| 1212 | } |
| 1213 | #ifndef CURL_DISABLE_BINDLOCAL |
| 1214 | conn->localportrange = data->set.localportrange; |
| 1215 | conn->localport = data->set.localport; |
| 1216 | #endif |
| 1217 | |
| 1218 | /* the close socket stuff needs to be copied to the connection struct as |
| 1219 | it may live on without (this specific) Curl_easy */ |
| 1220 | conn->fclosesocket = data->set.fclosesocket; |
| 1221 | conn->closesocket_client = data->set.closesocket_client; |
| 1222 | #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) |
| 1223 | conn->gssapi_delegation = data->set.gssapi_delegation; |
| 1224 | #endif |
| 1225 | return conn; |
| 1226 | error: |
| 1227 | |
| 1228 | curlx_free(conn->localdev); |
| 1229 | curlx_free(conn); |
| 1230 | return NULL; |
| 1231 | } |
Show 161 lines (not cited by the judge)
| 644 | static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, |
| 645 | curl_socket_t sockfd, int af, unsigned int scope, |
| 646 | uint8_t transport) |
| 647 | { |
| 648 | struct Curl_sockaddr_storage sa; |
| 649 | struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */ |
| 650 | curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */ |
| 651 | struct sockaddr_in *si4 = (struct sockaddr_in *)&sa; |
| 652 | #ifdef USE_IPV6 |
| 653 | struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa; |
| 654 | #endif |
| 655 | |
| 656 | struct Curl_dns_entry *h = NULL; |
| 657 | unsigned short port = data->set.localport; /* use this port number, 0 for |
| 658 | "random" */ |
| 659 | /* how many port numbers to try to bind to, increasing one at a time */ |
| 660 | int portnum = data->set.localportrange; |
| 661 | const char *dev = CURL_EASY_STR(data, STRING_DEVICE); |
| 662 | const char *iface_input = CURL_EASY_STR(data, STRING_INTERFACE); |
| 663 | const char *host_input = CURL_EASY_STR(data, STRING_BINDHOST); |
| 664 | const char *iface = iface_input ? iface_input : dev; |
| 665 | const char *host = host_input ? host_input : dev; |
| 666 | int sockerr; |
| 667 | #ifdef IP_BIND_ADDRESS_NO_PORT |
| 668 | int on = 1; |
| 669 | #endif |
| 670 | #ifndef USE_IPV6 |
| 671 | (void)scope; |
| 672 | #endif |
| 673 | |
| 674 | /************************************************************* |
| 675 | * Select device to bind socket to |
| 676 | *************************************************************/ |
| 677 | if(!iface && !host && !port) |
| 678 | /* no local kind of binding was requested */ |
| 679 | return CURLE_OK; |
| 680 | else if(iface && (strlen(iface) >= 255)) |
| 681 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 682 | |
| 683 | memset(&sa, 0, sizeof(struct Curl_sockaddr_storage)); |
| 684 | |
| 685 | if(iface || host) { |
| 686 | char myhost[256] = ""; |
| 687 | int done = 0; /* -1 for error, 1 for address found */ |
| 688 | if2ip_result_t if2ip_result = IF2IP_NOT_FOUND; |
| 689 | |
| 690 | #ifdef SO_BINDTODEVICE |
| 691 | if(iface) { |
| 692 | /* |
| 693 | * This binds the local socket to a particular interface. This will |
| 694 | * force even requests to other local interfaces to go out the external |
| 695 | * interface. Only bind to the interface when specified as interface, |
| 696 | * not as a hostname or ip address. |
| 697 | * |
| 698 | * The interface might be a VRF, eg: vrf-blue, which means it cannot be |
| 699 | * converted to an IP address and would fail Curl_if2ip. Try to |
| 700 | * use it straight away. |
| 701 | */ |
| 702 | if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, |
| 703 | iface, (curl_socklen_t)strlen(iface) + 1) == 0) { |
| 704 | /* This is often "errno 1, error: Operation not permitted" if you are |
| 705 | * not running as root or another suitable privileged user. If it |
| 706 | * succeeds it means the parameter was a valid interface and not an IP |
| 707 | * address. Return immediately. |
| 708 | */ |
| 709 | if(!host_input) { |
| 710 | infof(data, "socket successfully bound to interface '%s'", iface); |
| 711 | return CURLE_OK; |
| 712 | } |
| 713 | } |
| 714 | } |
| 715 | #endif |
| 716 | if(!host_input) { |
| 717 | /* Discover IP from input device, then bind to it */ |
| 718 | if2ip_result = Curl_if2ip(af, |
| 719 | #ifdef USE_IPV6 |
| 720 | scope, conn->scope_id, |
| 721 | #endif |
| 722 | iface, myhost, sizeof(myhost)); |
| 723 | } |
| 724 | switch(if2ip_result) { |
| 725 | case IF2IP_NOT_FOUND: |
| 726 | if(iface_input && !host_input) { |
| 727 | /* Do not fall back to treating it as a hostname */ |
| 728 | char buffer[STRERROR_LEN]; |
| 729 | data->state.os_errno = sockerr = SOCKERRNO; |
| 730 | failf(data, "Could not bind to interface '%s' with errno %d: %s", |
| 731 | iface, sockerr, curlx_strerror(sockerr, buffer, sizeof(buffer))); |
| 732 | return CURLE_INTERFACE_FAILED; |
| 733 | } |
| 734 | break; |
| 735 | case IF2IP_AF_NOT_SUPPORTED: |
| 736 | /* Signal the caller to try another address family if available */ |
| 737 | return CURLE_UNSUPPORTED_PROTOCOL; |
| 738 | case IF2IP_FOUND: |
| 739 | /* |
| 740 | * We now have the numerical IP address in the 'myhost' buffer |
| 741 | */ |
| 742 | host = myhost; |
| 743 | infof(data, "Local Interface %s is ip %s using address family %d", |
| 744 | iface, host, af); |
| 745 | done = 1; |
| 746 | break; |
| 747 | } |
| 748 | if(!iface_input || host_input) { |
| 749 | /* |
| 750 | * This was not an interface, resolve the name as a hostname |
| 751 | * or IP number |
| 752 | * |
| 753 | * Temporarily force name resolution to use only the address type |
| 754 | * of the connection. The resolve functions should really be changed |
| 755 | * to take a type parameter instead. |
| 756 | */ |
| 757 | uint8_t dns_queries = (af == AF_INET) ? |
| 758 | CURL_DNSQ_A : (CURL_DNSQ_A | CURL_DNSQ_AAAA); |
| 759 | #ifdef USE_IPV6 |
| 760 | if(af == AF_INET6) |
| 761 | dns_queries = CURL_DNSQ_AAAA; |
| 762 | #endif |
| 763 | |
| 764 | (void)Curl_resolv_blocking(data, dns_queries, host, 80, transport, &h); |
| 765 | if(h) { |
| 766 | int h_af = h->addr->ai_family; |
| 767 | /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ |
| 768 | Curl_printable_address(h->addr, myhost, sizeof(myhost)); |
| 769 | infof(data, "Name '%s' family %d resolved to '%s' family %d", |
| 770 | host, af, myhost, h_af); |
| 771 | Curl_dns_entry_unlink(data, &h); /* this will NULL, potential free h */ |
| 772 | if(af != h_af) { |
| 773 | /* bad IP version combo, signal the caller to try another address |
| 774 | family if available */ |
| 775 | return CURLE_UNSUPPORTED_PROTOCOL; |
| 776 | } |
| 777 | done = 1; |
| 778 | } |
| 779 | else { |
| 780 | /* |
| 781 | * provided dev was no interface (or interfaces are not supported |
| 782 | * e.g. Solaris) no ip address and no domain we fail here |
| 783 | */ |
| 784 | done = -1; |
| 785 | } |
| 786 | } |
| 787 | |
| 788 | if(done > 0) { |
| 789 | #ifdef USE_IPV6 |
| 790 | /* IPv6 address */ |
| 791 | if(af == AF_INET6) { |
| 792 | #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
| 793 | char *scope_ptr = strchr(myhost, '%'); |
| 794 | if(scope_ptr) |
| 795 | *(scope_ptr++) = '\0'; |
| 796 | #endif |
| 797 | if(curlx_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0) { |
| 798 | si6->sin6_family = AF_INET6; |
| 799 | si6->sin6_port = htons(port); |
| 800 | #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID |
| 801 | if(scope_ptr) { |
| 802 | /* The "myhost" string either comes from Curl_if2ip or from |
| 803 | Curl_printable_address. The latter returns only numeric scope |
| 804 | IDs and the former returns none at all. Making the scope ID, |
Show 37 lines (not cited by the judge)
| 602 | static bool url_match_connect_config(struct connectdata *conn, |
| 603 | struct url_conn_match *m) |
| 604 | { |
| 605 | /* connect-only or to-be-closed connections will not be reused */ |
| 606 | if(conn->bits.connect_only || conn->bits.close || conn->bits.no_reuse) |
| 607 | return FALSE; |
| 608 | |
| 609 | /* ip_version must match */ |
| 610 | if(m->data->set.ipver != CURL_IPRESOLVE_WHATEVER && |
| 611 | m->data->set.ipver != conn->ip_version) |
| 612 | return FALSE; |
| 613 | |
| 614 | if(m->needle->localdev || m->needle->localport) { |
| 615 | /* If we are bound to a specific local end (IP+port), we must not reuse a |
| 616 | random other one, although if we did not ask for a particular one we |
| 617 | can reuse one that was bound. |
| 618 | |
| 619 | This comparison is a bit rough and too strict. Since the input |
| 620 | parameters can be specified in numerous ways and still end up the same |
| 621 | it would take a lot of processing to make it really accurate. Instead, |
| 622 | this matching will assume that reuses of bound connections will most |
| 623 | likely also reuse the exact same binding parameters and missing out a |
| 624 | few edge cases should not hurt anyone much. */ |
| 625 | if((conn->localport != m->needle->localport) || |
| 626 | (conn->localportrange != m->needle->localportrange) || |
| 627 | (m->needle->localdev && |
| 628 | (!conn->localdev || strcmp(conn->localdev, m->needle->localdev)))) |
| 629 | return FALSE; |
| 630 | } |
| 631 | |
| 632 | if(!m->needle->via_peer != !conn->via_peer) |
| 633 | /* do not mix connections that use the "connect to host" feature and |
| 634 | * connections that do not use this feature */ |
| 635 | return FALSE; |
| 636 | |
| 637 | return TRUE; |
| 638 | } |