lwIP로 UDP 수신 코드를 붙였는데 데이터 길이가 이상하게 보일 때가 있다.
- 작은 패킷은 잘 된다
- 큰 패킷만 가끔 잘린다
- 로그에는 길이가 매번 다르게 찍힌다
- 상대는 같은 데이터를 보냈다고 한다
이럴 때 드라이버나 DMA부터 의심하기 쉽지만,
먼저 확인할 곳은 의외로 단순하다.
pbuf->len을 전체 길이처럼 쓰고 있지 않은가
오늘 메모는 pbuf 체인에서 len과 tot_len을 헷갈릴 때 생기는 버그다.
두 필드는 역할이 다르다
lwIP의 pbuf는 한 덩어리일 수도 있고,
여러 버퍼가 체인으로 이어진 형태일 수도 있다.
이때 의미는 이렇게 나뉜다.
len: 현재 pbuf 노드 하나에 들어 있는 길이tot_len: 현재 노드부터 체인 끝까지 합친 전체 길이
즉 첫 노드에서:
p->len = 첫 조각 길이
p->tot_len = 패킷 전체 길이
작은 패킷에서는 둘이 같을 수 있어서
초기 bring-up 때는 문제를 못 느끼기 쉽다.
왜 가끔만 터지나
버그가 늦게 드러나는 이유는 체인이 항상 생기지 않기 때문이다.
- payload가 작으면 pbuf 하나에 다 들어감
- 정렬/헤더 길이가 우연히 맞으면 한 번에 들어감
- 그런데 조금 큰 프레임이나 pool 상황에 따라 둘 이상으로 나뉨
그래서 테스트 초반에는 잘 되다가,
특정 길이 이상에서만 갑자기 잘리는 증상이 나온다.
현장에서 자주 보는 실수
1) 복사 길이에 len을 그대로 쓴다
가장 흔한 패턴은 이거다.
memcpy(app_buf, p->payload, p->len);
app_parse(app_buf, p->len);
작은 패킷이면 통과한다.
하지만 체인이 생기면 첫 조각만 복사하고 끝난다.
그 결과는 대개 이런 식이다.
- 헤더만 읽히고 payload 뒤가 잘림
- 애플리케이션 CRC가 틀린 것처럼 보임
- 가변 길이 프로토콜에서 끝부분 필드가 깨짐
2) p->payload가 연속 메모리라고 가정한다
체인 pbuf에서는 전체 패킷이 한 연속 버퍼가 아닐 수 있다.
즉 이런 가정이 위험하다.
p->payload부터 p->tot_len 바이트가 한 번에 이어져 있다
실제로는 다음 조각이 p->next에 있을 수 있다.
그래서 전체 payload가 필요하면:
- pbuf 체인을 순회해 복사하거나
- lwIP helper를 써서 선형화된 버퍼로 옮기거나
- 애플리케이션 파서를 체인 친화적으로 짜야 한다
3) 로그에는 tot_len, 실제 파싱은 len
이것도 자주 헷갈린다.
log: rx len = p->tot_len
copy: memcpy(..., p->payload, p->len)
그러면 로그상으로는 300바이트를 받았는데
실제 앱 버퍼에는 128바이트만 들어간 식이 된다.
디버깅할 때 “중간에 누가 데이터를 잃어버렸나”로 잘못 흘러가기 쉽다.
간단한 예로 보면
예를 들어 UDP payload 300바이트가
아래처럼 둘로 나뉘어 들어왔다고 하자.
첫 pbuf: len=128, tot_len=300
둘째 pbuf: len=172, tot_len=172
여기서 첫 노드만 보고 처리하면
앱은 128바이트짜리 패킷을 받은 것처럼 행동한다.
반면 전체 길이 검증은 300으로 찍힐 수 있어서
로그와 동작이 서로 안 맞는다.
구현 쪽에서 무난한 패턴
전체 패킷이 연속 버퍼로 꼭 필요하다면
먼저 전체 길이를 tot_len으로 확보하고,
체인을 끝까지 순회하며 복사하는 편이 안전하다.
needed = p->tot_len
for each node in chain:
copy node->len bytes
반대로 헤더 몇 바이트만 보면 되는 프로토콜이면
굳이 전체 복사를 하지 말고,
체인을 순회하면서 필요한 필드만 읽는 쪽이 메모리 낭비가 적다.
핵심은 단순하다.
- 길이 판단은
tot_len - 현재 조각 크기는
len - 연속 메모리 가정은 금물
로그를 이렇게 남기면 빨라진다
이 문제는 길이 하나만 찍으면 잘 안 보인다.
나는 보통 아래 항목을 같이 본다.
- 첫 pbuf의
len - 첫 pbuf의
tot_len - 체인 노드 개수
- 실제 앱 버퍼에 복사한 길이
- 파서가 소비한 길이
이 다섯 개가 있으면
“네트워크가 잘랐다”와
“앱이 첫 조각만 읽었다”를 금방 구분할 수 있다.
빠른 체크리스트
- 수신 코드가
p->len을 전체 패킷 길이처럼 쓰는지 확인 - 큰 UDP payload에서만 문제 재현되는지 확인
p->payload를 연속 메모리로 가정하는 코드가 있는지 확인- 체인 순회 없이 첫 노드만 memcpy 하는 경로가 있는지 확인
- 로그 길이와 실제 복사 길이를 분리해서 기록하는지 확인
한 줄 요약
lwIP에서 UDP 길이가 가끔 잘못 읽히면 드라이버보다 먼저 pbuf 체인 처리 코드를 보고, 전체 길이는 tot_len, 현재 조각 길이는 len으로 분리해서 다루는 게 안전하다.
추천 키워드
lwIP, pbuf, UDP, tot_len, RX buffer, embedded network
DevBJ | 오늘을살자, Log Today