323 lines
7.3 KiB
Python
323 lines
7.3 KiB
Python
import base64
|
|
import json
|
|
|
|
from .parser import props, LP_decode, LP_pk, VLP_hashs, VLP_bootstrap_ipi
|
|
|
|
|
|
# Document
|
|
# https://dnscrypt.info/stamps-specifications
|
|
# https://dnscrypt.info/protocol
|
|
# https://github.com/DNSCrypt/dnscrypt-resolvers/blob/21fcbaf858112c63fed2a504714cc829bd654483/utils/format.py#L101-L141
|
|
|
|
def parse(stamp: str):
|
|
b = base64.urlsafe_b64decode(stamp.removeprefix("sdns://") + "==")
|
|
if b[0] == 0x01:
|
|
return DNSCrypt.parse(stamp)
|
|
elif b[0] == 0x02:
|
|
return DNSoverHTTPS.parse(stamp)
|
|
elif b[0] == 0x03:
|
|
return DNSoverTLS.parse(stamp)
|
|
elif b[0] == 0x04:
|
|
return DNSoverQUIC.parse(stamp)
|
|
elif b[0] == 0x05:
|
|
return ObliviousDoH.parse(stamp)
|
|
elif b[0] == 0x81:
|
|
return DNSCryptRelay.parse(stamp)
|
|
elif b[0] == 0x85:
|
|
return ObliviousDoHRelay.parse(stamp)
|
|
elif b[0] == 0x00:
|
|
return PlainDNS.parse(stamp)
|
|
|
|
|
|
class Base:
|
|
def to_json(self):
|
|
return json.dumps(
|
|
self,
|
|
default=lambda o: o.__dict__
|
|
)
|
|
|
|
def __str__(self):
|
|
return self.__class__.__name__ + '<' + self.to_json() + '>'
|
|
|
|
|
|
class DNSCrypt(Base):
|
|
dnssec: bool = False
|
|
nolog: bool = False
|
|
nofilter: bool = False
|
|
addr: str = None
|
|
pk: str = None
|
|
provider: str = None
|
|
|
|
@staticmethod
|
|
def parse(stamp: str):
|
|
b = base64.urlsafe_b64decode(stamp.removeprefix("sdns://") + "==")
|
|
|
|
# 0x01
|
|
i = 0
|
|
if b[i] != 0x01:
|
|
raise ValueError("DNSCrypt: 0x01")
|
|
i = i + 1
|
|
|
|
parsed = DNSCrypt()
|
|
|
|
# props
|
|
i, parsed.dnssec, parsed.nolog, parsed.nofilter = props(b, i)
|
|
|
|
# LP(addr [:port])
|
|
i, parsed.addr = LP_decode(b, i)
|
|
|
|
# LP(pk)
|
|
i, parsed.pk = LP_pk(b, i)
|
|
|
|
# LP(providerName)
|
|
i, parsed.provider = LP_decode(b, i)
|
|
|
|
return parsed
|
|
|
|
|
|
class DNSoverHTTPS(Base):
|
|
dnssec: bool = False
|
|
nolog: bool = False
|
|
nofilter: bool = False
|
|
addr: str = None
|
|
hashs: list[str] = []
|
|
hostname: str = None
|
|
path: str = None
|
|
bootstrap_ipi: list[str] = []
|
|
|
|
@staticmethod
|
|
def parse(stamp: str):
|
|
b = base64.urlsafe_b64decode(stamp.removeprefix("sdns://") + "==")
|
|
|
|
# 0x02
|
|
i = 0
|
|
if b[i] != 0x02:
|
|
raise ValueError("DNSoverHTTPS: 0x02")
|
|
i = i + 1
|
|
|
|
parsed = DNSoverHTTPS()
|
|
|
|
# props
|
|
i, parsed.dnssec, parsed.nolog, parsed.nofilter = props(b, i)
|
|
|
|
# LP(addr)
|
|
i, parsed.addr = LP_decode(b, i)
|
|
|
|
# VLP(hash1, hash2, ...hashn)
|
|
i, parsed.hashs = VLP_hashs(b, i)
|
|
|
|
# LP(hostname [:port])
|
|
i, parsed.hostname = LP_decode(b, i)
|
|
|
|
# LP(path)
|
|
i, parsed.path = LP_decode(b, i)
|
|
|
|
# VLP(bootstrap_ip1, bootstrap_ip2, ...bootstrap_ipn) (optional)
|
|
if i < len(b):
|
|
i, parsed.bootstrap_ipi = VLP_bootstrap_ipi(b, i)
|
|
|
|
return parsed
|
|
|
|
|
|
class DNSoverTLS(Base):
|
|
dnssec: bool = False
|
|
nolog: bool = False
|
|
nofilter: bool = False
|
|
addr: str = None
|
|
hashs: list[str] = []
|
|
hostname: str = None
|
|
bootstrap_ipi: list[str] = []
|
|
|
|
@staticmethod
|
|
def parse(stamp: str):
|
|
b = base64.urlsafe_b64decode(stamp.removeprefix("sdns://") + "==")
|
|
|
|
# 0x03
|
|
i = 0
|
|
if b[i] != 0x03:
|
|
raise ValueError()
|
|
i = i + 1
|
|
|
|
parsed = DNSoverTLS()
|
|
|
|
# props
|
|
i, parsed.dnssec, parsed.nolog, parsed.nofilter = props(b, i)
|
|
|
|
# LP(addr)
|
|
i, parsed.addr = LP_decode(b, i)
|
|
|
|
# VLP(hash1, hash2, ...hashn)
|
|
i, parsed.hashs = VLP_hashs(b, i)
|
|
|
|
# LP(hostname[:port])
|
|
i, parsed.hostname = LP_decode(b, i)
|
|
|
|
# VLP(bootstrap_ip1, bootstrap_ip2, ...bootstrap_ipn) (optional)
|
|
if i < len(b):
|
|
i, parsed.bootstrap_ipi = VLP_bootstrap_ipi(b, i)
|
|
|
|
return parsed
|
|
|
|
|
|
class DNSoverQUIC(Base):
|
|
dnssec: bool = False
|
|
nolog: bool = False
|
|
nofilter: bool = False
|
|
addr: str = None
|
|
hashs: list[str] = []
|
|
hostname: str = None
|
|
bootstrap_ipi: list[str] = []
|
|
|
|
@staticmethod
|
|
def parse(stamp: str):
|
|
b = base64.urlsafe_b64decode(stamp.removeprefix("sdns://") + "==")
|
|
|
|
# 0x04
|
|
i = 0
|
|
if b[i] != 0x04:
|
|
raise ValueError()
|
|
i = i + 1
|
|
|
|
parsed = DNSoverQUIC()
|
|
|
|
# props
|
|
i, parsed.dnssec, parsed.nolog, parsed.nofilter = props(b, i)
|
|
|
|
# LP(addr)
|
|
i, parsed.addr = LP_decode(b, i)
|
|
|
|
# VLP(hash1, hash2, ...hashn)
|
|
i, parsed.hashs = VLP_hashs(b, i)
|
|
|
|
# LP(hostname[:port])
|
|
i, parsed.hostname = LP_decode(b, i)
|
|
|
|
# VLP(bootstrap_ip1, bootstrap_ip2, ...bootstrap_ipn) (optional)
|
|
if i < len(b):
|
|
i, parsed.bootstrap_ipi = VLP_bootstrap_ipi(b, i)
|
|
|
|
return parsed
|
|
|
|
|
|
class ObliviousDoH(Base):
|
|
dnssec: bool = False
|
|
nolog: bool = False
|
|
nofilter: bool = False
|
|
hostname: str = None
|
|
path: str = None
|
|
|
|
@staticmethod
|
|
def parse(stamp: str):
|
|
b = base64.urlsafe_b64decode(stamp.removeprefix("sdns://") + "==")
|
|
|
|
# 0x05
|
|
i = 0
|
|
if b[i] != 0x05:
|
|
raise ValueError()
|
|
i = i + 1
|
|
|
|
parsed = ObliviousDoH()
|
|
|
|
# props
|
|
i, parsed.dnssec, parsed.nolog, parsed.nofilter = props(b, i)
|
|
|
|
# LP(hostname [:port])
|
|
i, parsed.hostname = LP_decode(b, i)
|
|
|
|
# LP(path)
|
|
i, parsed.path = LP_decode(b, i)
|
|
|
|
return parsed
|
|
|
|
|
|
class DNSCryptRelay(Base):
|
|
addr: str = None
|
|
|
|
@staticmethod
|
|
def parse(stamp):
|
|
b = base64.urlsafe_b64decode(stamp.removeprefix("sdns://") + "==")
|
|
|
|
# 0x81
|
|
i = 0
|
|
if b[i] != 0x81:
|
|
raise ValueError()
|
|
i = i + 1
|
|
|
|
parsed = DNSCryptRelay()
|
|
|
|
# LP(addr)
|
|
i, parsed.addr = LP_decode(b, i)
|
|
|
|
return parsed
|
|
|
|
|
|
class ObliviousDoHRelay(Base):
|
|
dnssec: bool = False
|
|
nolog: bool = False
|
|
nofilter: bool = False
|
|
addr: str = None
|
|
hashs: list[str] = []
|
|
hostname: str = None
|
|
path: str = None
|
|
bootstrap_ipi: list[str] = []
|
|
|
|
@staticmethod
|
|
def parse(stamp: str):
|
|
b = base64.urlsafe_b64decode(stamp.removeprefix("sdns://") + "==")
|
|
|
|
# 0x85
|
|
i = 0
|
|
if b[i] != 0x85:
|
|
raise ValueError()
|
|
i = i + 1
|
|
|
|
parsed = ObliviousDoHRelay()
|
|
|
|
# props
|
|
i, parsed.dnssec, parsed.nolog, parsed.nofilter = props(b, i)
|
|
|
|
# LP(addr)
|
|
i, parsed.addr = LP_decode(b, i)
|
|
|
|
# VLP(hash1, hash2, ...hashn)
|
|
i, parsed.hashs = VLP_hashs(b, i)
|
|
|
|
# LP(hostname [:port])
|
|
i, parsed.hostname = LP_decode(b, i)
|
|
|
|
# LP(path)
|
|
i, parsed.path = LP_decode(b, i)
|
|
|
|
# VLP(bootstrap_ip1, bootstrap_ip2, ...bootstrap_ipn) (optional)
|
|
if i < len(b):
|
|
i, parsed.bootstrap_ipi = VLP_bootstrap_ipi(b, i)
|
|
|
|
return parsed
|
|
|
|
|
|
class PlainDNS(Base):
|
|
dnssec: bool = False
|
|
nolog: bool = False
|
|
nofilter: bool = False
|
|
addr: str = None
|
|
|
|
@staticmethod
|
|
def parse(stamp: str):
|
|
b = base64.urlsafe_b64decode(stamp.removeprefix("sdns://") + "==")
|
|
|
|
# 0x00
|
|
i = 0
|
|
if b[i] != 0x00:
|
|
raise ValueError()
|
|
i = i + 1
|
|
|
|
parsed = PlainDNS()
|
|
|
|
# props
|
|
i, parsed.dnssec, parsed.nolog, parsed.nofilter = props(b, i)
|
|
|
|
# LP(addr)
|
|
i, parsed.addr = LP_decode(b, i)
|
|
|
|
return parsed
|