DoIP로 UDS 요청을 보냈는데 이런 로그가 나올 때가 있다.
- TCP 연결은 살아 있다
- Routing Activation도 성공했다
- Diagnostic ACK도 보인다
- 그런데 UDS 응답은
7F xx 13이다
이때 처음에는 TCP stream parser나 DoIP framing 문제처럼 보인다.
하지만 NRC 0x13은 보통 ECU 애플리케이션이 요청 형식이나 길이를 받아들일 수 없다고 말하는 신호다.
오늘 메모는 DoIP에서 NRC 0x13이 반복될 때 길이를 어느 층에서 나눠 봐야 하는지다.
결론부터
DoIP Diagnostic Message에는 길이 기준이 두 개 섞여 있다.
DoIP Generic Header payload length
-> SA + TA + UDS payload 전체 길이
UDS service payload length
-> SID 이후 서비스별 파라미터 길이
이 둘을 같은 값처럼 보면 금방 꼬인다.
DoIP payload length가 맞아도 UDS payload가 서비스 요구 길이와 다르면 0x13이 올 수 있다.
반대로 UDS 요청 자체는 맞아도 DoIP 메시지 경계를 잘못 잡으면 ECU에는 깨진 UDS가 들어간다.
그래서 0x13을 보면 먼저
DoIP framing 문제인지, UDS service format 문제인지를 분리해야 한다.
DoIP length와 UDS length를 섞는 순간
Diagnostic Message는 대략 이런 구조로 본다.
DoIP header
protocol version
inverse version
payload type
payload length
Diagnostic Message payload
source address
target address
UDS payload
여기서 DoIP의 payload length는 UDS 길이만 뜻하지 않는다.
SA와 TA까지 포함한다.
예를 들어 UDS가 3바이트라면 DoIP Diagnostic Message payload는 보통 이렇게 계산된다.
SA 2 bytes
TA 2 bytes
UDS 3 bytes
----------------
payload length = 7 bytes
이걸 UDS 3바이트만 넣거나,
반대로 UDS parser에서 SA/TA까지 서비스 데이터로 넘기면
겉으로는 “패킷은 갔다”인데 ECU는 요청을 이상한 길이로 본다.
흔한 패턴 1: SA/TA를 UDS payload에 섞어 넣는다
테스터 내부 구조가 아래처럼 되어 있으면 실수하기 쉽다.
doip_payload = source + target + uds
app_payload = doip_payload
uds_send(app_payload)
이러면 UDS 레이어에서 첫 바이트를 SID로 읽어야 하는데,
실제로는 source address 일부를 SID처럼 읽게 된다.
증상은 다양하다.
0x11 Service Not Supported처럼 보인다0x13 Incorrect Message Length가 온다- 특정 DID나 RoutineControl에서만 실패한다
핵심은 ECU가 받은 UDS 시작점이 정말 SID인지 확인하는 것이다.
흔한 패턴 2: 서비스별 고정 길이를 대충 맞춘다
UDS 서비스는 SID만 맞는다고 끝나지 않는다.
예를 들어 간단히 보면 이런 차이가 있다.
ReadDataByIdentifier
-> 0x22 + DID(2 bytes) * N
DiagnosticSessionControl
-> 0x10 + sub-function(1 byte)
RoutineControl
-> 0x31 + control type(1 byte) + routine id(2 bytes) + option record
0x22는 DID가 2바이트 단위로 맞아야 한다.
0x31은 option record가 비어도 되는 서비스가 있고, 특정 길이를 요구하는 서비스도 있다.
그래서 “SID는 맞다” 수준으로 보면 부족하다.
ECU 입장에서는 서비스별 파라미터 길이가 맞지 않으면 0x13이 자연스럽다.
흔한 패턴 3: TCP 조각을 UDS 조각으로 오해한다
DoIP는 TCP 위에서 흐른다.
그래서 recv() 한 번이 DoIP 메시지 하나라는 보장이 없다.
이 문제는 DoIP는 TCP stream이다: recv()만 믿고 파싱하면 깨지는 이유에서 따로 정리했듯이,
Generic Header의 payload length 기준으로 메시지 경계를 잡아야 한다.
다만 0x13이 보인다는 것은 보통
ECU가 어떤 UDS 요청을 해석해서 negative response를 보냈다는 뜻이다.
즉 아래 둘을 구분해야 한다.
DoIP parser가 깨짐
-> payload length mismatch, DoIP NACK, 무응답 가능
UDS 형식이 틀림
-> 7F xx 13 같은 UDS negative response 가능
캡처에서 7F xx 13이 명확하다면
무작정 TCP 조각 문제만 볼 게 아니라
ECU에 도착한 UDS payload를 다시 펼쳐 보는 편이 빠르다.
흔한 패턴 4: suppress positive response bit를 길이로 오해한다
서브기능이 있는 서비스에서는 suppress positive response bit가 섞일 수 있다.
예를 들어 0x10 0x03과 0x10 0x83은 길이는 같지만 의미가 다르다.
0x10 0x03
-> Extended Session 요청
0x10 0x83
-> suppress positive response bit가 포함된 요청
길이 문제는 아닌데,
테스터 쪽 로그가 “응답이 없다”로만 찍히면 0x13과 같은 형식 문제처럼 섞여 보일 수 있다.
따라서 서브기능 서비스에서는
길이와 suppress bit를 따로 로그에 남기는 편이 좋다.
확인 순서
나는 0x13이 반복되면 보통 아래 순서로 본다.
- DoIP payload length가
SA + TA + UDS payload로 계산됐는지 확인 - UDS parser에 넘긴 시작 바이트가 실제 SID인지 확인
- 서비스별 파라미터 길이가 규칙에 맞는지 확인
- DID, Routine ID, option record 같은 다중 바이트 필드가 빠지지 않았는지 확인
- suppress positive response bit를 길이 문제와 섞어 해석하지 않는지 확인
여기까지 봐도 안 맞으면 그때 ECU별 서비스 정책을 확인하는 편이 낫다.
처음부터 “ECU가 이상하다”로 가면 오래 걸린다.
로그를 이렇게 남기면 빨라진다
0x13은 응답 코드 하나만 남기면 거의 도움이 안 된다.
나는 보통 아래 항목을 같이 남긴다.
- DoIP payload length 값
- SA와 TA 값
- ECU로 넘긴 UDS payload hex
- SID와 sub-function 또는 DID 목록
- 응답의 원 요청 SID
예를 들면 이런 식이다.
doip.len=7 sa=0x0E00 ta=0x1001 uds=22 F1 90
uds.sid=0x22 did_count=1
response=7F 22 13
이 정도만 있어도
“DoIP 길이가 틀렸는가”와
“UDS 서비스 payload가 틀렸는가”를 빠르게 나눌 수 있다.
빠른 체크리스트
- DoIP
payload length를 UDS 길이만으로 계산하고 있지 않은지 확인 - UDS decoder가 SA/TA를 SID처럼 읽고 있지 않은지 확인
0x13의 원 요청 SID가 어떤 서비스인지 로그에 남기는지 확인- DID나 Routine ID처럼 2바이트 단위 필드가 빠지지 않았는지 확인
- DoIP NACK와 UDS negative response를 같은 에러로 뭉개고 있지 않은지 확인
함께 보면 좋은 글
- DoIP는 TCP stream이다: recv()만 믿고 파싱하면 깨지는 이유
- DoIP에서 UDS를 ISO-TP처럼 자르면 안 된다: Diagnostic Message 경계를 먼저 봐야 한다
- DoIP Negative Acknowledge, UDS 에러랑은 다르다
한 줄 요약
DoIP에서 NRC 0x13이 반복되면 TCP 조각 문제로 단정하지 말고, DoIP payload length와 실제 UDS 서비스 payload 길이를 분리해서 확인하는 편이 빠르다.
추천 키워드
DoIP, UDS, NRC 0x13, Diagnostic Message, payload length, TCP socket
DevBJ | 오늘을살자, Log Today