1
0
Fork 0
mirror of https://github.com/SamTherapy/dnscrypt.git synced 2024-07-02 21:56:06 +00:00
dnscrypt/server_udp.go
2022-08-29 10:07:17 +03:00

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
}