First commit
This commit is contained in:
commit
7541763c24
3 changed files with 236 additions and 0 deletions
206
doh-proxy.go
Normal file
206
doh-proxy.go
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProtocolType represents the type of DNS protocol
|
||||||
|
type ProtocolType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Enum-like constants for ProtocolType
|
||||||
|
TCP ProtocolType = iota
|
||||||
|
UDP
|
||||||
|
)
|
||||||
|
|
||||||
|
// DoHProxy represents the DNS-over-HTTPS proxy
|
||||||
|
type DoHProxy struct {
|
||||||
|
listenAddress string
|
||||||
|
port string
|
||||||
|
upstreamURLs []string
|
||||||
|
client *http.Client
|
||||||
|
protocols []ProtocolType // List of protocols to listen on
|
||||||
|
logRequests bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDoHProxy initializes a new DoHProxy instance
|
||||||
|
func NewDoHProxy(listenAddress, port string, upstreamURLs []string, protocols []ProtocolType, logRequests bool) *DoHProxy {
|
||||||
|
// HTTP client with support for HTTP/2
|
||||||
|
transport := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{},
|
||||||
|
}
|
||||||
|
|
||||||
|
http2.ConfigureTransport(transport) // Enable HTTP/2 support
|
||||||
|
|
||||||
|
// HTTP client for DoH requests
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
Timeout: 5 * time.Second, // Set a timeout for requests
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DoHProxy{
|
||||||
|
listenAddress: listenAddress,
|
||||||
|
port: port,
|
||||||
|
upstreamURLs: upstreamURLs,
|
||||||
|
client: client,
|
||||||
|
protocols: protocols,
|
||||||
|
logRequests: logRequests,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleDNSRequest handles incoming DNS requests and forwards them to DoH servers
|
||||||
|
func (p *DoHProxy) HandleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
|
dnsQuery, err := r.Pack()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to pack DNS request: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var response *http.Response
|
||||||
|
var currentUpstream string
|
||||||
|
|
||||||
|
// Send the DNS query to each upstream DoH server until one succeeds
|
||||||
|
for _, upstream := range p.upstreamURLs {
|
||||||
|
req, _ := http.NewRequest("POST", upstream, bytes.NewBuffer(dnsQuery))
|
||||||
|
req.Header.Set("Content-Type", "application/dns-message")
|
||||||
|
req.Header.Set("Accept", "application/dns-message")
|
||||||
|
|
||||||
|
response, err = p.client.Do(req)
|
||||||
|
if err == nil && response.StatusCode == http.StatusOK {
|
||||||
|
currentUpstream = upstream
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if currentUpstream == "" {
|
||||||
|
if p.logRequests {
|
||||||
|
log.Printf("Failed to query any upstream")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if response == nil {
|
||||||
|
log.Printf("All upstream DoH servers failed")
|
||||||
|
dns.HandleFailed(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
// Read the response body
|
||||||
|
body, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to read response from upstream: %v", err)
|
||||||
|
dns.HandleFailed(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack the DNS response and send it back to the client
|
||||||
|
dnsResponse := new(dns.Msg)
|
||||||
|
if err := dnsResponse.Unpack(body); err != nil {
|
||||||
|
log.Printf("Failed to unpack DNS response: %v", err)
|
||||||
|
dns.HandleFailed(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.logRequests {
|
||||||
|
log.Println("Successfully proxied request through ", currentUpstream)
|
||||||
|
}
|
||||||
|
w.WriteMsg(dnsResponse)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run starts the DNS server and listens for incoming queries based on the protocol
|
||||||
|
func (p *DoHProxy) Run() {
|
||||||
|
for _, proto := range p.protocols {
|
||||||
|
switch proto {
|
||||||
|
case TCP:
|
||||||
|
go func() {
|
||||||
|
server := &dns.Server{Addr: net.JoinHostPort(p.listenAddress, p.port), Net: "tcp"}
|
||||||
|
dns.HandleFunc(".", p.HandleDNSRequest)
|
||||||
|
|
||||||
|
log.Printf("Starting DoH Proxy on %s:%s over TCP", p.listenAddress, p.port)
|
||||||
|
if err := server.ListenAndServe(); err != nil {
|
||||||
|
log.Fatalf("Failed to start DNS server (TCP): %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
case UDP:
|
||||||
|
go func() {
|
||||||
|
server := &dns.Server{Addr: net.JoinHostPort(p.listenAddress, p.port), Net: "udp"}
|
||||||
|
dns.HandleFunc(".", p.HandleDNSRequest)
|
||||||
|
|
||||||
|
log.Printf("Starting DoH Proxy on %s:%s over UDP", p.listenAddress, p.port)
|
||||||
|
if err := server.ListenAndServe(); err != nil {
|
||||||
|
log.Fatalf("Failed to start DNS server (UDP): %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the main goroutine running indefinitely
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Define flags using the flag library
|
||||||
|
listenAddress := flag.String("l", "127.0.0.1", "Listen address for the DNS server")
|
||||||
|
port := flag.String("p", "53", "Port for the DNS server")
|
||||||
|
|
||||||
|
// Define flags for protocols
|
||||||
|
tcpFlag := flag.Bool("tcp", false, "Listen on TCP")
|
||||||
|
udpFlag := flag.Bool("udp", false, "Listen on UDP")
|
||||||
|
|
||||||
|
// Define flag for logging
|
||||||
|
logFlag := flag.Bool("log", false, "Log each request proxied through an upstream")
|
||||||
|
|
||||||
|
var upstreamURLs []string
|
||||||
|
|
||||||
|
// Custom flag for handling multiple upstream URLs
|
||||||
|
flag.Func("u", `Upstream DoH server URL (can be specified multiple times)
|
||||||
|
Example:
|
||||||
|
-u https://dns.quad9.net/dns-query -u https://1.1.1.1/dns-query
|
||||||
|
WARNING:
|
||||||
|
If this is your system's default DNS resolver
|
||||||
|
and the server URL is a domain name, at least one other
|
||||||
|
DNS server after this one must be specified as an IP address
|
||||||
|
in order to resolve the domain name of the first one.`, func(value string) error {
|
||||||
|
log.Printf("Added %s as an upstream DoH server\n", value)
|
||||||
|
upstreamURLs = append(upstreamURLs, value)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Parse the flags
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Check if at least one upstream DoH URL is provided
|
||||||
|
if len(upstreamURLs) == 0 {
|
||||||
|
flag.PrintDefaults()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine which protocols to use
|
||||||
|
var protocols []ProtocolType
|
||||||
|
if *tcpFlag {
|
||||||
|
protocols = append(protocols, TCP)
|
||||||
|
}
|
||||||
|
if *udpFlag {
|
||||||
|
protocols = append(protocols, UDP)
|
||||||
|
}
|
||||||
|
if !*tcpFlag && !*udpFlag {
|
||||||
|
// Default to both if no specific flag is provided
|
||||||
|
protocols = []ProtocolType{TCP, UDP}
|
||||||
|
}
|
||||||
|
if *logFlag {
|
||||||
|
log.Println("Logging requests")
|
||||||
|
}
|
||||||
|
// Initialize and run the DoH proxy
|
||||||
|
proxy := NewDoHProxy(*listenAddress, *port, upstreamURLs, protocols, *logFlag)
|
||||||
|
proxy.Run()
|
||||||
|
}
|
16
go.mod
Normal file
16
go.mod
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
module dohproxy
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/miekg/dns v1.1.62
|
||||||
|
golang.org/x/net v0.28.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/mod v0.18.0 // indirect
|
||||||
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
|
golang.org/x/sys v0.23.0 // indirect
|
||||||
|
golang.org/x/text v0.17.0 // indirect
|
||||||
|
golang.org/x/tools v0.22.0 // indirect
|
||||||
|
)
|
14
go.sum
Normal file
14
go.sum
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||||
|
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||||
|
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||||
|
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||||
|
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||||
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||||
|
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||||
|
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
|
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||||
|
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
Loading…
Reference in a new issue