구독 가져오기를 마치고 v2rayN의 라우팅을 조정하려는 사용자를 위한 글입니다. V2Ray/Xray의 routing 설정을 바탕으로 도메인, IP, 분류 데이터와 규칙 순서를 설명하고, ‘키워드 매칭’과 실제로 존재하지 않는 기본 keyword: 접두사를 구분합니다. 최소 규칙을 작성하고 상위 규칙에 가려진 예외를 찾은 뒤, 실제 연결 로그로 아웃바운드를 확인할 수 있습니다.
1. 라우팅 규칙은 구독 노드가 아닌 연결을 처리합니다
구독 가져오기는 서버 주소, 인증 정보, 전송 매개변수를 설정하고, 라우팅은 각 연결을 어느 아웃바운드로 보낼지 결정합니다. VMess와 VLESS는 프록시 프로토콜이고 routing.rules는 트래픽을 선택하는 규칙이므로 서로 대체할 수 없습니다. 구독 업데이트가 완료되어도 사용자 지정 라우팅이 현재 설정에 적용됐다는 뜻은 아닙니다.
라우팅에서 확인하는 대상은 도메인일 수도 있고 IP 주소만 있을 수도 있습니다. 앱이 프록시 프로토콜을 통해 도메인을 전달하면 도메인 규칙을 바로 적용할 수 있지만, 앱이 직접 이름을 조회한 뒤 IP만 전달하면 도메인 규칙의 매칭 근거가 없을 수 있습니다. 지원되는 트래픽에서는 스니핑으로 도메인 정보를 복원할 수 있지만, 모든 암호화 트래픽에서 웹사이트 이름을 알아낼 수 있는 것은 아닙니다.
type: "field": 이 글에서 사용하는 필드 매칭 규칙 유형입니다.domain,ip,port: 대상 연결의 매칭 조건을 지정합니다.outboundTag: 이미 존재하는 아웃바운드 태그를 참조합니다. 서버 주소를 직접 입력하는 항목이 아닙니다.- 규칙은 노드의 TLS, 전송 경로 또는 인증 매개변수를 변경하지 않습니다. 연결에 실패하면 해당 설정도 따로 확인해야 합니다.
2. domain 문법: 정확한 일치, 하위 도메인, 키워드, 정규식
domain은 문자열 배열이며, 배열 안의 항목은 OR 조건입니다. 하나라도 일치하면 이 필드의 조건을 충족합니다. 범위가 명확한 문법을 우선 사용하세요. 호스트 하나만 일치시키려면 full:, 루트 도메인과 하위 도메인을 모두 포함하려면 domain:을 사용하고, 문자열 검색이 꼭 필요할 때만 접두사 없는 문자열을 사용합니다.
| 문법 | 매칭 범위 | 주의 사항 |
|---|---|---|
full:example.com | example.com만 | www.example.com은 포함하지 않음 |
domain:example.com | example.com 및 하위 도메인 | notexample.com은 매칭하지 않음 |
example | 도메인에 포함된 example 문자열 | 서로 관계없는 여러 사이트가 함께 매칭될 수 있음 |
regexp:^api[0-9]+\.example\.com$ | 예: api1.example.com | JSON에서는 역슬래시를 이스케이프해야 함 |
geosite:cn | 데이터 파일의 cn 분류에 포함된 도메인 | 실제로 설치된 데이터 파일에 따라 다름 |
keyword:는 기본 지원 접두사가 아닙니다
일부 규칙 편집기에서는 키워드 옵션을 keyword라고 표시하지만, V2Ray/Xray 기본 domain 배열에서 키워드 매칭은 일반 문자열로 지정합니다. keyword:example을 범용 문법처럼 복사해 사용하지 마세요. ‘도메인에 example이 포함됨’이라는 의도대로 자동 처리되지 않습니다. GUI의 유형 선택과 커널 JSON은 서로 다른 단계이므로 최종 생성된 규칙을 확인해야 합니다.
{
"type": "field",
"domain": [
"full:login.example.com",
"domain:docs.example.com",
"regexp:^api[0-9]+\\.example\\.com$"
],
"outboundTag": "proxy"
}
이 예시는 전체 클라이언트 설정이 아니라 규칙 객체 하나입니다. 세 항목은 각각 로그인 호스트, 문서 도메인과 하위 도메인, 숫자가 포함된 API 호스트를 처리합니다. 정규식의 \\.은 JSON 문자열 표기이며, 디코딩된 뒤 정규식 엔진에 전달되는 값은 \.입니다. 클라이언트의 일반 텍스트 정규식 입력란에 입력할 때는 해당 입력란의 형식을 따르고 역슬래시를 기계적으로 한 번 더 추가하지 마세요.
요점: 예외 도메인에는 full: 사용
로그인 또는 결제 호스트만 따로 조정하려면 먼저 정확한 도메인 규칙을 작성하고 넓은 범위의 분류 규칙보다 위에 배치하세요. 짧은 키워드로 사이트 전체를 매칭하면 이름이 비슷한 무관한 도메인까지 다른 경로로 보낼 수 있습니다.
3. ip·geoip·geosite: 주소 범위와 도메인 목록은 따로 지정
ip에는 IP 주소, CIDR 대역, geoip: 데이터 참조를 사용할 수 있고, domain에는 도메인 규칙과 geosite: 참조를 사용할 수 있습니다. 두 데이터는 서로 다른 기준입니다. 도메인 분류는 이름이 어떤 집합에 속하는지 나타내고, IP 분류는 대상 주소가 어떤 주소 집합에 포함되는지 나타냅니다. 웹사이트 콘텐츠, 회사 소유 여부 또는 실제 서버 위치를 보장하지는 않습니다.
도메인 분류 규칙
- 필드
domain- 참조
geosite:cn- 리소스
geosite.dat- 전제 조건
- 라우팅에서 대상 도메인을 확인할 수 있어야 함
분류 이름이 현재 데이터 파일에 있어야 합니다. 이름만 보고 사용할 수 있다고 판단하지 마세요.
IP 분류 규칙
- 필드
ip- 참조
geoip:private- 리소스
geoip.dat- 전제 조건
- 대상이 IP이거나 조회 결과를 확보해야 함
분류 내용은 데이터 파일에 따라 달라집니다. 특정 주소를 테스트할 때는 명확한 CIDR을 먼저 사용하세요.
아래 예시는 도메인 직접 연결 규칙과 사설 주소 직접 연결 규칙을 따로 작성해 ‘어느 한 집합에 해당하면 직접 연결’하도록 합니다. domain과 ip를 한 규칙에 넣으면 둘 중 하나가 아니라 두 필드 조건을 모두 충족해야 합니다.
{
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"domain": ["geosite:cn"],
"outboundTag": "direct"
},
{
"type": "field",
"ip": ["geoip:private", "192.0.2.10/32"],
"outboundTag": "direct"
}
]
}
}
192.0.2.10/32는 문서에 사용하는 단일 IPv4 예시 주소이며 실제 서비스 주소가 아닙니다. 테스트할 때는 실제 대상으로 바꾸세요.- IPv4의
/32는 단일 주소를 매칭합니다. 단일 IPv6 주소에는/128을 사용하고, 네트워크 대역에는 실제 프리픽스 길이를 지정하세요. geoip:cn과geosite:cn은 서로 다릅니다. 같은 도메인도 DNS 조회 결과에 따라 서로 다른 IP 집합에 포함될 수 있습니다.- 분류 로드 오류가 발생하면 먼저 커널 실행 환경에서 해당 데이터 파일을 찾을 수 있는지 확인한 다음 분류 이름을 점검하세요. 규칙 위치만 계속 바꾸지 마세요.
4. 우선순위: 위에서 아래로 검사하고 첫 매칭에서 종료
라우팅이 더 ‘구체적인’ 규칙을 자동으로 앞에 배치하지는 않습니다. 매번 rules 배열 순서대로 검사하며, 규칙이 하나 일치하면 해당 아웃바운드를 선택합니다. 정확한 도메인 규칙이라도 이미 매칭된 분류 규칙 아래에 있으면 추가로 적용되지 않습니다.
{
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"domain": ["full:login.example.com"],
"outboundTag": "proxy"
},
{
"type": "field",
"ip": ["geoip:private"],
"outboundTag": "direct"
},
{
"type": "field",
"domain": ["geosite:cn"],
"outboundTag": "direct"
},
{
"type": "field",
"network": "tcp,udp",
"outboundTag": "proxy"
}
]
}
}
위 예시는 routing 부분만 보여 줍니다. 전체 설정에 proxy와 direct 태그가 있는 아웃바운드가 이미 존재한다고 가정합니다. 이 이름들은 예시 태그이지 모든 클라이언트에서 고정으로 사용하는 이름이 아닙니다. 특히 direct라는 태그만 적는다고 직접 연결 아웃바운드가 자동 생성되는 것은 아닙니다.
- 먼저 로그인 호스트 하나만 프록시로 보내는 등 명확한 예외를 배치합니다.
- 다음으로 명시적인 LAN 대상과 같은 기본 네트워크 규칙을 배치합니다.
- 그 뒤에 범위가 넓은 도메인 분류 또는 IP 분류를 배치합니다.
- 마지막으로 기본 동작을 정합니다. 어떤 규칙에도 일치하지 않으면 V2Ray/Xray는 일반적으로 아웃바운드 목록의 첫 번째 항목을 사용합니다.
일반적으로 필드 간에는 AND 조건이 적용되고, 각 필드의 배열 안에서는 OR 조건이 적용됩니다. 예를 들어 한 규칙에 domain: ["domain:example.com"]과 port: "443"을 함께 지정하면 해당 도메인과 하위 도메인의 대상 포트 443 연결만 처리합니다. 여기서 포트는 대상 포트이며 클라이언트의 로컬 수신 포트가 아닙니다.
5. domainStrategy: DNS 조회 여부와 조회 시점
routing.domainStrategy는 라우팅 단계에서 도메인 조회 결과를 어떻게 사용할지 결정합니다. 브라우저의 DNS 설정이 아니며 아웃바운드 모듈에 있는 같은 이름의 옵션을 대체하지도 않습니다. 값을 변경하면 매칭 결과와 조회 시점이 달라질 수 있지만, 규칙 배열의 기본 우선순위는 바뀌지 않습니다.
| 값 | 라우팅 단계의 동작 | 확인할 항목 |
|---|---|---|
AsIs | 라우팅 매칭을 위해 도메인을 직접 조회하지 않음 | 원래 대상이 IP라면 ip 규칙은 여전히 매칭 가능 |
IPIfNonMatch | 도메인 요청이 첫 검사에서 매칭되지 않으면 조회 후 규칙을 다시 검사 | 먼저 도메인 규칙이나 기본 규칙에 매칭되면 이 절차는 실행되지 않음 |
IPOnDemand | 도메인 요청을 검사하다 IP 판정이 필요한 규칙에 도달하면 필요에 따라 조회 | 앞쪽의 IP 규칙이 뒤쪽 도메인 규칙보다 먼저 매칭될 수 있음 |
예를 들어 geoip:cn을 도메인 프록시 규칙보다 앞에 둔 경우를 살펴보겠습니다. IPOnDemand를 사용하면 앞의 IP 조건에서 DNS 조회가 실행될 수 있으며, 주소가 해당 집합에 속하면 직접 연결이 선택됩니다. AsIs에서는 도메인 요청이 이 IP 규칙을 위해 자동 조회되지 않으므로 뒤의 도메인 규칙에 매칭될 수 있습니다. 결과가 다른 것은 규칙 순서가 무시된 것이 아니라 매칭에 사용할 수 있는 데이터가 달라졌기 때문입니다.
다음은 IPIfNonMatch입니다. 첫 번째 검사에서 도메인 규칙이 매칭되지 않고 다른 규칙도 요청을 처리하지 않으면, 그때 커널이 도메인을 조회한 뒤 규칙을 다시 검사합니다. 인바운드에 도착할 때 대상이 이미 IP라면 먼저 도메인으로 검사할 필요가 없습니다. 문제를 확인할 때는 모든 연결을 도메인 요청이라고 가정하지 말고 입력 대상 유형부터 기록하세요.
- 명확한 도메인 기반 라우팅만 필요하다면 먼저
AsIs로 이해하기 쉬운 최소 설정을 구성하세요. - ‘도메인 규칙을 먼저 적용하고, 매칭되지 않으면 IP로 판단’하려면
IPIfNonMatch와 앞에서 요청을 처리하는 기본 규칙이 있는지 확인하세요. - 검사 중 조회된 IP를 사용해야 한다면
IPOnDemand를 고려하고 앞쪽의 광범위한 IP 분류 규칙도 다시 확인하세요. - 라우팅 단계의 DNS 조회에는 커널 DNS 메커니즘이 사용됩니다. 조회 결과가 잘못되거나 실패하거나 앱이 처음 조회한 결과와 다르면 분류 판단에 영향을 줄 수 있습니다.
6. v2rayN 사용자 지정 규칙 적용 확인
GUI의 라우팅 구성, 실행 중인 커널 설정, 디스크에 있는 편집 파일은 서로 다른 내용일 수 있습니다. 작업 전에 현재 라우팅 구성을 저장하세요. v2rayN 메인 창에서 현재 구성을 확인한 다음 생성된 설정의 routing.domainStrategy와 routing.rules를 점검합니다. GUI 배치에 따라 메뉴 이름이 다를 수 있으니 필드와 실제 실행 설정을 기준으로 확인하세요. 임시 생성 파일을 직접 편집해도 변경 사항이 계속 유지되지는 않습니다.
- 테스트 규칙 하나를 만듭니다. 직접 접속할 수 있는 전체 호스트명을 골라
full:로 지정하고 규칙 맨 위에 배치한 뒤, 이미 존재하는 아웃바운드 태그를 연결합니다. 처음에는 정규식이나 분류 데이터를 함께 사용하지 마세요. - 설정을 저장하고 다시 적용합니다. 클라이언트에서 설정을 다시 불러오거나 커널을 재시작해 변경 사항을 현재 실행 설정에 반영합니다. 시작 로그에 알 수 없는 필드, 아웃바운드 누락, 분류 로드 실패 등의 오류가 없는지 확인하세요.
- 새 연결을 만듭니다. 해당 웹페이지를 닫았다가 다시 열고, 필요하면 테스트 앱도 재시작합니다. HTTP/2 장기 연결이나 연결 재사용으로 이전 아웃바운드가 계속 사용될 수 있으므로 페이지 새로고침만으로 새 연결이 만들어진다고 볼 수 없습니다.
- 실제 로그를 확인합니다. 액세스 로그를 켜고 대상과 아웃바운드 태그를 확인하세요. 필요하면 잠시 로그 수준을 높입니다. 로그 형식은 커널마다 다르므로 웹페이지가 열리는지만 보고 어떤 규칙이 적용됐는지 판단해서는 안 됩니다.
- 복잡한 조건을 하나씩 다시 추가합니다. 정확한 도메인 규칙이 적용되는 것을 확인한 다음 도메인 분류, IP 조건, DNS 정책을 추가합니다. 매번 변수 하나만 바꾸고 변경 전후의 로그를 비교하세요.
브라우저 요청에는 메인 사이트, 정적 리소스, 로그인 API, 미디어 도메인이 함께 포함될 수 있습니다. 웹페이지 하나가 정상적으로 열려도 모든 하위 연결이 같은 아웃바운드를 사용한다는 뜻은 아닙니다. 로그를 확인할 때는 대상 호스트별로 살펴보고 페이지의 모든 요청을 하나의 연결로 취급하지 마세요.
domain:example.com을 썼는데 왜 다른 도메인까지 같은 경로로 연결되나요?
저장된 항목에서 domain:이 빠지지 않았는지 먼저 확인하세요. 일반 문자열 example.com은 부분 문자열 매칭이라 범위가 더 넓습니다. 호스트 하나만 매칭하려면 full:example.com으로 바꾸고 설정을 다시 적용한 뒤 새 연결을 만드세요.
geosite 규칙을 저장했는데 커널 시작 시 오류가 발생하는 이유는 무엇인가요?
시작 로그에 분류를 찾을 수 없거나 데이터 파일을 읽을 수 없다는 메시지가 있는지 확인한 다음, 현재 커널의 데이터 리소스 경로와 실제 분류 이름을 점검하세요. 먼저 full: 규칙으로 기본 라우팅을 테스트하고 리소스 로드 실패를 노드를 사용할 수 없는 문제와 혼동하지 마세요.
IPIfNonMatch를 켰는데도 IP 규칙이 적용되지 않나요?
앞쪽의 도메인 규칙이 이미 매칭됐는지, 마지막에 network: "tcp,udp" 같은 기본 규칙이 있는지 확인하세요. 첫 번째 검사에서 이미 매칭되면 이 설정에 따른 DNS 조회와 재검사는 실행되지 않습니다. 그다음 요청이 커널에 들어올 때 도메인이었는지 IP였는지 확인하세요.
포트 53을 직접 연결로 지정하면 모든 DNS를 제어할 수 있나요?
아닙니다. 대상 포트 53 규칙은 조건에 맞는 일반 DNS 연결만 처리합니다. 암호화 DNS는 다른 포트를 사용할 수 있고 커널 자체의 DNS 조회도 별도 경로로 처리됩니다. 포트 규칙 하나만 추가하지 말고 앱의 DNS 설정과 커널 DNS 설정을 함께 확인하세요.
요점: 대상·순서·아웃바운드 세 가지를 기록해 확인
설정을 바꿀 때마다 ‘테스트 호스트명, 규칙 위치, 로그에 표시된 아웃바운드 태그’를 기록하세요. 정확한 규칙이 적용되는 것을 확인한 뒤 분류 집합을 추가하면 문법 오류, 매칭 범위 오류, DNS 조회 시점 문제를 구분해 해결할 수 있습니다.