lwIP에서 UDP는 단순해 보인다.
연결도 없고, ACK도 없고, 그냥 목적지로 보내면 될 것처럼 느껴진다.
그런데 bring-up 중에는 이런 로그가 자주 나온다.
udp_sendto()가ERR_RTE를 반환한다- socket API에서는
sendto()가 실패한다 - ARP도 안 나간다
- payload를 줄여도 똑같다
이때 UDP 데이터나 체크섬부터 보면 오래 헤맨다.
ERR_RTE는 이름 그대로 보낼 경로를 못 찾았다는 쪽에 가깝다.
오늘 메모는 lwIP에서 UDP send가 ERR_RTE로 실패할 때 netif와 route를 어디서 확인해야 하는지다.
결론부터
UDP 송신 전에 lwIP는 먼저 이런 질문을 한다.
이 목적지 IP로 보낼 netif가 있는가?
그 netif가 up 상태인가?
IP 주소와 netmask/gateway가 말이 되는가?
link 상태는 송신 가능한가?
여기서 걸리면 payload는 아직 중요한 단계가 아니다.
ARP도 안 나갈 수 있다.
즉 ERR_RTE를 보면
패킷 내용보다 netif 선택과 상태 전환을 먼저 봐야 한다.
흔한 패턴 1: DHCP 완료 전에 송신한다
부팅 직후 앱 태스크가 먼저 떠서 UDP를 보내는 구조가 많다.
netif_add()
netif_set_up()
dhcp_start()
app task starts
udp_sendto()
이 흐름에서 아직 DHCP 주소를 받지 못했다면
netif는 존재하지만 실제 송신 경로로 쓰기 애매한 상태일 수 있다.
특히 로그에 아래가 같이 보이면 의심해 볼 만하다.
- IP 주소가
0.0.0.0 - gateway가 아직 없다
- DHCP bound 로그보다 UDP send 로그가 먼저 나온다
이 경우 UDP 코드가 아니라
앱 시작 조건을 DHCP bound 이후로 늦추는 것이 먼저다.
관련 흐름은 lwIP에서 링크는 붙었는데 DHCP가 안 돈다: netif_set_up() / netif_set_link_up() 순서를 같이 보자에서도 같이 볼 수 있다.
흔한 패턴 2: default netif가 없다
목적지 IP가 특정 netif에 명확히 매칭되지 않으면
lwIP는 default netif를 사용해야 할 수 있다.
그런데 초기화 코드에서 netif_set_default()를 빼먹으면
송신 경로를 못 잡는 경우가 나온다.
netif_add(&g_netif, &ipaddr, &netmask, &gw, NULL, ethernetif_init, tcpip_input);
netif_set_up(&g_netif);
netif_set_link_up(&g_netif);
/* 빠지기 쉬운 부분 */
netif_set_default(&g_netif);
보드에 네트워크 인터페이스가 하나뿐이어도
default netif 설정은 명시해 두는 편이 디버깅이 쉽다.
특히 나중에 loopback, PPP, Wi-Fi 같은 다른 interface가 추가되면
“하나뿐이라 괜찮다”는 가정이 바로 깨진다.
흔한 패턴 3: netif up과 link up을 같은 의미로 본다
lwIP에서 netif의 administratively up 상태와
물리 링크 상태는 같은 말이 아니다.
netif_set_up()
-> 이 interface를 사용할 준비가 됐다는 소프트웨어 상태
netif_set_link_up()
-> PHY/link가 살아 있다는 하드웨어 쪽 상태
프로젝트마다 옵션과 드라이버 구현 차이는 있지만,
두 상태를 섞으면 송신 판단이 애매해진다.
자주 보는 실수는 이렇다.
PHY link down
-> netif는 여전히 up
-> 앱은 UDP 송신 시도
-> route 또는 link 상태 문제로 실패
반대로 link는 올라왔는데 netif_set_up()이 늦게 불리면
송신 경로가 아직 닫혀 있을 수 있다.
흔한 패턴 4: 목적지 IP가 local network 밖인데 gateway가 없다
같은 subnet으로 보내는 UDP와
다른 subnet으로 보내는 UDP는 route 판단이 다르다.
예를 들어 보드 IP가 아래와 같다고 하자.
ip = 192.168.0.10
netmask = 255.255.255.0
gateway = 0.0.0.0
이 상태에서 192.168.0.20으로 보내는 건 local network 안이다.
하지만 10.0.0.20으로 보내려면 gateway가 필요하다.
gateway가 없으면 lwIP는 어디로 내보내야 할지 모른다.
이때도 payload 크기나 UDP port는 핵심이 아니다.
ERR_RTE와 ARP 실패를 구분해야 한다
ERR_RTE와 ARP 실패는 가까워 보이지만 같은 문제는 아니다.
ERR_RTE
-> 보낼 netif/route를 고르지 못함
ARP 실패
-> 보낼 netif는 골랐지만 next-hop MAC을 못 찾음
그래서 캡처에서 ARP request 자체가 안 보이면
ARP 테이블보다 route와 netif 상태를 먼저 봐야 한다.
반대로 ARP request가 나가는데 reply가 없다면
그때는 케이블, 스위치, 상대 IP, ARP cache 쪽으로 내려가면 된다.
링크 복구 뒤 UDP만 한동안 죽는 흐름은
lwIP에서 링크 복구 후 UDP가 한동안 죽어 보인다: ARP 캐시와 netif 상태를 같이 보자와 이어진다.
구현 쪽에서 무난한 패턴
앱 송신 조건을 한 곳에 모아 두면 디버깅이 쉬워진다.
can_send_udp():
netif exists
netif is up
link is up
ip address is valid
default netif is set or destination matches this netif
gateway is valid when destination is off-subnet
그리고 UDP send 실패 로그에는 최소한 이 정도는 같이 남기는 편이 좋다.
err=ERR_RTE
dst=10.0.0.20:5000
netif_up=1 link_up=1
ip=192.168.0.10 mask=255.255.255.0 gw=0.0.0.0
default_netif=eth0
이 로그가 있으면
“UDP가 왜 실패하지?”보다
“이 목적지로 나갈 route가 실제로 있나?”를 바로 볼 수 있다.
빠른 체크리스트
- DHCP bound 또는 static IP 설정 완료 전에 UDP 송신을 시작하지 않는지 확인
netif_set_default()가 초기화 흐름에 들어 있는지 확인netif_set_up()과netif_set_link_up()상태를 따로 로그에 남기는지 확인- 목적지가 local subnet 밖이면 gateway가 설정되어 있는지 확인
- ARP request가 아예 안 나가는지, 나가는데 reply가 없는지 캡처로 구분
한 줄 요약
lwIP에서 UDP 송신이 ERR_RTE로 실패하면 payload나 ARP보다 먼저 netif up/link/default route/IP/gateway 상태를 확인하는 편이 빠르다.
추천 키워드
lwIP, UDP, ERR_RTE, netif, DHCP, embedded network
DevBJ | 오늘을살자, Log Today