feat: per-domain backend routing
Each allowed_domains entry can now carry its own backend {host, port}.
That lets one proxy on port 25565 serve multiple MC servers, picking
the upstream from the domain the client typed.
- proxy/main.py: ProxyState.backend_for(domain) → tuple|None,
honors per-domain backend first, falls back to top-level backend.
handle_client uses backend_for(); blocked / disabled domains
return None (and still get a Login Disconnect on join attempts).
- api/routes/{config,domains}.py: DomainBackend model + optional
backend field on create/patch. PATCH supports clear_backend=true
to drop a per-domain override and revert to default.
- frontend/Domains.jsx: full rewrite — new-domain form has host/port
inputs, table shows each row's effective backend, inline edit +
reset button per row.
- frontend/Settings.jsx: backend section relabeled "기본 백엔드 (fallback)"
- README updated with multi-server example config.
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
- 허용 도메인 화이트리스트 매칭, 불일치 시 Login Disconnect 패킷으로 차단 사유(커스텀 가능) 표시 후 연결 종료
|
- 허용 도메인 화이트리스트 매칭, 불일치 시 Login Disconnect 패킷으로 차단 사유(커스텀 가능) 표시 후 연결 종료
|
||||||
- 통과한 연결은 백엔드 MC 서버로 투명 TCP 중계
|
- 통과한 연결은 백엔드 MC 서버로 투명 TCP 중계
|
||||||
(Fabric / Paper / Spigot / NeoForge 등 서버 종류 무관)
|
(Fabric / Paper / Spigot / NeoForge 등 서버 종류 무관)
|
||||||
|
- **도메인별 백엔드 라우팅**: 도메인마다 다른 IP:포트로 보낼 수 있어 하나의 25565 포트로 여러 MC 서버를 동시에 운영 가능 (예: `mc.tkrmagid.kr` → 게임PC:25565, `creative.tkrmagid.kr` → NAS:25566)
|
||||||
- 설정 파일(`data/config.json`) 변경을 프록시가 자동 감지해 hot reload (재시작 불필요)
|
- 설정 파일(`data/config.json`) 변경을 프록시가 자동 감지해 hot reload (재시작 불필요)
|
||||||
- 모든 연결 시도(허용 / 차단 / 에러)를 SQLite 에 기록
|
- 모든 연결 시도(허용 / 차단 / 에러)를 SQLite 에 기록
|
||||||
- 웹 대시보드 (NPM 스타일): 도메인 관리, 실시간 로그, 통계 카드, 백엔드/포트 설정
|
- 웹 대시보드 (NPM 스타일): 도메인 관리, 실시간 로그, 통계 카드, 백엔드/포트 설정
|
||||||
@@ -43,13 +44,18 @@ mc-filter-proxy 컨테이너 (25565)
|
|||||||
{
|
{
|
||||||
"proxy": { "listen_port": 25565, "enabled": true },
|
"proxy": { "listen_port": 25565, "enabled": true },
|
||||||
"backend": { "host": "192.168.0.20", "port": 25565 },
|
"backend": { "host": "192.168.0.20", "port": 25565 },
|
||||||
|
"block_message": "이 서버는 허용된 도메인에서만 접속 가능합니다.",
|
||||||
"allowed_domains": [
|
"allowed_domains": [
|
||||||
{ "domain": "mc.tkrmagid.kr", "enabled": true, "note": "메인 도메인" }
|
{ "domain": "mc.tkrmagid.kr", "enabled": true, "note": "메인 서버" },
|
||||||
|
{ "domain": "creative.tkrmagid.kr", "enabled": true, "note": "크리에이티브",
|
||||||
|
"backend": { "host": "192.168.0.21", "port": 25566 } }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
```
|
```
|
||||||
|
|
||||||
|
각 도메인 entry 에 `backend` 필드가 있으면 그 host:port 로, 없으면 top-level `backend` 로 라우팅됩니다.
|
||||||
|
|
||||||
3. 전체 스택 빌드 & 실행
|
3. 전체 스택 빌드 & 실행
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -19,10 +19,16 @@ class BackendConfig(BaseModel):
|
|||||||
port: int = Field(ge=1, le=65535)
|
port: int = Field(ge=1, le=65535)
|
||||||
|
|
||||||
|
|
||||||
|
class DomainBackend(BaseModel):
|
||||||
|
host: str
|
||||||
|
port: int = Field(ge=1, le=65535)
|
||||||
|
|
||||||
|
|
||||||
class DomainEntry(BaseModel):
|
class DomainEntry(BaseModel):
|
||||||
domain: str
|
domain: str
|
||||||
enabled: bool = True
|
enabled: bool = True
|
||||||
note: str = ""
|
note: str = ""
|
||||||
|
backend: DomainBackend | None = None # 없으면 top-level backend 로 fallback
|
||||||
|
|
||||||
|
|
||||||
class FullConfig(BaseModel):
|
class FullConfig(BaseModel):
|
||||||
|
|||||||
@@ -2,22 +2,32 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException, Response
|
from fastapi import APIRouter, HTTPException, Response
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from config_io import load_config, save_config
|
from config_io import load_config, save_config
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
class DomainBackend(BaseModel):
|
||||||
|
host: str
|
||||||
|
port: int = Field(ge=1, le=65535)
|
||||||
|
|
||||||
|
|
||||||
class DomainCreate(BaseModel):
|
class DomainCreate(BaseModel):
|
||||||
domain: str
|
domain: str
|
||||||
enabled: bool = True
|
enabled: bool = True
|
||||||
note: str = ""
|
note: str = ""
|
||||||
|
backend: DomainBackend | None = None # 비우면 기본 backend 사용
|
||||||
|
|
||||||
|
|
||||||
class DomainPatch(BaseModel):
|
class DomainPatch(BaseModel):
|
||||||
enabled: bool | None = None
|
enabled: bool | None = None
|
||||||
note: str | None = None
|
note: str | None = None
|
||||||
|
# 명시적으로 None 을 보내려면 클라이언트가 "backend": null 을 보내야 한다.
|
||||||
|
# 빠뜨리면 기존 값 유지.
|
||||||
|
backend: DomainBackend | None = None
|
||||||
|
clear_backend: bool = False # True 면 기존 backend 삭제 → 기본값으로 복귀
|
||||||
|
|
||||||
|
|
||||||
@router.get("/domains")
|
@router.get("/domains")
|
||||||
@@ -34,7 +44,9 @@ def add_domain(body: DomainCreate) -> dict:
|
|||||||
raise HTTPException(status_code=400, detail="domain required")
|
raise HTTPException(status_code=400, detail="domain required")
|
||||||
if any(d["domain"].lower() == name for d in domains):
|
if any(d["domain"].lower() == name for d in domains):
|
||||||
raise HTTPException(status_code=409, detail="domain already exists")
|
raise HTTPException(status_code=409, detail="domain already exists")
|
||||||
entry = {"domain": name, "enabled": body.enabled, "note": body.note}
|
entry: dict = {"domain": name, "enabled": body.enabled, "note": body.note}
|
||||||
|
if body.backend is not None:
|
||||||
|
entry["backend"] = body.backend.model_dump()
|
||||||
domains.append(entry)
|
domains.append(entry)
|
||||||
save_config(cfg)
|
save_config(cfg)
|
||||||
return entry
|
return entry
|
||||||
@@ -64,6 +76,10 @@ def patch_domain(domain: str, body: DomainPatch) -> dict:
|
|||||||
d["enabled"] = body.enabled
|
d["enabled"] = body.enabled
|
||||||
if body.note is not None:
|
if body.note is not None:
|
||||||
d["note"] = body.note
|
d["note"] = body.note
|
||||||
|
if body.clear_backend:
|
||||||
|
d.pop("backend", None)
|
||||||
|
elif body.backend is not None:
|
||||||
|
d["backend"] = body.backend.model_dump()
|
||||||
save_config(cfg)
|
save_config(cfg)
|
||||||
return d
|
return d
|
||||||
raise HTTPException(status_code=404, detail="domain not found")
|
raise HTTPException(status_code=404, detail="domain not found")
|
||||||
|
|||||||
@@ -1,32 +1,42 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { api } from '../api.js'
|
import { api } from '../api.js'
|
||||||
|
|
||||||
|
const blankBackend = { host: '', port: '' }
|
||||||
|
|
||||||
export default function Domains() {
|
export default function Domains() {
|
||||||
const [domains, setDomains] = useState([])
|
const [domains, setDomains] = useState([])
|
||||||
|
const [defaultBackend, setDefaultBackend] = useState(null)
|
||||||
const [newDomain, setNewDomain] = useState('')
|
const [newDomain, setNewDomain] = useState('')
|
||||||
const [newNote, setNewNote] = useState('')
|
const [newNote, setNewNote] = useState('')
|
||||||
|
const [newBackend, setNewBackend] = useState({ ...blankBackend })
|
||||||
const [error, setError] = useState(null)
|
const [error, setError] = useState(null)
|
||||||
|
const [editing, setEditing] = useState(null) // {domain, host, port}
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
try {
|
try {
|
||||||
setDomains(await api.domains())
|
const [doms, cfg] = await Promise.all([api.domains(), api.config()])
|
||||||
|
setDomains(doms)
|
||||||
|
setDefaultBackend(cfg.backend)
|
||||||
setError(null)
|
setError(null)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e.message)
|
setError(e.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => { load() }, [])
|
||||||
load()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
async function add(e) {
|
async function add(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!newDomain.trim()) return
|
if (!newDomain.trim()) return
|
||||||
try {
|
try {
|
||||||
await api.addDomain({ domain: newDomain.trim(), enabled: true, note: newNote })
|
const body = { domain: newDomain.trim(), enabled: true, note: newNote }
|
||||||
|
if (newBackend.host.trim() && newBackend.port) {
|
||||||
|
body.backend = { host: newBackend.host.trim(), port: +newBackend.port }
|
||||||
|
}
|
||||||
|
await api.addDomain(body)
|
||||||
setNewDomain('')
|
setNewDomain('')
|
||||||
setNewNote('')
|
setNewNote('')
|
||||||
|
setNewBackend({ ...blankBackend })
|
||||||
setError(null)
|
setError(null)
|
||||||
await load()
|
await load()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -38,9 +48,7 @@ export default function Domains() {
|
|||||||
try {
|
try {
|
||||||
await api.patchDomain(d.domain, { enabled: !d.enabled })
|
await api.patchDomain(d.domain, { enabled: !d.enabled })
|
||||||
await load()
|
await load()
|
||||||
} catch (e) {
|
} catch (e) { setError(e.message) }
|
||||||
setError(e.message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function remove(d) {
|
async function remove(d) {
|
||||||
@@ -48,66 +56,163 @@ export default function Domains() {
|
|||||||
try {
|
try {
|
||||||
await api.deleteDomain(d.domain)
|
await api.deleteDomain(d.domain)
|
||||||
await load()
|
await load()
|
||||||
} catch (e) {
|
} catch (e) { setError(e.message) }
|
||||||
setError(e.message)
|
}
|
||||||
}
|
|
||||||
|
function startEdit(d) {
|
||||||
|
setEditing({
|
||||||
|
domain: d.domain,
|
||||||
|
host: d.backend?.host ?? '',
|
||||||
|
port: d.backend?.port ?? '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveEdit() {
|
||||||
|
try {
|
||||||
|
const { domain, host, port } = editing
|
||||||
|
if (!host.trim() || !port) {
|
||||||
|
// 둘 다 비우면 backend 삭제 → 기본값으로 폴백
|
||||||
|
await api.patchDomain(domain, { clear_backend: true })
|
||||||
|
} else {
|
||||||
|
await api.patchDomain(domain, {
|
||||||
|
backend: { host: host.trim(), port: +port },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setEditing(null)
|
||||||
|
await load()
|
||||||
|
} catch (e) { setError(e.message) }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearBackend(d) {
|
||||||
|
if (!confirm(`${d.domain} 의 개별 backend 설정을 지우고 기본값으로 되돌릴까요?`)) return
|
||||||
|
try {
|
||||||
|
await api.patchDomain(d.domain, { clear_backend: true })
|
||||||
|
await load()
|
||||||
|
} catch (e) { setError(e.message) }
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>허용 도메인</h1>
|
<h1>허용 도메인</h1>
|
||||||
{error && <div className="error">{error}</div>}
|
{error && <div className="error">{error}</div>}
|
||||||
<form onSubmit={add} className="form-row card">
|
|
||||||
<input
|
<form onSubmit={add} className="card">
|
||||||
placeholder="mc.example.com"
|
<h2>새 도메인</h2>
|
||||||
value={newDomain}
|
<div className="form-grid">
|
||||||
onChange={(e) => setNewDomain(e.target.value)}
|
<label>
|
||||||
/>
|
도메인
|
||||||
<input
|
<input
|
||||||
placeholder="메모 (선택)"
|
placeholder="mc.example.com"
|
||||||
value={newNote}
|
value={newDomain}
|
||||||
onChange={(e) => setNewNote(e.target.value)}
|
onChange={(e) => setNewDomain(e.target.value)}
|
||||||
/>
|
required
|
||||||
<button type="submit">추가</button>
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
메모 (선택)
|
||||||
|
<input
|
||||||
|
placeholder="메인 서버, 모드팩 등"
|
||||||
|
value={newNote}
|
||||||
|
onChange={(e) => setNewNote(e.target.value)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
백엔드 호스트 (선택)
|
||||||
|
<input
|
||||||
|
placeholder={defaultBackend ? `기본값: ${defaultBackend.host}` : ''}
|
||||||
|
value={newBackend.host}
|
||||||
|
onChange={(e) => setNewBackend({ ...newBackend, host: e.target.value })}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
백엔드 포트 (선택)
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
placeholder={defaultBackend ? `기본값: ${defaultBackend.port}` : ''}
|
||||||
|
value={newBackend.port}
|
||||||
|
onChange={(e) => setNewBackend({ ...newBackend, port: e.target.value })}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="actions">
|
||||||
|
<button type="submit">추가</button>
|
||||||
|
<span className="muted">백엔드 호스트/포트를 비워두면 설정 페이지의 기본 backend 로 라우팅됩니다.</span>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<table className="table">
|
<table className="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>도메인</th>
|
<th>도메인</th>
|
||||||
<th>메모</th>
|
<th>메모</th>
|
||||||
|
<th>백엔드</th>
|
||||||
<th style={{ width: 80 }}>활성</th>
|
<th style={{ width: 80 }}>활성</th>
|
||||||
<th style={{ width: 90 }}></th>
|
<th style={{ width: 200 }}></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{domains.length === 0 && (
|
{domains.length === 0 && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={4} className="muted" style={{ textAlign: 'center', padding: 24 }}>
|
<td colSpan={5} className="muted" style={{ textAlign: 'center', padding: 24 }}>
|
||||||
등록된 도메인이 없습니다.
|
등록된 도메인이 없습니다.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
{domains.map((d) => (
|
{domains.map((d) => {
|
||||||
<tr key={d.domain}>
|
const isEditing = editing?.domain === d.domain
|
||||||
<td>
|
return (
|
||||||
<code>{d.domain}</code>
|
<tr key={d.domain}>
|
||||||
</td>
|
<td><code>{d.domain}</code></td>
|
||||||
<td>{d.note}</td>
|
<td>{d.note}</td>
|
||||||
<td>
|
<td>
|
||||||
<label className="switch">
|
{isEditing ? (
|
||||||
<input
|
<div className="inline-edit">
|
||||||
type="checkbox"
|
<input
|
||||||
checked={d.enabled}
|
placeholder="host"
|
||||||
onChange={() => toggle(d)}
|
value={editing.host}
|
||||||
/>
|
onChange={(e) => setEditing({ ...editing, host: e.target.value })}
|
||||||
<span />
|
/>
|
||||||
</label>
|
<input
|
||||||
</td>
|
type="number"
|
||||||
<td>
|
placeholder="port"
|
||||||
<button className="danger" onClick={() => remove(d)}>삭제</button>
|
value={editing.port}
|
||||||
</td>
|
onChange={(e) => setEditing({ ...editing, port: e.target.value })}
|
||||||
</tr>
|
style={{ width: 90 }}
|
||||||
))}
|
/>
|
||||||
|
</div>
|
||||||
|
) : d.backend ? (
|
||||||
|
<code>{d.backend.host}:{d.backend.port}</code>
|
||||||
|
) : (
|
||||||
|
<span className="muted">
|
||||||
|
기본값 ({defaultBackend ? `${defaultBackend.host}:${defaultBackend.port}` : '-'})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<label className="switch">
|
||||||
|
<input type="checkbox" checked={d.enabled} onChange={() => toggle(d)} />
|
||||||
|
<span />
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{isEditing ? (
|
||||||
|
<>
|
||||||
|
<button onClick={saveEdit}>저장</button>
|
||||||
|
<button className="ghost" onClick={() => setEditing(null)} style={{ marginLeft: 4 }}>취소</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button className="ghost" onClick={() => startEdit(d)}>백엔드</button>
|
||||||
|
{d.backend && (
|
||||||
|
<button className="ghost" onClick={() => clearBackend(d)} style={{ marginLeft: 4 }}>초기화</button>
|
||||||
|
)}
|
||||||
|
<button className="danger" onClick={() => remove(d)} style={{ marginLeft: 4 }}>삭제</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -69,7 +69,11 @@ export default function Settings() {
|
|||||||
</label>
|
</label>
|
||||||
</section>
|
</section>
|
||||||
<section className="card">
|
<section className="card">
|
||||||
<h2>백엔드 (실제 MC 서버)</h2>
|
<h2>기본 백엔드 (도메인별 backend 가 없을 때 fallback)</h2>
|
||||||
|
<p className="muted" style={{ marginTop: 0 }}>
|
||||||
|
허용 도메인 페이지에서 각 도메인에 별도 backend (host:port) 를 지정할 수 있습니다.
|
||||||
|
지정하지 않은 도메인은 여기 값으로 라우팅됩니다.
|
||||||
|
</p>
|
||||||
<label>
|
<label>
|
||||||
호스트 (IP 또는 hostname)
|
호스트 (IP 또는 hostname)
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -189,6 +189,22 @@ textarea:focus {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.form-row input { flex: 1; }
|
.form-row input { flex: 1; }
|
||||||
|
.form-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.form-grid label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.form-grid input { width: 100%; }
|
||||||
|
.inline-edit { display: flex; gap: 6px; }
|
||||||
|
.inline-edit input { padding: 4px 8px; font-size: 13px; }
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -129,9 +129,31 @@ class ProxyState:
|
|||||||
return cfg_mod.allowed_domain_set(self.cfg)
|
return cfg_mod.allowed_domain_set(self.cfg)
|
||||||
|
|
||||||
def backend(self) -> tuple[str, int]:
|
def backend(self) -> tuple[str, int]:
|
||||||
|
"""기본 백엔드 (도메인 entry 에 backend 가 없을 때 fallback)."""
|
||||||
b = self.cfg["backend"]
|
b = self.cfg["backend"]
|
||||||
return b["host"], int(b["port"])
|
return b["host"], int(b["port"])
|
||||||
|
|
||||||
|
def backend_for(self, domain: str) -> tuple[str, int] | None:
|
||||||
|
"""주어진 도메인이 활성 화이트리스트에 있으면 라우팅 대상을 돌려준다.
|
||||||
|
|
||||||
|
도메인 entry 에 `backend.host`/`backend.port` 가 있으면 그 값을 우선,
|
||||||
|
없으면 top-level `backend` 로 fallback. 도메인이 비활성이거나 없으면
|
||||||
|
None.
|
||||||
|
"""
|
||||||
|
d = domain.lower().strip()
|
||||||
|
for entry in self.cfg.get("allowed_domains", []):
|
||||||
|
if entry["domain"].lower().strip() != d:
|
||||||
|
continue
|
||||||
|
if not entry.get("enabled", True):
|
||||||
|
return None
|
||||||
|
be = entry.get("backend") or {}
|
||||||
|
host = (be.get("host") or "").strip()
|
||||||
|
port = be.get("port")
|
||||||
|
if host and port:
|
||||||
|
return host, int(port)
|
||||||
|
return self.backend()
|
||||||
|
return None
|
||||||
|
|
||||||
def enabled(self) -> bool:
|
def enabled(self) -> bool:
|
||||||
return bool(self.cfg.get("proxy", {}).get("enabled", True))
|
return bool(self.cfg.get("proxy", {}).get("enabled", True))
|
||||||
|
|
||||||
@@ -212,8 +234,8 @@ async def handle_client(
|
|||||||
return
|
return
|
||||||
|
|
||||||
domain = hs.server_address.lower().strip()
|
domain = hs.server_address.lower().strip()
|
||||||
allowed = state.allowed()
|
target = state.backend_for(domain)
|
||||||
if domain not in allowed:
|
if target is None:
|
||||||
log_event(client_ip, domain, hs.next_state, "blocked", "domain not allowed")
|
log_event(client_ip, domain, hs.next_state, "blocked", "domain not allowed")
|
||||||
log.info(
|
log.info(
|
||||||
"BLOCK %s domain=%r next_state=%d", client_ip, domain, hs.next_state
|
"BLOCK %s domain=%r next_state=%d", client_ip, domain, hs.next_state
|
||||||
@@ -230,7 +252,7 @@ async def handle_client(
|
|||||||
client_writer.close()
|
client_writer.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
backend_host, backend_port = state.backend()
|
backend_host, backend_port = target
|
||||||
try:
|
try:
|
||||||
backend_reader, backend_writer = await asyncio.wait_for(
|
backend_reader, backend_writer = await asyncio.wait_for(
|
||||||
asyncio.open_connection(backend_host, backend_port), timeout=5
|
asyncio.open_connection(backend_host, backend_port), timeout=5
|
||||||
|
|||||||
Reference in New Issue
Block a user