기술 가이드

임베디드 Linux SocketCAN 필터: ID 마스크·EFF·RTR 혼입 검증

표준 CAN 데이터만 받도록 EFF·RTR 플래그를 함께 비교하고, ID 범위와 8,192개 입력을 C11 예제로 검증합니다.

목차

임베디드 Linux 게이트웨이에서 특정 CAN ID만 수신하도록 했는데 확장 프레임이나 원격 요청까지 섞이면, ID 비교 마스크에 프레임 종류 비트가 들어 있는지 확인해야 합니다. Linux 커널 SocketCAN 문서의 수신 조건은 (received_can_id & mask) == (filter_can_id & mask)입니다.

CAN ID 정수에는 플래그도 들어 있다

Linux 공개 CAN 헤더는 can_id에 식별자와 프레임 플래그를 함께 담습니다. 표준 식별자는 11비트, 확장 식별자는 29비트입니다. CAN 데이터 바이트에 마스크를 적용하는 기능과는 구분하세요.

상수 값 용도
CAN_SFF_MASK 0x000007FF 표준 11비트 식별자
CAN_EFF_MASK 0x1FFFFFFF 확장 29비트 식별자
CAN_EFF_FLAG 0x80000000 확장 프레임 표시
CAN_RTR_FLAG 0x40000000 원격 요청 프레임 표시
CAN_ERR_FLAG 0x20000000 SocketCAN 오류 메시지 표시

filter.can_id = 0x123, filter.can_mask = CAN_SFF_MASK만 설정하면 확장 식별자 0x18FF0123도 하위 11비트가 같아서 통과할 수 있습니다. RTR도 마찬가지입니다. 표준 데이터 프레임 0x123만 원한다면 마스크를 CAN_EFF_FLAG | CAN_RTR_FLAG | CAN_SFF_MASK로 정하고 can_id의 EFF·RTR 비트는 0으로 둡니다. 확장 데이터 프레임만 받으려면 can_id에 EFF를 넣고 마스크에는 EFF·RTR·EFF_MASK를 넣습니다. 이 조합은 커널의 필터 최적화 설명에서도 제시합니다.

ID 범위와 여러 필터

표준 데이터 프레임 0x200~0x20F를 받고 싶다면 하위 4비트를 비교에서 제외합니다. can_id = 0x200, can_mask = CAN_EFF_FLAG | CAN_RTR_FLAG | 0x7F0입니다. 0x1FF와 0x210은 거부되고 16개의 표준 데이터 ID가 허용됩니다.

일반적인 CAN_RAW_FILTER 배열은 OR로 결합합니다. 필터 A가 0x123, B가 0x200~0x20F라면 어느 하나와 일치한 프레임을 수신합니다. AND를 원하는 경우의 CAN_RAW_JOIN_FILTERS, 반전 필터의 CAN_INV_FILTER는 이 예제 범위에 포함하지 않습니다. 수신 필터 배열의 길이가 0인 설정은 전부 받기가 아니라 해당 소켓의 수신을 끄는 의미입니다. 오류 메시지는 CAN_RAW_ERR_FILTER로 별도 선택합니다.

C11로 종류·범위 경계를 검증하기

아래 코드는 Windows나 Linux PC에서 실행할 수 있는 비트 비교 모델입니다. 실제 CAN 인터페이스와 커널 소켓을 열지 않습니다. 필터를 정상 데이터/RTR 프레임에 적용하는 논리를 모델링하고 오류 프레임은 별도 경로로 제외합니다. 반전 필터는 지원하지 않습니다. 2,048개 표준 ID 값 각각에 EFF·RTR 플래그 4가지 조합을 적용해 8,192개 입력을 검사합니다. 확장 식별자의 전체 29비트 공간을 전수 검사한 것은 아닙니다.

can_filter.c로 저장하고 gcc -std=c11 -Wall -Wextra -Werror can_filter.c -o can_filter로 빌드합니다. Linux는 ./can_filter, Windows는 ./can_filter.exe로 실행합니다.

#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

#define EFF UINT32_C(0x80000000)
#define RTR UINT32_C(0x40000000)
#define ERR UINT32_C(0x20000000)
#define SFF_MASK UINT32_C(0x000007ff)

