1/*
2 * IXGetFreePort.cpp
3 * Author: Benjamin Sergeant
4 * Copyright (c) 2019 Machine Zone. All rights reserved.
5 */
6
7// Using inet_addr will trigger an error on uwp without this
8// FIXME: use a different api
9#ifdef _WIN32
10#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
11#define _WINSOCK_DEPRECATED_NO_WARNINGS
12#endif
13#endif
14
15#include "IXGetFreePort.h"
16
17#include <ixwebsocket/IXNetSystem.h>
18#include <ixwebsocket/IXSocket.h>
19#include <random>
20#include <string>
21
22namespace ix
23{
24 int getAnyFreePortRandom()
25 {
26 std::random_device rd;
27 std::uniform_int_distribution<int> dist(1024 + 1, 65535);
28
29 return dist(rd);
30 }
31
32 int getAnyFreePort()
33 {
34 socket_t sockfd;
35 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
36 {
37 return getAnyFreePortRandom();
38 }
39
40 int enable = 1;
41 if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*) &enable, sizeof(enable)) < 0)
42 {
43 return getAnyFreePortRandom();
44 }
45
46 // Bind to port 0. This is the standard way to get a free port.
47 struct sockaddr_in server; // server address information
48 server.sin_family = AF_INET;
49 server.sin_port = htons(0);
50 server.sin_addr.s_addr = inet_addr("127.0.0.1");
51
52 if (bind(sockfd, (struct sockaddr*) &server, sizeof(server)) < 0)
53 {
54 Socket::closeSocket(sockfd);
55 return getAnyFreePortRandom();
56 }
57
58 struct sockaddr_in sa; // server address information
59 socklen_t len = sizeof(sa);
60 if (getsockname(sockfd, (struct sockaddr*) &sa, &len) < 0)
61 {
62 Socket::closeSocket(sockfd);
63 return getAnyFreePortRandom();
64 }
65
66 int port = ntohs(sa.sin_port);
67 Socket::closeSocket(sockfd);
68
69 return port;
70 }
71
72 int getFreePort()
73 {
74 while (true)
75 {
76#if defined(__has_feature)
77#if __has_feature(address_sanitizer)
78 int port = getAnyFreePortRandom();
79#else
80 int port = getAnyFreePort();
81#endif
82#else
83 int port = getAnyFreePort();
84#endif
85 //
86 // Only port above 1024 can be used by non root users, but for some
87 // reason I got port 7 returned with macOS when binding on port 0...
88 //
89 if (port > 1024)
90 {
91 return port;
92 }
93 }
94
95 return -1;
96 }
97} // namespace ix
98