NVIDIA DOCA SDK Data Center on a Chip Framework Documentation
tcp_socket.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2025 NVIDIA CORPORATION AND AFFILIATES. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without modification, are permitted
5  * provided that the following conditions are met:
6  * * Redistributions of source code must retain the above copyright notice, this list of
7  * conditions and the following disclaimer.
8  * * Redistributions in binary form must reproduce the above copyright notice, this list of
9  * conditions and the following disclaimer in the documentation and/or other materials
10  * provided with the distribution.
11  * * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
12  * to endorse or promote products derived from this software without specific prior written
13  * permission.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
17  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
20  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
21  * STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  *
24  */
25 
27 
28 #include <algorithm>
29 #include <cstring>
30 #include <limits>
31 #include <stdexcept>
32 #include <string>
33 
34 #include <arpa/inet.h>
35 #include <fcntl.h>
36 #include <netdb.h>
37 #include <poll.h>
38 #include <sys/socket.h>
39 #include <sys/types.h>
40 #include <unistd.h>
41 
42 #include <doca_log.h>
43 
46 
47 DOCA_LOG_REGISTER(TCP_SOCKET);
48 
49 using namespace std::string_literals;
50 
51 namespace storage {
52 namespace {
53 uint32_t constexpr invalid_socket{std::numeric_limits<uint32_t>::max()};
54 
55 } /* namespace */
56 
57 tcp_socket::~tcp_socket()
58 {
59  try {
60  close();
61  } catch (std::runtime_error const &ex) {
62  DOCA_LOG_ERR("Failed to close socket during destruction: %s", ex.what());
63  }
64 }
65 
66 tcp_socket::tcp_socket() : m_fd{invalid_socket}
67 {
68  auto ret = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
69  if (ret < 0) {
71  "Failed to create socket. Error: " + storage::strerror_r(errno)};
72  }
73 
74  m_fd = static_cast<uint32_t>(ret);
75  set_socket_options();
76 }
77 
78 tcp_socket::tcp_socket(uint32_t fd) : m_fd{fd}
79 {
80  if (fd != invalid_socket) {
81  // Only do this when not creating an "empty" socket
82  set_socket_options();
83  }
84 }
85 
86 tcp_socket::tcp_socket(tcp_socket &&other) noexcept : m_fd{other.m_fd}
87 {
88  other.m_fd = invalid_socket;
89 }
90 
92 {
93  if (std::addressof(other) == this)
94  return *this;
95 
96  m_fd = other.m_fd;
97  other.m_fd = invalid_socket;
98 
99  return *this;
100 }
101 
102 void tcp_socket::set_blocking(bool blocking)
103 {
104  auto flags = fcntl(static_cast<int>(m_fd), F_GETFL);
105  if (flags == -1) {
108  "Failed to get current blocking mode. Error: " + storage::strerror_r(errno) + "\n"};
109  }
110  if (blocking) {
111  flags &= ~O_NONBLOCK;
112  } else {
113  flags |= O_NONBLOCK;
114  }
115  auto ret = fcntl(static_cast<int>(m_fd), F_SETFL, flags);
116  if (ret == -1) {
119  "Failed to set new current blocking mode. Error: " + storage::strerror_r(errno) + "\n"};
120  }
121 }
122 
124 {
125  if (m_fd == invalid_socket)
126  return;
127 
128  DOCA_LOG_DBG("Close socket %u", m_fd);
129  auto const ret = ::close(static_cast<int>(m_fd));
130  if (ret != 0) {
132  "Failed to close. Error: " + storage::strerror_r(errno) + "\n"};
133  }
134 
135  m_fd = invalid_socket;
136 }
137 
139 {
140  sockaddr_in sa{};
141  sa.sin_family = AF_INET;
142  sa.sin_port = htons(address.get_port());
143 
144  if (::inet_pton(AF_INET, address.get_address().c_str(), &sa.sin_addr) < 0) {
146  "Failed to parse address: \"" + address.get_address() +
147  "\":" + std::to_string(address.get_port()) +
148  ". Error: " + storage::strerror_r(errno)};
149  }
150 
151  if (::connect(static_cast<int>(m_fd), reinterpret_cast<sockaddr *>(&sa), sizeof(sa)) != 0) {
152  // https://man7.org/linux/man-pages/man2/connect.2.html
153  auto const err = errno;
154  switch (err) {
155  case EINPROGRESS: // The socket is nonblocking and the connection cannot be completed
156  // immediately. It is possible to select(2) or poll(2) for completion by
157  // selecting the socket for writing. After select(2) indicates writability,
158  // use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to
159  // determine whether connect() completed successfully (SO_ERROR is zero) or
160  // unsuccessfully (SO_ERROR is one of the usual error codes listed here,
161  // explaining the reason for the failure)
162  break;
163  default:
165  "Failed to connect to: \"" + address.get_address() +
166  "\":" + std::to_string(address.get_port()) +
167  ". Error: " + storage::strerror_r(errno)};
168  }
169  }
170 }
171 
172 void tcp_socket::listen(uint16_t port)
173 {
174  sockaddr_in sa;
175  ::memset(&sa, 0, sizeof(sa));
176  sa.sin_family = AF_INET;
177  sa.sin_addr.s_addr = htonl(INADDR_ANY);
178  sa.sin_port = htons(port);
179 
180  DOCA_LOG_DBG("Listening on socket %u", m_fd);
181 
182  if (::bind(static_cast<int>(m_fd), reinterpret_cast<sockaddr *>(&sa), sizeof(sa)) != 0) {
184  "Failed to bind to port: " + std::to_string(port) +
185  ". Error: " + storage::strerror_r(errno)};
186  }
187 
188  if (::listen(static_cast<int>(m_fd), 1) != 0) {
190  "Failed to listen on port: " + std::to_string(port) +
191  ". Error: " + storage::strerror_r(errno)};
192  }
193 }
194 
196 {
197  pollfd pfd{};
198 
199  pfd.fd = static_cast<int>(m_fd);
200  pfd.events = POLLOUT;
201  pfd.revents = 0;
202 
203  auto ret = poll(&pfd, 1, 0);
204 
205  if (ret < 0) {
207  }
208 
209  if (ret == 0)
211 
212  if (pfd.revents & POLLOUT) {
213  socklen_t ret_size = sizeof(ret);
214  if (::getsockopt(static_cast<int>(m_fd), SOL_SOCKET, SO_ERROR, &ret, &ret_size) != 0) {
216  }
217 
218  if (ret == ECONNABORTED || ret == ECONNREFUSED || ret == ECONNRESET || ret == ENOTCONN) {
220  }
221 
222  if (ret == 0) {
223  struct sockaddr_in peeraddr {};
224  socklen_t peeraddrlen{};
225  ret = getpeername(static_cast<int>(m_fd), (struct sockaddr *)&peeraddr, &peeraddrlen);
226  if (ret == 0) {
227  DOCA_LOG_DBG("Connected socket %u to: %s:%u",
228  m_fd,
229  inet_ntoa(peeraddr.sin_addr),
230  ntohs(peeraddr.sin_port));
232  }
233 
234  auto const err = errno;
235  if (err == ENOTCONN)
237 
239  }
240  }
241 
243 }
244 
246 {
247  sockaddr_storage ss{};
248  socklen_t peer_addr_len = sizeof(ss);
249  auto const client_fd =
250  ::accept4(static_cast<int>(m_fd), reinterpret_cast<sockaddr *>(&ss), &peer_addr_len, SOCK_NONBLOCK);
251 
252  if (client_fd < 0) {
253  // https://man7.org/linux/man-pages/man2/accept.2.html
254  auto const err = errno;
255  switch (err) {
256  case EAGAIN: // The socket is marked nonblocking and no connections are present to be accepted
257 #if EWOULDBLOCK != EAGAIN
258  // POSIX.1-2001 allows either error to be returned for this case, and does not require these
259  // constants to have the same value, so a portable application should check for both
260  // possibilities
261  case EWOULDBLOCK:
262 #endif
263  return tcp_socket{invalid_socket};
264  default:
266  "Failed to accept new connection. Error: " + storage::strerror_r(err)};
267  }
268  }
269 
270  DOCA_LOG_DBG("Accepted socket %u", client_fd);
271  return tcp_socket{static_cast<uint32_t>(client_fd)};
272 }
273 
274 size_t tcp_socket::write(char const *buffer, size_t byte_count)
275 {
276  auto const ret = ::write(static_cast<int>(m_fd), buffer, byte_count);
277  if (ret < 0) {
278  auto const err = errno;
279  // https://man7.org/linux/man-pages/man2/write.2.html
280  switch (err) {
281  case EAGAIN: // The file descriptor fd refers to a file other than a socket and has been
282  // marked nonblocking (O_NONBLOCK), and the write would block
283 #if EWOULDBLOCK != EAGAIN
284  // POSIX.1-2001 allows either error to be returned for this case, and does not require
285  // these constants to have the same value, so a portable application should check for
286  // both possibilities
287  case EWOULDBLOCK:
288 #endif // DEBUG_TCP_SOCKET_OPS
289  case EINTR: // The call was interrupted by a signal before any data was written
290  return 0;
291  default:
292  DOCA_LOG_DBG("Write to socket %u failed: %s", m_fd, storage::strerror_r(err).c_str());
294  "Failed to write data. Error: " + storage::strerror_r(err)};
295  }
296  }
297 
298  if (ret > 0) {
299  DOCA_LOG_DBG("Tx %ld bytes", ret);
300  }
301 
302  return static_cast<size_t>(ret);
303 }
304 
305 size_t tcp_socket::read(char *buffer, size_t buffer_capacity)
306 {
307  auto const ret = ::read(static_cast<int>(m_fd), buffer, buffer_capacity);
308  if (ret < 0) {
309  auto const err = errno;
310  // https://man7.org/linux/man-pages/man2/read.2.html
311  switch (err) {
312  case EAGAIN: // The file descriptor fd refers to a file other than a socket and has
313  // been marked nonblocking (O_NONBLOCK), and the read would block
314 #if EWOULDBLOCK != EAGAIN
315  // POSIX.1-2001 allows either error to be returned for this case, and does not require
316  // these constants to have the same value, so a portable application should check for
317  // both possibilities
318  case EWOULDBLOCK:
319 #endif // DEBUG_TCP_SOCKET_OPS
320  case EINTR: // The call was interrupted by a signal before any data was read
321  return {};
322  default:
323  DOCA_LOG_DBG("Read from socket %u failed: %s\n", m_fd, storage::strerror_r(err).c_str());
325  "Failed to read data. Error: " + storage::strerror_r(err)};
326  }
327  }
328 
329  if (ret > 0) {
330  DOCA_LOG_DBG("Rx %ld bytes", ret);
331  }
332 
333  return static_cast<size_t>(ret);
334 }
335 
336 bool tcp_socket::is_valid(void) const noexcept
337 {
338  return m_fd != invalid_socket;
339 }
340 
341 void tcp_socket::set_socket_options(void)
342 {
343  int ret;
344 
345  {
346  linger linger_options{
347  0, // no linger
348  0 // 0 seconds
349  };
350 
351  ret = ::setsockopt(static_cast<int>(m_fd),
352  SOL_SOCKET,
353  SO_LINGER,
354  &linger_options,
355  sizeof(linger_options));
356  if (ret != 0) {
358  "Failed to set socket option SO_LINGER. Error: \""s +
359  ::gai_strerror(errno) + "\""};
360  }
361  }
362  {
363  int const reuse_addr{1};
364 
365  ret = ::setsockopt(static_cast<int>(m_fd), SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof(reuse_addr));
366  if (ret != 0) {
368  "Failed to set socket option SO_REUSEADDR. Error: \""s +
369  ::gai_strerror(errno) + "\""};
370  }
371  }
372 }
373 
374 } // namespace storage
uint16_t get_port() const noexcept
Definition: ip_address.cpp:45
std::string const & get_address() const noexcept
Definition: ip_address.cpp:40
size_t read(char *buffer, size_t buffer_capacity)
Definition: tcp_socket.cpp:305
void set_blocking(bool blocking)
Definition: tcp_socket.cpp:102
void listen(uint16_t port)
Definition: tcp_socket.cpp:172
tcp_socket & operator=(tcp_socket const &)=delete
bool is_valid(void) const noexcept
Definition: tcp_socket.cpp:336
size_t write(char const *buffer, size_t byte_count)
Definition: tcp_socket.cpp:274
void connect(storage::ip_address const &address)
Definition: tcp_socket.cpp:138
tcp_socket accept(void)
Definition: tcp_socket.cpp:245
connection_status poll_is_connected(void)
Definition: tcp_socket.cpp:195
@ DOCA_ERROR_OPERATING_SYSTEM
Definition: doca_error.h:58
#define DOCA_LOG_ERR(format,...)
Generates an ERROR application log message.
Definition: doca_log.h:466
#define DOCA_LOG_DBG(format,...)
Generates a DEBUG application log message.
Definition: doca_log.h:496
std::string to_string(storage::control::message_type type)
std::string strerror_r(int err) noexcept
Definition: os_utils.cpp:89
DOCA_LOG_REGISTER(TCP_SOCKET)