/* Ordinary positive filters only; error frames use a separate socket option. */
static bool matches(uint32_t rx, uint32_t id, uint32_t mask)
{
if ((rx & ERR) != 0u)
return false;
return (rx & mask) == (id & mask);
}

int main(void)
{
const uint32_t exact_mask = EFF | RTR | SFF_MASK;
const uint32_t range_mask = EFF | RTR | UINT32_C(0x7f0);
assert(matches(0x123u, 0x123u, exact_mask));
assert(!matches(EFF | 0x123u, 0x123u, exact_mask));
assert(!matches(RTR | 0x123u, 0x123u, exact_mask));
assert(!matches(ERR | 0x123u, 0x123u, exact_mask));
/* An ID-only mask admits EFF and RTR lookalikes. */
assert(matches(EFF | 0x18ff0123u, 0x123u, SFF_MASK));
assert(matches(RTR | 0x123u, 0x123u, SFF_MASK));

unsigned exact_count = 0u, range_count = 0u, checks = 0u;
for (uint32_t flags = 0u; flags < 4u; ++flags) {
for (uint32_t id = 0u; id <= SFF_MASK; ++id) {
uint32_t rx = id;
if ((flags & 1u) != 0u) rx |= EFF;
if ((flags & 2u) != 0u) rx |= RTR;
bool exact = matches(rx, 0x123u, exact_mask);
bool range = matches(rx, 0x200u, range_mask);
assert(exact == (flags == 0u && id == 0x123u));
assert(range == (flags == 0u && id >= 0x200u && id <= 0x20fu));
exact_count += exact ? 1u : 0u;
range_count += range ? 1u : 0u;
++checks;
}
}
assert(exact_count == 1u && range_count == 16u);
/* Two CAN_RAW_FILTER entries match with OR by default. */
assert(matches(0x207u, 0x123u, exact_mask) ||
matches(0x207u, 0x200u, range_mask));
printf("SocketCAN filter: %u cases passed; exact=%u range=%u\n",
checks, exact_count, range_count);
return 0;
}

검증 출력:

SocketCAN filter: 8192 cases passed; exact=1 range=16

exact=1은 표준 데이터 0x123 하나, range=16은 표준 데이터 0x200~0x20F가 통과했음을 뜻합니다. ID만 비교한 마스크의 EFF·RTR 혼입도 assertion으로 확인합니다.

실제 게이트웨이에 적용하기

Linux 펌웨어에서는 linux/can.h, linux/can/raw.h를 포함하고 struct can_filter를 구성한 뒤 setsockopt(socket_fd, SOL_CAN_RAW, CAN_RAW_FILTER, filters, sizeof filters)로 등록합니다. 반환 값이 -1이면 오류 원인을 확인하고 성공한 것처럼 수신을 계속하지 않습니다. 인터페이스에 bind()하는 코드와 수신 프레임 길이 검사는 별도로 필요합니다.

  1. 같은 버스에서 표준 데이터 0x123, 다른 ID, 확장 0x18FF0123, RTR 0x123을 각각 보내 기대 수신 여부를 확인합니다.
  2. 범위 경계 0x1FF·0x200·0x20F·0x210을 확인합니다.
  3. 필터는 소켓마다 적용됩니다. 다른 모니터 프로그램이 전체 트래픽을 받는 것은 해당 애플리케이션 필터가 실패했다는 뜻이 아닙니다.
  4. 버스 전체 부하나 CAN 컨트롤러의 하드웨어 수신 부하 감소까지 소켓 필터가 보장하지는 않습니다. CPU 사용량과 드라이버 수신 드롭은 따로 측정합니다.
  5. 수신이 전혀 없다면 비트레이트·인터페이스 UP·종단 저항·배선·필터 등록 성공 여부를 차례로 확인합니다. 같은 소켓의 자체 송신 수신은 기본 설정에서 꺼져 있다는 점도 확인합니다.

공식 자료 확인: 2026-10-11. 실물 CAN 버스 통신은 수행하지 않았으며, 본문 예제의 필터 계산과 경계를 실제 컴파일·실행해 검증했습니다.

관련 글

댓글

첫 댓글을 남겨 보세요.

관리자 확인 후 게시됩니다.

처리 중입니다...

잠시만 기다려주세요.