Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Response.status_code is 0 and response.text is empty for some of my users #1100

Closed
typeshi12 opened this issue Aug 26, 2024 · 12 comments
Closed

Comments

@typeshi12
Copy link

Description

Hello,

I am a developer for a product that uses CPR to access various apis. However, a good sum of my users (not me, however) are encountering an issue accessing my website through CPR / cURL.

I set up some debug message boxes, and this is what it shows on two users:
image

The code behind that messagebox is this:

auto response = cpr::Get(cpr::Url{ url });
if (response.status_code != 200) {
    std::string errorMessage = oxorany("API returned a ") + std::to_string(response.status_code) + oxorany(" error, with content") + response.text + oxorany(" . Can't access. Contact [redacted]");

    Warden::get_singleton()->display(errorMessage);
}

Addtionally, I had the users run a curl request to the same endpoint, which brings up this error for both users:
curl: (35) schannel: next InitializeSecurityContext failed: SEC_E_INVALID_TOKEN (0x80090308) - The token supplied to the function is invalid

Now this may not even be a CPR issue but rather a cURL issue, but seeing this is the library I'm using, I thought I'd try here first.
And before you say its an ISP block / country block issue, it's not. While a Vpn DOES fix this issue, it's still clearly not a block as the users can access the api endpoints just fine in their browser WITHOUT a vpn:

image
image

Example/How to Reproduce

  1. Set up a simple C++ project with this code
int main(){
    auto response = cpr::Get(cpr::Url{ "https://getsolara.dev/api/endpoint.json" });
     if (response.status_code != 200) {
         std::string errorMessage = "API returned a " + std::to_string(response.status_code) + " error, with content" + response.text + " . Can't access.";

         MessageBoxA(NULL, errorMessage.c_str(), "Error", MB_OK);

         
     }
    return 0;
}
  1. Observe the output
  2. Do curl 'https://getsolara.dev/api/endpoint.json' in a CMD prompt
  3. Observe output

Possible Fix

A noticed that with using a VPN, the vpn uses a different IP to connect to the API (there are three) rather than what is seemingly the default IP. Not experienced much with C++ but maybe you can try getting a request to the different IPs.

Where did you get it from?

vcpkg

Additional Context/Your Environment

  • OS: Windows 11
  • Version: 10.0.22631 Build 22631
@Enziferum
Copy link

Enziferum commented Aug 28, 2024

Hello,
I've got same error. Suppose founded place of problem. When Session prepare common features and options.

Session.cpp: 187  curl_easy_setopt(curl_->handle, CURLOPT_CERTINFO, 1L); 

On response CURLcode curl_error equal 43 error code. Commented this option inside session's prepare step and rebuild library manual and now works.

CURL Version 8.8-dev.
CURL Backend: "Schannel".

P.S According to CURL Docs CURLOPT_CERTINFO my backend is supporting. I'll continue work on solution and check where is error. Please wait news from me!

Where did you get it from?
vcpkg

Additional Context/Your Environment
OS: Windows 10

@COM8
Copy link
Member

COM8 commented Aug 28, 2024

@Enziferum thanks for looking into it and @typeshi12 thanks for reporting.

Please check the following curl issue for it: jeroen/curl#127

To me it does not make sense that removing CURLOPT_CERTINFO fixes it since why does a VPN then work?

One wild guess I would have is: Do the uses affected only have a IPv6 address and your server is only reachable via IPv4 (https://myip.wtf/)?
Yes, IPv4 can wrap IPv6 but for example my router at home has a lot of problems with that.

@Enziferum
Copy link

Enziferum commented Aug 28, 2024

@COM8 Thanks for fast answer!
After several hours of debugging :) i have news why CURL Internal error (43) is returned, I checked it on a request under the url from @typeshi12 and on mine urls under vpn and not vpn. VPN has nothing to do with it. As I said earlier, in my case, the “Schannel” protocol is used inside CURL/SSL and during connection initialization, certificates are checked, because the CURLOPT_CERTINFO flag is turned on, in my case and @typeshi12 one of the certificates cannot be decoded at the ASN.1 OID verification stage. in the decoding tables there is no such value "1.2.840.10045.4.3.3", so error 43 occurs and the request was not actually made, so the status code inside cpr is 0. Details insinde files curl/lib/vlts/x509asn1.c function OID2str.
Due to this problems i suggest make some option to manage state of CURLOPT_CERTINFO.

