mirror of
https://github.com/SamTherapy/dnscrypt.git
synced 2024-10-02 16:32:51 +00:00
223 lines
5.8 KiB
Go
223 lines
5.8 KiB
Go
package dnscrypt
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"net"
|
|
"runtime"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/AdguardTeam/golibs/log"
|
|
"github.com/miekg/dns"
|
|
"golang.org/x/net/ipv4"
|
|
"golang.org/x/net/ipv6"
|
|
)
|
|
|
|
type encryptionFunc func(m *dns.Msg, q EncryptedQuery) ([]byte, error)
|
|
|
|
// UDPResponseWriter is the ResponseWriter implementation for UDP
|
|
type UDPResponseWriter struct {
|
|
udpConn *net.UDPConn // UDP connection
|
|
sess *dns.SessionUDP // SessionUDP (necessary to use dns.WriteToSessionUDP)
|
|
encrypt encryptionFunc // DNSCrypt encryption function
|
|
req *dns.Msg // DNS query that was processed
|
|
query EncryptedQuery // DNSCrypt query properties
|
|
}
|
|
|
|
// type check
|
|
var _ ResponseWriter = &UDPResponseWriter{}
|
|
|
|
// LocalAddr is the server socket local address
|
|
func (w *UDPResponseWriter) LocalAddr() net.Addr {
|
|
return w.udpConn.LocalAddr()
|
|
}
|
|
|
|
// RemoteAddr is the client's address
|
|
func (w *UDPResponseWriter) RemoteAddr() net.Addr {
|
|
return w.sess.RemoteAddr()
|
|
}
|
|
|
|
// WriteMsg writes DNS message to the client
|
|
func (w *UDPResponseWriter) WriteMsg(m *dns.Msg) error {
|
|
normalize("udp", w.req, m)
|
|
|
|
res, err := w.encrypt(m, w.query)
|
|
if err != nil {
|
|
log.Tracef("Failed to encrypt the DNS query: %v", err)
|
|
return err
|
|
}
|
|
_, err = dns.WriteToSessionUDP(w.udpConn, res, w.sess)
|
|
return err
|
|
}
|
|
|
|
// ServeUDP listens to UDP connections, queries are then processed by Server.Handler.
|
|
// It blocks the calling goroutine and to stop it you need to close the listener
|
|
// or call Server.Shutdown.
|
|
func (s *Server) ServeUDP(l *net.UDPConn) error {
|
|
err := s.prepareServeUDP(l)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Tracks UDP handling goroutines
|
|
udpWg := &sync.WaitGroup{}
|
|
defer s.cleanUpUDP(udpWg, l)
|
|
|
|
// Track active goroutine
|
|
s.wg.Add(1)
|
|
|
|
log.Info("Entering DNSCrypt UDP listening loop on udp://%s", l.LocalAddr())
|
|
|
|
// Serialize the cert right away and prepare it to be sent to the client
|
|
certTxt, err := s.getCertTXT()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for s.isStarted() {
|
|
b, sess, err := s.readUDPMsg(l)
|
|
|
|
// Check the error code and exit loop if necessary
|
|
if err != nil {
|
|
if !s.isStarted() {
|
|
// Stopped gracefully
|
|
return nil
|
|
}
|
|
var netErr net.Error
|
|
if errors.As(err, &netErr) && netErr.Timeout() {
|
|
// Note that timeout errors will be here (i.e. hitting ReadDeadline)
|
|
continue
|
|
}
|
|
if isConnClosed(err) {
|
|
log.Info("udpListen.ReadFrom() returned because we're reading from a closed connection, exiting loop")
|
|
} else {
|
|
log.Info("got error when reading from UDP listen: %s", err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
if len(b) < minDNSPacketSize {
|
|
// Ignore the packets that are too short
|
|
continue
|
|
}
|
|
|
|
udpWg.Add(1)
|
|
go func() {
|
|
s.serveUDPMsg(b, certTxt, sess, l)
|
|
udpWg.Done()
|
|
}()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// prepareServeUDP prepares the server and listener to serving DNSCrypt
|
|
func (s *Server) prepareServeUDP(l *net.UDPConn) error {
|
|
// Check that server is properly configured
|
|
if !s.validate() {
|
|
return ErrServerConfig
|
|
}
|
|
|
|
// set UDP options to allow receiving OOB data
|
|
err := setUDPSocketOptions(l)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Protect shutdown-related fields
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
s.initOnce.Do(s.init)
|
|
|
|
// Mark the server as started.
|
|
// Note that we don't check if it was started before as
|
|
// Serve* methods can be called multiple times.
|
|
s.started = true
|
|
|
|
// Track an active UDP listener
|
|
s.udpListeners[l] = struct{}{}
|
|
return err
|
|
}
|
|
|
|
// cleanUpUDP waits until all UDP messages before cleaning up
|
|
func (s *Server) cleanUpUDP(udpWg *sync.WaitGroup, l *net.UDPConn) {
|
|
// Wait until UDP messages are processed
|
|
udpWg.Wait()
|
|
|
|
// Not using it anymore so can be removed from the active listeners
|
|
s.lock.Lock()
|
|
delete(s.udpListeners, l)
|
|
s.lock.Unlock()
|
|
|
|
// The work is finished
|
|
s.wg.Done()
|
|
}
|
|
|
|
// readUDPMsg reads incoming UDP message
|
|
func (s *Server) readUDPMsg(l *net.UDPConn) ([]byte, *dns.SessionUDP, error) {
|
|
_ = l.SetReadDeadline(time.Now().Add(defaultReadTimeout))
|
|
b := make([]byte, s.UDPSize)
|
|
n, sess, err := dns.ReadFromSessionUDP(l, b)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return b[:n], sess, err
|
|
}
|
|
|
|
// serveUDPMsg handles incoming DNS message
|
|
func (s *Server) serveUDPMsg(b []byte, certTxt string, sess *dns.SessionUDP, l *net.UDPConn) {
|
|
// First of all, check for "ClientMagic" in the incoming query
|
|
if !bytes.Equal(b[:clientMagicSize], s.ResolverCert.ClientMagic[:]) {
|
|
// If there's no ClientMagic in the packet, we assume this
|
|
// is a plain DNS query requesting the certificate data
|
|
reply, err := s.handleHandshake(b, certTxt)
|
|
if err != nil {
|
|
log.Tracef("failed to process a plain DNS query: %v", err)
|
|
}
|
|
if err == nil {
|
|
// Ignore errors, we don't care and can't handle them anyway
|
|
_, _ = dns.WriteToSessionUDP(l, reply, sess)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// If we got here, this is an encrypted DNSCrypt message
|
|
// We should decrypt it first to get the plain DNS query
|
|
m, q, err := s.decrypt(b)
|
|
if err == nil {
|
|
rw := &UDPResponseWriter{
|
|
udpConn: l,
|
|
sess: sess,
|
|
encrypt: s.encrypt,
|
|
req: m,
|
|
query: q,
|
|
}
|
|
err = s.serveDNS(rw, m)
|
|
if err != nil {
|
|
log.Tracef("failed to process a DNS query: %v", err)
|
|
}
|
|
} else {
|
|
log.Tracef("failed to decrypt incoming message len=%d: %v", len(b), err)
|
|
}
|
|
}
|
|
|
|
// setUDPSocketOptions method is necessary to be able to use dns.ReadFromSessionUDP / dns.WriteToSessionUDP
|
|
func setUDPSocketOptions(conn *net.UDPConn) error {
|
|
if runtime.GOOS == "windows" {
|
|
return nil
|
|
}
|
|
|
|
// We don't know if this a IPv4-only, IPv6-only or a IPv4-and-IPv6 connection.
|
|
// Try enabling receiving of ECN and packet info for both IP versions.
|
|
// We expect at least one of those syscalls to succeed.
|
|
err6 := ipv6.NewPacketConn(conn).SetControlMessage(ipv6.FlagDst|ipv6.FlagInterface, true)
|
|
err4 := ipv4.NewPacketConn(conn).SetControlMessage(ipv4.FlagDst|ipv4.FlagInterface, true)
|
|
if err6 != nil && err4 != nil {
|
|
return err4
|
|
}
|
|
return nil
|
|
}
|