| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- adb connect
- augmentedDevice
- github 100mb
- Recoil
- ffi-napi
- html
- vercel git lfs
- electron-packager
- animation
- camera permission
- Git
- Can't resolve
- camera access
- custom printing
- react-native-dotenv
- ELECTRON
- Each child in a list should have a unique "key" prop.
- react-native
- github pdf
- device in use
- Failed to compiled
- github lfs
- npm package
- dvh
- 이미지 데이터 타입
- silent printing
- 티스토리 성능
- adb pair
- rolldown
- nextjs
- Today
- Total
Bleeding edge
ROS 2에서 ROS_DOMAIN_ID를 나눴는데도 Multicast Flooding이 발생하는 이유 본문
ROS 2를 여러 대의 로봇, 여러 장비, 또는 여러 테스트 환경에서 동시에 사용하다 보면 ROS_DOMAIN_ID를 나누는 경우가 많다.
예를 들어 다음과 같은 환경을 생각해보자.
Robot A: ROS_DOMAIN_ID=0
Robot B: ROS_DOMAIN_ID=1
Robot C: ROS_DOMAIN_ID=2
일반적으로 우리는 이렇게 기대한다.
Domain 0과 Domain 1은 서로 보이지 않는다.
Domain 1과 Domain 2도 서로 보이지 않는다.
따라서 네트워크도 분리된 것처럼 동작할 것이다.
ROS 2 애플리케이션 관점에서는 이 기대가 어느 정도 맞다.
서로 다른 ROS_DOMAIN_ID를 가진 노드들은 기본적으로 서로 discovery되지 않고, 토픽이나 서비스도 보이지 않는다.
그런데 실제 네트워크 장비에서 보면 이상한 현상이 나타날 수 있다.
모든 장비가 239.255.0.1 multicast group에 가입되어 있음
다른 ROS_DOMAIN_ID의 discovery traffic도 네트워크 상에서 전달됨
스위치나 라우터에서 multicast flooding처럼 보임
왜 이런 일이 생길까?
ROS 2 Discovery와 DDS
ROS 2는 내부 통신 미들웨어로 DDS를 사용한다.
DDS에서는 노드들이 서로를 찾기 위해 discovery 과정을 수행한다.
이 discovery 과정에서 기본적으로 multicast가 사용될 수 있다.
대표적으로 자주 보이는 multicast 주소는 다음과 같다.
239.255.0.1
이 주소는 DDS participant discovery, 즉 “나 여기 있다”는 정보를 네트워크에 알리기 위해 사용된다.
ROS_DOMAIN_ID는 무엇을 분리하는가
ROS_DOMAIN_ID는 ROS 2 시스템을 논리적으로 분리하기 위한 값이다.
예를 들어:
ROS_DOMAIN_ID=0
ROS_DOMAIN_ID=1
ROS_DOMAIN_ID=2
가 있으면, 각 domain은 서로 다른 ROS graph를 가진다.
즉, domain 0의 노드는 domain 1의 노드를 보지 못하고, domain 2의 토픽도 보이지 않는다.
하지만 여기서 중요한 점이 있다.
ROS_DOMAIN_ID는 네트워크 multicast group IP를 기본적으로 분리하는 값이 아니다.
DDS에서는 domain ID에 따라 주로 UDP port가 달라진다.
예를 들어 개념적으로는 다음과 같다.
ROS_DOMAIN_ID=0 → 239.255.0.1:7400
ROS_DOMAIN_ID=1 → 239.255.0.1:7650
ROS_DOMAIN_ID=2 → 239.255.0.1:7900
즉, multicast IP는 동일하게 239.255.0.1일 수 있고, domain 구분은 UDP port와 DDS 내부 domain 정보로 이루어진다.
네트워크 장비는 ROS_DOMAIN_ID를 모른다
문제는 여기서 발생한다.
ROS 2/DDS는 다음 기준으로 domain을 구분한다.
Multicast IP
UDP port
DDS Domain ID
Participant 정보
하지만 스위치나 L3 장비의 multicast forwarding은 보통 다음 기준으로 동작한다.
Multicast group IP
즉, 네트워크 장비 입장에서는 다음 세 개가 서로 다른 ROS domain이라는 사실을 모른다.
239.255.0.1:7400
239.255.0.1:7650
239.255.0.1:7900
장비는 단순히 이렇게 본다.
239.255.0.1 group에 가입한 장비들
따라서 ROS_DOMAIN_ID가 달라도, 네트워크 장비의 multicast group table에는 모두 같은 239.255.0.1 group member로 등록될 수 있다.
왜 값은 Domain ID에 따라 오고 안 오는데, 가입은 같이 보일까?
이 현상은 계층별 처리 기준이 다르기 때문에 발생한다.
스위치나 L3 장비는 이렇게 판단한다.
239.255.0.1 group에 가입한 포트가 있다.
따라서 239.255.0.1 multicast traffic을 해당 포트들로 전달한다.
하지만 실제 장비의 OS와 DDS는 더 세밀하게 본다.
UDP port가 내 domain에 해당하는가?
DDS Domain ID가 일치하는가?
Participant 정보가 맞는가?
그래서 다른 domain의 discovery packet이 물리적으로 네트워크 인터페이스까지 도달하더라도, ROS 2/DDS 레벨에서는 무시된다.
결과적으로 다음과 같은 상태가 된다.
네트워크 장비 기준:
- 모두 239.255.0.1 multicast group에 가입한 것으로 보임
ROS 2 기준:
- 같은 ROS_DOMAIN_ID끼리만 discovery됨
- 다른 domain의 노드는 보이지 않음
네트워크 부하 기준:
- 다른 domain의 multicast packet도 물리적으로 전달될 수 있음
- 따라서 대역폭은 이미 소모됨
즉, ROS graph는 분리되어 있지만, 네트워크 traffic은 완전히 분리되지 않은 상태가 된다.
왜 DDS는 Domain ID를 포트로 분리했을까?
여기서 자연스럽게 이런 의문이 생긴다.
처음부터 domain별로 multicast IP를 다르게 쓰면 되지 않았을까?
예를 들어 이런 방식이다.
ROS_DOMAIN_ID=0 → 239.255.0.1
ROS_DOMAIN_ID=1 → 239.255.1.1
ROS_DOMAIN_ID=2 → 239.255.2.1
하지만 DDSI-RTPS 표준은 기본적으로 domain ID를 UDP well-known port 계산에 반영하는 방식을 사용한다.
개념적으로는 다음과 같은 계산식이다.
port = PB + DG * domainId + offset
일반적으로 많이 보이는 값은 다음과 같다.
PB = 7400
DG = 250
그래서 domain ID가 바뀌면 port가 일정 간격으로 바뀐다.
domain 0 → 7400 계열
domain 1 → 7650 계열
domain 2 → 7900 계열
domain 7 → 9150 계열
이 방식은 DDS 입장에서는 합리적이다.
첫째, OS와 프로세스 입장에서 UDP port는 자연스러운 demultiplexing 단위다.
같은 머신 안에서 여러 DDS domain이 떠 있어도, 커널은 destination UDP port를 보고 어느 소켓으로 전달할지 구분할 수 있다.
둘째, 서로 다른 DDS vendor 간 상호운용성을 맞추기 쉽다.
DDS는 Fast DDS, Cyclone DDS, RTI Connext, OpenDDS 등 여러 구현체가 존재한다. 이들이 같은 계산식을 알고 있으면, domain ID만 맞춰도 discovery port를 예측할 수 있다.
셋째, 별도 네트워크 설정 없이 자동 discovery가 가능하다.
사용자는 ROS_DOMAIN_ID만 설정하면 되고, multicast 주소 계획이나 group mapping을 따로 관리하지 않아도 된다.
즉, DDS의 기본 설계는 “네트워크 장비에서 domain별 multicast group을 깔끔하게 분리한다”보다는, “DDS 애플리케이션들이 별도 설정 없이 자동으로 discovery될 수 있게 한다”에 더 가깝다.
그런데 왜 지금 환경에서는 문제가 되는가
이 설계는 호스트와 DDS 프로세스 입장에서는 잘 동작한다.
같은 multicast IP를 쓰더라도 UDP port가 다르다.
OS가 port 기준으로 구분할 수 있다.
DDS가 domain ID 기준으로 한 번 더 구분할 수 있다.
하지만 네트워크 장비 입장에서는 다르다.
스위치나 L3 multicast 장비는 보통 multicast group IP 기준으로 membership과 forwarding을 관리한다.
239.255.0.1 group
반면 DDS는 이렇게 구분한다.
239.255.0.1:7400
239.255.0.1:7650
239.255.0.1:7900
여기서 mismatch가 발생한다.
DDS는 L4 UDP port까지 보고 domain을 나누지만, 네트워크 장비의 multicast forwarding은 대개 L3 multicast group IP만 본다.
그래서 다음과 같은 문제가 생긴다.
ROS_DOMAIN_ID는 다르다.
UDP port도 다르다.
하지만 multicast group IP는 같다.
네트워크 장비는 모두 같은 group으로 본다.
결과적으로 다른 domain의 discovery traffic도 같은 group 안에서 퍼질 수 있다.
이것이 multicast flooding 또는 discovery flooding처럼 보이는 핵심 원인이다.
“Multicast group을 domain별로 나누면 더 복잡해진다”는 뜻
그렇다면 domain별 multicast group을 쓰면 해결되는 것 아닐까?
예를 들어:
ROS_DOMAIN_ID=0 → 239.255.0.1
ROS_DOMAIN_ID=1 → 239.255.1.1
ROS_DOMAIN_ID=2 → 239.255.2.1
ROS_DOMAIN_ID=7 → 239.255.7.1
이렇게 하면 네트워크 장비도 group IP 기준으로 domain을 구분할 수 있다.
현재처럼 239.255.0.1 하나에 모든 domain이 묶이는 문제를 줄일 수 있다.
다만 이 방식은 운영 측면에서 복잡도가 증가한다.
1. IGMP snooping table이 늘어난다
기존 방식에서는 스위치가 하나의 group만 관리하면 된다.
239.255.0.1
├─ port 1
├─ port 2
└─ port 3
domain별 multicast group을 사용하면 group이 여러 개로 늘어난다.
239.255.0.1
└─ port 1
239.255.1.1
└─ port 2
239.255.2.1
└─ port 3
239.255.7.1
└─ port 4
스위치 입장에서는 IGMP membership을 group별로 관리해야 한다.
로봇 수, domain 수, VLAN 수가 많아지면 snooping table과 membership 관리 포인트가 늘어난다.
2. IGMP querier 설정이 더 중요해진다
IGMP snooping은 multicast group membership을 유지하기 위해 IGMP query/report를 사용한다.
네트워크에 querier가 없거나 VLAN별 querier 설정이 빠져 있으면, 스위치가 membership을 제대로 유지하지 못할 수 있다.
group이 하나일 때는 문제가 덜 드러날 수 있지만, group이 많아지면 어떤 group은 정상 전달되고, 어떤 group은 timeout되거나 flood되는 식의 애매한 문제가 생길 수 있다.
3. L3 multicast routing 정책이 복잡해진다
같은 VLAN 안에서만 multicast를 쓴다면 주로 IGMP snooping 문제다.
하지만 VLAN 간, 서브넷 간, 라우터를 넘어 multicast를 전달해야 한다면 multicast routing이 관여한다.
이때 group별 정책이 필요해질 수 있다.
예를 들어:
239.255.0.1 → VLAN 10까지만 허용
239.255.1.1 → VLAN 20까지만 허용
239.255.7.1 → VLAN 30과 40에 허용
기존에는 239.255.0.1 하나만 허용하거나 차단하면 됐지만, domain별 group을 사용하면 어떤 group을 어디까지 전달할지 별도로 설계해야 한다.
4. ACL과 방화벽 정책도 늘어난다
방화벽이나 ACL 정책도 마찬가지다.
기본값 하나만 사용할 때는 이렇게 관리할 수 있다.
239.255.0.1 허용
하지만 domain별 group을 사용하면 다음과 같이 관리 대상이 늘어난다.
239.255.0.1 허용
239.255.1.1 허용
239.255.2.1 허용
239.255.7.1 허용
대역으로 넓게 허용할 수도 있다.
239.255.0.0/16 허용
하지만 이렇게 하면 허용 범위가 너무 넓어질 수 있고, 다시 보안/운영 정책의 문제가 생긴다.
5. 주소 계획이 필요하다
239.255.<ROS_DOMAIN_ID>.1 방식은 직관적이다.
domain 7 → 239.255.7.1
domain 42 → 239.255.42.1
하지만 domain ID가 255를 넘으면 문제가 생긴다.
domain 300 → 239.255.300.1
IP octet에는 300을 넣을 수 없다.
따라서 domain ID 범위를 제한하거나, 별도 매핑 규칙을 만들어야 한다.
예를 들어:
domain 300 → 239.254.44.1
이런 방식의 매핑을 만들 수 있지만, 이때부터는 사람이 직관적으로 이해하기 어려워지고 운영 문서와 자동화가 필요해진다.
6. DDS 설정 파일 배포가 필요하다
ROS 2 기본값에서는 보통 환경변수 하나만 맞추면 된다.
export ROS_DOMAIN_ID=7
하지만 multicast group까지 domain별로 바꾸려면 DDS 설정 파일을 별도로 배포해야 한다.
Cyclone DDS 예시는 다음과 같다.
<?xml version="1.0" encoding="UTF-8"?>
<CycloneDDS>
<Domain id="any">
<General>
<AllowMulticast>true</AllowMulticast>
</General>
<Discovery>
<SPDPMulticastAddress>239.255.7.1</SPDPMulticastAddress>
</Discovery>
<Tracing>
<Verbosity>warning</Verbosity>
</Tracing>
</Domain>
</CycloneDDS>
그리고 실행 환경도 맞춰야 한다.
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export ROS_DOMAIN_ID=7
export CYCLONEDDS_URI=file:///path/to/cyclonedds.xml
중요한 점은, 같은 domain에 속한 모든 장비가 같은 설정을 가져야 한다는 것이다.
한 장비는 239.255.7.1을 쓰고, 다른 장비는 기본값인 239.255.0.1을 쓰면 서로 discovery되지 않는다.
그럼에도 domain별 multicast group 분리는 유효한가?
그렇다.
현재 문제처럼 네트워크 장비가 UDP port가 아니라 multicast group IP 기준으로만 forwarding을 처리하는 환경이라면, domain별 multicast group 분리는 충분히 유효한 해결책이다.
기존 구조:
domain 0 → 239.255.0.1:7400
domain 1 → 239.255.0.1:7650
domain 2 → 239.255.0.1:7900
네트워크 장비 관점:
전부 239.255.0.1 group
개선 구조:
domain 0 → 239.255.0.1:7400
domain 1 → 239.255.1.1:7650
domain 2 → 239.255.2.1:7900
네트워크 장비 관점:
domain 0 → 239.255.0.1 group
domain 1 → 239.255.1.1 group
domain 2 → 239.255.2.1 group
이렇게 하면 스위치나 L3 장비도 group 단위로 traffic을 분리할 수 있다.
즉, DDS 기본 설계보다 운영 복잡도는 올라가지만, 현재 네트워크 환경에서는 더 적합한 구조가 될 수 있다.
해결 방향 정리
이 문제를 해결하는 방법은 크게 네 가지다.
1. Domain별 multicast group 분리
예:
ROS_DOMAIN_ID=7 → 239.255.7.1
장점:
네트워크 장비가 group IP 기준으로 domain을 구분할 수 있음
기존 multicast discovery 구조를 유지할 수 있음
단점:
DDS XML 설정 필요
주소 계획 필요
모든 장비에 동일 설정 배포 필요
2. VLAN 분리
로봇 그룹이나 서비스 그룹별로 VLAN을 분리한다.
Robot group A → VLAN 10
Robot group B → VLAN 20
Robot group C → VLAN 30
장점:
broadcast/multicast domain 자체가 분리됨
가장 명확한 네트워크 레벨 격리
단점:
네트워크 설계 변경 필요
라우팅/방화벽 정책 추가 필요
3. Fast DDS Discovery Server 사용
Fast DDS Discovery Server를 사용하면 분산 multicast discovery 대신 중앙 discovery server를 통해 discovery 정보를 교환할 수 있다.
Node A ┐
Node B ├── Discovery Server
Node C ┘
장점:
multicast discovery traffic을 크게 줄일 수 있음
대규모 시스템이나 fleet 환경에 유리
단점:
Fast DDS 설정 필요
Discovery Server 운영 필요
서버 장애 대비 필요
4. Cyclone DDS peer list와 multicast off
Cyclone DDS를 사용하는 경우 multicast를 끄고 peer IP를 명시하는 방식도 가능하다.
개념적으로는 다음과 같다.
Multicast discovery 사용 안 함
지정된 peer IP로 unicast discovery
장점:
multicast traffic 자체를 줄일 수 있음
네트워크 장비의 multicast 처리 이슈를 회피 가능
단점:
peer IP 관리 필요
장비 추가/변경 시 설정 업데이트 필요
결론
ROS_DOMAIN_ID는 ROS 2 애플리케이션 레벨에서는 domain을 잘 분리한다.
하지만 네트워크 레벨에서 multicast traffic까지 완전히 분리해주는 것은 아니다.
기본 DDS discovery에서는 여러 domain이 같은 multicast group, 예를 들어 239.255.0.1을 사용할 수 있다.
이때 domain 구분은 주로 UDP port와 DDS 내부 정보로 이루어진다.
문제는 스위치나 L3 장비가 multicast forwarding을 UDP port가 아니라 multicast group IP 기준으로 처리한다는 점이다.
그래서 다음과 같은 상황이 발생한다.
ROS_DOMAIN_ID는 다르다.
ROS graph도 분리된다.
하지만 multicast group은 같다.
네트워크 장비는 같은 group으로 본다.
다른 domain의 discovery traffic도 물리 네트워크에 전달될 수 있다.
DDS는 받은 뒤 버린다.
하지만 네트워크 대역폭은 이미 사용된다.
이것이 ROS 2 환경에서 ROS_DOMAIN_ID를 나눴는데도 multicast flooding 또는 discovery flooding이 발생할 수 있는 이유다.
현재 환경처럼 네트워크 장비가 multicast group IP 기준으로만 forwarding하고, UDP port 단위로 분리하지 못하는 경우라면 ROS_DOMAIN_ID만으로는 부족하다.
이 경우에는 다음 중 하나 이상의 방법을 고려해야 한다.
domain별 multicast group 분리
VLAN 분리
Fast DDS Discovery Server
Cyclone DDS peer discovery + multicast off
IGMP snooping / querier / unknown multicast 정책 점검
특히 239.255.<ROS_DOMAIN_ID>.1처럼 domain별 multicast group을 분리하는 방식은, 운영 복잡도는 증가하지만 현재와 같은 네트워크 환경에서는 매우 현실적인 해결책이 될 수 있다.
'CS' 카테고리의 다른 글
| [Docker] 한 PC를 여러 대처럼? ipc: host 옵션으로 네트워크 경계 허물기 (1) | 2026.04.19 |
|---|---|
| JPEG 안의 FF C0와 FF C2는 무엇일까? (1) | 2026.04.14 |
| 각 코덱이 생긴 이유 (0) | 2026.04.04 |
| AV1와 NVENC의 차이 (0) | 2026.04.04 |
| ROS 2에서 카메라 노드 제어: Inactive vs Subprocess (0) | 2025.09.03 |