Skip to content
This repository was archived by the owner on Dec 17, 2025. It is now read-only.

Commit db2c9aa

Browse files
MagolvesOliver Wieland (HC/XAG1)
andauthored
Feature/refactor doip server (#10)
* feat: Add server config, introduce daemonize * WIP: Track UDP reception problem on server * fix: Split up rx/tx socket (WIP - still not work) * doc: Add generated UDP example * WIP: Migration to new code * fix: UDP connection/discover works now * fix: Return explicit return code * style: Reorder includes; replace printf with LOG_... * chore: Remove dup test; add enable_testing * refactor: Update DoIPServer to use structured types and improve method names * refactor: Update Doxyfile configuration for improved documentation generation * style: Add comments, format code * WIP: Code cleanup * chore: revisit clang-tidy rules * refactor: Introduce sendUdpResponse fix: Make setMulticastGroup const * feat: Introduce DOIP_MAXIMUM_MTU * refactor: Adjust member names * feat: Add accessors for VIn, EID, GID and logical address * feat: Add stream operator * - refcator: Cleanup client code - refactor: Rename identifier types --------- Co-authored-by: Oliver Wieland (HC/XAG1) <oliver.wieland@de.bosch.com>
1 parent 1cc3fc2 commit db2c9aa

26 files changed

Lines changed: 1882 additions & 656 deletions

.clang-tidy

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
11
---
2-
Checks: '*,-llvmlibc-*,-fuchsia-*,-google-readability-todo,-readability-else-after-return,-llvm-header-guard,-llvm-namespace-comment,-modernize-use-trailing-return-type,-altera-struct-pack-align,-google-explicit-constructor,-hicpp-explicit-conversions,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-hicpp-signed-bitwise,-readability-identifier-length,-bugprone-reserved-identifier,-cert-dcl37-c,-cert-dcl51-cpp,-google-runtime-int,-misc-include-cleaner,-misc-non-private-member-variables-in-classes,-google-build-using-namespace,-readability-convert-member-functions-to-static,-llvm-include-order,-misc-const-correctness,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-modernize-use-nodiscard'
2+
Checks: |
3+
-bugprone-*
4+
-performance-*
5+
-readability-braces-around-statements
6+
-readability-inconsistent-declaration-parameter-name
7+
-readability-implicit-bool-conversion
8+
-readability-redundant-declaration
9+
-readability-redundant-member-init
10+
-readability-static-definition-in-anonymous-namespace
11+
-readability-uppercase-literal-suffix
12+
-cppcoreguidelines-avoid-goto
13+
-cppcoreguidelines-avoid-magic-numbers
14+
-cppcoreguidelines-no-malloc
15+
-cppcoreguidelines-owning-memory
16+
-cppcoreguidelines-slicing
17+
-modernize-use-override
18+
-modernize-avoid-c-arrays
19+
-modernize-loop-convert
20+
-modernize-redundant-void-arg
21+
-modernize-use-auto
22+
-modernize-use-equals-default
23+
-modernize-use-equals-delete
24+
-modernize-use-nullptr
25+
-misc-definitions-in-headers
26+
-misc-misplaced-const
27+
-misc-no-recursion
28+
-misc-static-assert
329
WarningsAsErrors: ''
430
HeaderFilterRegex: '.*'
531
FormatStyle: none

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ endif()
5858

5959
# Configuration options
6060
set(DOIP_ALIVE_CHECK_RETRIES "1" CACHE STRING "Number of retries for DoIP alive check messages")
61+
set(DOIP_MAXIMUM_MTU "4095" CACHE STRING "Maximum Transmission Unit (MTU) size for DoIP messages")
6162

6263
# Validate numeric options
63-
foreach(VAR DOIP_ALIVE_CHECK_RETRIES)
64+
foreach(VAR DOIP_ALIVE_CHECK_RETRIES DOIP_MAXIMUM_MTU)
6465
if(NOT ${VAR} MATCHES "^[0-9]+$")
6566
message(FATAL_ERROR "${VAR} must be a positive integer")
6667
endif()

Doxyfile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ INLINE_GROUPED_CLASSES = NO
4949
INLINE_SIMPLE_STRUCTS = NO
5050
TYPEDEF_HIDES_STRUCT = NO
5151
LOOKUP_CACHE_SIZE = 0
52-
NUM_PROC_THREADS = 1
52+
NUM_PROC_THREADS = 4
5353

5454
#---------------------------------------------------------------------------
5555
# Build related configuration options
@@ -60,7 +60,7 @@ EXTRACT_PRIV_VIRTUAL = NO
6060
EXTRACT_PACKAGE = NO
6161
EXTRACT_STATIC = YES
6262
EXTRACT_LOCAL_CLASSES = YES
63-
EXTRACT_LOCAL_METHODS = NO
63+
EXTRACT_LOCAL_METHODS = YES
6464
EXTRACT_ANON_NSPACES = NO
6565
RESOLVE_UNNAMED_PARAMS = YES
6666
HIDE_UNDOC_MEMBERS = NO
@@ -103,7 +103,7 @@ WARNINGS = YES
103103
WARN_IF_UNDOCUMENTED = YES
104104
WARN_IF_DOC_ERROR = YES
105105
WARN_IF_INCOMPLETE_DOC = YES
106-
WARN_NO_PARAMDOC = NO
106+
WARN_NO_PARAMDOC = YES
107107
WARN_AS_ERROR = NO
108108
WARN_FORMAT = "$file:$line: $text"
109109
WARN_LINE_FORMAT = "at line $line of file $file"
@@ -153,7 +153,7 @@ USE_MDFILE_AS_MAINPAGE = README.md
153153
#---------------------------------------------------------------------------
154154
SOURCE_BROWSER = YES
155155
INLINE_SOURCES = NO
156-
STRIP_CODE_COMMENTS = YES
156+
STRIP_CODE_COMMENTS = NO
157157
REFERENCED_BY_RELATION = YES
158158
REFERENCES_RELATION = YES
159159
REFERENCES_LINK_SOURCE = YES

doc/Fix-UDP-reception.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
From your **tcpdump output**, the **8-byte UDP packet** from the client (port 50290) to the server (port 13400) **is arriving** at the loopback interface:
2+
3+
```
4+
17:31:49.091339 lo In IP localhost.50290 > localhost.13400: UDP, length 8
5+
0x0020: 04fb 0001 0000 0000
6+
```
7+
This is a **DoIP Vehicle Identification Request** (type `0x0001`), and it is **reaching the kernel**.
8+
9+
---
10+
11+
### **Root Cause Analysis**
12+
Since the packet is visible in `tcpdump` but **not logged by your server**, the issue is **almost certainly in your server code**. Here’s what’s happening:
13+
14+
#### **1. The Server Socket is Not Receiving the Packet**
15+
- The server socket is bound to port 13400, but **something is preventing `recvfrom` from returning the packet**.
16+
- Possible reasons:
17+
- The socket is **not in the expected state** (e.g., closed, re-bound, or filtered).
18+
- The socket is **not the same as the one bound to port 13400** (e.g., `m_udp_sock` was overwritten or closed).
19+
- The socket is **blocked by a firewall or SELinux** (unlikely for loopback, but possible).
20+
- The socket is **not actually bound to port 13400** (e.g., `bind` failed silently).
21+
22+
#### **2. The Server is Stuck in a Timeout Loop**
23+
- Your logs show `recvfrom returned: -1 (errno: 11)` repeatedly.
24+
- This means `recvfrom` is **timing out** (`EAGAIN`), but the packet is **not being delivered to userspace**.
25+
- This suggests the socket is **not properly bound** or is **not the same socket** as the one receiving the packet.
26+
27+
---
28+
29+
### **Debugging Steps**
30+
#### **1. Verify the Socket is Bound Correctly**
31+
Add this **right after `bind()`** in `setupUdpSocket()`:
32+
```cpp
33+
// After bind()
34+
sockaddr_in bound_addr;
35+
socklen_t bound_addr_len = sizeof(bound_addr);
36+
getsockname(m_udp_sock, (sockaddr*)&bound_addr, &bound_addr_len);
37+
LOG_UDP_DEBUG(
38+
"Socket {} bound to {}:{}",
39+
m_udp_sock,
40+
inet_ntoa(bound_addr.sin_addr),
41+
ntohs(bound_addr.sin_port)
42+
);
43+
```
44+
- If this logs `0.0.0.0:13400` or `127.0.0.1:13400`, the socket is bound correctly.
45+
- If not, `bind()` failed or was not called.
46+
47+
#### **2. Check for Socket Descriptor Leaks or Overwrites**
48+
- Log `m_udp_sock` in `receiveUdpMessage()`:
49+
```cpp
50+
LOG_UDP_DEBUG("Using socket fd: {}", m_udp_sock);
51+
```
52+
- If the value changes between `setupUdpSocket()` and `receiveUdpMessage()`, something is overwriting it.
53+
54+
#### **3. Test with a Minimal UDP Server**
55+
Replace your `receiveUdpMessage()` with this **minimal test**:
56+
```cpp
57+
ssize_t DoIPServer::receiveUdpMessage() {
58+
char buf[1024];
59+
sockaddr_in client_addr;
60+
socklen_t client_len = sizeof(client_addr);
61+
ssize_t read_bytes = recvfrom(
62+
m_udp_sock, buf, sizeof(buf), 0,
63+
(sockaddr*)&client_addr, &client_len
64+
);
65+
LOG_UDP_DEBUG(
66+
"recvfrom returned: {} (errno: {})",
67+
read_bytes, errno
68+
);
69+
if (read_bytes > 0) {
70+
LOG_UDP_INFO(
71+
"RX {} bytes from {}:{}",
72+
read_bytes,
73+
inet_ntoa(client_addr.sin_addr),
74+
ntohs(client_addr.sin_port)
75+
);
76+
}
77+
return read_bytes;
78+
}
79+
```
80+
- If this works, the issue is in your buffer or processing logic.
81+
- If it still times out, the socket is **not receiving packets**.
82+
83+
#### **4. Check for Firewall/SELinux**
84+
Even for loopback, run:
85+
```bash
86+
sudo iptables -L -n -v # Check firewall rules
87+
sudo setenforce 0 # Disable SELinux temporarily
88+
```
89+
Retest. If it works, adjust your firewall/SELinux rules.
90+
91+
#### **5. Use `strace` to Confirm `recvfrom` Behavior**
92+
Run the server with:
93+
```bash
94+
strace -e trace=recvfrom ./your_server
95+
```
96+
- If `recvfrom` is **never called**, your loop is broken.
97+
- If it returns `-1 EAGAIN` repeatedly, the socket is **not receiving packets**.
98+
99+
#### **6. Check for Multiple Sockets**
100+
- If your code creates **multiple UDP sockets**, ensure the correct one is used for `recvfrom`.
101+
- Log all socket creation and `bind()` calls.
102+
103+
---
104+
105+
### **Most Likely Fixes**
106+
1. **The socket is not bound to port 13400.**
107+
- Confirm `bind()` succeeds and logs the correct port.
108+
2. **`m_udp_sock` is overwritten or closed.**
109+
- Log its value in `setupUdpSocket()` and `receiveUdpMessage()`.
110+
3. **The socket is non-blocking and the timeout is too short.**
111+
- Try removing `SO_RCVTIMEO` temporarily to see if packets arrive.
112+
113+
---
114+
115+
### **Summary**
116+
- The packet **is arriving at the kernel** (confirmed by `tcpdump`).
117+
- The server **is not receiving it**, likely due to:
118+
- Socket not bound correctly.
119+
- Wrong socket file descriptor used in `recvfrom`.
120+
- Firewall/SELinux blocking delivery to userspace.
121+
122+
**Next step:**
123+
Add the debug logs for `m_udp_sock` and `getsockname()` as shown above. If the socket is correct, test with the minimal `recvfrom` code. If it still fails, the issue is **outside your code** (firewall, SELinux, or kernel networking stack).

doc/udp/Makefile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
CC = gcc
2+
CFLAGS = -Wall -Wextra -pthread -g
3+
LDFLAGS = -pthread
4+
5+
all: doip_server doip_client
6+
7+
doip_server: doip_server.c
8+
$(CC) $(CFLAGS) -o doip_server doip_server.c $(LDFLAGS)
9+
10+
doip_client: doip_client.c
11+
$(CC) $(CFLAGS) -o doip_client doip_client.c $(LDFLAGS)
12+
13+
clean:
14+
rm -f doip_server doip_client
15+
16+
.PHONY: all clean

doc/udp/README.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Minimal DoIP UDP Discovery Implementation
2+
3+
This is a minimal implementation of the DoIP (ISO 13400) UDP discovery mechanism with both server and client.
4+
5+
## Features
6+
7+
- **Server**: Sends periodic Vehicle Announcements and responds to Vehicle Identification Requests
8+
- **Client**: Listens for Vehicle Announcements, stores server IP, and sends Vehicle Identification Request
9+
- **Loopback mode**: For testing client and server on the same host (avoids multicast issues)
10+
11+
## Building
12+
13+
```bash
14+
make
15+
```
16+
17+
This will create two executables:
18+
- `doip_server` - The DoIP server
19+
- `doip_client` - The DoIP client
20+
21+
## Usage
22+
23+
### Running in Loopback Mode (Recommended for Testing)
24+
25+
**Terminal 1 - Start the server:**
26+
```bash
27+
./doip_server --loopback
28+
```
29+
30+
**Terminal 2 - Start the client:**
31+
```bash
32+
./doip_client --loopback
33+
```
34+
35+
### Running in Broadcast Mode (For Network Testing)
36+
37+
**Terminal 1 - Start the server:**
38+
```bash
39+
./doip_server
40+
```
41+
42+
**Terminal 2 - Start the client:**
43+
```bash
44+
./doip_client
45+
```
46+
47+
## How It Works
48+
49+
### Server (Port 13400)
50+
1. Creates two UDP sockets (both bound to port 13400 with SO_REUSEADDR):
51+
- Receive socket: Listens for incoming Vehicle Identification Requests
52+
- Send socket: Sends Vehicle Announcements and responses
53+
2. Starts two threads:
54+
- Listener thread: Continuously listens for requests
55+
- Announcement thread: Sends 5 Vehicle Announcements (every 2 seconds) to port 13401
56+
3. When a Vehicle Identification Request is received:
57+
- Parses the request
58+
- Sends a Vehicle Identification Response back to the client
59+
60+
### Client (Port 13401)
61+
1. Creates two UDP sockets:
62+
- Request socket: Sends Vehicle Identification Requests (unbound, OS assigns port)
63+
- Announcement socket: Bound to port 13401 to receive Vehicle Announcements
64+
2. Workflow:
65+
- Listens for Vehicle Announcement from server
66+
- Extracts vehicle information (VIN, Logical Address, EID, GID, IP address)
67+
- Sends Vehicle Identification Request to the discovered server IP on port 13400
68+
- Waits for and displays the response
69+
70+
## Ports
71+
72+
- **13400**: DoIP UDP Discovery Port (server listens here, client sends requests here)
73+
- **13401**: DoIP Test Equipment Port (client listens here for announcements)
74+
75+
## Protocol Details
76+
77+
### DoIP Header (8 bytes)
78+
- Protocol Version: 0x04
79+
- Inverse Protocol Version: 0xFB
80+
- Payload Type: 2 bytes
81+
- Payload Length: 4 bytes
82+
83+
### Payload Types
84+
- `0x0001`: Vehicle Identification Request (no payload)
85+
- `0x0004`: Vehicle Identification Response (33 bytes minimum)
86+
87+
### Vehicle Identification Response Payload
88+
- VIN: 17 bytes (ASCII)
89+
- Logical Address: 2 bytes
90+
- EID: 6 bytes
91+
- GID: 6 bytes
92+
- Further Action Required: 1 byte
93+
- VIN/GID sync status: 1 byte
94+
95+
## Key Design Decisions
96+
97+
1. **Two sockets on server**: Avoids race conditions between sending announcements and receiving requests
98+
2. **SO_REUSEADDR**: Allows both server sockets to bind to port 13400
99+
3. **Loopback mode**: Uses unicast (127.0.0.1) instead of broadcast for local testing
100+
4. **Non-blocking receive**: Server uses timeout to allow clean shutdown
101+
5. **Separate announcement socket on client**: Dedicated socket for receiving broadcasts on port 13401
102+
103+
## Troubleshooting
104+
105+
If the client doesn't receive announcements:
106+
- Check firewall rules
107+
- Verify both programs are running
108+
- Use `tcpdump` to monitor UDP traffic:
109+
```bash
110+
sudo tcpdump -i any udp port 13400 or udp port 13401 -X
111+
```
112+
113+
## Cleanup
114+
115+
```bash
116+
make clean
117+
```

0 commit comments

Comments
 (0)