Skip to content

Commit

Permalink
MDEV-22743 Windows 10 MSI installer : port in use is not determined
Browse files Browse the repository at this point in the history
when checking for free port, use the same logic (IPv6 socket address
/ dual socket), like the server would.

Previous solution for testing whether port is free was trying to bind
IPv4 socket on INADDR_ANY.

This not work now on some reason, that attempt succeeds, even if there is
an existing IPv6-dual socket listening on 0.0.0.0:3306
  • Loading branch information
vaintroub committed May 29, 2020
1 parent ff72f36 commit b00cd3e
Showing 1 changed file with 76 additions and 21 deletions.
97 changes: 76 additions & 21 deletions win/packaging/ca/CustomAction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
#undef NOMINMAX

#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <winreg.h>
#include <msi.h>
Expand All @@ -33,6 +34,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
#include <stdlib.h>
#include <winservice.h>


#define ONE_MB 1048576
UINT ExecRemoveDataDirectory(wchar_t *dir)
{
Expand Down Expand Up @@ -255,36 +257,89 @@ bool ExecRemoveService(const wchar_t *name)
return ret;
}

/*
Check if port is free by trying to bind to the port
*/
bool IsPortFree(short port)
/* Find whether TCP port is in use by trying to bind to the port. */
static bool IsPortInUse(unsigned short port)
{
WORD wVersionRequested;
WSADATA wsaData;

wVersionRequested = MAKEWORD(2, 2);
struct addrinfo* ai, * a;
struct addrinfo hints {};

WSAStartup(wVersionRequested, &wsaData);
char port_buf[NI_MAXSERV];
SOCKET ip_sock = INVALID_SOCKET;
hints.ai_flags = AI_PASSIVE;
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_UNSPEC;
snprintf(port_buf, NI_MAXSERV, "%u", (unsigned)port);

struct sockaddr_in sin;
SOCKET sock;
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == -1)
if (getaddrinfo(NULL, port_buf, &hints, &ai))
{
return false;
}
sin.sin_port = htons(port);
sin.sin_addr.s_addr = 0;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_family = AF_INET;
if(bind(sock, (struct sockaddr *)&sin,sizeof(struct sockaddr_in) ) == -1)

/*
Prefer IPv6 socket to IPv4, since we'll use IPv6 dual socket,
which coveres both IP versions.
*/
for (a = ai; a; a = a->ai_next)
{
if (a->ai_family == AF_INET6 &&
(ip_sock = socket(a->ai_family, a->ai_socktype, a->ai_protocol)) != INVALID_SOCKET)
{
break;
}
}

if (ip_sock == INVALID_SOCKET)
{
for (a = ai; a; a = a->ai_next)
{
if (ai->ai_family == AF_INET &&
(ip_sock = socket(a->ai_family, a->ai_socktype, a->ai_protocol)) != INVALID_SOCKET)
{
break;
}
}
}

if (ip_sock == INVALID_SOCKET)
{
return false;
}
closesocket(sock);

/* Use SO_EXCLUSIVEADDRUSE to prevent multiple binding. */
int arg = 1;
setsockopt(ip_sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char*)&arg, sizeof(arg));

/* Allow dual socket, so that IPv4 and IPv6 are both covered.*/
if (a->ai_family == AF_INET6)
{
arg = 0;
setsockopt(ip_sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&arg, sizeof(arg));
}

bool in_use = false;
if (bind(ip_sock, a->ai_addr, a->ai_addrlen) == SOCKET_ERROR)
{
DWORD last_error = WSAGetLastError();
in_use = (last_error == WSAEADDRINUSE || last_error == WSAEACCES);
}

freeaddrinfo(ai);
closesocket(ip_sock);
return in_use;
}


/*
Check if TCP port is free
*/
bool IsPortFree(unsigned short port)
{
WORD wVersionRequested = MAKEWORD(2, 2);
WSADATA wsaData;
WSAStartup(wVersionRequested, &wsaData);
bool in_use = IsPortInUse(port);
WSACleanup();
return true;
return !in_use;
}


Expand Down Expand Up @@ -634,7 +689,7 @@ extern "C" UINT __stdcall CheckDatabaseProperties (MSIHANDLE hInstall)
goto LExit;
}

short port = (short)_wtoi(Port);
unsigned short port = (unsigned short)_wtoi(Port);
if (!IsPortFree(port))
{
ErrorMsg =
Expand Down

0 comments on commit b00cd3e

Please sign in to comment.