libdoip  0.1.0
DoIP (Diagnostics over Internet Protocol) ISO 13400 C++17 Library
DoIPServer.cpp
Go to the documentation of this file.
1 #include <algorithm> // for std::remove_if
2 #include <cerrno> // for errno
3 #include <cstring> // for strerror
4 #include <fcntl.h>
5 #include <sys/stat.h>
6 #include <sys/types.h>
7 #include <unistd.h>
8 
9 #include "DoIPConnection.h"
10 #include "DoIPMessage.h"
11 #include "DoIPServer.h"
12 #include "DoIPServerModel.h"
13 #include "Logger.h"
14 #include "MacAddress.h"
15 
16 using namespace doip;
17 
19  if (m_running.load()) {
20  stop();
21  }
22 }
23 
25  : m_config(config) {
26  m_receiveBuf.reserve(DOIP_MAXIMUM_MTU);
27 
28  setLoopbackMode(m_config.loopback);
29 
30  if (m_config.daemonize) {
31  daemonize();
32  }
33 }
34 
35 void DoIPServer::daemonize() {
36  LOG_DOIP_INFO("Daemonizing DoIP Server...");
37  pid_t pid = fork();
38  if (pid < 0) {
39  LOG_DOIP_ERROR("First fork failed: {}", strerror(errno));
40  return;
41  }
42 
43  if (pid > 0) {
44  // Parent exits; child continues
45  _exit(0);
46  }
47 
48  // Child: create new session and become session leader
49  if (setsid() < 0) {
50  LOG_DOIP_ERROR("setsid failed: {}", strerror(errno));
51  return;
52  }
53 
54  // Second fork to ensure the daemon can't reacquire a tty
55  pid = fork();
56  if (pid < 0) {
57  LOG_DOIP_ERROR("Second fork failed: {}", strerror(errno));
58  return;
59  }
60  if (pid > 0) {
61  _exit(0);
62  }
63 
64  // Set file mode creation mask to a safe default
65  umask(0);
66 
67  // Change working directory to root to avoid blocking mounts
68  if (chdir("/") != 0) {
69  LOG_DOIP_WARN("chdir to / failed: {}", strerror(errno));
70  }
71 
72  // Close and redirect standard file descriptors to /dev/null
73  int fd = open("/dev/null", O_RDWR);
74  if (fd >= 0) {
75  dup2(fd, STDIN_FILENO);
76  dup2(fd, STDOUT_FILENO);
77  dup2(fd, STDERR_FILENO);
78  if (fd > STDERR_FILENO) {
79  close(fd);
80  }
81  } else {
82  LOG_DOIP_WARN("Failed to open /dev/null: {}", strerror(errno));
83  }
84 
85  LOG_DOIP_INFO("DoIP Server daemonized and running");
86 }
87 
88 /*
89  * Stop the server and cleanup
90  */
91 void DoIPServer::stop() {
92  LOG_DOIP_INFO("Stopping DoIP Server...");
93  m_running.store(false);
94 
95  // Close sockets to unblock any pending accept/recv calls
98 
99  // Wait for all threads to finish
100  for (auto &thread : m_workerThreads) {
101  if (thread.joinable()) {
102  thread.join();
103  }
104  }
105  m_workerThreads.clear();
106 
107  LOG_DOIP_INFO("DoIP Server stopped");
108 }
109 
110 /*
111  * Background thread: Handle individual TCP connection
112  */
113 void DoIPServer::connectionHandlerThread(std::unique_ptr<DoIPConnection> connection) {
114  LOG_TCP_INFO("Connection handler thread started");
115 
116  while (m_running.load() && connection->isSocketActive()) {
117  int result = connection->receiveTcpMessage();
118 
119  if (result < 0) {
120  LOG_TCP_INFO("Connection closed or error occurred");
121  break;
122  }
123  }
124 
125  // Connection is automatically closed when unique_ptr goes out of scope
126  LOG_TCP_INFO("Connection handler thread stopped");
127 }
128 
129 /*
130  * Set up a tcp socket, so the socket is ready to accept a connection
131  */
133  LOG_DOIP_DEBUG("Setting up TCP socket on port {}", DOIP_SERVER_TCP_PORT);
134 
135  m_tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
136  if (m_tcp_sock < 0) {
137  LOG_TCP_ERROR("Failed to create TCP socket: {}", strerror(errno));
138  return false;
139  }
140 
141  // Allow socket reuse
142  int reuse = 1;
143  if (setsockopt(m_tcp_sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
144  LOG_TCP_WARN("Failed to set SO_REUSEADDR: {}", strerror(errno));
145  }
146 
147  m_serverAddress.sin_family = AF_INET;
148  m_serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
149  m_serverAddress.sin_port = htons(DOIP_SERVER_TCP_PORT);
150 
151  // binds the socket to the address and port number
152  if (bind(m_tcp_sock, reinterpret_cast<const struct sockaddr *>(&m_serverAddress), sizeof(m_serverAddress)) < 0) {
153  LOG_TCP_ERROR("Failed to bind TCP socket: {}", strerror(errno));
154  return false;
155  }
156 
157  LOG_TCP_INFO("TCP socket successfully bound to port {}", DOIP_SERVER_TCP_PORT);
158  return true;
159 }
160 
161 /*
162  * Closes the socket for this server
163  */
165  close(m_tcp_sock);
166 }
167 
169  LOG_UDP_DEBUG("Setting up UDP socket on port {}", DOIP_UDP_DISCOVERY_PORT);
170 
171  m_udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
172  if (m_udp_sock < 0) {
173  perror("Failed to create socket");
174  return 1;
175  }
176 
177  // Set socket to non-blocking with timeout
178  struct timeval timeout;
179  timeout.tv_sec = 1;
180  timeout.tv_usec = 0;
181  setsockopt(m_udp_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
182 
183  // Enable SO_REUSEADDR
184  int reuse = 1;
185  setsockopt(m_udp_sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
186 
187  // Bind socket to port 13400
188  struct sockaddr_in server_addr;
189  memset(&server_addr, 0, sizeof(server_addr));
190  server_addr.sin_family = AF_INET;
191  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
192  server_addr.sin_port = htons(DOIP_UDP_DISCOVERY_PORT);
193 
194  if (bind(m_udp_sock, reinterpret_cast<struct sockaddr *>(&server_addr), sizeof(server_addr)) < 0) {
195  perror("Failed to bind socket");
196  close(m_udp_sock);
197  return 1;
198  }
199  // setting the IP DoIPAddress for Multicast/Broadcast
200  if (!m_config.loopback) { //
201  setMulticastGroup("224.0.0.2");
202  LOG_UDP_INFO("UDP socket successfully bound to port {} with multicast group", DOIP_UDP_DISCOVERY_PORT);
203  } else {
204  LOG_UDP_INFO("UDP socket successfully bound to port {} with broadcast", DOIP_UDP_DISCOVERY_PORT);
205  }
206 
208  "Socket {} bound to {}:{}",
209  m_udp_sock,
210  inet_ntoa(m_serverAddress.sin_addr),
211  ntohs(m_serverAddress.sin_port));
212 
213  m_running.store(true);
214  m_workerThreads.emplace_back([this]() { udpListenerThread(); });
215  m_workerThreads.emplace_back([this]() { udpAnnouncementThread(); });
216 
217  return true;
218 }
219 
221  m_running.store(false);
222  for (auto &thread : m_workerThreads) {
223  if (thread.joinable()) {
224  thread.join();
225  }
226  }
227  close(m_udp_sock);
228 }
229 
231  MacAddress mac = {0};
232  if (!getFirstMacAddress(mac)) {
233  LOG_DOIP_ERROR("Failed to get MAC address, using default EID");
234  m_config.eid = DoIpEid::Zero;
235  return false;
236  }
237  // Set EID based on MAC address (last 6 bytes)
238  m_config.eid = DoIpEid(mac.data(), m_config.eid.ID_LENGTH);
239  return true;
240 }
241 
242 void DoIPServer::setVin(const std::string &VINString) {
243 
244  m_config.vin = DoIpVin(VINString);
245 }
246 
247 void DoIPServer::setVin(const DoIpVin &vin) {
248  m_config.vin = vin;
249 }
250 
252  m_config.logicalAddress = logicalAddress;
253 }
254 
255 void DoIPServer::setEid(const uint64_t inputEID) {
256  m_config.eid = DoIpEid(inputEID);
257 }
258 
259 void DoIPServer::setGid(const uint64_t inputGID) {
260  m_config.gid = DoIpGid(inputGID);
261 }
262 
264  m_FurtherActionReq = furtherActionRequired;
265 }
266 
268  m_config.announceCount = Num;
269 }
270 
271 void DoIPServer::setAnnounceInterval(unsigned int Interval) {
272  m_config.announceInterval = Interval;
273 }
274 
275 void DoIPServer::setLoopbackMode(bool useLoopback) {
276  m_config.loopback = useLoopback;
277  if (m_config.loopback) {
278  LOG_DOIP_INFO("Vehicle announcements will use loopback (127.0.0.1)");
279  } else {
280  LOG_DOIP_INFO("Vehicle announcements will use broadcast (255.255.255.255)");
281  }
282 }
283 
284 void DoIPServer::setMulticastGroup(const char *address) const {
285  int loop = 1;
286 
287  // set Option using the same Port for multiple Sockets
288  int setPort = setsockopt(m_udp_sock, SOL_SOCKET, SO_REUSEADDR, &loop, sizeof(loop));
289 
290  if (setPort < 0) {
291  LOG_UDP_ERROR("Setting Port Error");
292  }
293 
294  struct ip_mreq mreq;
295 
296  mreq.imr_multiaddr.s_addr = inet_addr(address);
297  mreq.imr_interface.s_addr = htonl(INADDR_ANY);
298 
299  // set Option to join Multicast Group
300  int setGroup = setsockopt(m_udp_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, reinterpret_cast<const char *>(&mreq), sizeof(mreq));
301 
302  if (setGroup < 0) {
303  LOG_UDP_ERROR("Setting address failed: {}", strerror(errno));
304  }
305 }
306 
307 ssize_t DoIPServer::sendNegativeUdpAck(DoIPNegativeAck ackCode) {
309 
310  return sendUdpResponse(msg);
311 }
312 
313 // new version starts here
314 void DoIPServer::udpListenerThread() {
315  socklen_t client_len = sizeof(m_clientAddress);
316 
317  LOG_UDP_INFO("UDP listener thread started");
318 
319  while (m_running) {
320  ssize_t received = recvfrom(m_udp_sock, m_receiveBuf.data(), sizeof(m_receiveBuf), 0,
321  reinterpret_cast<struct sockaddr *>(&m_clientAddress), &client_len);
322 
323  if (received < 0) {
324  if (errno == EAGAIN /* || errno == EWOULDBLOCK*/) {
325  // Timeout, continue
326  continue;
327  }
328  if (m_running) {
329  perror("recvfrom error");
330  }
331  break;
332  }
333 
334  if (received > 0) {
335  std::scoped_lock lock(m_mutex);
336  char client_ip[INET_ADDRSTRLEN];
337  inet_ntop(AF_INET, &m_clientAddress.sin_addr, client_ip, sizeof(client_ip));
338  m_clientIp = std::string(client_ip);
339  m_clientPort = ntohs(m_clientAddress.sin_port);
340 
341  LOG_UDP_INFO("Received {} bytes from {}:{}", received, m_clientIp, m_clientPort);
342 
343  auto optHeader = DoIPMessage::tryParseHeader(m_receiveBuf.data(), DOIP_HEADER_SIZE);
344  if (!optHeader.has_value()) {
345  auto sentBytes = sendNegativeUdpAck(DoIPNegativeAck::IncorrectPatternFormat);
346  if (sentBytes < 0) {
347  if (errno == EAGAIN /*|| errno == EWOULDBLOCK*/) {
348  usleep(100);
349  continue;
350  }
351  break;
352  }
353  }
354  auto plType = optHeader->first;
355  // auto payloadLength = optHeader->second;
356  LOG_UDP_INFO("RX: {}", fmt::streamed(plType));
357 
358  ssize_t sentBytes = 0;
359  switch (plType) {
361  DoIPMessage msg = message::makeVehicleIdentificationResponse(m_config.vin, m_config.logicalAddress, m_config.eid, m_config.gid);
362  sentBytes = sendUdpResponse(msg);
363  } break;
364 
365  default:
366  LOG_DOIP_ERROR("Invalid payload type 0x{:04X} received (receiveUdpMessage())", static_cast<uint16_t>(plType));
367  sentBytes = sendNegativeUdpAck(DoIPNegativeAck::UnknownPayloadType);
368 
369  } // switch
370 
371  if (sentBytes < 0) {
372  if (errno == EAGAIN /*|| errno == EWOULDBLOCK*/) {
373  usleep(100);
374  continue;
375  }
376  break;
377  }
378  }
379  }
380 
381  LOG_UDP_INFO("UDP listener thread stopped");
382 }
383 
384 void DoIPServer::udpAnnouncementThread() {
385  LOG_DOIP_INFO("Announcement thread started");
386 
387  // Send announcements with configured interval and count
388  for (int i = 0; i < m_config.announceCount && m_running; i++) {
389  sendVehicleAnnouncement();
390  usleep(m_config.announceInterval * 1000);
391  }
392 
393  LOG_DOIP_INFO("Announcement thread stopped");
394 }
395 
396 ssize_t DoIPServer::sendVehicleAnnouncement() {
397  DoIPMessage msg = message::makeVehicleIdentificationResponse(m_config.vin, m_config.logicalAddress, m_config.eid, m_config.gid);
398 
399  struct sockaddr_in dest_addr;
400  memset(&dest_addr, 0, sizeof(dest_addr));
401  dest_addr.sin_family = AF_INET;
402  dest_addr.sin_port = htons(DOIP_UDP_TEST_EQUIPMENT_REQUEST_PORT);
403 
404  const char *dest_ip;
405  if (m_config.loopback) {
406  dest_ip = "127.0.0.1";
407  inet_pton(AF_INET, dest_ip, &dest_addr.sin_addr);
408  } else {
409  dest_ip = "255.255.255.255";
410  dest_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
411 
412  // Enable broadcast
413  int broadcast = 1;
414  setsockopt(m_udp_sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
415  }
416 
417  ssize_t sentBytes = sendto(m_udp_sock, msg.data(), msg.size(), 0,
418  reinterpret_cast<struct sockaddr *>(&dest_addr), sizeof(dest_addr));
419 
420  LOG_DOIP_INFO("TX {}", fmt::streamed(msg));
421  if (sentBytes > 0) {
422  LOG_UDP_INFO("Sent Vehicle Announcement: {} bytes to {}:{}",
423  sentBytes, dest_ip, DOIP_UDP_TEST_EQUIPMENT_REQUEST_PORT);
424  } else {
425  LOG_UDP_ERROR("Failed to send announcement: {}", strerror(errno));
426  }
427  return sentBytes;
428 }
429 
430 ssize_t DoIPServer::sendUdpResponse(DoIPMessage msg) {
431  auto sentBytes = sendto(m_udp_sock, msg.data(), msg.size(), 0,
432  reinterpret_cast<struct sockaddr *>(&m_clientAddress), sizeof(m_clientAddress));
433 
434  if (sentBytes > 0) {
435  LOG_DOIP_INFO("TX {}", fmt::streamed(msg));
436  LOG_UDP_INFO("Sent UDS response: {} bytes to {}:{}",
437  sentBytes, m_clientIp, ntohs(m_clientAddress.sin_port));
438  } else {
439  LOG_DOIP_ERROR("Failed to send message: {}", strerror(errno));
440  }
441  return sentBytes;
442 }
DoIPNegativeAck
#define LOG_TCP_INFO(...)
Definition: Logger.h:142
#define LOG_UDP_ERROR(...)
Definition: Logger.h:136
#define LOG_TCP_ERROR(...)
Definition: Logger.h:144
#define LOG_UDP_INFO(...)
Definition: Logger.h:134
#define LOG_DOIP_WARN(...)
Definition: Logger.h:127
#define LOG_DOIP_ERROR(...)
Definition: Logger.h:128
#define LOG_TCP_WARN(...)
Definition: Logger.h:143
#define LOG_UDP_DEBUG(...)
Definition: Logger.h:133
#define LOG_DOIP_INFO(...)
Definition: Logger.h:126
#define LOG_DOIP_DEBUG(...)
Definition: Logger.h:125
Represents a complete DoIP message with internal ByteArray representation.
Definition: DoIPMessage.h:82
static std::optional< std::pair< DoIPPayloadType, uint32_t > > tryParseHeader(const uint8_t *data, size_t length)
Gets the payload type from raw data.
Definition: DoIPMessage.h:399
size_t size() const
Gets the size for use with legacy APIs.
Definition: DoIPMessage.h:231
const uint8_t * data() const
Gets direct pointer to the message data (for legacy APIs).
Definition: DoIPMessage.h:222
void setLoopbackMode(bool useLoopback)
Enable/disable loopback mode for announcements (no broadcast).
Definition: DoIPServer.cpp:275
bool setupTcpSocket()
Initialize and bind the TCP socket for DoIP.
Definition: DoIPServer.cpp:132
void setAnnounceNum(int Num)
Set the number of vehicle announcements to send.
Definition: DoIPServer.cpp:267
void setAnnounceInterval(unsigned int Interval)
Set the interval between announcements in milliseconds.
Definition: DoIPServer.cpp:271
void setVin(const std::string &VINString)
Set VIN from a 17-character string.
Definition: DoIPServer.cpp:242
void setEid(uint64_t nputEID)
Set EID value.
Definition: DoIPServer.cpp:255
void setGid(uint64_t inputGID)
Set GID value.
Definition: DoIPServer.cpp:259
void closeTcpSocket()
Close the TCP socket if open.
Definition: DoIPServer.cpp:164
void setFurtherActionRequired(DoIPFurtherAction furtherActionRequired)
Set further action requirement status.
Definition: DoIPServer.cpp:263
~DoIPServer()
Destructor.
Definition: DoIPServer.cpp:18
DoIPServer(const ServerConfig &config=DefaultServerConfig)
Construct a DoIP server with the given configuration.
Definition: DoIPServer.cpp:24
bool setDefaultEid()
Sets the EID to a default value based on the MAC address.
Definition: DoIPServer.cpp:230
bool setupUdpSocket()
Initialize and bind the UDP socket for announcements and UDP messages.
Definition: DoIPServer.cpp:168
void setLogicalGatewayAddress(DoIPAddress logicalAddress)
Set the logical DoIP gateway address.
Definition: DoIPServer.cpp:251
void closeUdpSocket()
Close the UDP socket if open.
Definition: DoIPServer.cpp:220
static const GenericFixedId Zero
Static instance containing only zeros.
static constexpr size_t ID_LENGTH
Length of the identifier in bytes.
DoIPMessage makeNegativeAckMessage(DoIPNegativeAck nack)
Creates a generic DoIP negative response (NACK).
Definition: DoIPMessage.h:555
DoIPMessage makeVehicleIdentificationResponse(const DoIpVin &vin, const DoIPAddress &logicalAddress, const DoIpEid &entityType, const DoIpGid &groupId, DoIPFurtherAction furtherAction=DoIPFurtherAction::NoFurtherAction, DoIPSyncStatus syncStatus=DoIPSyncStatus::GidVinSynchronized)
Creates a vehicle identification response message.
Definition: DoIPMessage.h:528
Definition: AnsiColors.h:3
uint16_t DoIPAddress
Represents a 16-bit DoIP address consisting of high and low significant bytes.
Definition: DoIPAddress.h:26
constexpr size_t DOIP_HEADER_SIZE
Size of the DoIP header.
Definition: DoIPMessage.h:57
GenericFixedId< 17, true, '0'> DoIpVin
Vehicle Identification Number (VIN) - 17 bytes according to ISO 3779 Padded with ASCII '0' characters...
bool getFirstMacAddress(MacAddress &mac)
Retrieves the MAC address of the first available network interface.
@ VehicleIdentificationRequest
Vehicle Identification Request Request for vehicle identification.
GenericFixedId< 6, false > DoIpEid
Entity Identifier (EID) - 6 bytes for unique entity identification.
constexpr int DOIP_SERVER_TCP_PORT
Definition: DoIPServer.h:57
std::array< uint8_t, 6 > MacAddress
Type alias for MAC address (6 bytes)
Definition: MacAddress.h:12
GenericFixedId< 6, false > DoIpGid
Group Identifier (GID) - 6 bytes for group identification.
streamed_t< T > streamed(const T &v)
Definition: Logger.h:24
Server configuration structure used to initialize a DoIP server.
Definition: DoIPServer.h:34
unsigned int announceInterval
Definition: DoIPServer.h:52
DoIPAddress logicalAddress
Definition: DoIPServer.h:43