실습 목표#
- DoIP 예제를 실행하고 라우팅 활성화부터 진단 응답까지의 흐름을 확인한다.
- 진단 요청과 응답 프레임을 바이트 단위로 나눠 읽는다.
- 서버 코드의 빈칸을 채워 라우팅 활성화 허용과 거부, VIN을 직접 구현한다.
실습. DoIP 예제 코드 실행#
실습 환경 세팅#
다음 스크립트를 라즈베리파이 터미널과 WSL에 각각 입력하여 DoIP 실습 환경을 세팅한다.
make
./ecu_server
make
./tester_client
WireShark 캡처 분석#
WireShark에서 필터를 ‘tcp.port==13400'으로 설정한다.

같은 DoIP 프레임이라도 표시되는 이름이 다른 것을 확인할 수 있다. 이는 WireShark에서 DoIP 안에 UDS가 들어 있으면 구체적으로 UDS라고 표시해주기 때문이다.
Routing Activation Request 분석#
다음 표를 참고하여 Routing activation 요청을 분석한다.
02 FD 00 05 00 00 00 07 0E 00 00 00 00 00 00
| 오프셋 | 바이트 | 필드 | 의미 |
|---|---|---|---|
| 0 ~ 1 | 02 FD |
Version / Inverse | 0x02와 그 반전 값, 오류 검출용 |
| 2 ~ 3 | 00 05 |
Payload Type | 라우팅 활성화 요청 |
| 4 ~ 7 | 00 00 00 07 |
Payload Length | 7 bytes |
| 8 ~ 9 | 0E 00 |
Source Address | 테스터의 논리 주소 |
| 10 | 00 |
Activation Type | 기본 활성화 |
| 11 ~ 14 | 00 00 00 00 |
Reserved | - |
Routing Activation Response 분석#
다음 표를 참고하여 Routing activation 응답을 분석한다.
02 FD 00 06 00 00 00 09 0E 00 00 01 10 00 00 00 00
| 오프셋 | 바이트 | 필드 | 의미 |
|---|---|---|---|
| 0 ~ 1 | 02 FD |
Version / Inverse | 0x02와 그 반전 값, 오류 검출용 |
| 2 ~ 3 | 00 06 |
Payload Type | 라우팅 활성화 응답 |
| 4 ~ 7 | 00 00 00 09 |
Payload Length | 9 bytes |
| 8 ~ 9 | 0E 00 |
Tester Address | 요청한 테스터 주소를 그대로 돌려줌 |
| 10 ~ 11 | 00 01 |
ECU Address | 응답하는 ECU의 논리 주소 |
| 12 | 10 |
Response Code | 0x10, 활성화 성공 |
| 13 ~ 16 | 00 00 00 00 |
Reserved | - |
진단 요청 프레임 분석#
다음 표를 참고하여 진단 요청 프레임을 분석한다.
02 FD 80 01 00 00 00 07 0E 00 00 01 22 F1 90
| 오프셋 | 바이트 | 필드 | 의미 |
|---|---|---|---|
| 0 ~ 1 | 02 FD |
Version / Inverse | 0x02와 그 반전 값, 오류 검출용 |
| 2 ~ 3 | 80 01 |
Payload Type | 라우팅 활성화 응답 |
| 4 ~ 7 | 00 00 00 07 |
Payload Length | 7 Bytes, 주소 4 + UDS 3 |
| 8 ~ 9 | 0E 00 |
Source Address | 테스터의 논리 주소 |
| 10 ~ 11 | 00 01 |
Target Address | ECU의 논리 주소 |
| 12 ~ 14 | 22 F1 90 |
UDS | 식별자 F190 읽기 요청 |
진단 응답 프레임 분석#
다음 표를 참고하여 진단 응답 프레임을 분석한다.
02 FD 80 01 00 00 00 18 00 01 0E 00 62 F1 90 54
45 53 54 56 49 4E 30 31 32 33 34 35 36 37 38 39
| 오프셋 | 바이트 | 필드 | 의미 |
|---|---|---|---|
| 0 ~ 1 | 02 FD |
Version / Inverse | 0x02와 그 반전 값, 오류 검출용 |
| 2 ~ 3 | 80 01 |
Payload Type | 진단 메시지 |
| 4 ~ 7 | 00 00 00 18 |
Payload Length | 24 Bytes, 주소 4 + UDS 3 + VIN 17 |
| 8 ~ 9 | 00 01 |
Source Address | ECU의 논리 주소 |
| 10 ~ 11 | 0E 00 |
Target Address | 테스터의 논리 주소 |
| 12 ~ 14 | 62 F1 90 |
UDS | 긍정 응답, 요청 22에 0x40을 더한 값 |
| 15 ~ 31 | 54 45 53 54 ⋯ 38 39 |
VIN | TESTVIN0123456789 |
테스터 주소 대역 분석#
Routing Activation Request에는 테스터의 논리 주소가 들어 있다. 이 때 ECU는 그 주소가 외부 진단기 대역인지 보고 허용할지 거부할지 정한다.
다음 표를 참고하여 주소에 따른 ECU의 허용/거부 여부를 진단할 수 있다.
| 대역 | 용도 | 허용/거부 여부 |
|---|---|---|
0x0001 ~ 0x0DFF |
ECU 등 차량 내부 장치 | 거부 |
0x0E00 ~ 0x0FFF |
외부 진단기 전용 | 허용 |
0x1000 ~ 0x7FFF |
제조사가 정하는 범위 | 제조사에 따라 상이 |
0x8000 ~ 0xCFFF |
표준이 예약해 둔 범위 | 거부 |
ECU가 해당 주소를 거부하는 사유는 여러 가지이다. 이번 실습에서는 성공(0x10)과 주소 거부(0x00) 두 가지만 다룬다.
다음 표를 참고하여 거부 사유를 확인한다.
| 코드 | 의미 |
|---|---|
0x00 |
알 수 없는 소스 주소 · 유효 대역 밖 |
0x01 |
이 소스 주소가 이미 다른 소켓에서 활성화됨 |
0x02 |
소스 주소가 이미 다른 곳에 등록됨 |
0x03 |
소켓 자원 부족 · 동시 연결 초과 |
0x04 |
인증 없이 활성화를 시도함 |
0x05 |
차량이 활성화를 거부함 |
0x06 |
지원하지 않는 활성화 타입 |
0x07 |
보안 연결(TLS)이 필요함 |
0x10 |
성공 · 활성화됨 |
0x11 |
성공했으나 추가 확인이 필요함 |
과제 1. 라우팅 활성화 허용과 거부#
방금 실습한 스크립트는 주소를 보지 않고 무조건 활성화시키는 단순 버전이다. 이번에는 ECU가 주소를 보고 허용/거부를 판단할 수 있게 테스터 주소를 검사하는 세 자리를 채운다.
uint16_t tester = (buf[8] << 8) | buf[9];
uint8_t code;
if ( /* 빈칸 1a */ ) {
/* 빈칸 1b */
printf("routing activation OK (tester=0x%04X)\n", tester);
} else {
/* 빈칸 1c */
printf("routing activation REJECTED (tester=0x%04X)\n", tester);
}
make
./ecu_server_blank
라우팅 활성화 허용과 거부#
테스터 주소가 외부 진단기 대역 안이라면 성공 코드 0x10, 밖이라면 거부 코드 0x00이 표시되도록 설정한다.
if (tester >= 0x0E00 && tester <= 0x0FFF) {
code = 0x10; activated = true;
} else {
code = 0x00;
}
./tester_client 0x0E00
./tester_client 0x8000
관찰 포인트
0x0E00은 활성화 성공 뒤 VIN 응답을, 0x8000은 거부 코드(0x00)를 받고 멈추면 정답이다.
과제 2. VIN 직접 만들기#
VIN은 차 한 대를 식별하는 17자리 코드로, 자리마다 뜻이 정해져 있다. 실습 서버는 임의의 값을 사용하지만 실제로는 아래 표의 규칙을 따른다.
| 자리 | 의미 | 값의 예 |
|---|---|---|
| 1 | 제조 국가 | K 한국 · J 일본 · W 독일 |
| 2 | 제조사 | M 현대 · L 기아 · B BMW · D 벤츠 |
| 3 | 차량 형태 | H 승용 · J 승합 · F 화물 |
| 4 ~ 6 | 차종과 세부 차종, 차체 형상 | 모델별로 정해진 코드 |
| 7 | 안전장치 | 1 없음 · 4 에어백 |
| 8 | 배기량 | A 1800cc · D 3300cc |
| 9 | 보안 코드 | P 좌핸들 · R 우핸들 |
| 10 | 제조 연도 | M 2021 · P 2023 · T 2026 |
| 11 | 생산 공장 | A 아산 · C 전주 · U 울산 |
| 12 ~ 17 | 생산 번호 | 여섯 자리 일련번호 |
이제 위의 표를 보고 자기만의 17자리를 만들어 서버 코드에 넣어본다. 한 글자라도 모자라거나 넘으면 Payload Length가 달라지기 때문에 반드시 17글자여야 한다. "_"안에 입력하여 실행해보자.
const char* vin = "_________________";
make
./ecu_server_blank
관찰 포인트
테스터 출력과 캡처의 ASCII 창에서 바뀐 값이 보이면 성공이다. Length는 17글자라면 0x18 그대로 출력된다.
VIN 예시#
현대 승용차, 좌핸들, 2026년 제조, 울산 공장, 42번째 생산분으로 구성한 예는 다음과 같다.
const char* vin = "KMHMA14TPTU000042";
관찰 포인트
응답의 hex의 ASCII 부분이 바뀌고 길이값이 그대로인지 확인해 본다.
긍정 응답과 부정 응답#
서버가 요청을 처리할 수 있으면 긍정, 못 하면 부정으로 응답한다. 부정 응답은 7F로 시작하여 요청 SID와 거부 사유가 붙는다.
아래 스크립트를 입력하여 응답을 확인해본다.
./ecu_server_blank
./tester_client 0x0E00
./tester_client 0x0E00 11 01 00
02 FD 80 01 00 00 00 07 00 01 0E 00 7F 11 11