SatCat5
tcp_socket.cc
1 // Copyright 2025 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <climits>
8 #include <hal_posix/tcp_socket.h>
9 #include <satcat5/log.h>
10 #include <satcat5/ip_core.h>
11 #include <satcat5/utils.h>
12 
13 // Include files for Windows or Linux?
14 #if SATCAT5_WIN32
15  #include <winsock2.h>
16  #include <ws2tcpip.h>
17  #define CLOSE_SOCKET(x) {closesocket(x); x = -1;}
18  #undef ERROR // Deconflict Windows "ERROR" macro
19 #else
20  #include <arpa/inet.h>
21  #include <fcntl.h>
22  #include <netdb.h>
23  #include <sys/socket.h>
24  #include <sys/types.h>
25  #include <unistd.h>
26  #define CLOSE_SOCKET(x) {::close(x); x = -1;}
27 #endif
28 
29 using satcat5::ip::Addr;
30 using satcat5::ip::Port;
34 
35 // Make a list of sockets with a single item.
36 static inline fd_set make_fdset(int fd) {
37  fd_set tmp;
38  FD_ZERO(&tmp);
39  FD_SET(fd, &tmp);
40  return tmp;
41 }
42 
43 // Is the provided socket in a state that can read/write/accept?
44 static bool can_read(int fd) {
45  if (fd < 0) return false;
46  auto query = make_fdset(fd);
47  timeval right_now = {0, 0};
48  int count = select(fd+1, &query, nullptr, nullptr, &right_now);
49  return (count > 0);
50 }
51 
52 static bool can_write(int fd) {
53  if (fd < 0) return false;
54  auto query = make_fdset(fd);
55  timeval right_now = {0, 0};
56  int count = select(fd+1, nullptr, &query, nullptr, &right_now);
57  return (count > 0);
58 }
59 
60 static bool got_event(int fd) {
61  if (fd < 0) return false;
62  auto query = make_fdset(fd);
63  timeval right_now = {0, 0};
64  int count = select(fd+1, nullptr, nullptr, &query, &right_now);
65  return (count > 0);
66 }
67 
68 // Mark a socket descriptor as non-blocking.
69 static int set_nonblock(int fd) {
70  #if SATCAT5_WIN32
71  u_long enable = 1;
72  return ioctlsocket(fd, FIONBIO, &enable);
73  #else
74  int flags = fcntl(fd, F_GETFL, 0);
75  return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
76  #endif
77 }
78 
79 // Get the most recent socket-related error code.
80 static int get_error() {
81  #if SATCAT5_WIN32
82  return WSAGetLastError();
83  #else
84  return errno;
85  #endif
86 }
87 
88 // Is a given error code a "real" error?
89 // (i.e., Ignore special return codes for non-blocking sockets.)
90 static bool is_error(int err) {
91  if (err >= 0) return false;
92  int sub_code = get_error();
93  #if SATCAT5_WIN32
94  return sub_code != WSAEINPROGRESS
95  && sub_code != WSAEWOULDBLOCK;
96  #else
97  return sub_code != EAGAIN
98  && sub_code != EINPROGRESS
99  && sub_code != EWOULDBLOCK;
100  #endif
101 }
102 
103 // Shortcut for printing a network error message.
104 static void log_socket_error(const char* label) {
105  int err_code = get_error();
106  const char* err_msg = strerror(err_code);
107  satcat5::log::Log(satcat5::log::ERROR, "SocketPosix: ")
108  .write(label).write10(err_code).write("\r\n ").write(err_msg);
109 }
110 
111 // Internal status flags:
112 constexpr u32 FLAG_WSA_CLEANUP = (1u << 0);
113 
114 SocketPosix::SocketPosix(unsigned txbytes, unsigned rxbytes)
115  : BufferedIO(
116  new u8[txbytes], txbytes, 0, // Allocate Tx buffer
117  new u8[rxbytes], rxbytes, 0) // Allocate Rx buffer
118  , m_flags(0)
119  , m_last_rx(SATCAT5_CLOCK->now())
120  , m_last_tx(SATCAT5_CLOCK->now())
121  , m_sock_listen(-1)
122  , m_sock_data(-1)
123  , m_rate_kbps(0)
124 {
125  // Windows only: Perform first-time setup of WinSock API.
126  // Request version 2.2, which has been stable from 1996-2024.
127  // Note: Microsoft counts how many times each application calls
128  // WSAStartup, and last call to WSACleanup turns out the lights.
129  #if SATCAT5_WIN32
130  WSADATA wsadata;
131  int err = WSAStartup(MAKEWORD(2, 2), &wsadata);
132  if (err) log_socket_error("ctor");
133  else m_flags |= FLAG_WSA_CLEANUP;
134  #endif
135 }
136 
137 SocketPosix::~SocketPosix() {
138  // Close open connections.
139  close();
140 
141  // Windows only: Additional cleanup required.
142  #if SATCAT5_WIN32
143  if (m_flags & FLAG_WSA_CLEANUP) WSACleanup();
144  #endif
145 
146  // Free the I/O working buffers.
147  delete[] m_tx.get_buff_dtor();
148  delete[] m_rx.get_buff_dtor();
149 }
150 
152  // Close both sockets.
153  if (m_sock_listen >= 0) CLOSE_SOCKET(m_sock_listen);
154  if (m_sock_data >= 0) CLOSE_SOCKET(m_sock_data);
155 
156  // Reset reference timestamps.
157  m_last_rx = SATCAT5_CLOCK->now();
158  m_last_tx = SATCAT5_CLOCK->now();
159 
160  // Stop timer polling.
161  timer_stop();
162 }
163 
164 bool SocketPosix::bind(const Port& port) {
165  // Sanity checks before we start...
166  close();
167 
168  // Setup request information.
169  struct sockaddr_in request;
170  request.sin_family = AF_INET;
171  request.sin_addr.s_addr = INADDR_ANY;
172  request.sin_port = htons(port.value);
173 
174  // Open the socket and mark it as non-blocking.
175  m_sock_listen = open_nonblock_socket();
176  if (m_sock_listen < 0) return false;
177 
178  // Attempt to set the REUSEADDR flag to allow server restarts.
179  // This is nonessential, so ignore errors in this operation.
180  const int enable = 1;
181  setsockopt(m_sock_listen, SOL_SOCKET, SO_REUSEADDR, (const char*)&enable, sizeof(enable));
182 
183  // Attempt to bind to the requested port.
184  int err = ::bind(m_sock_listen, (const sockaddr*)&request, sizeof(request));
185  if (err) {
186  log_socket_error("bind");
187  close(); return false;
188  }
189 
190  // Start listening on that port.
191  err = listen(m_sock_listen, 1);
192  if (err) {
193  log_socket_error("listen");
194  close(); return false;
195  }
196 
197  // On success, start the timer.
198  timer_every(1);
199  return true;
200 }
201 
202 bool SocketPosix::connect(const char* hostname, const Port& port) {
203  bool ok = true;
204 
205  // Setup query for hostname lookup.
206  struct addrinfo hints;
207  memset(&hints, 0, sizeof(hints));
208  hints.ai_family = AF_INET; // Prefer IPv4
209  hints.ai_socktype = SOCK_STREAM;
210  hints.ai_protocol = IPPROTO_TCP;
211 
212  struct addrinfo *result = nullptr;
213  int err = getaddrinfo(hostname, nullptr, &hints, &result);
214  if (err) {
215  log_socket_error("addr");
216  ok = false;
217  }
218 
219  // Extract first IPv4 address from the list of results,
220  // then attempt to proceed with connection by address.
221  if (ok) {
222  struct sockaddr_in* tmp = (struct sockaddr_in*)result->ai_addr;
223  Addr addr(ntohl(tmp->sin_addr.s_addr));
224  ok = connect(Addr{addr}, port);
225  }
226 
227  // Cleanup before returning.
228  freeaddrinfo(result);
229  return ok;
230 }
231 
232 bool SocketPosix::connect(const Addr& addr, const Port& port) {
233  // Sanity checks before we start...
234  close();
235  if (!addr.is_unicast()) return false;
236 
237  // Setup request information:
238  struct sockaddr_in request;
239  request.sin_family = AF_INET;
240  request.sin_addr.s_addr = htonl(addr.value);
241  request.sin_port = htons(port.value);
242 
243  // Open the socket and mark it as non-blocking.
244  m_sock_data = open_nonblock_socket();
245  if (m_sock_data < 0) return false;
246 
247  // Attempt connection to the remote server.
248  int err = ::connect(m_sock_data, (struct sockaddr*)&request, sizeof(request));
249  if (is_error(err)) {
250  log_socket_error("connect");
251  close(); return false;
252  }
253 
254  // On success, start the timer.
255  timer_every(1);
256  return true;
257 }
258 
260  return can_write(m_sock_data);
261 }
262 
264  // Copy data from working buffer to the socket.
265  if (can_write(m_sock_data)) {
266  unsigned limit = rate_limit(m_last_tx);
267  while (limit) {
268  unsigned len = min_unsigned(limit, m_tx.get_peek_ready());
269  const char* tmp = (const char*)m_tx.peek(len);
270  int sent = send(m_sock_data, tmp, len, 0);
271  if (sent < 0) log_socket_error("send");
272  if (sent <= 0) break;
273  m_tx.read_consume(unsigned(sent));
274  limit -= sent;
275  if (unsigned(sent) < len) break;
276  }
277  }
278 }
279 
281  // Handle events for m_sock_data or m_sock_listen...
282  if (can_read(m_sock_data)) {
283  // Copy new data to the working buffer.
284  unsigned limit = rate_limit(m_last_rx);
285  u8 tmp[256];
286  while (limit) {
287  unsigned rmax = min_unsigned(sizeof(tmp), limit);
288  rmax = min_unsigned(rmax, m_rx.get_write_space());
289  int rcvd = recv(m_sock_data, (char*)tmp, rmax, 0);
290  if (is_error(rcvd)) log_socket_error("recv");
291  if (rcvd <= 0) break;
292  m_rx.write_bytes(rcvd, tmp);
293  limit -= rcvd;
294  if (unsigned(rcvd) == rmax) break;
295  }
297  } else if (got_event(m_sock_data)) {
298  // Error closes current connection.
299  log_socket_error("poll");
300  CLOSE_SOCKET(m_sock_data);
301  // Client reverts to idle, server resumes listening.
302  if (m_sock_listen < 0) {
303  close();
304  } else if (listen(m_sock_listen, 1)) {
305  log_socket_error("listen");
306  close();
307  }
308  } else if (m_sock_data < 0 && can_read(m_sock_listen)) {
309  // Accept incoming connection.
310  m_sock_data = accept(m_sock_listen, nullptr, nullptr);
311  if (m_sock_data < 0 || set_nonblock(m_sock_data)) {
312  log_socket_error("accept");
313  close();
314  }
315  } else if (got_event(m_sock_listen)) {
316  // Other error while listening for connections.
317  log_socket_error("server");
318  close();
319  }
320 }
321 
322 int SocketPosix::open_nonblock_socket() {
323  // Create a new socket descriptor..
324  int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
325  if (sock < 0) {
326  log_socket_error("socket");
327  return sock;
328  }
329 
330  // Mark it as non-blocking.
331  int err = set_nonblock(sock);
332  if (err) {
333  log_socket_error("nonblk");
334  CLOSE_SOCKET(sock);
335  }
336  return sock;
337 }
338 
339 unsigned SocketPosix::rate_limit(TimeVal& tv) {
340  // Calculate maximum Tx/Rx bytes based on previous Tx/Rx timestamp.
341  unsigned elapsed = min_unsigned(10, tv.increment_msec());
342  return m_rate_kbps ? (elapsed * m_rate_kbps / 8) : UINT_MAX;
343 }
satcat5::io::PacketBuffer m_tx
Transmit data (user writes, child reads)
Definition: io_buffer.h:57
satcat5::io::PacketBuffer m_rx
Receive data (user reads, child writes)
Definition: io_buffer.h:60
const u8 * peek(unsigned nbytes) const
Peek nbytes into the circular buffer.
Definition: pkt_buffer.cc:248
unsigned get_peek_ready() const
Find the longest available contiguous segment that can be requested by peek().
Definition: pkt_buffer.cc:242
bool write_finalize() override
Mark end of frame and release temporary working data.
Definition: pkt_buffer.cc:105
unsigned get_write_space() const override
How many bytes can be written without blocking?
Definition: pkt_buffer.cc:52
void write_bytes(unsigned nbytes, const void *src) override
Write 0 or more bytes from a buffer.
Definition: pkt_buffer.cc:73
u8 * get_buff_dtor() const
Accessor for children that need to delete underlying buffer.
Definition: pkt_buffer.h:137
bool read_consume(unsigned nbytes) override
Read and discard 0 or more bytes.
Definition: pkt_buffer.cc:256
Abstract API for reading byte-streams and packets.
Definition: io_readable.h:68
The Log class creates and formats one log message.
Definition: log.h:195
Log & write10(s32 val)
Print integer as a decimal value with no leading zeros.
Definition: log.cc:261
Log & write(const char *str)
Formatting methods for various data types.
Definition: log.cc:198
void timer_stop()
Stop all future notifications.
Definition: polling.cc:326
void timer_every(unsigned msec)
Configure a repeating notification every X milliseconds.
Definition: polling.cc:321
Connect a SatCat5 byte-stream to a Linux or Windows TCP socket.
Definition: tcp_socket.h:26
bool ready()
Is this connection ready to send and receive data?
Definition: tcp_socket.cc:259
void close()
Close any open sockets and return to idle.
Definition: tcp_socket.cc:151
int m_sock_data
Socket for data transfer, if connected.
Definition: tcp_socket.h:66
util::TimeVal m_last_rx
Time since last receive event.
Definition: tcp_socket.h:63
unsigned m_rate_kbps
Maximum bytes/msec, if applicable.
Definition: tcp_socket.h:67
bool bind(const satcat5::ip::Port &port)
Prepare to accept connection from a remote client endpoint.
Definition: tcp_socket.cc:164
u32 m_flags
Additional status flags.
Definition: tcp_socket.h:62
void data_rcvd(satcat5::io::Readable *src) override
The data_rcvd() callback is polled whenever data is available.
Definition: tcp_socket.cc:263
void timer_event() override
Child class MUST override this method.
Definition: tcp_socket.cc:280
bool connect(const char *hostname, const satcat5::ip::Port &port)
Attempt connection to a remote server endpoint.
Definition: tcp_socket.cc:202
int m_sock_listen
Socket for accept/bind, if applicable.
Definition: tcp_socket.h:65
util::TimeVal m_last_tx
Time since last transmit event.
Definition: tcp_socket.h:64
Diagnostic logging to UART and/or Ethernet ports.
Miscellaneous POSIX wrappers (e.g., heap allocation, log to console...)
IPv4 address is a 32-bit unsigned integer.
Definition: ip_core.h:15
u32 value
Raw access to the underlying representation.
Definition: ip_core.h:17
bool is_unicast() const
Any normal single-destination address.
Definition: ip_core.cc:59
UDP and TCP ports are both 16-bit unsigned integers.
Definition: ip_core.h:119
Timestamp for measuring elapsed time.
Definition: timeref.h:48
unsigned increment_msec()
Measure elapsed time in milliseconds, then increment by the returned quantized value.
Definition: timeref.cc:39
Miscellaneous mathematical utility functions.
constexpr unsigned min_unsigned(unsigned a, unsigned b)
Min and max functions.
Definition: utils.h:111