From 4f97692245112ef27b80d2cfe99ed8b125a4db8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 2 Nov 2022 01:03:01 +0800 Subject: [PATCH] Networking: support nowait connection (Windows) --- CMakeLists.txt | 3 +- src/common/networking.c | 115 ----------------------------- src/common/networking.h | 30 +++++--- src/common/networking_linux.c | 77 +++++++++++++++++++ src/common/networking_windows.c | 127 ++++++++++++++++++++++++++++++++ src/modules/publicip.c | 13 ++-- src/modules/weather.c | 11 +-- 7 files changed, 239 insertions(+), 137 deletions(-) delete mode 100644 src/common/networking.c create mode 100644 src/common/networking_linux.c create mode 100644 src/common/networking_windows.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 79e059926..91649c043 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -220,7 +220,6 @@ set(LIBFASTFETCH_SRC src/common/parsing.c src/common/settings.c src/common/library.c - src/common/networking.c src/common/bar.c src/logo/logo.c src/logo/builtin.c @@ -293,6 +292,7 @@ set(LIBFASTFETCH_SRC if(LINUX OR APPLE OR ANDROID OR BSD) list(APPEND LIBFASTFETCH_SRC src/common/processing_linux.c + src/common/networking_linux.c src/detection/users/users_linux.c src/detection/terminalshell/terminalshell_linux.c src/detection/localip/localip_linux.c @@ -360,6 +360,7 @@ endif() if(WIN32) list(APPEND LIBFASTFETCH_SRC src/common/processing_windows.c + src/common/networking_windows.c src/detection/host/host_windows.cpp src/detection/bios/bios_windows.cpp src/detection/board/board_windows.cpp diff --git a/src/common/networking.c b/src/common/networking.c deleted file mode 100644 index a5c8ed629..000000000 --- a/src/common/networking.c +++ /dev/null @@ -1,115 +0,0 @@ -#ifdef _WIN32 - #include - #include - #include - - static BOOL WINAPI initWsaData(PINIT_ONCE once, PVOID param, PVOID* context) - { - (void)once; - (void)param; - static WSADATA wsaData; - *context = &wsaData; - return WSAStartup(MAKEWORD(2, 2), &wsaData) == 0; - } - - //Types of winsock2 are full of mess. Disable warnings for them and keep clean for posix - #pragma GCC diagnostic ignored "-Wincompatible-pointer-types" - #pragma GCC diagnostic ignored "-Wconversion" - #pragma GCC diagnostic ignored "-Wsign-conversion" -#else - #include - #include - #include - #include - - #define closesocket close -#endif - -//Must be included after -#include "fastfetch.h" -#include "common/networking.h" - -FFSockType ffNetworkingSendHttpRequest(const char* host, const char* path, const char* headers, uint32_t timeout) -{ - #ifdef _WIN32 - static INIT_ONCE once = INIT_ONCE_STATIC_INIT; - WSADATA* pData; - if(!InitOnceExecuteOnce(&once, initWsaData, NULL, (LPVOID*) &pData)) - return INVALID_SOCKET; - #endif - - struct addrinfo hints = { - .ai_family = AF_INET, - .ai_socktype = SOCK_STREAM, - }; - - struct addrinfo* addr; - - if(getaddrinfo(host, "80", &hints, &addr) != 0) - return INVALID_SOCKET; - - FFSockType sockfd = (FFSockType)socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); - if(sockfd == INVALID_SOCKET) - { - freeaddrinfo(addr); - return INVALID_SOCKET; - } - - if(timeout > 0) - { - struct timeval timev; - timev.tv_sec = 0; - timev.tv_usec = (__typeof__(timev.tv_usec)) (timeout * 1000); //milliseconds to microseconds - setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timev, sizeof(timev)); - } - - if(connect(sockfd, addr->ai_addr, addr->ai_addrlen) == -1) - { - closesocket(sockfd); - freeaddrinfo(addr); - return INVALID_SOCKET; - } - - freeaddrinfo(addr); - - FFstrbuf command; - ffStrbufInitA(&command, 64); - ffStrbufAppendS(&command, "GET "); - ffStrbufAppendS(&command, path); - ffStrbufAppendS(&command, " HTTP/1.1\nHost: "); - ffStrbufAppendS(&command, host); - ffStrbufAppendS(&command, "\r\n"); - ffStrbufAppendS(&command, headers); - ffStrbufAppendS(&command, "\r\n"); - - if(send(sockfd, command.chars, command.length, 0) == -1) - { - ffStrbufDestroy(&command); - closesocket(sockfd); - return INVALID_SOCKET; - } - ffStrbufDestroy(&command); - return sockfd; -} - -bool ffNetworkingRecvHttpResponse(FFSockType sockfd, FFstrbuf* buffer) -{ - ssize_t received = recv(sockfd, buffer->chars + buffer->length, ffStrbufGetFree(buffer), 0); - - if(received > 0) - { - buffer->length += (uint32_t) received; - buffer->chars[buffer->length] = '\0'; - } - - closesocket(sockfd); - return ffStrbufStartsWithS(buffer, "HTTP/1.1 200 OK\r\n"); -} - -bool ffNetworkingGetHttp(const char* host, const char* path, uint32_t timeout, const char* headers, FFstrbuf* buffer) -{ - FFSockType sockfd = ffNetworkingSendHttpRequest(host, path, headers, timeout); - if(sockfd != INVALID_SOCKET) - return ffNetworkingRecvHttpResponse(sockfd, buffer); - return false; -} diff --git a/src/common/networking.h b/src/common/networking.h index 0a1b36e5d..aaad894c9 100644 --- a/src/common/networking.h +++ b/src/common/networking.h @@ -6,17 +6,27 @@ #include "util/FFstrbuf.h" #ifdef _WIN32 - typedef uintptr_t FFSockType; //SOCKET, unsigned - #ifndef INVALID_SOCKET //Don't conflict with - #define INVALID_SOCKET ((uintptr_t)~0) + #include +#endif + +typedef struct FFNetworkingState { + #ifdef _WIN32 + uintptr_t sockfd; + OVERLAPPED overlapped; + #else + int sockfd; #endif -#else - typedef int FFSockType; // signed - #define INVALID_SOCKET (-1) -#endif +} FFNetworkingState; -FFSockType ffNetworkingSendHttpRequest(const char* host, const char* path, const char* headers, uint32_t timeout); -bool ffNetworkingRecvHttpResponse(FFSockType sockfd, FFstrbuf* buffer); -bool ffNetworkingGetHttp(const char* host, const char* path, uint32_t timeout, const char* headers, FFstrbuf* buffer); +bool ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers); +bool ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer, uint32_t timeout); + +static inline bool ffNetworkingGetHttp(const char* host, const char* path, uint32_t timeout, const char* headers, FFstrbuf* buffer) +{ + FFNetworkingState state; + if(ffNetworkingSendHttpRequest(&state, host, path, headers)) + return ffNetworkingRecvHttpResponse(&state, buffer, timeout); + return false; +} #endif diff --git a/src/common/networking_linux.c b/src/common/networking_linux.c new file mode 100644 index 000000000..22e5311fa --- /dev/null +++ b/src/common/networking_linux.c @@ -0,0 +1,77 @@ +#include "fastfetch.h" +#include "common/networking.h" + +#include +#include +#include +#include + +bool ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers) +{ + struct addrinfo hints = { + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + }; + + struct addrinfo* addr; + + if(getaddrinfo(host, "80", &hints, &addr) != 0) + return false; + + state->sockfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if(state->sockfd == -1) + { + freeaddrinfo(addr); + return false; + } + + if(connect(state->sockfd, addr->ai_addr, addr->ai_addrlen) == -1) + { + close(state->sockfd); + freeaddrinfo(addr); + return false; + } + + freeaddrinfo(addr); + + FFstrbuf command; + ffStrbufInitA(&command, 64); + ffStrbufAppendS(&command, "GET "); + ffStrbufAppendS(&command, path); + ffStrbufAppendS(&command, " HTTP/1.1\nHost: "); + ffStrbufAppendS(&command, host); + ffStrbufAppendS(&command, "\r\n"); + ffStrbufAppendS(&command, headers); + ffStrbufAppendS(&command, "\r\n"); + + if(send(state->sockfd, command.chars, command.length, 0) < 0) + { + ffStrbufDestroy(&command); + close(state->sockfd); + return false; + } + ffStrbufDestroy(&command); + return true; +} + +bool ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer, uint32_t timeout) +{ + if(timeout > 0) + { + struct timeval timev; + timev.tv_sec = 0; + timev.tv_usec = (__typeof__(timev.tv_usec)) (timeout * 1000); //milliseconds to microseconds + setsockopt(state->sockfd, SOL_SOCKET, SO_RCVTIMEO, &timev, sizeof(timev)); + } + + ssize_t received = recv(state->sockfd, buffer->chars + buffer->length, ffStrbufGetFree(buffer), 0); + + if(received > 0) + { + buffer->length += (uint32_t) received; + buffer->chars[buffer->length] = '\0'; + } + + close(state->sockfd); + return ffStrbufStartsWithS(buffer, "HTTP/1.1 200 OK\r\n"); +} diff --git a/src/common/networking_windows.c b/src/common/networking_windows.c new file mode 100644 index 000000000..dd32fac16 --- /dev/null +++ b/src/common/networking_windows.c @@ -0,0 +1,127 @@ +#include +#include + +//Must be included after +#include "fastfetch.h" +#include "common/networking.h" + +static LPFN_CONNECTEX ConnectEx; + +static BOOL WINAPI initWsaData(PINIT_ONCE once, PVOID param, PVOID* context) +{ + (void)once; + (void)param; + static WSADATA wsaData; + *context = &wsaData; + if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) + return FALSE; + + if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) + return FALSE; + + //Dummy socket needed for WSAIoctl + SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0); + if(sockfd == INVALID_SOCKET) + return FALSE; + + DWORD dwBytes; + GUID guid = WSAID_CONNECTEX; + if(WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER, + &guid, sizeof(guid), + &ConnectEx, sizeof(ConnectEx), + &dwBytes, NULL, NULL) != 0) + return FALSE; + + return closesocket(sockfd) == 0; +} + +bool ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers) +{ + static INIT_ONCE once = INIT_ONCE_STATIC_INIT; + WSADATA* pData; + if(!InitOnceExecuteOnce(&once, initWsaData, NULL, (LPVOID*) &pData)) + return false; + + memset(state, 0, sizeof(*state)); + + struct addrinfo hints = { + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + }; + + struct addrinfo* addr; + + if(getaddrinfo(host, "80", &hints, &addr) != 0) + return false; + + state->sockfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if(state->sockfd == INVALID_SOCKET) + { + freeaddrinfo(addr); + return false; + } + + { + //ConnectEx requires the socket to be initially bound + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr.s_addr = INADDR_ANY, + .sin_port = 0, + }; + if(bind(state->sockfd, (SOCKADDR *)&addr, sizeof(addr)) != 0) + { + printf("bind %d\n", WSAGetLastError()); + return false; + } + } + + FFstrbuf command; + ffStrbufInitA(&command, 64); + ffStrbufAppendS(&command, "GET "); + ffStrbufAppendS(&command, path); + ffStrbufAppendS(&command, " HTTP/1.1\nHost: "); + ffStrbufAppendS(&command, host); + ffStrbufAppendS(&command, "\r\n"); + ffStrbufAppendS(&command, headers); + ffStrbufAppendS(&command, "\r\n"); + + BOOL result = ConnectEx(state->sockfd, addr->ai_addr, (int)addr->ai_addrlen, command.chars, command.length, NULL, &state->overlapped); + freeaddrinfo(addr); + ffStrbufDestroy(&command); + + if(!result && WSAGetLastError() != WSA_IO_PENDING) + { + closesocket(state->sockfd); + return false; + } + + ffStrbufDestroy(&command); + return true; +} + +bool ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer, uint32_t timeout) +{ + DWORD transfer, flags; + if (!WSAGetOverlappedResult(state->sockfd, &state->overlapped, &transfer, TRUE, &flags)) + { + closesocket(state->sockfd); + return false; + } + + if(timeout > 0) + { + //https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-setsockopt + setsockopt(state->sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)); + } + + ssize_t received = recv(state->sockfd, buffer->chars + buffer->length, (int)ffStrbufGetFree(buffer), 0); + + if(received > 0) + { + buffer->length += (uint32_t) received; + buffer->chars[buffer->length] = '\0'; + } + + closesocket(state->sockfd); + return ffStrbufStartsWithS(buffer, "HTTP/1.1 200 OK\r\n"); +} diff --git a/src/modules/publicip.c b/src/modules/publicip.c index 019f13dfb..61c85698e 100644 --- a/src/modules/publicip.c +++ b/src/modules/publicip.c @@ -5,12 +5,13 @@ #define FF_PUBLICIP_MODULE_NAME "Public IP" #define FF_PUBLICIP_NUM_FORMAT_ARGS 1 -static FFSockType sockfd; +static FFNetworkingState state; +static int status = -1; void ffPreparePublicIp(FFinstance* instance) { if(instance->config.publicIpUrl.length == 0) - sockfd = ffNetworkingSendHttpRequest("ipinfo.io", "/ip", NULL, instance->config.publicIpTimeout); + status = ffNetworkingSendHttpRequest(&state, "ipinfo.io", "/ip", NULL); else { FFstrbuf host; @@ -27,7 +28,7 @@ void ffPreparePublicIp(FFinstance* instance) host.chars[pathStartIndex] = '\0'; } - sockfd = ffNetworkingSendHttpRequest(host.chars, path.length == 0 ? "/" : path.chars, NULL, instance->config.publicIpTimeout); + status = ffNetworkingSendHttpRequest(&state, host.chars, path.length == 0 ? "/" : path.chars, NULL); ffStrbufDestroy(&path); ffStrbufDestroy(&host); @@ -36,10 +37,10 @@ void ffPreparePublicIp(FFinstance* instance) void ffPrintPublicIp(FFinstance* instance) { - if(sockfd == 0) + if(status == -1) ffPreparePublicIp(instance); - if(sockfd == INVALID_SOCKET) + if(status == 0) { ffPrintError(instance, FF_PUBLICIP_MODULE_NAME, 0, &instance->config.publicIP, "Failed to connect to an IP detection server"); return; @@ -47,7 +48,7 @@ void ffPrintPublicIp(FFinstance* instance) FFstrbuf result; ffStrbufInitA(&result, 4096); - bool success = ffNetworkingRecvHttpResponse(sockfd, &result); + bool success = ffNetworkingRecvHttpResponse(&state, &result, instance->config.publicIpTimeout); if(success) ffStrbufSubstrAfterFirstS(&result, "\r\n\r\n"); if(!success || result.length == 0) diff --git a/src/modules/weather.c b/src/modules/weather.c index 11dc9748e..a6bc63ce9 100644 --- a/src/modules/weather.c +++ b/src/modules/weather.c @@ -5,23 +5,24 @@ #define FF_WEATHER_MODULE_NAME "Weather" #define FF_WEATHER_NUM_FORMAT_ARGS 1 -static FFSockType sockfd; +static FFNetworkingState state; +static int status = -1; void ffPrepareWeather(FFinstance* instance) { FFstrbuf path; ffStrbufInitS(&path, "/?format="); ffStrbufAppend(&path, &instance->config.weatherOutputFormat); - sockfd = ffNetworkingSendHttpRequest("wttr.in", path.chars, "User-Agent: curl/0.0.0\r\n", instance->config.weatherTimeout); + status = ffNetworkingSendHttpRequest(&state, "wttr.in", path.chars, "User-Agent: curl/0.0.0\r\n"); ffStrbufDestroy(&path); } void ffPrintWeather(FFinstance* instance) { - if(sockfd == 0) + if(status == -1) ffPrepareWeather(instance); - if(sockfd == INVALID_SOCKET) + if(status == 0) { ffPrintError(instance, FF_WEATHER_MODULE_NAME, 0, &instance->config.weather, "Failed to connect to 'wttr.in'"); return; @@ -29,7 +30,7 @@ void ffPrintWeather(FFinstance* instance) FFstrbuf result; ffStrbufInitA(&result, 4096); - bool success = ffNetworkingRecvHttpResponse(sockfd, &result); + bool success = ffNetworkingRecvHttpResponse(&state, &result, instance->config.weatherTimeout); if (success) ffStrbufSubstrAfterFirstS(&result, "\r\n\r\n"); if(!success || result.length == 0)