mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-30 17:53:21 -04:00 
			
		
		
		
	[RFC-9250]: Add QUIC server support (#6182)
Add DNS-over-QUIC server Signed-off-by: jaehnri <joao.henri.cr@gmail.com> Signed-off-by: João Henri <joao.henri.cr@gmail.com>
This commit is contained in:
		
							
								
								
									
										18
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								README.md
									
									
									
									
									
								
							| @@ -18,9 +18,12 @@ CoreDNS is a fast and flexible DNS server. The key word here is *flexible*: with | ||||
| are able to do what you want with your DNS data by utilizing plugins. If some functionality is not | ||||
| provided out of the box you can add it by [writing a plugin](https://coredns.io/explugins). | ||||
|  | ||||
| CoreDNS can listen for DNS requests coming in over UDP/TCP (go'old DNS), TLS ([RFC | ||||
| 7858](https://tools.ietf.org/html/rfc7858)), also called DoT, DNS over HTTP/2 - DoH - | ||||
| ([RFC 8484](https://tools.ietf.org/html/rfc8484)) and [gRPC](https://grpc.io) (not a standard). | ||||
| CoreDNS can listen for DNS requests coming in over: | ||||
| * UDP/TCP (go'old DNS). | ||||
| * TLS - DoT ([RFC 7858](https://tools.ietf.org/html/rfc7858)). | ||||
| * DNS over HTTP/2 - DoH ([RFC 8484](https://tools.ietf.org/html/rfc8484)). | ||||
| * DNS over QUIC - DoQ ([RFC 9250](https://tools.ietf.org/html/rfc9250)).  | ||||
| * [gRPC](https://grpc.io) (not a standard). | ||||
|  | ||||
| Currently CoreDNS is able to: | ||||
|  | ||||
| @@ -211,6 +214,15 @@ tls://example.org grpc://example.org { | ||||
| } | ||||
| ~~~ | ||||
|  | ||||
| Similarly, for QUIC (DoQ): | ||||
|  | ||||
| ~~~ corefile | ||||
| quic://example.org { | ||||
|     whoami | ||||
|     tls mycert mykey | ||||
| } | ||||
| ~~~ | ||||
|  | ||||
| And for DNS over HTTP/2 (DoH) use: | ||||
|  | ||||
| ~~~ corefile | ||||
|   | ||||
							
								
								
									
										60
									
								
								core/dnsserver/quic.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								core/dnsserver/quic.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| package dnsserver | ||||
|  | ||||
| import ( | ||||
| 	"encoding/binary" | ||||
| 	"net" | ||||
|  | ||||
| 	"github.com/miekg/dns" | ||||
| 	"github.com/quic-go/quic-go" | ||||
| ) | ||||
|  | ||||
| type DoQWriter struct { | ||||
| 	localAddr  net.Addr | ||||
| 	remoteAddr net.Addr | ||||
| 	stream     quic.Stream | ||||
| 	Msg        *dns.Msg | ||||
| } | ||||
|  | ||||
| func (w *DoQWriter) Write(b []byte) (int, error) { | ||||
| 	b = AddPrefix(b) | ||||
| 	return w.stream.Write(b) | ||||
| } | ||||
|  | ||||
| func (w *DoQWriter) WriteMsg(m *dns.Msg) error { | ||||
| 	bytes, err := m.Pack() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	_, err = w.Write(bytes) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return w.Close() | ||||
| } | ||||
|  | ||||
| // Close sends the STREAM FIN signal. | ||||
| // The server MUST send the response(s) on the same stream and MUST | ||||
| // indicate, after the last response, through the STREAM FIN | ||||
| // mechanism that no further data will be sent on that stream. | ||||
| // See https://www.rfc-editor.org/rfc/rfc9250#section-4.2-7 | ||||
| func (w *DoQWriter) Close() error { | ||||
| 	return w.stream.Close() | ||||
| } | ||||
|  | ||||
| // AddPrefix adds a 2-byte prefix with the DNS message length. | ||||
| func AddPrefix(b []byte) (m []byte) { | ||||
| 	m = make([]byte, 2+len(b)) | ||||
| 	binary.BigEndian.PutUint16(m, uint16(len(b))) | ||||
| 	copy(m[2:], b) | ||||
|  | ||||
| 	return m | ||||
| } | ||||
|  | ||||
| // These methods implement the dns.ResponseWriter interface from Go DNS. | ||||
| func (w *DoQWriter) TsigStatus() error     { return nil } | ||||
| func (w *DoQWriter) TsigTimersOnly(b bool) {} | ||||
| func (w *DoQWriter) Hijack()               {} | ||||
| func (w *DoQWriter) LocalAddr() net.Addr   { return w.localAddr } | ||||
| func (w *DoQWriter) RemoteAddr() net.Addr  { return w.remoteAddr } | ||||
							
								
								
									
										20
									
								
								core/dnsserver/quic_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								core/dnsserver/quic_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| package dnsserver | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestDoQWriterAddPrefix(t *testing.T) { | ||||
| 	byteArray := []byte{0x1, 0x2, 0x3} | ||||
|  | ||||
| 	byteArrayWithPrefix := AddPrefix(byteArray) | ||||
|  | ||||
| 	if len(byteArrayWithPrefix) != 5 { | ||||
| 		t.Error("Expected byte array with prefix to have length of 5") | ||||
| 	} | ||||
|  | ||||
| 	size := int16(byteArrayWithPrefix[0])<<8 | int16(byteArrayWithPrefix[1]) | ||||
| 	if size != 3 { | ||||
| 		t.Errorf("Expected prefixed size to be 3, got: %d", size) | ||||
| 	} | ||||
| } | ||||
| @@ -82,6 +82,8 @@ func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddy | ||||
| 					port = Port | ||||
| 				case transport.TLS: | ||||
| 					port = transport.TLSPort | ||||
| 				case transport.QUIC: | ||||
| 					port = transport.QUICPort | ||||
| 				case transport.GRPC: | ||||
| 					port = transport.GRPCPort | ||||
| 				case transport.HTTPS: | ||||
| @@ -174,6 +176,13 @@ func (h *dnsContext) MakeServers() ([]caddy.Server, error) { | ||||
| 			} | ||||
| 			servers = append(servers, s) | ||||
|  | ||||
| 		case transport.QUIC: | ||||
| 			s, err := NewServerQUIC(addr, group) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 			servers = append(servers, s) | ||||
|  | ||||
| 		case transport.GRPC: | ||||
| 			s, err := NewServergRPC(addr, group) | ||||
| 			if err != nil { | ||||
|   | ||||
							
								
								
									
										346
									
								
								core/dnsserver/server_quic.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										346
									
								
								core/dnsserver/server_quic.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,346 @@ | ||||
| package dnsserver | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"encoding/binary" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"math" | ||||
| 	"net" | ||||
|  | ||||
| 	"github.com/coredns/coredns/plugin/metrics/vars" | ||||
| 	clog "github.com/coredns/coredns/plugin/pkg/log" | ||||
| 	"github.com/coredns/coredns/plugin/pkg/reuseport" | ||||
| 	"github.com/coredns/coredns/plugin/pkg/transport" | ||||
|  | ||||
| 	"github.com/miekg/dns" | ||||
| 	"github.com/quic-go/quic-go" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// DoQCodeNoError is used when the connection or stream needs to be | ||||
| 	// closed, but there is no error to signal. | ||||
| 	DoQCodeNoError quic.ApplicationErrorCode = 0 | ||||
|  | ||||
| 	// DoQCodeInternalError signals that the DoQ implementation encountered | ||||
| 	// an internal error and is incapable of pursuing the transaction or the | ||||
| 	// connection. | ||||
| 	DoQCodeInternalError quic.ApplicationErrorCode = 1 | ||||
|  | ||||
| 	// DoQCodeProtocolError signals that the DoQ implementation encountered | ||||
| 	// a protocol error and is forcibly aborting the connection. | ||||
| 	DoQCodeProtocolError quic.ApplicationErrorCode = 2 | ||||
| ) | ||||
|  | ||||
| // ServerQUIC represents an instance of a DNS-over-QUIC server. | ||||
| type ServerQUIC struct { | ||||
| 	*Server | ||||
| 	listenAddr   net.Addr | ||||
| 	tlsConfig    *tls.Config | ||||
| 	quicConfig   *quic.Config | ||||
| 	quicListener *quic.Listener | ||||
| } | ||||
|  | ||||
| // NewServerQUIC returns a new CoreDNS QUIC server and compiles all plugin in to it. | ||||
| func NewServerQUIC(addr string, group []*Config) (*ServerQUIC, error) { | ||||
| 	s, err := NewServer(addr, group) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	// The *tls* plugin must make sure that multiple conflicting | ||||
| 	// TLS configuration returns an error: it can only be specified once. | ||||
| 	var tlsConfig *tls.Config | ||||
| 	for _, z := range s.zones { | ||||
| 		for _, conf := range z { | ||||
| 			// Should we error if some configs *don't* have TLS? | ||||
| 			tlsConfig = conf.TLSConfig | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tlsConfig != nil { | ||||
| 		tlsConfig.NextProtos = []string{"doq"} | ||||
| 	} | ||||
|  | ||||
| 	var quicConfig *quic.Config | ||||
| 	quicConfig = &quic.Config{ | ||||
| 		MaxIdleTimeout:        s.idleTimeout, | ||||
| 		MaxIncomingStreams:    math.MaxUint16, | ||||
| 		MaxIncomingUniStreams: math.MaxUint16, | ||||
| 		// Enable 0-RTT by default for all connections on the server-side. | ||||
| 		Allow0RTT: true, | ||||
| 	} | ||||
|  | ||||
| 	return &ServerQUIC{Server: s, tlsConfig: tlsConfig, quicConfig: quicConfig}, nil | ||||
| } | ||||
|  | ||||
| // ServePacket implements caddy.UDPServer interface. | ||||
| func (s *ServerQUIC) ServePacket(p net.PacketConn) error { | ||||
| 	s.m.Lock() | ||||
| 	s.listenAddr = s.quicListener.Addr() | ||||
| 	s.m.Unlock() | ||||
|  | ||||
| 	return s.ServeQUIC() | ||||
| } | ||||
|  | ||||
| // ServeQUIC listens for incoming QUIC packets. | ||||
| func (s *ServerQUIC) ServeQUIC() error { | ||||
| 	for { | ||||
| 		conn, err := s.quicListener.Accept(context.Background()) | ||||
| 		if err != nil { | ||||
| 			if s.isExpectedErr(err) { | ||||
| 				s.closeQUICConn(conn, DoQCodeNoError) | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			s.closeQUICConn(conn, DoQCodeInternalError) | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		go s.serveQUICConnection(conn) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // serveQUICConnection handles a new QUIC connection. It waits for new streams | ||||
| // and passes them to serveQUICStream. | ||||
| func (s *ServerQUIC) serveQUICConnection(conn quic.Connection) { | ||||
| 	for { | ||||
| 		// In DoQ, one query consumes one stream. | ||||
| 		// The client MUST select the next available client-initiated bidirectional | ||||
| 		// stream for each subsequent query on a QUIC connection. | ||||
| 		stream, err := conn.AcceptStream(context.Background()) | ||||
| 		if err != nil { | ||||
| 			if s.isExpectedErr(err) { | ||||
| 				s.closeQUICConn(conn, DoQCodeNoError) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			s.closeQUICConn(conn, DoQCodeInternalError) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		go s.serveQUICStream(stream, conn) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *ServerQUIC) serveQUICStream(stream quic.Stream, conn quic.Connection) { | ||||
| 	buf, err := readDOQMessage(stream) | ||||
|  | ||||
| 	// io.EOF does not really mean that there's any error, it is just | ||||
| 	// the STREAM FIN indicating that there will be no data to read | ||||
| 	// anymore from this stream. | ||||
| 	if err != nil && err != io.EOF { | ||||
| 		s.closeQUICConn(conn, DoQCodeProtocolError) | ||||
|  | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	req := &dns.Msg{} | ||||
| 	err = req.Unpack(buf) | ||||
| 	if err != nil { | ||||
| 		clog.Debugf("unpacking quic packet: %s", err) | ||||
| 		s.closeQUICConn(conn, DoQCodeProtocolError) | ||||
|  | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if !validRequest(req) { | ||||
| 		// If a peer encounters such an error condition, it is considered a | ||||
| 		// fatal error. It SHOULD forcibly abort the connection using QUIC's | ||||
| 		// CONNECTION_CLOSE mechanism and SHOULD use the DoQ error code | ||||
| 		// DOQ_PROTOCOL_ERROR. | ||||
| 		// See https://www.rfc-editor.org/rfc/rfc9250#section-4.3.3-3 | ||||
| 		s.closeQUICConn(conn, DoQCodeProtocolError) | ||||
|  | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	w := &DoQWriter{ | ||||
| 		localAddr:  conn.LocalAddr(), | ||||
| 		remoteAddr: conn.RemoteAddr(), | ||||
| 		stream:     stream, | ||||
| 		Msg:        req, | ||||
| 	} | ||||
|  | ||||
| 	dnsCtx := context.WithValue(stream.Context(), Key{}, s.Server) | ||||
| 	dnsCtx = context.WithValue(dnsCtx, LoopKey{}, 0) | ||||
| 	s.ServeDNS(dnsCtx, w, req) | ||||
| 	s.countResponse(DoQCodeNoError) | ||||
| } | ||||
|  | ||||
| // ListenPacket implements caddy.UDPServer interface. | ||||
| func (s *ServerQUIC) ListenPacket() (net.PacketConn, error) { | ||||
| 	p, err := reuseport.ListenPacket("udp", s.Addr[len(transport.QUIC+"://"):]) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	s.m.Lock() | ||||
| 	defer s.m.Unlock() | ||||
|  | ||||
| 	s.quicListener, err = quic.Listen(p, s.tlsConfig, s.quicConfig) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return p, nil | ||||
| } | ||||
|  | ||||
| // OnStartupComplete lists the sites served by this server | ||||
| // and any relevant information, assuming Quiet is false. | ||||
| func (s *ServerQUIC) OnStartupComplete() { | ||||
| 	if Quiet { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	out := startUpZones(transport.QUIC+"://", s.Addr, s.zones) | ||||
| 	if out != "" { | ||||
| 		fmt.Print(out) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Stop stops the server non-gracefully. It blocks until the server is totally stopped. | ||||
| func (s *ServerQUIC) Stop() error { | ||||
| 	s.m.Lock() | ||||
| 	defer s.m.Unlock() | ||||
|  | ||||
| 	if s.quicListener != nil { | ||||
| 		return s.quicListener.Close() | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Serve implements caddy.TCPServer interface. | ||||
| func (s *ServerQUIC) Serve(l net.Listener) error { return nil } | ||||
|  | ||||
| // Listen implements caddy.TCPServer interface. | ||||
| func (s *ServerQUIC) Listen() (net.Listener, error) { return nil, nil } | ||||
|  | ||||
| // closeQUICConn quietly closes the QUIC connection. | ||||
| func (s *ServerQUIC) closeQUICConn(conn quic.Connection, code quic.ApplicationErrorCode) { | ||||
| 	if conn == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	clog.Debugf("closing quic conn %s with code %d", conn.LocalAddr(), code) | ||||
| 	err := conn.CloseWithError(code, "") | ||||
| 	if err != nil { | ||||
| 		clog.Debugf("failed to close quic connection with code %d: %s", code, err) | ||||
| 	} | ||||
|  | ||||
| 	// DoQCodeNoError metrics are already registered after s.ServeDNS() | ||||
| 	if code != DoQCodeNoError { | ||||
| 		s.countResponse(code) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // validRequest checks for protocol errors in the unpacked DNS message. | ||||
| // See https://www.rfc-editor.org/rfc/rfc9250.html#name-protocol-errors | ||||
| func validRequest(req *dns.Msg) (ok bool) { | ||||
| 	// 1. a client or server receives a message with a non-zero Message ID. | ||||
| 	if req.Id != 0 { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	// 2. an implementation receives a message containing the edns-tcp-keepalive | ||||
| 	// EDNS(0) Option [RFC7828]. | ||||
| 	if opt := req.IsEdns0(); opt != nil { | ||||
| 		for _, option := range opt.Option { | ||||
| 			if option.Option() == dns.EDNS0TCPKEEPALIVE { | ||||
| 				clog.Debug("client sent EDNS0 TCP keepalive option") | ||||
|  | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// 3. the client or server does not indicate the expected STREAM FIN after | ||||
| 	// sending requests or responses. | ||||
| 	// | ||||
| 	// This is quite problematic to validate this case since this would imply | ||||
| 	// we have to wait until STREAM FIN is arrived before we start processing | ||||
| 	// the message. So we're consciously ignoring this case in this | ||||
| 	// implementation. | ||||
|  | ||||
| 	// 4. a server receives a "replayable" transaction in 0-RTT data | ||||
| 	// | ||||
| 	// The information necessary to validate this is not exposed by quic-go. | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // readDOQMessage reads a DNS over QUIC (DOQ) message from the given stream | ||||
| // and returns the message bytes. | ||||
| // Drafts of the RFC9250 did not require the 2-byte prefixed message length. | ||||
| // Thus, we are only supporting the official version (DoQ v1). | ||||
| func readDOQMessage(r io.Reader) ([]byte, error) { | ||||
| 	// All DNS messages (queries and responses) sent over DoQ connections MUST | ||||
| 	// be encoded as a 2-octet length field followed by the message content as | ||||
| 	// specified in [RFC1035]. | ||||
| 	// See https://www.rfc-editor.org/rfc/rfc9250.html#section-4.2-4 | ||||
| 	sizeBuf := make([]byte, 2) | ||||
| 	_, err := io.ReadFull(r, sizeBuf) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	size := binary.BigEndian.Uint16(sizeBuf) | ||||
|  | ||||
| 	if size == 0 { | ||||
| 		return nil, fmt.Errorf("message size is 0: probably unsupported DoQ version") | ||||
| 	} | ||||
|  | ||||
| 	buf := make([]byte, size) | ||||
| 	_, err = io.ReadFull(r, buf) | ||||
|  | ||||
| 	// A client or server receives a STREAM FIN before receiving all the bytes | ||||
| 	// for a message indicated in the 2-octet length field. | ||||
| 	// See https://www.rfc-editor.org/rfc/rfc9250#section-4.3.3-2.2 | ||||
| 	if size != uint16(len(buf)) { | ||||
| 		return nil, fmt.Errorf("message size does not match 2-byte prefix") | ||||
| 	} | ||||
|  | ||||
| 	return buf, err | ||||
| } | ||||
|  | ||||
| // isExpectedErr returns true if err is an expected error, likely related to | ||||
| // the current implementation. | ||||
| func (s *ServerQUIC) isExpectedErr(err error) bool { | ||||
| 	if err == nil { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	// This error is returned when the QUIC listener was closed by us. As | ||||
| 	// graceful shutdown is not implemented, the connection will be abruptly | ||||
| 	// closed but there is no error to signal. | ||||
| 	if errors.Is(err, quic.ErrServerClosed) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// This error happens when the connection was closed due to a DoQ | ||||
| 	// protocol error but there's still something to read in the closed stream. | ||||
| 	// For example, when the message was sent without the prefixed length. | ||||
| 	var qAppErr *quic.ApplicationError | ||||
| 	if errors.As(err, &qAppErr) && qAppErr.ErrorCode == 2 { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// When a connection hits the idle timeout, quic.AcceptStream() returns | ||||
| 	// an IdleTimeoutError. In this, case, we should just drop the connection | ||||
| 	// with DoQCodeNoError. | ||||
| 	var qIdleErr *quic.IdleTimeoutError | ||||
| 	return errors.As(err, &qIdleErr) | ||||
| } | ||||
|  | ||||
| func (s *ServerQUIC) countResponse(code quic.ApplicationErrorCode) { | ||||
| 	switch code { | ||||
| 	case DoQCodeNoError: | ||||
| 		vars.QUICResponsesCount.WithLabelValues(s.Addr, "0x0").Inc() | ||||
| 	case DoQCodeInternalError: | ||||
| 		vars.QUICResponsesCount.WithLabelValues(s.Addr, "0x1").Inc() | ||||
| 	case DoQCodeProtocolError: | ||||
| 		vars.QUICResponsesCount.WithLabelValues(s.Addr, "0x2").Inc() | ||||
| 	} | ||||
| } | ||||
| @@ -48,6 +48,11 @@ func TestNewServer(t *testing.T) { | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error for NewServerTLS, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	_, err = NewServerQUIC("127.0.0.1:53", []*Config{testConfig("quic", testPlugin{})}) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error for NewServerQUIC, got %s", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDebug(t *testing.T) { | ||||
|   | ||||
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
								
							| @@ -24,6 +24,7 @@ require ( | ||||
| 	github.com/prometheus/client_golang v1.16.0 | ||||
| 	github.com/prometheus/client_model v0.4.0 | ||||
| 	github.com/prometheus/common v0.44.0 | ||||
| 	github.com/quic-go/quic-go v0.35.1 | ||||
| 	go.etcd.io/etcd/api/v3 v3.5.9 | ||||
| 	go.etcd.io/etcd/client/v3 v3.5.9 | ||||
| 	golang.org/x/crypto v0.11.0 | ||||
| @@ -70,13 +71,16 @@ require ( | ||||
| 	github.com/go-openapi/jsonpointer v0.19.6 // indirect | ||||
| 	github.com/go-openapi/jsonreference v0.20.1 // indirect | ||||
| 	github.com/go-openapi/swag v0.22.3 // indirect | ||||
| 	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect | ||||
| 	github.com/gogo/protobuf v1.3.2 // indirect | ||||
| 	github.com/golang-jwt/jwt/v4 v4.5.0 // indirect | ||||
| 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | ||||
| 	github.com/golang/mock v1.6.0 // indirect | ||||
| 	github.com/golang/protobuf v1.5.3 // indirect | ||||
| 	github.com/google/gnostic v0.5.7-v3refs // indirect | ||||
| 	github.com/google/go-cmp v0.5.9 // indirect | ||||
| 	github.com/google/gofuzz v1.2.0 // indirect | ||||
| 	github.com/google/pprof v0.0.0-20230509042627-b1315fad0c5a // indirect | ||||
| 	github.com/google/s2a-go v0.1.4 // indirect | ||||
| 	github.com/google/uuid v1.3.0 // indirect | ||||
| 	github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect | ||||
| @@ -90,12 +94,15 @@ require ( | ||||
| 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||||
| 	github.com/modern-go/reflect2 v1.0.2 // indirect | ||||
| 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | ||||
| 	github.com/onsi/ginkgo/v2 v2.9.1 // indirect | ||||
| 	github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 // indirect | ||||
| 	github.com/oschwald/maxminddb-golang v1.11.0 // indirect | ||||
| 	github.com/outcaste-io/ristretto v0.2.1 // indirect | ||||
| 	github.com/philhofer/fwd v1.1.1 // indirect | ||||
| 	github.com/pkg/errors v0.9.1 // indirect | ||||
| 	github.com/prometheus/procfs v0.10.1 // indirect | ||||
| 	github.com/quic-go/qtls-go1-19 v0.3.2 // indirect | ||||
| 	github.com/quic-go/qtls-go1-20 v0.2.2 // indirect | ||||
| 	github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect | ||||
| 	github.com/spf13/pflag v1.0.5 // indirect | ||||
| 	github.com/tinylib/msgp v1.1.6 // indirect | ||||
| @@ -106,6 +113,7 @@ require ( | ||||
| 	go.uber.org/zap v1.17.0 // indirect | ||||
| 	go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect | ||||
| 	go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect | ||||
| 	golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect | ||||
| 	golang.org/x/mod v0.9.0 // indirect | ||||
| 	golang.org/x/net v0.12.0 // indirect | ||||
| 	golang.org/x/oauth2 v0.10.0 // indirect | ||||
|   | ||||
							
								
								
									
										12
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								go.sum
									
									
									
									
									
								
							| @@ -134,6 +134,7 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4er | ||||
| github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= | ||||
| github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||
| github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= | ||||
| github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= | ||||
| github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= | ||||
| github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||
| github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||
| @@ -168,6 +169,7 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= | ||||
| github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||||
| github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= | ||||
| github.com/google/pprof v0.0.0-20230509042627-b1315fad0c5a h1:PEOGDI1kkyW37YqPWHLHc+D20D9+87Wt12TCcfTUo5Q= | ||||
| github.com/google/pprof v0.0.0-20230509042627-b1315fad0c5a/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= | ||||
| github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= | ||||
| github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= | ||||
| github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||
| @@ -226,9 +228,9 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W | ||||
| github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= | ||||
| github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= | ||||
| github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= | ||||
| github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= | ||||
| github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= | ||||
| github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= | ||||
| github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo= | ||||
| github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= | ||||
| github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= | ||||
| github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= | ||||
| @@ -264,6 +266,12 @@ github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdO | ||||
| github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= | ||||
| github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= | ||||
| github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= | ||||
| github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= | ||||
| github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= | ||||
| github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= | ||||
| github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= | ||||
| github.com/quic-go/quic-go v0.35.1 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62poo= | ||||
| github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g= | ||||
| github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 h1:Qp27Idfgi6ACvFQat5+VJvlYToylpM/hcyLBI3WaKPA= | ||||
| github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= | ||||
| github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= | ||||
| @@ -329,6 +337,8 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58 | ||||
| golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= | ||||
| golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= | ||||
| golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= | ||||
| golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= | ||||
| golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||
| golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= | ||||
| golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||||
|   | ||||
							
								
								
									
										103
									
								
								man/coredns-timeouts.7
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								man/coredns-timeouts.7
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| .\" Generated by Mmark Markdown Processer - mmark.miek.nl | ||||
| .TH "COREDNS-TIMEOUTS" 7 "July 2023" "CoreDNS" "CoreDNS Plugins" | ||||
|  | ||||
| .SH "NAME" | ||||
| .PP | ||||
| \fItimeouts\fP - allows you to configure the server read, write and idle timeouts for the TCP, TLS and DoH servers. | ||||
|  | ||||
| .SH "DESCRIPTION" | ||||
| .PP | ||||
| CoreDNS is configured with sensible timeouts for server connections by default. | ||||
| However in some cases for example where CoreDNS is serving over a slow mobile | ||||
| data connection the default timeouts are not optimal. | ||||
|  | ||||
| .PP | ||||
| Additionally some routers hold open connections when using DNS over TLS or DNS | ||||
| over HTTPS. Allowing a longer idle timeout helps performance and reduces issues | ||||
| with such routers. | ||||
|  | ||||
| .PP | ||||
| The \fItimeouts\fP "plugin" allows you to configure CoreDNS server read, write and | ||||
| idle timeouts. | ||||
|  | ||||
| .SH "SYNTAX" | ||||
| .PP | ||||
| .RS | ||||
|  | ||||
| .nf | ||||
| timeouts { | ||||
|     read DURATION | ||||
|     write DURATION | ||||
|     idle DURATION | ||||
| } | ||||
|  | ||||
| .fi | ||||
| .RE | ||||
|  | ||||
| .PP | ||||
| For any timeouts that are not provided, default values are used which may vary | ||||
| depending on the server type. At least one timeout must be specified otherwise | ||||
| the entire timeouts block should be omitted. | ||||
|  | ||||
| .SH "EXAMPLES" | ||||
| .PP | ||||
| Start a DNS-over-TLS server that picks up incoming DNS-over-TLS queries on port | ||||
| 5553 and uses the nameservers defined in \fB\fC/etc/resolv.conf\fR to resolve the | ||||
| query. This proxy path uses plain old DNS. A 10 second read timeout, 20 | ||||
| second write timeout and a 60 second idle timeout have been configured. | ||||
|  | ||||
| .PP | ||||
| .RS | ||||
|  | ||||
| .nf | ||||
| tls://.:5553 { | ||||
|     tls cert.pem key.pem ca.pem | ||||
|     timeouts { | ||||
|         read 10s | ||||
|         write 20s | ||||
|         idle 60s | ||||
|     } | ||||
|     forward . /etc/resolv.conf | ||||
| } | ||||
|  | ||||
| .fi | ||||
| .RE | ||||
|  | ||||
| .PP | ||||
| Start a DNS-over-HTTPS server that is similar to the previous example. Only the | ||||
| read timeout has been configured for 1 minute. | ||||
|  | ||||
| .PP | ||||
| .RS | ||||
|  | ||||
| .nf | ||||
| https://. { | ||||
|     tls cert.pem key.pem ca.pem | ||||
|     timeouts { | ||||
|         read 1m | ||||
|     } | ||||
|     forward . /etc/resolv.conf | ||||
| } | ||||
|  | ||||
| .fi | ||||
| .RE | ||||
|  | ||||
| .PP | ||||
| Start a standard TCP/UDP server on port 1053. A read and write timeout has been | ||||
| configured. The timeouts are only applied to the TCP side of the server. | ||||
|  | ||||
| .PP | ||||
| .RS | ||||
|  | ||||
| .nf | ||||
| \&.:1053 { | ||||
|     timeouts { | ||||
|         read 15s | ||||
|                 write 30s | ||||
|     } | ||||
|     forward . /etc/resolv.conf | ||||
| } | ||||
|  | ||||
| .fi | ||||
| .RE | ||||
|  | ||||
| @@ -21,6 +21,7 @@ the following metrics are exported: | ||||
| * `coredns_dns_response_size_bytes{server, zone, view, proto}` - response size in bytes. | ||||
| * `coredns_dns_responses_total{server, zone, view, rcode, plugin}` - response per zone, rcode and plugin. | ||||
| * `coredns_dns_https_responses_total{server, status}` - responses per server and http status code. | ||||
| * `coredns_dns_quic_responses_total{server, status}` - responses per server and QUIC application code. | ||||
| * `coredns_plugin_enabled{server, zone, view, name}` - indicates whether a plugin is enabled on per server, zone and view basis. | ||||
|  | ||||
| Almost each counter has a label `zone` which is the zonename used for the request/response. | ||||
|   | ||||
| @@ -72,6 +72,13 @@ var ( | ||||
| 		Name:      "https_responses_total", | ||||
| 		Help:      "Counter of DoH responses per server and http status code.", | ||||
| 	}, []string{"server", "status"}) | ||||
|  | ||||
| 	QUICResponsesCount = promauto.NewCounterVec(prometheus.CounterOpts{ | ||||
| 		Namespace: plugin.Namespace, | ||||
| 		Subsystem: subsystem, | ||||
| 		Name:      "quic_responses_total", | ||||
| 		Help:      "Counter of DoQ responses per server and QUIC application code.", | ||||
| 	}, []string{"server", "status"}) | ||||
| ) | ||||
|  | ||||
| const ( | ||||
|   | ||||
| @@ -61,6 +61,8 @@ func HostPortOrFile(s ...string) ([]string, error) { | ||||
| 				ss = net.JoinHostPort(host, transport.Port) | ||||
| 			case transport.TLS: | ||||
| 				ss = transport.TLS + "://" + net.JoinHostPort(host, transport.TLSPort) | ||||
| 			case transport.QUIC: | ||||
| 				ss = transport.QUIC + "://" + net.JoinHostPort(host, transport.QUICPort) | ||||
| 			case transport.GRPC: | ||||
| 				ss = transport.GRPC + "://" + net.JoinHostPort(host, transport.GRPCPort) | ||||
| 			case transport.HTTPS: | ||||
|   | ||||
| @@ -19,6 +19,10 @@ func Transport(s string) (trans string, addr string) { | ||||
| 		s = s[len(transport.DNS+"://"):] | ||||
| 		return transport.DNS, s | ||||
|  | ||||
| 	case strings.HasPrefix(s, transport.QUIC+"://"): | ||||
| 		s = s[len(transport.QUIC+"://"):] | ||||
| 		return transport.QUIC, s | ||||
|  | ||||
| 	case strings.HasPrefix(s, transport.GRPC+"://"): | ||||
| 		s = s[len(transport.GRPC+"://"):] | ||||
| 		return transport.GRPC, s | ||||
|   | ||||
| @@ -4,6 +4,7 @@ package transport | ||||
| const ( | ||||
| 	DNS   = "dns" | ||||
| 	TLS   = "tls" | ||||
| 	QUIC  = "quic" | ||||
| 	GRPC  = "grpc" | ||||
| 	HTTPS = "https" | ||||
| 	UNIX  = "unix" | ||||
| @@ -15,6 +16,8 @@ const ( | ||||
| 	Port = "53" | ||||
| 	// TLSPort is the default port for DNS-over-TLS. | ||||
| 	TLSPort = "853" | ||||
| 	// QUICPort is the default port for DNS-over-QUIC. | ||||
| 	QUICPort = "853" | ||||
| 	// GRPCPort is the default port for DNS-over-gRPC. | ||||
| 	GRPCPort = "443" | ||||
| 	// HTTPSPort is the default port for DNS-over-HTTPS. | ||||
|   | ||||
							
								
								
									
										165
									
								
								test/quic_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								test/quic_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,165 @@ | ||||
| package test | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"encoding/binary" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/coredns/coredns/core/dnsserver" | ||||
| 	ctls "github.com/coredns/coredns/plugin/pkg/tls" | ||||
|  | ||||
| 	"github.com/miekg/dns" | ||||
| 	"github.com/quic-go/quic-go" | ||||
| ) | ||||
|  | ||||
| var quicCorefile = `quic://.:0 { | ||||
| 		tls ../plugin/tls/test_cert.pem ../plugin/tls/test_key.pem ../plugin/tls/test_ca.pem | ||||
| 		whoami | ||||
| 	}` | ||||
|  | ||||
| func TestQUIC(t *testing.T) { | ||||
| 	q, udp, _, err := CoreDNSServerAndPorts(quicCorefile) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Could not get CoreDNS serving instance: %s", err) | ||||
| 	} | ||||
| 	defer q.Stop() | ||||
|  | ||||
| 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||||
| 	defer cancel() | ||||
|  | ||||
| 	conn, err := quic.DialAddr(ctx, convertAddress(udp), generateTLSConfig(), nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error but got: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	m := createTestMsg() | ||||
|  | ||||
| 	streamSync, err := conn.OpenStreamSync(ctx) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error but got: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	_, err = streamSync.Write(m) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error but got: %s", err) | ||||
| 	} | ||||
| 	_ = streamSync.Close() | ||||
|  | ||||
| 	sizeBuf := make([]byte, 2) | ||||
| 	_, err = io.ReadFull(streamSync, sizeBuf) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error but got: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	size := binary.BigEndian.Uint16(sizeBuf) | ||||
| 	buf := make([]byte, size) | ||||
| 	_, err = io.ReadFull(streamSync, buf) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error but got: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	d := new(dns.Msg) | ||||
| 	err = d.Unpack(buf) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error but got: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	if d.Rcode != dns.RcodeSuccess { | ||||
| 		t.Errorf("Expected success but got %d", d.Rcode) | ||||
| 	} | ||||
|  | ||||
| 	if len(d.Extra) != 2 { | ||||
| 		t.Errorf("Expected 2 RRs in additional section, but got %d", len(d.Extra)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestQUICProtocolError(t *testing.T) { | ||||
| 	q, udp, _, err := CoreDNSServerAndPorts(quicCorefile) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Could not get CoreDNS serving instance: %s", err) | ||||
| 	} | ||||
| 	defer q.Stop() | ||||
|  | ||||
| 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||||
| 	defer cancel() | ||||
|  | ||||
| 	conn, err := quic.DialAddr(ctx, convertAddress(udp), generateTLSConfig(), nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error but got: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	m := createInvalidDOQMsg() | ||||
|  | ||||
| 	streamSync, err := conn.OpenStreamSync(ctx) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error but got: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	_, err = streamSync.Write(m) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error but got: %s", err) | ||||
| 	} | ||||
| 	_ = streamSync.Close() | ||||
|  | ||||
| 	errorBuf := make([]byte, 2) | ||||
| 	_, err = io.ReadFull(streamSync, errorBuf) | ||||
| 	if err == nil { | ||||
| 		t.Errorf("Expected protocol error but got: %s", errorBuf) | ||||
| 	} | ||||
|  | ||||
| 	if !isProtocolErr(err) { | ||||
| 		t.Errorf("Expected \"Application Error 0x2\" but got: %s", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func isProtocolErr(err error) bool { | ||||
| 	var qAppErr *quic.ApplicationError | ||||
| 	return errors.As(err, &qAppErr) && qAppErr.ErrorCode == 2 | ||||
| } | ||||
|  | ||||
| // convertAddress transforms the address given in CoreDNSServerAndPorts to a format | ||||
| // that quic.DialAddr can read. It is unable to use [::]:61799, see: | ||||
| // "INTERNAL_ERROR (local): write udp [::]:50676->[::]:61799: sendmsg: no route to host" | ||||
| // So it transforms it to localhost:61799. | ||||
| func convertAddress(address string) string { | ||||
| 	if strings.HasPrefix(address, "[::]") { | ||||
| 		address = strings.Replace(address, "[::]", "localhost", 1) | ||||
| 	} | ||||
| 	return address | ||||
| } | ||||
|  | ||||
| func generateTLSConfig() *tls.Config { | ||||
| 	tlsConfig, err := ctls.NewTLSConfig( | ||||
| 		"../plugin/tls/test_cert.pem", | ||||
| 		"../plugin/tls/test_key.pem", | ||||
| 		"../plugin/tls/test_ca.pem") | ||||
|  | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	tlsConfig.NextProtos = []string{"doq"} | ||||
| 	tlsConfig.InsecureSkipVerify = true | ||||
|  | ||||
| 	return tlsConfig | ||||
| } | ||||
|  | ||||
| func createTestMsg() []byte { | ||||
| 	m := new(dns.Msg) | ||||
| 	m.SetQuestion("whoami.example.org.", dns.TypeA) | ||||
| 	m.Id = 0 | ||||
| 	msg, _ := m.Pack() | ||||
| 	return dnsserver.AddPrefix(msg) | ||||
| } | ||||
|  | ||||
| func createInvalidDOQMsg() []byte { | ||||
| 	m := new(dns.Msg) | ||||
| 	m.SetQuestion("whoami.example.org.", dns.TypeA) | ||||
| 	msg, _ := m.Pack() | ||||
| 	return msg | ||||
| } | ||||
		Reference in New Issue
	
	Block a user