-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsocket_handler.cc
More file actions
157 lines (146 loc) · 6.53 KB
/
socket_handler.cc
File metadata and controls
157 lines (146 loc) · 6.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#include "socket_handler.h"
SocketHandler::SocketHandler() : fd_(CreateSocket()), port_(0) {}
SocketHandler::SocketHandler(int fd) : fd_(fd), port_(0) {}
SocketHandler::SocketHandler(int fd, const std::string& ip, int port) : fd_(fd), ip_addr_(ip), port_(port) {}
SocketHandler::~SocketHandler() { Close(); }
bool SocketHandler::SetTcpNoDelay(bool _flag){
int optVal = _flag ? 1 : 0;
return ::setsockopt(fd_, IPPROTO_TCP, TCP_NODELAY, &optVal, sizeof(optVal)) == 0;
}
bool SocketHandler::SetReuseAddr(bool _flag){
int optVal = _flag ? 1 : 0;
return ::setsockopt(fd_, SOL_SOCKET, SO_REUSEADDR, &optVal, sizeof(optVal)) == 0;
}
bool SocketHandler::SetReusePort(bool _flag){
int optVal = _flag ? 1 : 0;
return ::setsockopt(fd_, SOL_SOCKET, SO_REUSEPORT, &optVal, sizeof(optVal)) == 0;
}
bool SocketHandler::SetKeepAlive(bool _flag){
int optVal = _flag ? 1 : 0;
return ::setsockopt(fd_, SOL_SOCKET, SO_KEEPALIVE, &optVal, sizeof(optVal)) == 0;
}
int SocketHandler::CreateSocket() {
int listenfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenfd == -1) {
std::cout << "[Socket Handler] Invalid socket..." << std::endl;
throw std::runtime_error("Invalid socket...");
}
SetNonBlocking(listenfd);
return listenfd;
}
void SocketHandler::Bind(const InetAddr& _servAddr){
if(::bind(fd_, _servAddr.Addr(), sizeof(sockaddr_in)) < 0){
int saved_errno = errno; // Save errno before any other calls
Close();
std::cout << "[Socket Handler] Error occurred when binding port: "
<< strerror(saved_errno) << " (errno=" << saved_errno << ")" << std::endl;
throw std::runtime_error(std::string("Error binding port: ") + strerror(saved_errno));
}
}
void SocketHandler::Listen(int _maxLen){
if(::listen(fd_, _maxLen) != 0){
Close();
std::cout << "[Socket Handler] Error occurred when listening ..." << std::endl;
throw std::runtime_error("Error occurred when listening ...");
}
}
int SocketHandler::Accept(InetAddr& _clientAddr){
sockaddr_in acceptAddr;
socklen_t len = sizeof(acceptAddr);
#if defined(__linux__)
// Linux: use accept4 with SOCK_NONBLOCK for atomic non-blocking accept
int clientfd = accept4(fd_, reinterpret_cast<sockaddr*>(&acceptAddr), &len, SOCK_NONBLOCK);
#elif defined(__APPLE__) || defined(__MACH__)
// macOS: use regular accept and set non-blocking separately
int clientfd = accept(fd_, reinterpret_cast<sockaddr*>(&acceptAddr), &len);
#endif
if(clientfd == -1){
// Don't close listening socket on accept error
if(errno == EAGAIN || errno == EWOULDBLOCK) {
return ACCEPT_QUEUE_DRAINED;
}
if(errno == ECONNABORTED) {
return ACCEPT_CONN_ABORTED;
}
if(errno == EMFILE || errno == ENFILE) {
std::cerr << "[Socket Handler] Accept failed (fd exhaustion): " << strerror(errno) << std::endl;
return ACCEPT_FD_EXHAUSTION;
}
if(errno == ENOBUFS || errno == ENOMEM) {
std::cerr << "[Socket Handler] Accept failed (memory pressure): " << strerror(errno) << std::endl;
return ACCEPT_MEMORY_PRESSURE;
}
std::cout << "[Socket Handler] Error occurred when accepting connection: " << strerror(errno) << std::endl;
throw std::runtime_error(std::string("Error accepting connection: ") + strerror(errno));
}
#if defined(__APPLE__) || defined(__MACH__)
// Set non-blocking after successful accept on macOS.
// On Linux, accept4(SOCK_NONBLOCK) handles this atomically.
// Check fd validity after SetNonBlocking: if it silently returned
// on EBADF (fd-reuse race), the fd is dead and must not be handed
// to the reactor — later epoll/kqueue registration would operate
// on an fd that may already belong to another connection.
SetNonBlocking(clientfd);
if (fcntl(clientfd, F_GETFL) == -1) {
// fd is dead — treat as aborted connection, continue draining
return ACCEPT_CONN_ABORTED;
}
#endif
_clientAddr.SetAddr(acceptAddr);
return clientfd;
}
void SocketHandler::Close() {
if (fd_ != -1) {
::close(fd_);
fd_ = -1;
}
}
void SocketHandler::SetNonBlocking(int fd) {
// RACE CONDITION FIX: Handle TOCTOU race between accept() and SetNonBlocking()
//
// Scenario that triggers this:
// 1. Server accepts connection, gets clientfd
// 2. Client immediately closes (sends FIN/RST)
// 3. Kernel processes close, invalidates clientfd
// 4. Server tries to set non-blocking on already-closed fd
// 5. fcntl() fails with EBADF (Bad file descriptor)
//
// This is especially common in rapid connect/disconnect tests (RC-TEST-3)
// where clients connect and immediately close without sending data.
//
// Solution: Check errno and handle closed fds gracefully instead of throwing.
// The connection is already gone, so there's nothing to set non-blocking.
// Just log and return - the fd will be cleaned up elsewhere.
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
// fd is likely already closed by peer (EBADF)
// This is not an error - just a race condition we need to handle
if (errno == EBADF) {
// File descriptor already closed - this is expected in rapid close scenarios
// No need to log as it's a normal race condition, not an error
return;
}
// Other errors are unexpected - log them
std::cerr << "[Socket Handler] Unexpected error getting socket flags: "
<< strerror(errno) << " (errno=" << errno << ")" << std::endl;
throw std::runtime_error(std::string("Failed to get socket flags: ") + strerror(errno));
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
// Same logic - fd might have been closed between the two fcntl calls
if (errno == EBADF) {
// File descriptor closed between F_GETFL and F_SETFL
return;
}
std::cerr << "[Socket Handler] Unexpected error setting non-blocking mode: "
<< strerror(errno) << " (errno=" << errno << ")" << std::endl;
throw std::runtime_error(std::string("Failed to set non-blocking mode: ") + strerror(errno));
}
// macOS: suppress SIGPIPE per-socket since MSG_NOSIGNAL is not available
#ifdef SO_NOSIGPIPE
int set = 1;
if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &set, sizeof(set)) < 0) {
std::cerr << "[Socket Handler] Failed to set SO_NOSIGPIPE: "
<< strerror(errno) << std::endl;
}
#endif
}