diff --git a/dns/transport/https.go b/dns/transport/https.go index a13d9116..5b0499f6 100644 --- a/dns/transport/https.go +++ b/dns/transport/https.go @@ -3,6 +3,7 @@ package transport import ( "bytes" "context" + "encoding/base64" "io" "net" "net/http" @@ -42,6 +43,7 @@ type HTTPSTransport struct { logger logger.ContextLogger dialer N.Dialer destination *url.URL + method string headers http.Header transport *http.Transport } @@ -104,6 +106,7 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options logger, transportDialer, &destinationURL, + options.Method, headers, serverAddr, tlsConfig, @@ -115,6 +118,7 @@ func NewHTTPSRaw( logger log.ContextLogger, dialer N.Dialer, destination *url.URL, + method string, headers http.Header, serverAddr M.Socksaddr, tlsConfig tls.Config, @@ -147,6 +151,7 @@ func NewHTTPSRaw( TransportAdapter: adapter, logger: logger, dialer: dialer, + method: method, destination: destination, headers: headers, transport: transport, @@ -176,13 +181,26 @@ func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS requestBuffer.Release() return nil, err } - request, err := http.NewRequestWithContext(ctx, http.MethodPost, t.destination.String(), bytes.NewReader(rawMessage)) + destination := *t.destination + var request *http.Request + var body io.Reader + switch t.method { + case http.MethodGet: + query := url.Values{} + query.Set("dns", base64.RawURLEncoding.EncodeToString(rawMessage)) + destination.RawQuery = query.Encode() + case http.MethodPost: + body = bytes.NewReader(rawMessage) + } + request, err = http.NewRequestWithContext(ctx, t.method, destination.String(), body) if err != nil { requestBuffer.Release() return nil, err } request.Header = t.headers.Clone() - request.Header.Set("Content-Type", MimeType) + if t.method == http.MethodPost { + request.Header.Set("Content-Type", MimeType) + } request.Header.Set("Accept", MimeType) response, err := t.transport.RoundTrip(request) requestBuffer.Release() diff --git a/dns/transport/quic/http3.go b/dns/transport/quic/http3.go index fd1591a3..037acba1 100644 --- a/dns/transport/quic/http3.go +++ b/dns/transport/quic/http3.go @@ -3,6 +3,7 @@ package quic import ( "bytes" "context" + "encoding/base64" "io" "net" "net/http" @@ -40,6 +41,7 @@ type HTTP3Transport struct { logger logger.ContextLogger dialer N.Dialer destination *url.URL + method string headers http.Header transport *http3.Transport } @@ -100,6 +102,7 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options logger: logger, dialer: transportDialer, destination: &destinationURL, + method: options.Method, headers: headers, transport: &http3.Transport{ Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (quic.EarlyConnection, error) { @@ -132,13 +135,26 @@ func (t *HTTP3Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS requestBuffer.Release() return nil, err } - request, err := http.NewRequestWithContext(ctx, http.MethodPost, t.destination.String(), bytes.NewReader(rawMessage)) + destination := *t.destination + var request *http.Request + var body io.Reader + switch t.method { + case http.MethodGet: + query := url.Values{} + query.Set("dns", base64.RawURLEncoding.EncodeToString(rawMessage)) + destination.RawQuery = query.Encode() + case http.MethodPost: + body = bytes.NewReader(rawMessage) + } + request, err = http.NewRequestWithContext(ctx, t.method, destination.String(), body) if err != nil { requestBuffer.Release() return nil, err } request.Header = t.headers.Clone() - request.Header.Set("Content-Type", transport.MimeType) + if t.method == http.MethodPost { + request.Header.Set("Content-Type", transport.MimeType) + } request.Header.Set("Accept", transport.MimeType) response, err := t.transport.RoundTrip(request) requestBuffer.Release() diff --git a/docs/configuration/dns/server/http3.md b/docs/configuration/dns/server/http3.md index dd81ba2d..5d6589f6 100644 --- a/docs/configuration/dns/server/http3.md +++ b/docs/configuration/dns/server/http3.md @@ -20,6 +20,7 @@ icon: material/new-box "server_port": 443, "path": "", + "method": "", "headers": {}, "tls": {}, @@ -58,6 +59,14 @@ The path of the DNS server. `/dns-query` will be used by default. +#### method + +The method of the DNS server. + +Only `GET` and `POST` are supported. + +`POST` will be used by default. + #### headers Additional headers to be sent to the DNS server. diff --git a/docs/configuration/dns/server/https.md b/docs/configuration/dns/server/https.md index 46e69a55..10f2041e 100644 --- a/docs/configuration/dns/server/https.md +++ b/docs/configuration/dns/server/https.md @@ -20,6 +20,7 @@ icon: material/new-box "server_port": 443, "path": "", + "method": "", "headers": {}, "tls": {}, @@ -58,6 +59,14 @@ The path of the DNS server. `/dns-query` will be used by default. +#### method + +The method of the DNS server. + +Only `GET` and `POST` are supported. + +`POST` will be used by default. + #### headers Additional headers to be sent to the DNS server. diff --git a/option/dns.go b/option/dns.go index f303b894..bb923eed 100644 --- a/option/dns.go +++ b/option/dns.go @@ -2,6 +2,7 @@ package option import ( "context" + "net/http" "net/netip" "net/url" @@ -371,13 +372,39 @@ type RemoteTLSDNSServerOptions struct { OutboundTLSOptionsContainer } -type RemoteHTTPSDNSServerOptions struct { +type _RemoteHTTPSDNSServerOptions struct { RemoteTLSDNSServerOptions Path string `json:"path,omitempty"` Method string `json:"method,omitempty"` Headers badoption.HTTPHeader `json:"headers,omitempty"` } +type RemoteHTTPSDNSServerOptions _RemoteHTTPSDNSServerOptions + +func (o *RemoteHTTPSDNSServerOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) { + switch o.Method { + case http.MethodPost: + o.Method = "" + } + return badjson.MarshallObjectsContext(ctx, (*_RemoteHTTPSDNSServerOptions)(o)) +} + +func (o *RemoteHTTPSDNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.UnmarshalContext(ctx, content, (*_RemoteHTTPSDNSServerOptions)(o)) + if err != nil { + return err + } + switch o.Method { + case "", http.MethodPost: + o.Method = http.MethodPost + case http.MethodGet: + o.Method = http.MethodGet + default: + return E.New("unsupported method") + } + return nil +} + type FakeIPDNSServerOptions struct { Inet4Range *badoption.Prefix `json:"inet4_range,omitempty"` Inet6Range *badoption.Prefix `json:"inet6_range,omitempty"` diff --git a/protocol/tailscale/dns_transport.go b/protocol/tailscale/dns_transport.go index 3447b6b2..3c093802 100644 --- a/protocol/tailscale/dns_transport.go +++ b/protocol/tailscale/dns_transport.go @@ -180,13 +180,13 @@ func (t *DNSTransport) createResolver(directDialer func() N.Dialer, resolver *dn tlsConfig := common.Must1(tls.NewClient(t.ctx, serverAddr.AddrString(), option.OutboundTLSOptions{ ALPN: []string{http2.NextProtoTLS, "http/1.1"}, })) - return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, tlsConfig), nil + return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.MethodPost, http.Header{}, serverAddr, tlsConfig), nil case "http": serverAddr = M.ParseSocksaddrHostPortStr(serverURL.Hostname(), serverURL.Port()) if serverAddr.Port == 0 { serverAddr.Port = 80 } - return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, nil), nil + return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.MethodPost, http.Header{}, serverAddr, nil), nil // case "tls": default: return nil, E.New("unknown resolver scheme: ", serverURL.Scheme)