개요
본 페이지에서는 여러 NIC를 갖는 기기가 특정 NIC를 통해 통신을 수행하는 방법을 설명한다.
Linux OS 환경에서 수행하였고 실험을 위해 클라이언트와 서버간에 Python UDP 통신 프로그램을 구현하였다.
서버와 클라이언트에서 사용된 Python UDP 통신 프로그램 소스코드는 가장 아래 쪽에 작성해두었다.
실험 환경
실험 환경은 위 그림과 같이 구성하였으며, 클라이언트 측에 두 대의 유선 NIC(eth0, eth1)를 부착하였다.
클라이언트는 RaspberryPi, 서버는 Linux PC를 사용하였다.
✔ 클라이언트 NIC
- eth0
- IP : 192.168.0.8
- MAC : B8:27:XX:XX:XX:XX
- eth1
- IP : 192.168.0.9
- MAC : 00:E0:XX:XX:XX:XX
✔ 서버 NIC
- eth0
- IP : 192.168.0.10
기본 환경에서의 UDP 통신 실험
본 단락에서는 클라이언트의 기본적인 라우팅 테이블을 통해 실험을 하였을 때 나타나는 현상을 설명한다.
클라이언트의 기본적인 라우팅 테이블 상태는 다음과 같다.
라우팅 테이블을 확인하는 명령어는 route와 ip rule show가 있다.
$ route -n
$ ip rule show
기본적인 라우팅 테이블은 위 두 그림과 같다.
route와 ip rule의 차이점은 다음과 같다.
- route : 대상 주소(destination IP)를 기반으로 라우팅 결정
- ip rule : 대상 주소뿐만이 아닌 여러 정책을 통해 라우팅 결정 (라우팅 결정에 더 세세한 조절이 가능)
위 테이블을 통해 알 수 있는 것은 "192.168.0.0 네트워크나 192.168.0.0이 아닌 네트워크로 통하는 데이터(패킷)들은 모두 Metric(값이 낮을 수록 높은 우선순위를 가짐)이 낮은 eth0을 통해 라우팅 된다" 이다.
이것을 증명하기 위해 서버와 클라이언트에 구현된 UDP 통신 프로그램을 실행하였으며 결과는 아래와 같다.
또한 두 UDP 프로그램 간의 트래픽을 tcpdump를 통해 캡처하였다.
✔ 클라이언트 UDP 통신 결과
클라이언트측 UDP 통신 프로그램에서 192.168.0.8과 192.168.0.9의 Source 주소를 통해 "Hello"라는 메시지를 서버측(192.168.0.10) UDP 통신 프로그램으로 송신하는 동작을 수행하였다.
*통신 시 소켓의 Source 주소를 설정하는 방법은 가장 아래의 클라이언트 UDP 통신 프로그램 소스코드 참조.
✔ 서버 UDP 통신 결과
서버는 클라이언트에서 도착한 메시지인 "Hello"를 출력하고 메시지를 보낸 주체의 IP와 Port번호를 출력한다.
서버의 결과에서 클라이언트의 두 NIC 주소인 192.168.0.8과 192.168.0.9를 통해 전송되는 것을 확인할 수 있다.
하지만 아래의 tcpdump 통해서 해당 메시지의 패킷을 캡처한 결과를 보면 한 NIC를 통해서만 전송된 것을 알 수 있다.
✔ 서버측에서의 UDP 통신 프로그램 패킷 캡처 결과
tcpdump를 통해 클라이언트와 서버간에 발생된 UDP 패킷을 캡처해보면 알 수 있듯이 192.168.0.8과 192.168.0.9 주소에서 발생된 메시지가 모두 클라이언트의 b8:27:로 시작하는 eth0 NIC를 통해 통신하는 것을 확인할 수 있다.
위 실험을 통해 기본적인 라우팅 테이블 환경에서는 Source측 주소가 변경되더라도 Metric 값이 낮은, 즉 우선순위가 가장 높은 NIC를 통해서만 데이터가 송신되는 것을 알 수 있다. 이것을 해결하기 위해선 더 세세한 Source 주소를 기반으로 하는 라우팅 테이블 정책이 추가되어야 한다.
라우팅 테이블에 Source 주소를 기반으로 하는 정책 추가
본 단락에서는 Source측 주소를 기반으로하는 라우팅 정책을 추가하는 것을 설명한다.
해당 정책을 추가하기 위해 ip 명령어를 사용하였으며, 특정 Source 주소 기반 라우팅 정책을 추가하는 방법은 다음과 같다.
$ ip rule add from [IP Address] table [number]
$ ip route add [Network Address]/[Subnet mask] dev [NIC name] scope link table [number]
$ ip route add default via [Gateway Address] dev [NIC name] table [number]
본 실험에서는 클라이언트 측에 위 세개의 명령어를 차례대로 2번씩 입력하여 클라이언트의 NIC IP 주소인 192.168.0.8과 192.168.0.9에 대한 Source 주소 기반 라우팅 정책을 추가하였다.
✔ 클라이언트 측에 Source 주소 기반 라우팅 정책 추가
☑ Source IP가 192.168.0.8인 패킷을 eth0로 통하게 하는 라우팅 정책 추가
☑ Source IP가 192.168.0.9인 패킷을 eth1로 통하게 하는 라우팅 정책 추가
클라이언트에서 위 명령어들을 입력하게 되면 시스템에서 발생되는 Source IP가 192.168.0.8인 패킷들은 table 1의 정책을 따르고 Source IP가 192.168.0.9인 패킷들은 table 2의 정책을 따르게 된다.
✔ 클라이언트 측에 추가된 라우팅 테이블 정책 확인
$ ip rule show
최종적으로 ip rule show 명령어를 통해 확인한 결과 192.168.0.8의 Source IP를 가지는 패킷은 table 1의 정책이 결정된 것과 192.168.0.9의 Source IP를 가지는 패킷은 table 2의 정책이 결정된 것을 확인할 수 있다.
Source 주소 기반 정책을 추가한 뒤의 UDP 통신 실험
✔ 클라이언트 UDP 통신 결과
*통신 시 소켓의 Source 주소를 설정하는 방법은 가장 아래의 클라이언트 UDP 통신 프로그램 소스코드 참조.
✔ 서버 UDP 통신 결과
앞선 실험과 마찬가지로 클라이언트측 UDP 통신 프로그램에서 192.168.0.8과 192.168.0.9의 Source 주소를 통해 "Hello"라는 메시지를 서버측(192.168.0.10) UDP 통신 프로그램으로 송신하는 동작을 수행하였고, 서버측 또한 마찬가지로 192.168.0.8과 192.168.0.9에서 전달된 "Hello" 메시지를 전송받은 것을 확인할 수 있다.
하지만 앞선 기본 환경 실험 결과와는 다르게 서버측에서의 tcpdump를 통한 패킷 캡처 결과는 다른 것을 확인할 수 있다.
✔ 서버측에서의 UDP 통신 프로그램 패킷 캡처 결과
앞선 기본 환경 실험에서는 192.168.0.8과 192.168.0.9로 전송된 두 패킷이 b8:27인 eth0을 통해서만 데이터가 전송되는 것을 확인할 수 있었지만 Source 주소 기반 라우팅 정책이 추가된 환경에서의 실험은 다른 결과가 확인되었다.
192.168.0.8은 b8:27인 eth0을 통해 전송되고, 192.168.0.9는 00:e0인 eth1을 통해 전송되는 것을 최종적으로 확인할 수 있었다.
즉 클라이언트 측의 Source 주소 기반 라우팅 정책이 올바르게 동작하는 것을 볼 수 있었다.
끝으로..
본 페이지에서는 Linux 환경의 기기가 여러 NIC를 소유하고 있을 때 특정 NIC로 패킷을 전달하는 방법을 알아보았다. 가장 기본적인 환경에서는 Source 주소가 아닌 Destination(대상) 주소를 기반으로 라우팅되어 여러 NIC가 같은 네트워크를 통해서 통신될 때는 Metric이 낮은 즉 무조건적으로 우선순위가 높은 NIC로 통신되는 것을 알 수 있었다. 이러한 문제를 해결하기 위해 본 페이지에서는 Source 주소 기반 라우팅 정책을 추가하는 방법을 알아보았고 최종적으로 실험까지 완료하였다.
UDP 통신 프로그램 Python 소스코드
✔ 클라이언트
import sys
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((sys.argv[1], 55555))
sock.sendto("Hello".encode(), ('192.168.0.10', 9999))
✔ 서버
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('192.168.0.10', 9999))
while True:
data, addr = sock.recvfrom(1024)
print('Received Data [', data.decode(), ']', sep='')
print('Client Address [', addr[0], ':', addr[1], ']', sep='')
print()
Ref..
'Linux' 카테고리의 다른 글
Linux 커널버전 4.x, 5.x의 proc 파일 시스템 예제 코드 (0) | 2022.08.30 |
---|---|
Linux 커널의 TC(Traffic Control) 구조 (0) | 2022.07.15 |
새로운 소켓 옵션 생성 방법 (in Linux Kernel) (0) | 2022.04.11 |
iperf3 사용법 (0) | 2021.12.26 |
C언어 NIC(네트워크 인터페이스 카드) IP 확인 소스코드 (0) | 2021.08.24 |