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 | |
22 | namespace 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 | |