P.S @COM8 I'll make PR tomorrow. OK ?)

@typeshi12
Copy link
Author

@Enziferum thanks for looking into it and @typeshi12 thanks for reporting.

Please check the following curl issue for it: jeroen/curl#127

To me it does not make sense that removing CURLOPT_CERTINFO fixes it since why does a VPN then work?

One wild guess I would have is: Do the uses affected only have a IPv6 address and your server is only reachable via IPv4 (https://myip.wtf/)? Yes, IPv4 can wrap IPv6 but for example my router at home has a lot of problems with that.

According to my cloudflare settings, ipv6 support is enabled and is not able to be turned off anyway lol.
Using the verbose option, it's clear a connection is made - but when the certificate is being verified, that's where a problem is arising. Thank you @Enziferum for looking into it deeper, I will try your solution

@Enziferum
Copy link

Enziferum commented Aug 28, 2024

@Enziferum thanks for looking into it and @typeshi12 thanks for reporting.
Please check the following curl issue for it: jeroen/curl#127
To me it does not make sense that removing CURLOPT_CERTINFO fixes it since why does a VPN then work?
One wild guess I would have is: Do the uses affected only have a IPv6 address and your server is only reachable via IPv4 (https://myip.wtf/)? Yes, IPv4 can wrap IPv6 but for example my router at home has a lot of problems with that.

According to my cloudflare settings, ipv6 support is enabled and is not able to be turned off anyway lol. Using the verbose option, it's clear a connection is made - but when the certificate is being verified, that's where a problem is arising. Thank you @Enziferum for looking into it deeper, I will try your solution

@typeshi12 Thanks for response!
Please check my example to confirm my idea, according to your description, there is a problem at the stage of checking certificates, which is exactly what I wrote above. Important! link cpr to executable.

#include "curl/curl.h"
#include <iostream>

size_t writeFunction(void* ptr, size_t size, size_t nmemb, std::string* data) {
	data->append((char*)ptr, size * nmemb);
	return size * nmemb;
}

int main(int argc, char* argv[])
{	
	auto curl = curl_easy_init();
	if (!curl)
		return -1;

	curl_easy_setopt(curl, CURLOPT_URL, "https://getsolara.dev/api/endpoint.json");
	curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
	curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);

	curl_version_info_data* version_info = curl_version_info(CURLVERSION_NOW);
	const std::string version = "curl/" + std::string{ version_info->version };
	curl_easy_setopt(curl, CURLOPT_USERAGENT, version.c_str());

	curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L);
	curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
	curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https");

	std::string response_string;
	std::string header_string;
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunction);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_string);
	curl_easy_setopt(curl, CURLOPT_HEADERDATA, &header_string);

	auto code = curl_easy_perform(curl);

	char* url;
	long response_code;
	double elapsed;
	curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
	curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &elapsed);
	curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);

	if (response_code == 200)
		std::cout << "Success Request" << std::endl;
	else
		std::cerr << "UnSuccess Request" << std::endl;

	std::cout << "Response: \n" << response_string << std::endl;
	std::cout << "Headers: \n" << header_string << std::endl;

	curl_easy_perform(curl);
	curl_easy_cleanup(curl);
	curl = NULL;

	return 0;
}

@typeshi12
Copy link
Author

