요약
2달러짜리 ESP32-C3 보드 하나로 Pi-hole 같은 DNS 광고 차단기를 만들었습니다.
GitHub 이용자 M-Abozaid가 MIT 라이선스로 공개한 프로젝트입니다. 외장 메모리(PSRAM)가 없는 값싼 보드에 큰 차단 목록을 담으려고 도메인 문자열 대신 해시를 플래시에 저장했습니다.
왜 중요한가
- 문자열을 RAM에 올리는 방식은 PSRAM이 달린 8달러 안팎 보드가 필요했지만, 이 방식은 그보다 싼 보드로 충분합니다.
- 공유기의 남는 USB 포트에 꽂아 두는 작은 기기로 네트워크 전체의 광고 도메인을 막을 수 있습니다.
핵심 내용
- 도메인마다 40비트 FNV-1a 해시(문자열을 고정 길이 숫자로 바꾸는 함수)를 정렬해 저장하고 이진 탐색으로 찾습니다.
- 도메인 14만 1천 개가 플래시 0.67MB를 차지하고, RAM은 약 50KB만 씁니다.
- 차단할 도메인이면 0.0.0.0으로 답하고, 아니면 상위 DNS 서버의 답을 그대로 전달합니다.
- 펌웨어 무선 업데이트(OTA)를 쓰면 저장 공간이 줄어 차단 목록은 약 25만 개까지만 담깁니다.
- 웹 대시보드에서 기기별 통계와 차단 목록을 관리하지만, 통신은 모두 암호화되지 않은 HTTP입니다.
HN 반응
- README의 해시 충돌 설명을 두고, 진짜 위험은 목록에 없는 정상 도메인이 잘못 막히는 경우라는 지적과 계산 방식을 둘러싼 반론이 이어졌습니다.
- 기본 C3 보드의 내장 안테나가 너무 약하니 외장 안테나형을 사라는 조언이 나왔고, 실내에서는 쓸 만했다는 경험담도 있었습니다.
README가 해시 충돌을 이야기하는데, 제가 예상한 방향이 아니라서 좀 이상하네요. 차단 대상 도메인 두 개가 같은 해시를 갖는 건 문제가 안 됩니다. 어차피 둘 다 차단해야 하니까요.
해시 충돌이 문제가 되는 건 오탐(false positive)입니다. 예를 들어 hash(google.com) = hash(adserver.com)인 경우죠. 웹 대시보드와 /unblock API가 있는 것 같으니 해결하는 건 어렵지 않아 보입니다.
README를 AI가 썼으니 놀랄 일도 아니죠.
위 댓글도 AI 봇이 썼는지 누가 압니까?
HN에서 우려되는 흐름입니다. 거의 모든 글마다 맨 위에 "이거 AI가 썼다"는 댓글이 하나씩 달리는데, 정량적 근거도 증명도 없이 감정만 있습니다. 새로운 마녀사냥이거나, 토론에 보탬이 되는 건 하나도 없이 카르마만 챙기려는 짓입니다.
이 저장소의 커밋 거의 전부가 claude와 공저로 되어 있는데요.
Claude가 기여자로 올라 있으니 증명된 겁니다. 증거도 없이 감정만으로 하는 소리가 아니에요.
추측할 시간에 원본을 직접 보면 되잖아요. README를 읽어 보세요. 딱 봐도 AI가 쓴 겁니다.
네, 그쪽이 거꾸로 생각한 것 같습니다. 말씀하신 대로 로컬에 항목이 몇 개든 충돌 위험은 오탐 해시 일치에서 오는 것이지, 차단 대상 해시끼리 충돌할지 걱정할 일이 아닙니다.
제가 보기엔 그쪽이 맞게 한 겁니다.
항목이 N개이고 N >> 1이면, 임의의 값이 기존 값들과 충돌할 확률은 Pcol(N+1)인데 이건 대략 Pcol(N)과 같습니다.
수정: 4gotunameagain의 답글로 건너뛰세요. 이 글에는 읽을 만한 내용이 아마 없습니다.
계산 자체는 맞지만, 그건 다음 질문에 대한 계산입니다.
그런데 이들이 충돌 계산을 해 보고 싶었을 대상은 아마 이쪽일 겁니다.
해시 충돌 때문에 엉뚱한 동작을 할 위험은 이쪽이니까요. 중요한 점은, 두 번째 계산은 차단 목록의 크기가 아니라 전체 도메인 수에 의해 한계가 정해진다는 겁니다.
앞의 질문은 별로 쓸모가 없습니다. 항목이 100개인 차단 목록에서 충돌이 100번 나도, 100개 모두 같은 동작을 하니 충돌이 0번일 때와 똑같이 작동하거든요. 그건 해시마다 고유 ID를 만들어야 할 때의 문제고, 여기서는 분류 문제라 상황이 반대입니다.
아니요, 두 번째 질문에 맞게 한 겁니다.
차단 목록(크기 N)과 방문할 도메인(목록에 없는 것)을 둘 다 균일하게 샘플링한다고 가정하면, 우리가 신경 쓰는 충돌 확률은 Pcol(N+1)입니다.
존재하는 도메인이 몇 개인지는 상관없고, 차단 목록의 크기만 중요합니다.
물론 4억 1백만 개 도메인 전체를 놓고 보면 충돌이 훨씬 많이 나겠지만, 각 경우마다 우리가 신경 쓰는 건 N+1에서의 충돌뿐입니다.
아, 충돌 확률로 하자는 말씀은 이해했고 분명 말이 됩니다. 그런데 README에서 실제로 한 계산은 그 방식으로 나와야 할 값과 약 4배 차이가 납니다. 예를 들어 "다음 도메인" 질문을 훨씬 단순하게 2^40/537000으로 직접 계산하면 1/537k이 아니라 약 1/2.05M이 나옵니다.
수정: 아니면 그 경로로 식을 유도해서 되돌리는 방법까지 정리했는데, 이 규모의 생일 문제를 근사해 주는 도구를 금방 못 찾아서 직접 테스트로 때운 걸 수도 있겠네요. 예컨대 값을 조금씩 올려 가며 충돌이 언제 나는지 봤다면, 사실상 시행을 여러 번 한 셈이니 예상보다 일찍 충돌을 만난 것도 납득이 됩니다. 아니면 정말 그렇게 했다면 그냥 우연일 수도 있고요 :).
수정 2: README를 너무 오래 들여다본 끝에, 원래 README가 일본어였다는 걸 알았습니다. 좋은 번역기로 돌려 보니 표현이 훨씬 분명해지고 바로잡히는 부분도 많았어요. 예를 들어 537000개 중 과차단된 도메인 수에만 초점을 맞추는 대신 "후자의 경우 운 나쁜 다른 도메인 하나도 같이 차단된다"고 되어 있습니다. 이 버전의 글에서는 원문이 이미 새 쿼리 한 건에서 벌어지는 일로 관점을 돌려 놓았으니, 말씀하신 수학적 접근을 썼다면 맞는 숫자가 나왔어야 한다는 데 동의합니다. 다만 (아마도) 실제로 그 계산을 하지 않았을 뿐이고요.
날카로운 지적이었습니다 :).
시뮬레이션할 필요 없습니다. 이미 잘 연구된 문제예요 [1]
537k일 때 기대 충돌 수 평균은 1이 아니라 0.13인 것 같습니다.
같은 생일(또는 해시 충돌)을 가진 쌍의 평균값은 E[X] = k*(k-1)/(2*n)입니다. 40비트니까 k=2^40, n=537e3을 넣으면 그쪽 결과인 0.1311이 나옵니다.
덕분에 낚였네요. 오늘 몫의 LLM 실수 고쳐 주기 할당량이 다 떨어졌습니다!
[1] https://en.wikipedia.org/wiki/Birthday_problem#Average_number_of_people_to_get_at_least_one_shared_birthday
이 스레드의 접근 방식은 README 내용과 맞지 않는 것 같습니다. 또 차단 목록을 만드는 파이썬 스크립트 [1]가 해시를 생성할 때 발견된 충돌 수를 보고한다는 점도 눈여겨보세요.
저는 그 문장을 "14만 1천 개 도메인으로 차단 목록을 만들 때는 충돌이 0건이었고, 53만 7천 개로 만들 때는 1건이었다"는 뜻으로 읽었고, 스크립트를 보면 그 숫자는 차단 목록 안에서 난 충돌에서 바로 나온 것입니다.
1: https://github.com/M-Abozaid/esp32-c3-adblock/blob/238ef7b27097e8597f39e9a5ce07a958488572a2/tools/build_blocklist.py#L106
그래서 숫자가 틀렸던 거군요, 감사합니다! 어쨌든 재미있는 탐구였어요 ㅎㅎ
둘째 도메인이 실제로 꼭 필요한 주요 도메인이라면 문제가 됩니다. HN과 adserver의 해시가 같다면 문제가 생기죠. 이건 HN에 대한 오탐이라고 할 수 있습니다.
이건 제가 위에서 설명한 것과 같은 문제로 들립니다. Google은 써야 하는데 adserver는 막아야 하는 상황이니까요. 그게 아니라면 허용 목록을 구현한 경우에만 문제가 되는 거죠? Google을 명시적으로 허용했는데 해시 충돌 때문에 adserver까지 암묵적으로 같이 허용되는 경우 말입니다.
말이 좀 불분명했네요, 제가 말한 "차단 도메인 두 개"는 원래 차단 목록에 들어 있는 도메인 두 개의 해시가 같다는 뜻이었습니다. 말씀하신 예로 치면 hn과 adserver가 둘 다 우리가 원하는 차단 목록에 있는 경우입니다.
그 경우라면 글과 다른 댓글들이 짚은 위험 하나만 중요합니다. 차단한 도메인의 해시가 접속하고 싶은 도메인의 해시와 겹쳐서 둘 다 차단되는 경우죠. 오탐이냐 미탐이냐는 구분할 필요가 없고, 어느 쪽이든 충돌 한 번이 의도하지 않은 차단 하나로 이어질 뿐입니다.
ESP32-C3를 살 생각이시라면, 외부 안테나를 연결할 수 있는 모델로 사세요. 일반 C3는 내장 안테나가 극도로 약해서, 제가 하려던 대부분의 용도에는 쓸모가 없었습니다.
그리고 리뷰를 꼭 읽어 보세요. 4MB 플래시 칩이 없는 가짜 제품이 돌아다니는 모양이거든요. 그래도 저는 2달러짜리 C3 super mini를 모험 삼아 한 무더기 샀는데, 지하실에서조차 실내용으로는 놀랄 만큼 충분한 신호 세기가 나왔습니다. (-60 dBm대)
C3와 꼭 관련 있는 얘기는 아니지만, S3도 똑같다고 말하는 사람들을 본 적이 있는데, 알고 보니 그 사람들이 가진 건 외장 안테나가 필요한 변종이었습니다. 공식 Espressif 모듈은 0201(어쩌면 0402) 저항의 위치만 다르기 때문에, 눈이 얼마나 좋은지와 뭘 봐야 하는지 아는지에 따라 겉보기엔 똑같습니다. (부품 번호도 다르긴 한데, 그걸 끝까지 읽는 건 매뉴얼을 읽는 거나 마찬가지라 대부분 안 읽죠.)
흔히 쓰는 "mini" 개발 보드에 작은 안테나를 만들어 납땜해 붙이는 방법을 설명한 자료가 있습니다. 꽤 도움이 된다고들 해요.
저는 목록을 좋아하는 편집증 환자 기질이 있어서, PiHole에 차단 도메인이 총 1,400만~1,600만 개 들어 있는 목록을 쓰고 있습니다.
좋은 아이디어이고 가벼운 차단용으로는 좋지만, 저는 거의 "허용 목록" 쪽 사고방식으로 넘어가고 있습니다. 이 솔루션은 그쪽에 더 잘 맞을 것 같은데, 인터넷의 쓸 만한 부분이 14만 개 목록에 들어갈지 궁금하네요.
목록을 공유할 수 있게 어딘가에 올려 두셨나요?
지금 (일상 틈틈이 아주 천천히) 목록을 호스팅하고 설명과 사용법도 알려 주는 사이트를 만들고 있습니다.
아직 준비가 안 됐으니, 제가 모아 온 출처를 알려 드리는 게 낫겠네요.
https://firebog.net/
https://github.com/hagezi/dns-blocklists
https://github.com/StevenBlack/hosts
https://github.com/jerryn70/GoodbyeAds
작업 중인 목록 파일 호스팅 링크: https://lists.uninvitedactivity.com/DNS/
00_Tiers 디렉터리:
목록을 Base, Recommended, Aggressive, Paranoid의 네 단계로 묶었습니다. 이런 세트가 두 벌 있는데, 하나는 신규 등록 도메인(NRD)을 포함한 것(Full 디렉터리)이고 하나는 NRD를 뺀 것(NoNRD 디렉터리)입니다. NRD는 ... 무겁거든요. Paranoid 목록은 NRD 포함 910만 개, 제외하면 370만 개입니다.
11_Allow 디렉터리:
https://discourse.pi-hole.net/t/commonly-whitelisted-domains/212 에서 가져온 허용 목록 모음입니다. 허용 목록으로는 https://github.com/anudeepND/whitelist 도 같이 씁니다.
21_SpecificTopics 디렉터리:
파일명과 같은 주제로 목록들을 모아 놓은 것입니다. Fake News 목록은 지나치게 범위가 넓은 목록이 섞여 있어서 모음에서 빼야 하니 갱신이 필요합니다.
이 정보로 neocities 페이지를 만들면 멋질 것 같네요.
솔직히 ESP32-C3의 성능을 생각하면 허용 목록이 RAM과 CPU 시간을 아껴 줄 겁니다.
"이 솔루션은 그쪽에 더 잘 맞을 것 같은데, 인터넷의 쓸 만한 부분[즉 www]이 14만 개 목록에 들어갈지 궁금하네요."
저는 거의 20년 동안 "허용 목록"을 써 왔습니다. 처음에는 권한 있는(authoritative) DNS로 했고, 프록시 소프트웨어가 DNS 데이터를 메모리에 매핑하는 기능을 지원하기 시작하면서부터는 DNS를 쓸 필요가 없어졌습니다.
제 필요를 가늠할 만큼 과거 DNS 데이터가 충분히 있는데, 14만 개보다 훨씬 적습니다.
인기 있는 차단 목록은 시간을 들여 훑어보면 꽤 이상합니다.
대부분의 사용자가 방문할 일이 없을 것 같은, 정말 "인터넷(www)의 나쁜 부분"으로 보이는 것들이 들어 있습니다. 흔한 광고·추적 도메인 말고도 더 많은 것이 이 파일들에 들어 있어요. 그러니 "비대하다"고 말할 수도 있겠죠.
이 목록 관리자들은 그런 나쁜 부분을 대체 어떻게 아는 걸까 하는 의문이 듭니다.
제 생각에 어디에나 있는 광고, 추적, 텔레메트리에 가장 크게 노출되는 경로는 서드파티 리졸버를 쓰는 겁니다(관련 HTTP 요청 직전에 실시간으로). 사용자 컴퓨터의 어떤 프로세스든 아무 도메인이나 조회할 수 있으니까요.
"차단 목록"으로 원치 않는 조회를 모조리 예상할 수 있는 척하는 건 제 생각에 위험도가 높은 접근입니다.
그래도 인기는 많죠. 취향은 제각각이니까요.
사용자마다 다릅니다. 원치 않는 도메인을 전부 예상하려는 "차단 목록"에 기대는 건 제 방식이 아닙니다. 반면 "허용 목록" 방식은 수년 동안 믿음직하게 제 몫을 해 줬어요. 해가 갈수록 얼마나 잘 작동하는지 가끔 놀랍니다.
철학적으로는 "허용 목록 대 차단 목록"의 차이가, (a) www에서 "원하는 것을 찾아가는 것"과 (b) "원치 않는 것을 피하는 것"의 차이라고 봅니다.
(a)의 양은 비교적 적고 관리할 만합니다. 하지만 (b)는 시간이 갈수록 커지기만 하는, 쓰레기로 가득한 쓰나미죠.
저는 "광고 차단"이라는 말이 늘 잘못된 이름이라고 생각했습니다. 사용자가 하고 싶은 건 "차단"이 아니라 광고·추적·텔레메트리 서버에 "요청을 보내지 않는 것"이니까요.
www(HTTP)는 요청과 응답으로 돌아갑니다. 보통 사용자가 요청을 보내고, 퍼블리셔(광고주 포함)가 요청을 받아 (대개) 응답을 보냅니다. 사용자는 대체로 요청을 받는 쪽이 아니고, 광고·추적·텔레메트리 서버에 일부러 요청(개인 정보를 POST하는 것 포함)을 보내는 사용자도 거의 없습니다. 이런 광고·추적·텔레메트리 요청을 하는 건 사용자가 아니라 "개발자"이고, 사용자에게 배포하는 소프트웨어(자바스크립트 포함)로 그 요청을 자동으로 만들어 내는 겁니다.
제 생각에 우리는 광고를 "차단"하는 게 아닙니다. 애초에 요청한 적이 없으니까요. 우리가 하는 일은 개발자가 보낸 요청이 성공하지 못하게 하는 것, 즉 우리가 한 적 없는 요청을 "막는" 것입니다.
사용자의 "주체성(agency)"을 둘러싼 논쟁으로 넘어가는 대목이네요...
화이트리스트/허용 목록이 더 쉽습니다. 예컨대 더 작거든요.
사용자에 따라 다르지만, 누구나 매일 새 웹사이트를 방문하는 건 아닙니다.
그런 사람이라 해도 필요한 도메인-IP 매핑 수는 비교적 적을 겁니다.
확실히 140,000개 미만입니다.
제가 쓰는 DNS 데이터는 대부분 "정적"이라 거의 바뀌지 않습니다. 그래서 대부분 DNS 쿼리를 할 필요가 없습니다. 도메인-IP 매핑을 프록시 메모리에 저장해 두는데, DNS보다 빠릅니다.
"차단 목록"이 필요 없어요.
Hacker News를 정기적으로 보는 사용자라면 매일 새 웹사이트를 방문할 것 같은데요. (그래도 14만 개 미만인 건 분명하겠지만요.)
어디까지나 짐작이지만, HN 사용자 상당수는 링크된 글은 아예 읽지 않고 여기 댓글란만 본다는 게 제 감입니다.