
완전한 기능을 갖춘 WiFi NAT 라우터 (이제 WiFi 리피터도 지원)
완전한 기능을 갖춘 WiFi NAT 라우터 (그리고 이제는 WiFi 리피터, 즉 L2 브리지)
2026년 신규: 첫 출시 후 10년 만에 드디어 원래 의도했던 대로 진정한 WiFi 리피터가 되었습니다. 기존 문서와 링크를 깨지 않기 위해 표준 버전은 여전히 잘 알려진 고급 기능을 갖춘 NAT 라우터 버전으로 유지됩니다. 하지만 단순화된 진정한 L2 브리지에 관심이 있다면 아래 ESP8266 WiFi Repeater - L2 bridge 섹션을 참조하세요.
이것은 esp8266 및 esp8285에서 WiFi NAT 라우터를 구현한 것입니다. 패킷 필터링 방화벽(ACL 포함), 포트 매핑, 트래픽 쉐이핑, 원격 모니터링(또는 패킷 스니핑)을 위한 훅, MQTT 관리 인터페이스, 간단한 GPIO 상호작용 및 전원 관리를 지원합니다. 더 넓은 영역을 커버하기 위해 여러 라우터를 메시로 설정하는 새로운 모드 "Automesh"가 포함되었습니다.
NAT 기능을 Arduino 프로젝트에 통합하는 방법을 찾고 있다면 - 여기를 참조하세요.
EPS32 NAT Router는 ESP32를 위한 고급 프로젝트입니다.
일반적인 사용 시나리오는 다음과 같습니다:
기본적으로 ESP는 STA 및 soft-AP로 작동하며 모든 IP 트래픽을 투명하게 전달합니다. NAT를 사용하므로 네트워크 측이나 연결된 스테이션에 라우팅 항목이 필요하지 않습니다. 스테이션은 기본적으로 DHCP를 통해 192.168.4.0/24 네트워크에서 구성되며 기존 WiFi 네트워크에서 DNS 응답자 주소를 받습니다.
측정 결과 양방향으로 약 5Mbps를 달성할 수 있어 스트리밍도 가능합니다.
일부 세부 사항은 이 비디오에서 설명됩니다.
기기에 직접 플래싱하려면 웹 설치 프로그램을 사용하세요.
esp_wifi_repeater는 다음 기본 구성으로 시작됩니다:
첫 부팅(또는 공장 초기화) 후에는 SSID "MyAP"로 개방형 AP를 제공하는 WiFi 네트워크를 제공합니다. 아직 업링크 AP에 자동으로 재연결을 시도하지 않습니다(유효한 SSID 또는 비밀번호를 모르기 때문).
이 WiFi 네트워크에 연결하고 간단한 웹 인터페이스 또는 콘솔을 통한 모든 옵션이 포함된 전체 구성을 통해 기본 구성을 수행합니다.
웹 인터페이스는 기본 전달 기능에 필요한 모든 매개변수를 구성할 수 있습니다. rubfi의 주요 작업에 감사드립니다: https://github.com/rubfi/esp_wifi_repeater/ . 브라우저를 "http://192.168.4.1"로 지정하세요. 이 페이지가 나타납니다:
먼저 업링크 WiFi 네트워크에 적합한 값인 "STA 설정"을 입력하세요. 개방형 네트워크의 경우 비밀번호 "none"을 사용하세요. 실제로 automesh 모드를 사용하려는 경우에만 "Automesh" 상자를 선택하세요. "Connect"를 클릭합니다. ESP가 재부팅되고 WiFi 라우터에 연결됩니다. 몇 초 후 상태 LED가 깜빡여야 합니다.
automesh를 선택했다면 구성이 완료된 것입니다. automesh 모드에서는 이러한 설정이 "STA 설정"과 동일하므로 "Soft AP 설정"을 구성할 필요가 없습니다. 모든 연결된 ESP 리피터에서 동일한 SSID가 제공됩니다.
automesh를 사용하지 않는 경우, 이제 페이지를 다시 로드하고 "Soft AP 설정"을 변경할 수 있습니다. "Set"을 클릭하면 ESP가 다시 재부팅됩니다. 이제 새로 구성된 Soft AP를 통해 트래픽을 전달할 준비가 되었습니다. 이러한 변경 사항은 구성 인터페이스에도 영향을 미치므로 추가 구성을 위해 새로 구성된 WiFi 네트워크 중 하나를 통해 ESP에 연결하세요. Soft AP를 통한 액세스의 경우 Soft AP 네트워크의 주소가 변경된 경우 기억하세요(이 네트워크에서 ESP는 항상 x.x.x.1 주소를 가집니다).
원한다면 "lock" 확인란을 표시하고 "Lock"을 클릭할 수 있습니다. 그러면 업링크 WiFi 네트워크의 비밀번호로 잠금 해제하지 않고는 구성을 변경할 수 없습니다(네트워크가 개방형이더라도 비밀번호를 하나 정의하세요).
웹 인터페이스에 ASCII가 아니거나 특수 문자를 입력하려면 "My%20AccessPoint"와 같은 HTTP 스타일 16진수 인코딩을 사용해야 합니다. 그러면 문자열 "My AccessPoint"가 생성됩니다. 이 16진수 인코딩을 사용하면 0을 제외한 모든 바이트 값을 입력할 수 있습니다(C 내부 이유).
실수로 ESP와의 모든 접촉을 잃었다면 직렬 콘솔을 사용하여 복구할 수 있습니다("reset factory", 아래 참조).
고급 구성은 콘솔 인터페이스의 명령줄을 통해 수행해야 합니다. 이 콘솔은 직렬 포트(115200 보드) 또는 tcp 포트 7777(예: 연결된 STA에서 "telnet 192.168.4.1 7777")을 통해 사용할 수 있습니다.
초기 설정에는 다음 명령을 사용하세요:
다시 말하지만, ASCII가 아니거나 특수 문자를 입력하려면 HTTP 스타일 16진수 인코딩(예: "My%20AccessPoint")을 사용하거나 CLI에서만 단축키로 C 스타일 따옴표와 백슬래시(예: "My\ AccessPoint")를 사용할 수 있습니다. 두 방법 모두 문자열 "My AccessPoint"를 생성합니다.
명령줄은 훨씬 더 많은 명령을 이해합니다:
거의 모든 환경에서 작동하도록 하기에 충분합니다.
대부분의 set 명령은 save와 reset 후에 적용됩니다.
명령줄 입력에서 단일 "#" 뒤의 모든 내용은 줄 끝까지 주석으로 처리되어 무시됩니다.
기본 구성에서 GPIO2는 상태 LED(GND에 연결)를 구동하도록 구성되며 다음과 같은 표시가 있습니다:
"set status_led GPIOno"로 GPIO 핀을 변경할 수 있습니다 (16보다 큰 값, 예: "set status_led 255"는 상태 LED를 완전히 비활성화합니다). GPIO1로 구성하면 ESP-01 보드의 내장 파란색 LED와 함께 작동합니다. 그러나 GPIO1은 UART-TX 핀이기도 하므로 직렬 콘솔이 작동하지 않습니다. 구성은 네트워크 액세스로 제한됩니다.
선택한 GPIO를 3초 이상 로우로 당기면 리피터가 공장 초기화를 수행하고 기본 구성으로 다시 시작합니다. "set hw_reset GPIOno"로 GPIO 핀을 변경할 수 있습니다 (16보다 큰 값, 예: "set hw_reset 255"는 하드웨어 공장 초기화 기능을 비활성화합니다).
ESP-01 및 NodeMCU를 포함한 많은 모듈의 경우 GPIO 0을 사용하는 것이 이미 사용 중이므로 좋은 생각일 수 있습니다. 그러나 플래싱 중에 풀다운을 방해할 수 있으므로 기본 핀이 아닙니다. 따라서 GPIO 0의 기존 푸시 버튼을 하드웨어 공장 초기화에 사용하려면 플래싱 후 "set hw_reset 0" 및 "save"로 구성하세요. 하드웨어 핀에 의해 트리거된 공장 초기화는 구성된 hw_reset GPIO 번호를 재설정하지 않습니다(콘솔의 "reset factory"는 재설정합니다).
외부 네트워크의 클라이언트가 내부 네트워크의 서버 포트에 연결할 수 있도록 하려면 포트를 매핑해야 합니다. 외부 포트는 특정 내부 IP 주소의 내부 포트에 매핑됩니다. 이를 위해 "portmap add" 명령을 사용하세요. 포트 매핑은 "show" 명령으로 확인할 수 있으며 현재 구성과 함께 저장됩니다.
그러나 예상되는 장치가 특정 IP 주소에서 수신 대기 중인지 확인하려면 이 장치가 ESP가 재부팅된 후에도 동일한 IP 주소를 가지도록 해야 합니다. 이를 위해 장치에서 고정 IP 주소를 구성하거나 ESP가 DHCP 임대를 기억하도록 할 수 있습니다. 이는 "save dhcp" 명령으로 가능합니다. 이 명령은 현재 상태와 모든 DHCP 임대를 저장하여 재부팅 후 복원됩니다. DHCP 임대는 "show stats" 명령으로 확인할 수 있습니다.
WPA2 Enterprise (PEAP) 지원이 이제 프로젝트에 포함되었습니다. PEAP 인증을 사용하는 WPA2 Enterprise 네트워크를 WPA2-PSK 네트워크로 변환하는 "변환기"를 허용합니다. 이는 특히 대학 환경에서 일반적인 문제를 해결합니다: 로컬 WiFi 네트워크가 PEAP-MSCHAPv2 인증을 사용하는 WPA2 Enterprise 네트워크인 경우입니다. 가장 대표적인 예는 전 세계 많은 대학에서 사용 가능한 "eduroam" 네트워크입니다. 문제는 많은 IoT 장치가 WPA2 Enterprise 인증을 처리할 수 없다는 것입니다. 따라서 개발 및 데모가 어렵습니다. 매우 유용한 것은 WPA2 Enterprise 네트워크에 로그인하고 클라이언트에 더 간단한 WPA-PSK 네트워크를 제공하는 "변환기"입니다.
이를 사용하려면 다음 구성 매개변수를 설정하세요: ssid, use_peap, peap_identity, peap_username, peap_password (일반 password 매개변수는 필요 없음). 이 구성은 CLI를 통해 수행(및 저장)되어야 하며 웹 인터페이스에서는 사용할 수 없습니다.
코드는 현재 RADIUS 서버의 인증서를 확인하지 않습니다. 누군가가 가짜 AP와 RADIUS 서버를 설정하는 경우 MITM 공격에 취약합니다. 비밀번호가 평문으로 전송되지는 않지만 사용된 MSCHAPv2는 깨진 것으로 알려져 있습니다. 또한 ESP8266에는 이제 엔터프라이즈 네트워크 비밀번호가 포함되어 있습니다. ESP에 의해 전달되는 모든 트래픽은 네트워크 관리자가 귀하의 계정과 연관시킬 수 있습니다. 이를 오용하지 말고, 예를 들어 개방형 네트워크를 구성하여 신뢰할 수 없는 다른 사람에게 제공하지 마십시오. 장치가 잠겨 있어도 엔터프라이즈 네트워크 비밀번호는 직렬 포트를 통해 ESP의 플래시에서 평문으로 추출될 수 있습니다.
때로는 더 먼 거리나 영역을 커버하기 위해 여러 esp_wifi_repeater를 연속으로 또는 메시로 사용하고 싶을 수 있습니다. 일반적으로 NAT 라우터로는 문제 없이 수행할 수 있지만 실제로는 여러 계층의 NAT가 생깁니다. 그러나 이는 연결성에 제한을 의미합니다: 모든 노드는 인터넷과 통신할 수 있지만 일반적으로 노드 간의 직접 IP 연결은 없습니다. 그리고 당연히 홉 수가 많을수록 사용 가능한 대역폭이 감소합니다. 그러나 사용자들은 5개의 esp_wifi_repeater를 연속으로 사용해도 꽤 잘 작동한다고 보고했습니다.
이러한 설정에서 구성은 시간이 많이 소요되고 오류가 발생하기 쉽습니다. 이를 단순화하기 위해 esp_wifi_repeater는 이제 새로운 모드인 "Automesh"를 제공합니다. SSID와 비밀번호를 구성하고 "automesh"를 켜기만 하면 됩니다 (CLI에서 "set automesh 1" 또는 웹 인터페이스에서 확인란 선택). 그러면 다음이 수행됩니다:이렇게 구성된 각 esp_wifi_repeater는 AP에서 연결된 것과 동일한 SSID/비밀번호로 WiFi 네트워크를 자동으로 제공합니다. 클라이언트는 원래 네트워크나 중계된 네트워크에 대해 동일한 WiFi 설정을 사용할 수 있습니다. "automesh"로 구성된 각 esp_wifi_repeater는 먼저 연결할 최상의 다른 AP를 검색합니다. 이는 원래 WiFi 네트워크에 가장 가깝고 신호 강도(RSSI)가 가장 좋은 AP입니다.
신호 강도는 스캔으로 쉽게 측정할 수 있지만, 동일한 SSID를 가진 여러 AP가 보일 때 원래 WiFi 네트워크에 가장 가까운 AP는 어떻게 알 수 있을까요? 따라서 프로토콜은 다소 더러운 트릭을 사용합니다: "automesh" 모드의 esp_wifi_repeater는 BSSID(실제로 IEEE 802.11 표준에 따르면 AP이므로 "ESSID"이지만 SDK에서는 "BSSID"라고 부릅니다), 즉 AP 인터페이스의 MAC 주소를 조작합니다. 이 MAC 주소는 초당 약 10번의 비콘 프레임마다 전송됩니다. 형식은 24:24:mm:rr:rr:rr입니다. "24:24"는 리피터의 고유 식별자일 뿐입니다 (실제 AP의 MAC과 충돌할 최소한의 확률이 있지만, 정말 필요한 경우 해당 접두사를 변경할 수 있으므로 무시할 수 있습니다). "mm"은 "메시 레벨(mesh level)"을 의미하며, 이는 원래 WiFi 네트워크까지의 홉 거리입니다. 마지막 세 개 "rr:rr:rr"은 다양한 ESP를 구분하기 위한 난수입니다. 원래 AP는 자체 BSSID를 유지합니다. 즉, 접두사 "24:24"가 없는 것은 루트로 인식되며 메시 레벨 0이라고 합니다.
이제 각 esp_wifi_repeater는 어떤 다른 esp_wifi_repeater가 원래 WiFi 네트워크에 가장 가까운지 학습하고, 해당 리피터에 연결한 후 자체 BSSID를 그에 따라 선택할 수 있습니다. 또한 내부 네트워크의 IP 주소도 메시 레벨에 맞게 조정됩니다: 10.24.m.0. 이렇게 하면 원래 WiFi AP를 루트로 하고 여러 메시 레벨에 중계 노드가 있는 트리(매우 특별한 메시)가 생성됩니다 (실제로는 링크 계층의 Spanning Tree Protocol(STP)이나 네트워크 계층의 Distance Vector 프로토콜을 사용한 라우팅과 다소 유사하게 작동합니다). 업링크 링크 손실이 감지되는 즉시 구성이 다시 시작됩니다. 이는 (재)구성 중에도 BSSID가 포함된 비콘이 전송되지 않으므로 루프를 방지해야 합니다.
편의를 위해, "automesh" 구성 후 esp_wifi_repeater는 먼저 업링크 AP에 연결할 수 있는지 확인합니다. 올바른 SSID를 가진 AP가 발견되었음에도 연결에 실패하면, 사용자가 비밀번호를 잘못 입력한 것으로 간주하고 공장 초기값으로 재설정합니다. 한 번 성공적으로 연결되면 구성이 올바른 것으로 간주하고 연결 손실이나 재설정 후에도 계속 시도합니다 (잘못 구성된 AP로 인한 DOS 공격을 방지하기 위해).
범위 내에 둘 이상의 ESP가 있는 경우, 더 짧은 "나쁜" 경로와 더 긴 "좋은" 경로(링크 품질 측면에서 좋고 나쁨) 사이에 트레이드오프가 있을 수 있습니다. am_threshold 매개변수는 나쁜 연결이 무엇인지 결정합니다: 스캔에서 RSSI가 이 임계값보다 작으면 연결이 나쁜 것으로 간주되고 홉이 하나 더 많은 경로가 선호됩니다. 즉, _am_threshold_가 85이고 스캔에서 두 개의 automesh 노드가 감지된 경우: 레벨 1이고 RSSI가 -88 dB인 A와 레벨 2이고 RSSI가 -60 dB인 B가 있다면, A에 대한 링크는 너무 나쁜 것으로 간주되어(-88 dB < -am_threshold) B가 선호됩니다. 새 노드는 B를 통해 업링크되는 레벨 3 노드가 됩니다. _am_threshold_는 양수 값으로 제공되지만 음수 dB를 의미합니다. 값이 작을수록 더 좋습니다.
automesh 네트워크의 토폴로지에 대한 더 많은 통찰력을 얻으려면 모든 노드를 MQTT 브로커에 연결하고 "Topology" 토픽을 게시하도록 고려할 수 있습니다 (아래 참조). 이제 "/WiFi/+/system/Topology"를 구독하면 메시의 완전한 그래프를 재구성하고 약한 링크를 감지하는 데 필요한 모든 노드 및 링크 정보(연결된 ESP의 RSSI 포함)를 얻을 수 있습니다. TopologyInfo 토픽에는 다음과 같은 JSON 구조가 포함되어 있으며, automesh 네트워크의 완전한 그래프를 재구성하는 데 사용할 수 있습니다:``` { "nodeinfo" { "id":"ESP_07e37e", "ap_mac":"24:24:01:72:c7:f9", "sta_mac":"60:01:bc:07:e3:7e", "uplink_bssid":"00:1a:54:93:23:0a", "ap_ip":"10.24.1.1", "sta_ip":"192.168.178.33", "rssi":"-66", "mesh_level":"1", "no_stas":"2" }, "stas":[ {"mac":"5c:cf:45:11:7f:13","ip":"10.24.1.2"}, {"mac":"00:14:22:76:99:c5","ip":"10.24.1.3"} ] }
두 개의 매개변수 _am_scan_time_ 및 _am_sleep_time_을 사용하여 automesh 모드에서 전원 관리를 구현할 수 있습니다. GPIO16이 RST에 연결된 경우에 해당합니다. ESP_WiFi_리피터는 부팅 후 _am_scan_time_초 동안 사용 가능한 업링크 AP를 스캔합니다. 찾지 못하면 _am_sleep_time_초 동안 딥슬립으로 들어간 후 재부팅 후 다시 시도합니다(기본값은 두 매개변수 모두 0 = 비활성화).
# 모니터링
콘솔에서 모니터 서비스를 시작할 수 있습니다("monitor on [portno]"). 이 서비스는 내부 네트워크의 트래픽을 pcap 형식으로 TCP 스트림에 미러링합니다. 예를 들어 외부 네트워크의 컴퓨터에서 "netcat [external_ip_of_the_repeater] [portno] | sudo wireshark -k -S -i -" 명령을 사용하면 내부 네트워크의 트래픽을 실시간으로 관찰할 수 있습니다. 예를 들어 내부 클라이언트가 어떤 인터넷 사이트와 통신하는지 관찰하는 데 사용하십시오. 이는 ESP와 WiFi 네트워크의 부하를 최소 두 배로 증가시킵니다. 과부하 상태에서는 일부 패킷이 잘리거나 모니터 세션에서 드롭될 수 있습니다. 주의: 이 포트를 열어두는 것은 잠재적인 보안 문제입니다. 로컬 네트워크의 누구든지 연결하여 트래픽을 관찰할 수 있습니다.
# 방화벽
ESP 라우터에는 통합 기본 방화벽이 있습니다. ACL(액세스 제어 목록)을 SoftAP 인터페이스에 적용할 수 있습니다. 이는 라우터가 다른 IoT 장치를 인터넷에 연결하는 데 사용될 때 IoT 보안의 핵심 요소입니다. 예를 들어 타사 IoT 장치가 "집에 전화 걸기"를 하거나 멀웨어 봇으로 오용되는 것을 방지하고, PC, 태블릿, 스마트폰이 있는 홈 네트워크를 홈 오토메이션 장치에 보이지 않도록 보호하는 데 사용할 수 있습니다.
네 개의 ACL 목록은 각각 "from_sta", "to_sta", "from_ap" 및 "to_ap"로 명명되며, 두 인터페이스에서 들어오고 나가는 패킷을 대상으로 합니다("sta"는 연결된 클라이언트에 대한 인터페이스, "ap"는 업링크 AP에 대한 인터페이스입니다). ACL은 "CISCO IOS 스타일"로 정의됩니다.
다음 예제는 게스트 서브넷에 유용합니다. 인터넷에 대한 액세스를 허용하지만 다른 로컬 주소에는 액세스할 수 없습니다(로컬 네트워크 범위를 xx.xx.xx.xx 주소에 사용). 이 규칙 세트는 로컬 브로드캐스트(DHCP용)와 UDP 53(DNS)의 발신을 허용하며, 업스트림 라우터의 서브넷으로 향하는 다른 패킷은 모두 차단되며, 다른 모든 패킷은 인터넷으로 통과할 수 있습니다.```
acl from_sta clear
acl from_sta IP any 255.255.255.255 allow
acl from_sta UDP any any any 53 allow
acl from_sta IP any xx.xx.xx.xx/24 deny
acl from_sta IP any any allow
다음 예제는 더 제한적이며, ESP의 AP에서 매우 제한된 접근 권한으로 IoT 서브넷을 계획할 때 유용합니다. 또한 로컬 브로드캐스트(DHCP 용), UDP 53(DNS), 로컬 브로커에 대한 TCP 1883(MQTT) 아웃바운드를 허용하지만, 다른 패킷(임의의 인터넷 접근 포함)은 차단됩니다(네 번째 문장은 필요에 따라 다른 호스트를 활성화하도록 조정할 수 있습니다):``` acl from_sta clear acl from_sta IP any 255.255.255.255 allow acl from_sta UDP any any any 53 allow acl from_sta TCP any any 192.168.0.0/16 1883 allow acl from_sta IP any any deny
ACLs는 "to_sta" 방향에 대해서도 정의할 수 있지만, 일반적으로는 필요하지 않습니다. 역방향은 NAT 변환에 의해 원치 않는 트래픽으로부터 상당히 잘 보호되기 때문입니다.
ACL은 각 패킷에 대해 처리되는 필터링 규칙으로 구성됩니다. 각 규칙은 프로토콜(IP, TCP 또는 UDP), 출발지 주소/포트, 목적지 주소/포트, 그리고 "allow" 또는 "deny" 동작으로 구성됩니다. 일반 IP의 경우 포트는 없고 주소만 지정됩니다. IP 규칙은 TCP 및 UDP 패킷을 포함합니다. 주소는 서브넷 주소로 "/" 표기법(예: 192.168.178.0/24)으로 지정할 수 있습니다. 또한 "any"를 와일드카드로 사용할 수 있으며, 모든 주소나 포트 번호와 일치합니다. 규칙은 "acl" 명령어로 정의됩니다:
- acl [from_sta|to_sta|from_ap|to_ap] [TCP|UDP|IP] _src-ip_ [_src_port_] _dest-ip_ [_dest_port_] [allow|deny|allow_monitor|deny_monitor]
규칙은 목록에 나타난 순서대로 위에서 아래로 처리됩니다. 패킷과 일치하는 첫 번째 규칙이 적용되어 패킷이 허용(전달)되는지 거부(드롭)되는지 결정합니다. 즉, 특수한 경우를 먼저 배치하고 일반 규칙은 마지막에 둡니다. ACL에 규칙이 있는 경우, 어떤 규칙과도 일치하지 않는 모든 패킷은 기본적으로 거부됩니다. 따라서 위 예제에서 마지막 규칙인 "from_sta IP any any deny"는 어차피 기본값이므로 실제로 필요하지 않습니다. ACL이 비어 있으면 모든 패킷이 허용됩니다.
ACL 규칙 정의도 위에서 아래로 작동합니다: 새 규칙은 항상 목록의 끝에 추가됩니다. ACL을 변경하려면 먼저 전체를 지우고(acl from_sta clear) 다시 구축해야 합니다. ACL은 설정과 함께 저장됩니다. "show acl"은 ACL과 각 규칙의 적중 횟수 및 허용/거부된 전체 패킷 수에 대한 통계를 출력합니다.
"set acl_debug 1" 명령어를 사용하면 거부된 모든 패킷의 요약이 콘솔에 출력됩니다. 또한 MQTT 주제가 이 요약을 게시할 수 있습니다. 이는 방화벽 구성 시 연결된 장치가 작동하는 데 필요한 규칙을 결정하는 데 사용될 수 있습니다. 또한 예상치 못한 트래픽이 발생하는 경우(그리고 거부되는 경우)에 대한 힌트를 제공합니다.
더 깊은 분석을 위해 모니터링 서비스를 사용할 수 있습니다(거부된 패킷도 드롭되기 전에 모니터에 보고됩니다). "monitor acl _port_" 명령어로 모니터가 시작되면 ACL을 온라인 필터로 사용할 수 있습니다. "allow" 대신 "allow_monitor"로, "deny" 대신 "deny_monitor"로 정의된 모든 규칙은 평소와 같이 처리되어 패킷 전달을 허용하거나 거부하지만, 패킷을 모니터에도 보냅니다. 따라서 기본적으로 모든 패킷을 "allow" 또는 "allow_monitor"하는 규칙 목록도 의미가 있습니다. 캡처 시간에 어떤 패킷을 기록할지 선택하는 데 사용할 수 있기 때문입니다. 예를 들어, 목록:```
acl from_sta clear
acl from_sta IP 192.168.0.0/16 any allow_monitor
acl from_sta IP any any allow
acl to_sta clear
acl to_sta IP any 192.168.0.0/16 allow_monitor
cl to_sta IP any any allow
모든 패킷을 허용하고, 스테이션에서 192.168.0.0/16 (로컬) 서브넷으로 가는 패킷과 192.168.0.0/16에서 스테이션으로 가는 패킷을 모니터링하기 위해 모든 패킷을 선택할 수 있습니다. 물론 이러한 필터는 캡처 후 전체 모니터링 트레이스에도 적용할 수 있지만, 이미 찾고 있는 것이 무엇인지 알고 있다면 이러한 온라인 필터는 모니터링 오버헤드를 크게 줄이는 데 도움이 됩니다. 또한 "deny" 대신 "deny_monitor"를 사용하여 모든 거부 방화벽 규칙을 디버깅하는 데 사용할 수 있습니다.
기본적으로 AP 인터페이스는 NAT 처리되므로, AP에 연결된 모든 노드는 ESP의 STA 인터페이스를 통해 투명하게 외부 세계에 접근할 수 있습니다. 따라서 진정한 네트워크 전문가가 아니라면 추가 조치가 필요하지 않습니다.
네트워크 구성에 더 관심이 있는 분들을 위해 설명하자면: 이 프로젝트에서는 ESP의 lwip IPv4 스택이 정적 라우팅을 지원하도록 개선되었습니다. "show route"는 알려진 모든 경로(연결된 네트워크 인터페이스(AP 및 STA 인터페이스)에 대한 링크 포함)가 포함된 라우팅 테이블을 표시합니다. 이 두 인터페이스 간의 라우팅은 추가 구성 없이 작동합니다. 다른 네트워크로의 추가 경로는 Linux 상자나 라우터에서 알려진 "route add network gateway" 명령을 통해 설정할 수 있습니다. "save" 명령은 라우팅 테이블의 현재 상태를 플래시 구성에 기록합니다.
다음은 정적 라우팅으로 수행할 수 있는 간단한 예입니다. 중앙 홈 라우터를 통해 STA 인터페이스로 연결된 두 개의 ESP가 있는 다음과 같은 네트워크 설정이 있다고 가정합니다:``` | 10.0.1.1 AP-ESP1-STA 192.168.1.10 | <-> |Home Router| <-> | 192.168.1.20 STA-ESP2-AP 10.0.2.1|
각 ESP는 AP 뒤에 다른 네트워크 주소(10.0.1.0/24 및 10.0.2.0/24)를 가진 두 번째 네트워크를 가지고 있습니다. ESP1은 192.168.1.20으로 ESP2에 ping을 보낼 수 있지만, 192.168.1.20을 통해 10.0.2.1에 도달할 수 있다는 것을 모르기 때문에 10.0.2.1에는 ping을 보낼 수 없습니다. 이는 두 개의 정적 경로(static routes)를 추가하면 변경됩니다. ESP1에서:```
route add 10.0.2.0/24 192.168.1.20
그리고 ESP2:``` route add 10.0.1.0/24 192.168.1.10
이제 ESP1에서 "ping 10.0.2.1"이 성공할 것입니다. 이는 192.168.1.20으로 전송된 후 ESP2에 의해 응답됩니다.
이제 각 네트워크에 추가 클라이언트가 연결됩니다(주소 10.0.1.2 및 10.0.2.2):```
| STA1 10.0.1.2 | <-> | 10.0.1.1 ESP1 192.168.1.10 | <-> |Home Router| <-> | 192.168.1.20 ESP2 10.0.2.1| <-> | STA2 10.0.2.2 |
Now even client STA1 with the local address 10.0.1.2 can ping to STA2 with 10.0.2.2 as it sends its request first to its default router ESP1 and this knows, that all packets to a 10.0.2.0/24 address have to be forwarded to 192.168.1.20. There the ESP2 knows how to send it to STA2. The same applies for the reply in the other direction.
이를 통해 각 ESP와 해당 STA 클라이언트가 (포트 맵 없이) 서로 직접 연결될 수 있는 다중 스타 토폴로지의 ESP를 구성할 수 있습니다. 필요한 경로 구성은 다소 번거로울 수 있지만 - 네트워킹에 좋은 연습이 됩니다. 다음 단계는 ESP에서 RIP와 같은 동적 라우팅 프로토콜을 포팅하는 것입니다.
upstream_kbps와 downstream_kbps를 0이 아닌 값(0은 기본값)으로 설정하면 ESP AP의 최대 비트레이트를 제한할 수 있습니다. 이 값은 연결된 모든 클라이언트의 트래픽에 적용되는 제한입니다. 정의된 비트레이트를 초과하는 패킷은 드롭됩니다. 트래픽 셰이퍼는 "Token Bucket" 알고리즘을 사용하며, 현재 버킷 크기는 초당 비트레이트의 4배입니다. 이전에 트래픽이 없었을 경우 버스트를 허용합니다.
버전 1.3부터 라우터에는 내장 MQTT 클라이언트가 있습니다 (라이브러리 제공에 감사드립니다: Tuan PM, https://github.com/tuanpmt/esp_mqtt). 이는 라우터/리피터를 IoT에 통합하는 데 도움이 될 수 있습니다. 예를 들어, 홈 오토메이션 시스템은 현재 연결된 스테이션에 대한 정보를 기반으로 결정을 내리거나, 리피터를 켜고 끄거나(예: 시간 일정 기반), 간단히 부하를 모니터링하는 데 사용할 수 있습니다. 라우터는 로컬 MQTT 브로커 또는 클라우드의 공개 브로커에 연결할 수 있습니다. 그러나 현재 TLS 암호화를 지원하지 않습니다.
기본적으로 MQTT 클라이언트는 비활성화되어 있습니다. 구성 매개변수 "mqtt_host"를 "none"이 아닌 호스트 이름으로 설정하면 활성화할 수 있습니다. MQTT를 구성하려면 다음 매개변수를 설정할 수 있습니다:
MQTT 매개변수는 "show mqtt" 명령으로 표시할 수 있습니다.
라우터는 주기적으로(매 mqtt_interval마다) 다음 상태 토픽을 게시할 수 있습니다:
또한 리피터는 이벤트 기반으로 게시할 수 있습니다:
LWT 및 상태 보고서로 리피터는 게시합니다:
라우터는 다음 토픽을 사용하여 구성할 수 있습니다:
이제 라우터가 예를 들어 Vdd, IP 및 명령줄 출력만 게시하도록 하려면 mqtt_mask를 0x0001 | 0x0002 | 0x0040으로 설정합니다 (= "set mqtt_mask 0043").
이제 esp_wifi_repeater는 SPI를 통해 연결된 ENC28J60 이더넷 NIC 지원을 포함합니다 (정확히 구현해주신 Andrew Kroll https://github.com/xxxajk에게 감사드립니다). "user_config.h"에서 HAVE_ENC28J60 컴파일 옵션을 켜면 활성화됩니다. 이더넷 인터페이스는 ESP가 160MHz로 동작할 때 약 1Mbps를 지원합니다. AP 인터페이스를 켜고 이더넷을 업링크로 사용하면 esp_wifi_repeater가 WiFi 장치(예: 다른 ESP)를 위한 저렴한 AP가 됩니다.
The connection via SPI has to be:``` NodeMCU/Wemos ESP8266 ENC28J60
D6 GPIO12 <---> MISO
D7 GPIO13 <---> MOSI
D5 GPIO14 <---> SCLK
D8 GPIO15 <---> CS
D1 GPIO5 <---> INT
D2 GPIO4 <---> RESET
Q3/V33 <---> 3.3V
GND <---> GND
짧고 납땜된 전선이 가장 잘 작동합니다. 또한 GPIO16를 분리하기 위해 트랜지스터가 필요합니다. 그렇지 않으면 ESP가 더 이상 부팅되지 않습니다. 참조: https://esp8266hints.wordpress.com/category/ethernet/ . 또한, 좋은 전원 공급 장치가 중요합니다: ENC28j60는 활성 상태에서 약 160mA를 필요로 합니다. 제 경우에는 ESP 보드의 3.3V를 사용하려고 하면 실패합니다.
이제 새 이더넷 인터페이스를 구성할 수 있습니다:
- set eth_enable [0|1]: SPI 버스에서 ENC28J60 이더넷 NIC를 활성화/비활성화합니다 (기본값: 0 - 비활성화)
- set eth_ip _ip-addr_: ETH 인터페이스에 고정 IP 주소를 설정합니다
- set eth_netmask _netmask_: ETH 인터페이스에 고정 넷마스크를 설정합니다
- set eth_gw _gw-addr_: ETH 인터페이스에 고정 게이트웨이 주소를 설정합니다
- set eth_dhcpd [0|1]: ETH 인터페이스에서 동적 IP 주소를 위한 DHCP 서버를 시작합니다 (기본값: 0 - 비활성화)
# 전원 관리
리피터는 현재 공급 전압을 모니터링합니다 ("show stats" 명령에 표시됨). 이는 esp_init_data_default.bin의 107번째 바이트(vdd33_const라고 함)가 255(0xFF)로 설정된 경우에만 작동합니다. 이를 달성하는 가장 쉬운 방법은 esp_init_data_default_v08_vdd33.bin을 플래시에 쓰는 것입니다 (아래 참조).
만약 _vmin_ (mV 단위, 기본값 0)이 0보다 큰 값으로 설정되고 공급 전압이 이 값 아래로 떨어지면, _vmin_sleep_ 초 동안 딥 슬립 모드로 들어갑니다. GPIO16을 RST에 연결한 경우 (ESP-01에서 납땜하기 어렵습니다) 이 간격 후에 재부팅되어 재연결을 시도하고 측정을 계속합니다. _vmin_이 구성과 함께 저장되면, 공급 전압이 임계값 위로 올라갈 때까지 반복적으로 슬립합니다. 이러한 설정은 ESP를 과방전 보호 기능이 없는 (리튬) 배터리로 전원을 공급하는 경우 특히 (만?) 유용합니다. 그러면 2900mV-3000mV 값이 도움이 될 수 있으며, ESP의 전력 소비를 최소로 줄여 손상 전에 배터리를 재충전하거나 교체할 시간을 더 확보할 수 있습니다. 이는 ESP가 배터리에 직접 연결된 경우에만 의미가 있습니다. 추가 로직이 있으면 여전히 배터리를 소모합니다.
한 번 "sleep" 명령을 사용하여 ESP를 수동으로 슬립 상태로 보낼 수 있습니다.
주의: 최대 공급 전압보다 높은 _vmin_ 값을 플래시에 저장하면, 리피터는 재부팅 후 매번 즉시 종료됩니다. 그런 다음 blank.bin (또는 다른 파일)을 0x0c000에 플래시하여 전체 구성을 지워야 합니다.
# WiFi 리피터 - L2 브리지
이제 프로젝트는 두 가지 별개의 운영 모드를 제공합니다: **NAT 라우터**와 **레이어 2 브리지** ("리피터 모드"라고 함). 두 모드 모두 네트워크 범위를 확장하지만, 트래픽 처리 및 장치 식별 방식에서 근본적으로 다릅니다.
### NAT 라우터 모드 (표준)
이 모드에서는 위에서 설명한 대로 장치가 표준 게이트웨이 역할을 합니다. 새로운 서브넷을 생성하고 액세스 포인트(AP)에 연결된 모든 장치에 대해 네트워크 주소 변환(NAT)을 수행합니다.
* **서브넷 격리**: 연결된 클라이언트는 개인 서브넷(예: 192.168.4.x)에 있으며, 기본 네트워크로부터 차단됩니다.
* **트래픽 식별**: 클라이언트의 모든 트래픽은 메인 라우터에 ESP8266 자체 IP/MAC 주소에서 발생한 것처럼 나타납니다.
* **간편함**: 업스트림 라우터에 특별한 구성이 필요하지 않으며 사실상 모든 표준 Wi-Fi 네트워크와 호환됩니다.
* **제한 사항**: 포트 매핑을 사용하지 않는 한, 기본 네트워크의 장치는 NAT 장벽 때문에 리피터 뒤에 있는 장치에 쉽게 연결을 시작할 수 없습니다.
### 레이어 2 브리지 모드 ("리피터" 변형)
이 모드는 투명한 레이어 2 (데이터 링크 계층) 브리지를 구현합니다. ESP8266은 보조 서브넷을 생성하는 대신 기존 기본 네트워크를 확장합니다.
* **투명 브리징**: ESP8266은 이더넷 프레임 수준에서 트래픽을 브리징합니다. 연결된 클라이언트는 기본 네트워크의 DHCP 서버로부터 직접 IP 주소를 받습니다 (DHCP 스누핑/릴레이를 통해).
* **통합 네트워크**: 모든 장치 (리피터와 메인 라우터 모두)는 동일한 L2 브로드캐스트 도메인에 존재합니다.
* **장치 가시성**: 리피터 뒤의 장치는 메인 네트워크에서 원래 MAC 및 IP 식별자를 유지합니다. 이는 로컬 검색 프로토콜 (예: mDNS/Bonjour, UPnP 또는 네트워크 검색)이 전체 네트워크에서 원활하게 작동하도록 합니다.
* **복잡성**: 업스트림 네트워크가 리피터를 통해 연결된 "숨겨진" 클라이언트로 트래픽을 올바르게 라우팅하기 위해 Proxy ARP 및 DHCP 스누핑과 같은 고급 처리가 필요합니다.
* **사용 사례**: 전체 네트워크에서 장치 검색 (예: 휴대폰 앱을 통해 프린터 또는 스마트 홈 장치 제어)이 필요한 경우에 이상적입니다.
리피터 모드는 기능이 적습니다: 라우팅, 포트 매핑 및 DHCP가 필요하지 않으며, ACL과 Automesh도 실제로 의미가 없고 pcap을 통한 네트워크 모니터링도 마찬가지입니다. 따라서 이러한 모든 기능은 리피터 모드에서 사용할 수 없습니다. 또한 MQTT도 제거되었습니다. 나머지 기능은 콘솔 또는 원격 콘솔을 통해 여전히 사용할 수 있습니다.
미리 컴파일된 바이너리는 "firmware-repeater" 폴더에서 찾을 수 있습니다.
리피터 모드 버전의 첫 번째 구성은 기본적으로 NAT 라우터만큼 간단합니다. 직렬 콘솔을 통해 ssid, password, ap_ssid, ap_password를 설정한 다음 저장하고 재설정하면 됩니다. 웹 인터페이스를 통해 수행하려는 경우도 간단하지만 올바른 순서를 따라야 합니다:
- 클라이언트로 "MyAP" WiFi에 연결
- 브라우저를 "http://192.168.4.1"로 지정
- **먼저** AP 설정 ssid와 password를 입력하고 설정 및 재시작
- 그런 다음 새로 정의된 AP ssid에 연결하고 브라우저를 다시 "http://192.168.4.1"로 지정
- 이제 STA 설정 ssid와 password를 입력하고 연결
STA ssid가 정의되면 리피터는 더 이상 자체 DHCP 서버를 실행하지 않고 업스트림 DHCP에서 IP를 받습니다 (더 이상 192.168.4.1이 아님). 웹 페이지 또는 원격 콘솔에 연결하려면 클라이언트가 mDNS를 지원하는 경우 "esp-wifi-repeater.local" 이름을 사용하거나, 업스트림 라우터 (또는 "show stats" 명령이 있는 직렬 콘솔)에서 할당된 주소를 조회해야 합니다. 콘솔과 "reset factory" 명령을 통해 항상 ESP를 재설정할 수 있습니다.
### 주요 대조 요약
| 기능 | NAT 라우터 | 레이어 2 브리지 |
| :--- | :--- | :--- |
| **네트워크 아키텍처** | 새롭고 격리된 서브넷 생성 | 기존 브로드캐스트 도메인 확장 |
| **IP 주소 지정** | 클라이언트가 보조 풀 사용 | 클라이언트가 업스트림 DHCP 서버 사용 |
| **검색 (mDNS/UPnP)** | 종종 차단/어려움 | 완전 지원 (투명) |
| **업스트림 가시성** | 클라이언트 ID 숨김 (NAT) | 클라이언트 ID 유지 |
| **구현** | 표준 네트워킹 | 고급 프록시 (Proxy ARP/스누핑) |
# 빌드 및 플래싱
미리 컴파일된 바이너리를 장치에 직접 플래싱하려면 [Web-Installer](https://martin-ger.github.io/esp_wifi_repeater/)를 사용하세요.
Docker가 설치된 경우 전체 빌드 환경에 접근하는 가장 쉬운 방법은 ESP8266을 /dev/ttyUSB0에 연결하고 다음을 사용하여 이미지를 실행하는 것입니다:```
git clone https://github.com/martin-ger/esp_wifi_repeater.git
docker run -it --rm --device=/dev/ttyUSB0 -v $(pwd)/esp_wifi_repeater:/home/esp/esp_wifi_repeater martinfger/iot_devel:1.0
cd esp_wifi_repeater
make
make flash
To build the L2 WiFi Repeater version just use the VARIANT=bridge option for the make command: L2 WiFi 리피터 버전을 빌드하려면 make 명령에 VARIANT=bridge 옵션을 사용하기만 하면 됩니다:``` git clone https://github.com/martin-ger/esp_wifi_repeater.git docker run -it --rm --device=/dev/ttyUSB0 -v $(pwd)/esp_wifi_repeater:/home/esp/esp_wifi_repeater martinfger/iot_devel:1.0 cd esp_wifi_repeater make VARIANT=bridge make flash
빌드 환경을 처음부터 설정하고 이 바이너리를 빌드하려면 esp-open-sdk를 다운로드하여 설치하세요(기본 NONOS SDK 2.2가 포함된 이 버전을 권장합니다: https://github.com/xxxajk/esp-open-sdk). "blinky" 예제를 컴파일하고 다운로드할 수 있는지 확인하세요.
그런 다음 이 소스 트리를 별도의 디렉토리에 다운로드하고 Makefile의 BUILD_AREA 변수와 user/user_config.h의 원하는 옵션을 조정합니다. 기본 구성의 변경은 user/config_flash.c에서 수행할 수 있습니다. "make"로 esp_wifi_repeater 펌웨어를 빌드합니다. "make flash"는 이를 esp8266에 플래시합니다.
소스 트리에는 liblwip_open의 바이너리 버전과 내 esp-open-lwip 포크에서 필요한 추가 include 파일, 그리고 rboot 도구의 바이너리가 포함되어 있습니다. *추가 설치 작업이 필요하지 않습니다.* 사전 컴파일된 라이브러리를 사용하지 않으려면 https://github.com/martin-ger/esp-open-lwip 에서 소스를 체크아웃하세요. 이를 사용하여 esp-open-sdk 트리의 "esp-open-lwip" 디렉토리를 교체합니다. esp_open_lwip 디렉토리에서 "make clean"을 실행하고 상위 esp_open_sdk 디렉토리에서 다시 "make"를 실행합니다. 그러면 NAT 기능이 포함된 liblwip_open.a가 컴파일됩니다. liblwip_open_napt.a를 그 바이너리로 교체합니다. 또한 https://github.com/raburton/rboot 에서 "rboot.bin" 바이너리를 빌드하여 프로젝트의 루트 디렉토리에 교체할 수 있습니다.
*업데이트*: 웹에서 "0x10000.bin"을 사용하는 설치 지침을 읽었다면 - OTA로 인해 현재 "0x02000.bin"으로 변경되었습니다.
전체 사전 컴파일된 펌웨어 바이너리를 사용하려면 "esptool.py --port /dev/ttyUSB0 write_flash -fs 4MB -ff 80m -fm dio 0x00000 firmware/0x00000.bin 0x02000 firmware/0x02000.bin" 명령어로 플래시할 수 있습니다 (ESP-01의 경우 -fs 1MB 사용). esp8285의 경우 -fs 1MB와 -fm dout을 사용해야 합니다.
Windows에서는 https://espressif.com/en/support/download/other-tools 에서 제공하는 "ESP8266 Download Tool"을 사용하여 플래시할 수 있습니다. firmware 디렉토리에서 0x00000.bin과 0x02000.bin 두 파일을 다운로드하세요. 일반적인 ESP12, NodeMCU 또는 Wemos D1의 경우 다음 설정을 사용하세요 (ESP-01의 경우 FLASH SIZE를 "8Mbit"로 변경):
<img src="https://raw.githubusercontent.com/martin-ger/esp_wifi_repeater/master/FlashRepeaterWindows.jpg">
"QIO" 모드가 기기에서 실패하면 "DIO"를 대신 시도하세요. 또한 "Detected Info"를 확인하여 플래시 칩의 크기와 모드를 확인하세요. 다운로드한 펌웨어가 여전히 제대로 시작되지 않으면, 첨부된 체크섬으로 바이너리 파일이 손상되었는지 확인하세요. 펌웨어 바이너리가 손상된 것 같으면 전체 저장소를 zip으로 다운로드하고 그 zip에서 바이너리를 추출하세요. 이렇게 하면 HTTP 다운로드 문제(예: CR-LF 변환)를 피할 수 있습니다.
# OTA(무선) 업데이트 지원
rboot 라이브러리 사용 기반: https://github.com/raburton/rboot 그리고 christianchristensen의 기여에 감사드립니다.
빌드 프로세스는 firmware 디렉토리에 esp_wifi_repeater 바이너리의 두 개의 복사본(0x02000.bin 및 0x82000.bin)을 생성합니다. 초기 설치의 경우 0x00000.bin (rboot 부트 로더)와 0x02000.bin (프로그램의 한 복사본)만 플래시하면 됩니다. esp_wifi_repeater가 작동합니다.
최소 1MB 플래시가 있다면 다른 버전으로 OTA(무선) 업데이트를 수행할 수 있습니다. 즉, CLI에서 새 바이너리를 대화식으로 로드하고 전환할 수 있습니다. 다른 바이너리는 현재 비활성화된 메모리 위치(0x02000 (rom0) 또는 0x82000 (rom1))에 로드되고 성공 시 시작됩니다. 또한 설치된 두 바이너리 간에 대화식으로 전환할 수 있습니다. 현재 구성은 형식이 변경되지 않는 한 두 바이너리 모두에서 사용됩니다.
다음 명령어로 OTA 기능을 제어할 수 있습니다:
- show ota: 현재 활성 바이너리와 다음 업데이트 URL을 표시합니다
- set ota_host _hostname_: OTA 서버의 호스트명 또는 IP 주소를 설정합니다 (기본값: "none")
- set ota_port _portno_: OTA 서버의 포트 번호를 설정합니다 (기본값: 80)
- ota update: ota_host:ota_port에서 HTTP를 통해 새 바이너리(0x02000.bin 또는 0x82000.bin)를 다운로드하여 시작합니다
- ota switch: 다른 바이너리로 전환합니다 (설치된 경우)
OTA 기능을 테스트하려면 ESP를 (STA 또는 AP로) 업데이트 서버가 있는 네트워크에 연결되도록 구성하세요. 그런 다음 firmware 디렉토리에서 간단한 웹 서버를 시작하세요. 예:```
cd firmware
python -m SimpleHTTPServer 8080
매개변수 _hostname_을(를) 컴퓨터의 호스트 이름 또는 IP로 설정하고, _portno_를 8080으로 설정한 다음 "저장"합니다. 그런 다음 CLI에 입력하세요:``` ota update
올바르게 구성된 경우 업데이트가 시작되고 ESP가 새 바이너리로 재부팅됩니다.
# 알려진 문제
- ESP의 SoftAP 구현 제한으로 인해 최대 8개의 동시 연결 스테이션이 가능합니다.
- ESP8266은 전송 중 최대 170mA의 전류 스파이크를 발생시키므로 안정적인 전원 공급이 필요합니다 (WiFi 켜짐 시 일반 평균 소비 전류는 약 70mA). ESP가 불안정하게 작동하거나 자주 재부팅되는 경우 먼저 전원 공급 장치를 확인하세요. Vdd와 Gnd 사이에 큰 커패시터를 연결하면 문제가 발생할 경우 도움이 될 수 있습니다.
# 라이선스
이 소프트웨어는 오픈 소스입니다. 타사 소스 파일에는 자체 라이선스 헤더가 있습니다. 다른 모든 파일에는 MIT 라이선스가 적용됩니다.