@Enziferum thanks for looking into it and @typeshi12 thanks for reporting.
Please check the following curl issue for it: jeroen/curl#127
To me it does not make sense that removing CURLOPT_CERTINFO fixes it since why does a VPN then work?
One wild guess I would have is: Do the uses affected only have a IPv6 address and your server is only reachable via IPv4 (https://myip.wtf/)? Yes, IPv4 can wrap IPv6 but for example my router at home has a lot of problems with that.

According to my cloudflare settings, ipv6 support is enabled and is not able to be turned off anyway lol. Using the verbose option, it's clear a connection is made - but when the certificate is being verified, that's where a problem is arising. Thank you @Enziferum for looking into it deeper, I will try your solution

@typeshi12 Thanks for response! Please check my example to confirm my idea, according to your description, there is a problem at the stage of checking certificates, which is exactly what I wrote above. Important! link cpr to executable.

#include "curl/curl.h"
#include <iostream>

size_t writeFunction(void* ptr, size_t size, size_t nmemb, std::string* data) {
	data->append((char*)ptr, size * nmemb);
	return size * nmemb;
}

int main(int argc, char* argv[])
{	
	auto curl = curl_easy_init();
	if (!curl)
		return -1;

	curl_easy_setopt(curl, CURLOPT_URL, "https://getsolara.dev/api/endpoint.json");
	curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
	curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);

	curl_version_info_data* version_info = curl_version_info(CURLVERSION_NOW);
	const std::string version = "curl/" + std::string{ version_info->version };
	curl_easy_setopt(curl, CURLOPT_USERAGENT, version.c_str());

	curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L);
	curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
	curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https");

	std::string response_string;
	std::string header_string;
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunction);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_string);
	curl_easy_setopt(curl, CURLOPT_HEADERDATA, &header_string);

	auto code = curl_easy_perform(curl);

	char* url;
	long response_code;
	double elapsed;
	curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
	curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &elapsed);
	curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);

	if (response_code == 200)
		std::cout << "Success Request" << std::endl;
	else
		std::cerr << "UnSuccess Request" << std::endl;

	std::cout << "Response: \n" << response_string << std::endl;
	std::cout << "Headers: \n" << header_string << std::endl;

	curl_easy_perform(curl);
	curl_easy_cleanup(curl);
	curl = NULL;

	return 0;
}

Had a user with this issue run this code - it returned empty for him, however it was successful for me.

@typeshi12
Copy link
Author

it isnt even just my site either
image

@Enziferum
Copy link

@typeshi12
Let's summarize, can we say that there are 2 problems, one is related to the fact that the certificates are incorrect, and there is a solution for it that works for me, for you and some of your users, and there are 2 problems outside the scope of cpr or even curl, it is inside the ssl backend, the schannel protocol, and as you can see in the screenshot, even other sites do not work for the person, with normal certificates. Therefore, what I propose is that I will make a PR on the option to disable certificate verification and the problem within the issue and cpr framework can be said to be solved, and we can try to solve the second problem privately.

@typeshi12
Copy link
Author

Alright, do you have discord? or what communications do you prefer

@rminderhoud
Copy link

rminderhoud commented Sep 9, 2024

Hi all, experiencing the same issue on our window server box. The request fails with status 0 however accessing the page with curl or Invoke-WebRequest returns 200. I do not get the same cURL error as @typeshi12 above. The only recent change was updating MSVC compiler and updating CPR.

@rminderhoud
Copy link

rminderhoud commented Sep 11, 2024

So I've dug in more, I observed the issue with two SSL certificates, one from Let's Encrypt and one from Google Trusted Host via cloudflare. The following oid's appear when validating the certification (thanks @Enziferum for your investigation):

  • 1.2.840.10045.4.3.2
  • 1.2.840.10045.4.3.3

These do not appear in libcurl's OID tables but a recent change to libcurl made this an explicit error:

Previously this was not an error, in a newer commit more known OID are added so this shouldn't be an issue if libcurl is updated:

So generally the proper fix is for CPR to update to a newer libcurl later to incorporate these new OID. For users, these are the options:

  • Use a different type of SSL certificate if you control the host
  • Patch libcurl in local build to include these OID or restore old behavior
  • Patch cpr to a newer libcurl version
  • Downgrade cpr/libcurl to older version

@COM8
Copy link
Member

COM8 commented Sep 20, 2024

@rminderhoud good catch! I will work on an update to libcurl 8.10.1 this weekend.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants
@rminderhoud @COM8 @Enziferum @typeshi12 and others