diff --git a/mosdns/patches/117-pool-simplify-PackBuffer.patch b/mosdns/patches/117-pool-simplify-PackBuffer.patch
deleted file mode 100644
index 9922c6b5a..000000000
--- a/mosdns/patches/117-pool-simplify-PackBuffer.patch
+++ /dev/null
@@ -1,222 +0,0 @@
-From 64a83b8e28b3988df9eec4425130b57a09b15032 Mon Sep 17 00:00:00 2001
-From: Irine Sistiana <49315432+IrineSistiana@users.noreply.github.com>
-Date: Thu, 21 Sep 2023 22:06:49 +0800
-Subject: [PATCH 1/9] pool: simplify PackBuffer
-
----
- pkg/dnsutils/net_io.go | 7 +++---
- pkg/pool/msg_buf.go | 41 ++++++++++++++++++------------------
- pkg/pool/msg_buf_test.go | 6 +-----
- pkg/server/http_handler.go | 6 +++---
- pkg/server/tcp.go | 6 +++---
- pkg/server/udp.go | 6 +++---
- pkg/upstream/doh/upstream.go | 5 +++--
- 7 files changed, 37 insertions(+), 40 deletions(-)
-
-diff --git a/pkg/dnsutils/net_io.go b/pkg/dnsutils/net_io.go
-index f165446..26e6efb 100644
---- a/pkg/dnsutils/net_io.go
-+++ b/pkg/dnsutils/net_io.go
-@@ -101,13 +101,12 @@ func WriteRawMsgToTCP(c io.Writer, b []byte) (n int, err error) {
- }
-
- func WriteMsgToUDP(c io.Writer, m *dns.Msg) (int, error) {
-- b, buf, err := pool.PackBuffer(m)
-+ b, err := pool.PackBuffer(m)
- if err != nil {
- return 0, err
- }
-- defer pool.ReleaseBuf(buf)
--
-- return c.Write(b)
-+ defer pool.ReleaseBuf(b)
-+ return c.Write(*b)
- }
-
- func ReadMsgFromUDP(c io.Reader, bufSize int) (*dns.Msg, int, error) {
-diff --git a/pkg/pool/msg_buf.go b/pkg/pool/msg_buf.go
-index 11faf7d..b5f861c 100644
---- a/pkg/pool/msg_buf.go
-+++ b/pkg/pool/msg_buf.go
-@@ -26,47 +26,48 @@ import (
- "github.com/miekg/dns"
- )
-
--// There is no such way to give dns.Msg.PackBuffer() a buffer
--// with a proper size.
--// Just give it a big buf and hope the buf will be reused in most scenes.
--const packBufSize = 4096
-+// dns.Msg.PackBuffer requires a buffer with length of m.Len() + 1.
-+// Don't know why it needs one more byte.
-+func getPackBuffer(m *dns.Msg) int {
-+ return m.Len() + 1
-+}
-
- // PackBuffer packs the dns msg m to wire format.
- // Callers should release the buf by calling ReleaseBuf after they have done
- // with the wire []byte.
--func PackBuffer(m *dns.Msg) (wire []byte, buf *[]byte, err error) {
-- buf = GetBuf(packBufSize)
-- wire, err = m.PackBuffer(*buf)
-+func PackBuffer(m *dns.Msg) (*[]byte, error) {
-+ b := GetBuf(getPackBuffer(m))
-+ wire, err := m.PackBuffer(*b)
- if err != nil {
-- ReleaseBuf(buf)
-- return nil, nil, err
-+ ReleaseBuf(b)
-+ return nil, err
- }
-- return wire, buf, nil
-+ if &((*b)[0]) != &wire[0] { // reallocated
-+ ReleaseBuf(b)
-+ return nil, dns.ErrBuf
-+ }
-+ return b, nil
- }
-
- // PackBuffer packs the dns msg m to wire format, with to bytes length header.
- // Callers should release the buf by calling ReleaseBuf.
--func PackTCPBuffer(m *dns.Msg) (buf *[]byte, err error) {
-- b := GetBuf(packBufSize)
-+func PackTCPBuffer(m *dns.Msg) (*[]byte, error) {
-+ b := GetBuf(2 + getPackBuffer(m))
- wire, err := m.PackBuffer((*b)[2:])
- if err != nil {
- ReleaseBuf(b)
- return nil, err
- }
-+ if &((*b)[2]) != &wire[0] { // reallocated
-+ ReleaseBuf(b)
-+ return nil, dns.ErrBuf
-+ }
-
- l := len(wire)
- if l > dns.MaxMsgSize {
- ReleaseBuf(b)
- return nil, fmt.Errorf("dns payload size %d is too large", l)
- }
--
-- if &((*b)[2]) != &wire[0] { // reallocated
-- ReleaseBuf(b)
-- b = GetBuf(l + 2)
-- binary.BigEndian.PutUint16((*b)[:2], uint16(l))
-- copy((*b)[2:], wire)
-- return b, nil
-- }
- binary.BigEndian.PutUint16((*b)[:2], uint16(l))
- *b = (*b)[:2+l]
- return b, nil
-diff --git a/pkg/pool/msg_buf_test.go b/pkg/pool/msg_buf_test.go
-index 97d9a76..bfd98d1 100644
---- a/pkg/pool/msg_buf_test.go
-+++ b/pkg/pool/msg_buf_test.go
-@@ -28,12 +28,8 @@ import (
- func TestPackBuffer_No_Allocation(t *testing.T) {
- m := new(dns.Msg)
- m.SetQuestion("123.", dns.TypeAAAA)
-- wire, buf, err := PackBuffer(m)
-+ _, err := PackBuffer(m)
- if err != nil {
- t.Fatal(err)
- }
--
-- if cap(wire) != cap(*buf) {
-- t.Fatalf("wire and buf have different cap, wire %d, buf %d", cap(wire), cap(*buf))
-- }
- }
-diff --git a/pkg/server/http_handler.go b/pkg/server/http_handler.go
-index 58f5811..3e671e3 100644
---- a/pkg/server/http_handler.go
-+++ b/pkg/server/http_handler.go
-@@ -103,17 +103,17 @@ func (h *HttpHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- panic(err) // Force http server to close connection.
- }
-
-- b, buf, err := pool.PackBuffer(r)
-+ b, err := pool.PackBuffer(r)
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- h.warnErr(req, "failed to unpack handler's response", err)
- return
- }
-- defer pool.ReleaseBuf(buf)
-+ defer pool.ReleaseBuf(b)
-
- w.Header().Set("Content-Type", "application/dns-message")
- w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", dnsutils.GetMinimalTTL(r)))
-- if _, err := w.Write(b); err != nil {
-+ if _, err := w.Write(*b); err != nil {
- h.warnErr(req, "failed to write response", err)
- return
- }
-diff --git a/pkg/server/tcp.go b/pkg/server/tcp.go
-index 5f479b1..ddc4846 100644
---- a/pkg/server/tcp.go
-+++ b/pkg/server/tcp.go
-@@ -101,14 +101,14 @@ func ServeTCP(l net.Listener, h Handler, opts TCPServerOpts) error {
- c.Close() // abort the connection
- return
- }
-- b, buf, err := pool.PackBuffer(r)
-+ b, err := pool.PackTCPBuffer(r)
- if err != nil {
- logger.Error("failed to unpack handler's response", zap.Error(err), zap.Stringer("msg", r))
- return
- }
-- defer pool.ReleaseBuf(buf)
-+ defer pool.ReleaseBuf(b)
-
-- if _, err := dnsutils.WriteRawMsgToTCP(c, b); err != nil {
-+ if _, err := c.Write(*b); err != nil {
- logger.Warn("failed to write response", zap.Stringer("client", c.RemoteAddr()), zap.Error(err))
- return
- }
-diff --git a/pkg/server/udp.go b/pkg/server/udp.go
-index 4dc1087..22e8d2b 100644
---- a/pkg/server/udp.go
-+++ b/pkg/server/udp.go
-@@ -95,18 +95,18 @@ func ServeUDP(c *net.UDPConn, h Handler, opts UDPServerOpts) error {
- }
- if r != nil {
- r.Truncate(getUDPSize(q))
-- b, buf, err := pool.PackBuffer(r)
-+ b, err := pool.PackBuffer(r)
- if err != nil {
- logger.Error("failed to unpack handler's response", zap.Error(err), zap.Stringer("msg", r))
- return
- }
-- defer pool.ReleaseBuf(buf)
-+ defer pool.ReleaseBuf(b)
-
- var oob []byte
- if oobWriter != nil && dstIpFromCm != nil {
- oob = oobWriter(dstIpFromCm)
- }
-- if _, _, err := c.WriteMsgUDPAddrPort(b, oob, remoteAddr); err != nil {
-+ if _, _, err := c.WriteMsgUDPAddrPort(*b, oob, remoteAddr); err != nil {
- logger.Warn("failed to write response", zap.Stringer("client", remoteAddr), zap.Error(err))
- }
- }
-diff --git a/pkg/upstream/doh/upstream.go b/pkg/upstream/doh/upstream.go
-index abc124b..9cc72c4 100644
---- a/pkg/upstream/doh/upstream.go
-+++ b/pkg/upstream/doh/upstream.go
-@@ -54,11 +54,12 @@ var (
- )
-
- func (u *Upstream) ExchangeContext(ctx context.Context, q *dns.Msg) (*dns.Msg, error) {
-- wire, buf, err := pool.PackBuffer(q)
-+ bp, err := pool.PackBuffer(q)
- if err != nil {
- return nil, fmt.Errorf("failed to pack query msg, %w", err)
- }
-- defer pool.ReleaseBuf(buf)
-+ defer pool.ReleaseBuf(bp)
-+ wire := *bp
-
- // In order to maximize HTTP cache friendliness, DoH clients using media
- // formats that include the ID field from the DNS message header, such
---
-2.34.8
-
diff --git a/mosdns/patches/118-utils-update-BytesToStringUnsafe-remove-SplitLineReg.patch b/mosdns/patches/118-utils-update-BytesToStringUnsafe-remove-SplitLineReg.patch
deleted file mode 100644
index b4f8dc576..000000000
--- a/mosdns/patches/118-utils-update-BytesToStringUnsafe-remove-SplitLineReg.patch
+++ /dev/null
@@ -1,39 +0,0 @@
-From 4c1a7967a9367a8cce2b37fa6c81de1b50b9fa42 Mon Sep 17 00:00:00 2001
-From: Irine Sistiana <49315432+IrineSistiana@users.noreply.github.com>
-Date: Thu, 21 Sep 2023 22:30:15 +0800
-Subject: [PATCH 2/9] utils: update BytesToStringUnsafe, remove SplitLineReg
-
----
- pkg/utils/strings.go | 10 +---------
- 1 file changed, 1 insertion(+), 9 deletions(-)
-
-diff --git a/pkg/utils/strings.go b/pkg/utils/strings.go
-index 632aadb..23471c2 100644
---- a/pkg/utils/strings.go
-+++ b/pkg/utils/strings.go
-@@ -20,21 +20,13 @@
- package utils
-
- import (
-- "regexp"
- "strings"
- "unsafe"
- )
-
- // BytesToStringUnsafe converts bytes to string.
- func BytesToStringUnsafe(b []byte) string {
-- return *(*string)(unsafe.Pointer(&b))
--}
--
--var charBlockExpr = regexp.MustCompile("\\S+")
--
--// SplitLineReg extracts words from s by using regexp "\S+".
--func SplitLineReg(s string) []string {
-- return charBlockExpr.FindAllString(s, -1)
-+ return unsafe.String(unsafe.SliceData(b), len(b))
- }
-
- // RemoveComment removes comment after "symbol".
---
-2.34.8
-
diff --git a/mosdns/patches/119-server-simplify-Handler-interface-add-more-meta.patch b/mosdns/patches/119-server-simplify-Handler-interface-add-more-meta.patch
deleted file mode 100644
index cde19fcc5..000000000
--- a/mosdns/patches/119-server-simplify-Handler-interface-add-more-meta.patch
+++ /dev/null
@@ -1,254 +0,0 @@
-From c0af4b587311766650c8c103656dcb595bcfef34 Mon Sep 17 00:00:00 2001
-From: Irine Sistiana <49315432+IrineSistiana@users.noreply.github.com>
-Date: Fri, 22 Sep 2023 09:24:05 +0800
-Subject: [PATCH 3/9] server: simplify Handler interface, add more meta
-
----
- pkg/server/http_handler.go | 25 +++++++++--------
- pkg/server/iface.go | 18 ++++++++-----
- pkg/server/tcp.go | 21 ++++++++-------
- pkg/server/udp.go | 42 ++++++++---------------------
- pkg/server_handler/entry_handler.go | 29 +++++++++++++++++---
- 5 files changed, 71 insertions(+), 64 deletions(-)
-
-diff --git a/pkg/server/http_handler.go b/pkg/server/http_handler.go
-index 3e671e3..5a41314 100644
---- a/pkg/server/http_handler.go
-+++ b/pkg/server/http_handler.go
-@@ -28,7 +28,6 @@ import (
- "net/netip"
- "strings"
-
-- "github.com/IrineSistiana/mosdns/v5/pkg/dnsutils"
- "github.com/IrineSistiana/mosdns/v5/pkg/pool"
- "github.com/miekg/dns"
- "go.uber.org/zap"
-@@ -97,23 +96,23 @@ func (h *HttpHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- return
- }
-
-- r, err := h.dnsHandler.Handle(req.Context(), q, QueryMeta{ClientAddr: clientAddr})
-- if err != nil {
-- h.warnErr(req, "handler err", err)
-- panic(err) // Force http server to close connection.
-+ queryMeta := QueryMeta{
-+ ClientAddr: clientAddr,
- }
--
-- b, err := pool.PackBuffer(r)
-- if err != nil {
-+ if u := req.URL; u != nil {
-+ queryMeta.UrlPath = u.Path
-+ }
-+ if tlsStat := req.TLS; tlsStat != nil {
-+ queryMeta.ServerName = tlsStat.ServerName
-+ }
-+ resp := h.dnsHandler.Handle(req.Context(), q, queryMeta, pool.PackBuffer)
-+ if resp == nil {
- w.WriteHeader(http.StatusInternalServerError)
-- h.warnErr(req, "failed to unpack handler's response", err)
- return
- }
-- defer pool.ReleaseBuf(b)
--
-+ defer pool.ReleaseBuf(resp)
- w.Header().Set("Content-Type", "application/dns-message")
-- w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", dnsutils.GetMinimalTTL(r)))
-- if _, err := w.Write(*b); err != nil {
-+ if _, err := w.Write(*resp); err != nil {
- h.warnErr(req, "failed to write response", err)
- return
- }
-diff --git a/pkg/server/iface.go b/pkg/server/iface.go
-index 2f15be1..c45b502 100644
---- a/pkg/server/iface.go
-+++ b/pkg/server/iface.go
-@@ -10,14 +10,20 @@ import (
- // Handler handles incoming request q and MUST ALWAYS return a response.
- // Handler MUST handle dns errors by itself and return a proper error responses.
- // e.g. Return a SERVFAIL if something goes wrong.
--// If Handle() returns an error, caller considers that the error is associated
--// with the downstream connection and will close the downstream connection
--// immediately.
-+// If Handle() returns a nil resp, caller will
-+// udp: do nothing.
-+// tcp/dot: close the connection immediately.
-+// doh: send a 500 response.
-+// doq: close the stream immediately.
- type Handler interface {
-- Handle(ctx context.Context, q *dns.Msg, meta QueryMeta) (resp *dns.Msg, err error)
-+ Handle(ctx context.Context, q *dns.Msg, meta QueryMeta, packMsgPayload func(m *dns.Msg) (*[]byte, error)) (respPayload *[]byte)
- }
-
- type QueryMeta struct {
-- ClientAddr netip.Addr // Maybe invalid
-- FromUDP bool
-+ FromUDP bool
-+
-+ // Optional
-+ ClientAddr netip.Addr
-+ ServerName string
-+ UrlPath string
- }
-diff --git a/pkg/server/tcp.go b/pkg/server/tcp.go
-index ddc4846..6faba76 100644
---- a/pkg/server/tcp.go
-+++ b/pkg/server/tcp.go
-@@ -21,6 +21,7 @@ package server
-
- import (
- "context"
-+ "crypto/tls"
- "fmt"
- "net"
- "net/netip"
-@@ -93,22 +94,22 @@ func ServeTCP(l net.Listener, h Handler, opts TCPServerOpts) error {
- return // read err, close the connection
- }
-
-+ // Try to get server name from tls conn.
-+ var serverName string
-+ if tlsConn, ok := c.(*tls.Conn); ok {
-+ serverName = tlsConn.ConnectionState().ServerName
-+ }
-+
- // handle query
- go func() {
-- r, err := h.Handle(tcpConnCtx, req, QueryMeta{ClientAddr: clientAddr})
-- if err != nil {
-- logger.Warn("handler err", zap.Error(err))
-+ r := h.Handle(tcpConnCtx, req, QueryMeta{ClientAddr: clientAddr, ServerName: serverName}, pool.PackTCPBuffer)
-+ if r == nil {
- c.Close() // abort the connection
- return
- }
-- b, err := pool.PackTCPBuffer(r)
-- if err != nil {
-- logger.Error("failed to unpack handler's response", zap.Error(err), zap.Stringer("msg", r))
-- return
-- }
-- defer pool.ReleaseBuf(b)
-+ defer pool.ReleaseBuf(r)
-
-- if _, err := c.Write(*b); err != nil {
-+ if _, err := c.Write(*r); err != nil {
- logger.Warn("failed to write response", zap.Stringer("client", c.RemoteAddr()), zap.Error(err))
- return
- }
-diff --git a/pkg/server/udp.go b/pkg/server/udp.go
-index 22e8d2b..89e57e2 100644
---- a/pkg/server/udp.go
-+++ b/pkg/server/udp.go
-@@ -63,10 +63,10 @@ func ServeUDP(c *net.UDPConn, h Handler, opts UDPServerOpts) error {
- n, oobn, _, remoteAddr, err := c.ReadMsgUDPAddrPort(*rb, ob)
- if err != nil {
- if n == 0 {
-- // err with zero read. Most likely becasue c was closed.
-+ // Err with zero read. Most likely because c was closed.
- return fmt.Errorf("unexpected read err: %w", err)
- }
-- // err with some read. Tempory err.
-+ // Temporary err.
- logger.Warn("read err", zap.Error(err))
- continue
- }
-@@ -88,42 +88,22 @@ func ServeUDP(c *net.UDPConn, h Handler, opts UDPServerOpts) error {
-
- // handle query
- go func() {
-- r, err := h.Handle(listenerCtx, q, QueryMeta{ClientAddr: remoteAddr.Addr(), FromUDP: true})
-- if err != nil {
-- logger.Warn("handler err", zap.Error(err))
-+ payload := h.Handle(listenerCtx, q, QueryMeta{ClientAddr: remoteAddr.Addr(), FromUDP: true}, pool.PackBuffer)
-+ if payload == nil {
- return
- }
-- if r != nil {
-- r.Truncate(getUDPSize(q))
-- b, err := pool.PackBuffer(r)
-- if err != nil {
-- logger.Error("failed to unpack handler's response", zap.Error(err), zap.Stringer("msg", r))
-- return
-- }
-- defer pool.ReleaseBuf(b)
-+ defer pool.ReleaseBuf(payload)
-
-- var oob []byte
-- if oobWriter != nil && dstIpFromCm != nil {
-- oob = oobWriter(dstIpFromCm)
-- }
-- if _, _, err := c.WriteMsgUDPAddrPort(*b, oob, remoteAddr); err != nil {
-- logger.Warn("failed to write response", zap.Stringer("client", remoteAddr), zap.Error(err))
-- }
-+ var oob []byte
-+ if oobWriter != nil && dstIpFromCm != nil {
-+ oob = oobWriter(dstIpFromCm)
-+ }
-+ if _, _, err := c.WriteMsgUDPAddrPort(*payload, oob, remoteAddr); err != nil {
-+ logger.Warn("failed to write response", zap.Stringer("client", remoteAddr), zap.Error(err))
- }
- }()
- }
- }
-
--func getUDPSize(m *dns.Msg) int {
-- var s uint16
-- if opt := m.IsEdns0(); opt != nil {
-- s = opt.UDPSize()
-- }
-- if s < dns.MinMsgSize {
-- s = dns.MinMsgSize
-- }
-- return int(s)
--}
--
- type getSrcAddrFromOOB func(oob []byte) (net.IP, error)
- type writeSrcAddrToOOB func(a net.IP) []byte
-diff --git a/pkg/server_handler/entry_handler.go b/pkg/server_handler/entry_handler.go
-index 520e3d2..9e3a386 100644
---- a/pkg/server_handler/entry_handler.go
-+++ b/pkg/server_handler/entry_handler.go
-@@ -71,9 +71,9 @@ func NewEntryHandler(opts EntryHandlerOpts) *EntryHandler {
- }
-
- // ServeDNS implements server.Handler.
--// If entry returns an error, a SERVFAIL response will be set.
--// If entry returns without a response, a REFUSED response will be set.
--func (h *EntryHandler) Handle(ctx context.Context, q *dns.Msg, qInfo server.QueryMeta) (*dns.Msg, error) {
-+// If entry returns an error, a SERVFAIL response will be returned.
-+// If entry returns without a response, a REFUSED response will be returned.
-+func (h *EntryHandler) Handle(ctx context.Context, q *dns.Msg, qInfo server.QueryMeta, packMsgPayload func(m *dns.Msg) (*[]byte, error)) *[]byte {
- ddl := time.Now().Add(h.opts.QueryTimeout)
- ctx, cancel := context.WithDeadline(ctx, ddl)
- defer cancel()
-@@ -100,5 +100,26 @@ func (h *EntryHandler) Handle(ctx context.Context, q *dns.Msg, qInfo server.Quer
- respMsg.Rcode = dns.RcodeServerFailure
- }
- respMsg.RecursionAvailable = true
-- return respMsg, nil
-+
-+ if qInfo.FromUDP {
-+ respMsg.Truncate(getUDPSize(q))
-+ }
-+
-+ payload, err := packMsgPayload(respMsg)
-+ if err != nil {
-+ h.opts.Logger.Error("internal err: failed to pack resp msg", zap.Error(err))
-+ return nil
-+ }
-+ return payload
-+}
-+
-+func getUDPSize(m *dns.Msg) int {
-+ var s uint16
-+ if opt := m.IsEdns0(); opt != nil {
-+ s = opt.UDPSize()
-+ }
-+ if s < dns.MinMsgSize {
-+ s = dns.MinMsgSize
-+ }
-+ return int(s)
- }
---
-2.34.8
-
diff --git a/mosdns/patches/120-server-add-doq-server.patch b/mosdns/patches/120-server-add-doq-server.patch
deleted file mode 100644
index 70afd9832..000000000
--- a/mosdns/patches/120-server-add-doq-server.patch
+++ /dev/null
@@ -1,321 +0,0 @@
-From df0762ce550c33e1cfd423fef95020c41ca770da Mon Sep 17 00:00:00 2001
-From: Irine Sistiana <49315432+IrineSistiana@users.noreply.github.com>
-Date: Fri, 22 Sep 2023 10:39:07 +0800
-Subject: [PATCH 4/9] server: add doq server
-
----
- pkg/server/doq.go | 121 +++++++++++++++++++++++
- plugin/enabled_plugins.go | 20 ++--
- plugin/server/quic_server/quic_server.go | 120 ++++++++++++++++++++++
- 3 files changed, 248 insertions(+), 13 deletions(-)
- create mode 100644 pkg/server/doq.go
- create mode 100644 plugin/server/quic_server/quic_server.go
-
-diff --git a/pkg/server/doq.go b/pkg/server/doq.go
-new file mode 100644
-index 0000000..8fb5f81
---- /dev/null
-+++ b/pkg/server/doq.go
-@@ -0,0 +1,121 @@
-+/*
-+ * Copyright (C) 2020-2022, IrineSistiana
-+ *
-+ * This file is part of mosdns.
-+ *
-+ * mosdns is free software: you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License as published by
-+ * the Free Software Foundation, either version 3 of the License, or
-+ * (at your option) any later version.
-+ *
-+ * mosdns is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-+ * GNU General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU General Public License
-+ * along with this program. If not, see .
-+ */
-+
-+package server
-+
-+import (
-+ "context"
-+ "fmt"
-+ "net"
-+ "net/netip"
-+ "time"
-+
-+ "github.com/IrineSistiana/mosdns/v5/pkg/dnsutils"
-+ "github.com/IrineSistiana/mosdns/v5/pkg/pool"
-+ "github.com/quic-go/quic-go"
-+ "go.uber.org/zap"
-+)
-+
-+const (
-+ defaultQuicIdleTimeout = time.Second * 30
-+ streamReadTimeout = time.Second * 1
-+ quicFirstReadTimeout = time.Millisecond * 500
-+)
-+
-+type DoQServerOpts struct {
-+ Logger *zap.Logger
-+ IdleTimeout time.Duration
-+}
-+
-+// ServeDoQ starts a server at l. It returns if l had an Accept() error.
-+// It always returns a non-nil error.
-+func ServeDoQ(l *quic.Listener, h Handler, opts DoQServerOpts) error {
-+ logger := opts.Logger
-+ if logger == nil {
-+ logger = nopLogger
-+ }
-+ idleTimeout := opts.IdleTimeout
-+ if idleTimeout <= 0 {
-+ idleTimeout = defaultQuicIdleTimeout
-+ }
-+
-+ listenerCtx, cancel := context.WithCancel(context.Background())
-+ defer cancel()
-+ for {
-+ c, err := l.Accept(listenerCtx)
-+ if err != nil {
-+ return fmt.Errorf("unexpected listener err: %w", err)
-+ }
-+
-+ // handle connection
-+ connCtx, cancelConn := context.WithCancel(listenerCtx)
-+ go func() {
-+ defer c.CloseWithError(0, "")
-+ defer cancelConn()
-+
-+ var clientAddr netip.Addr
-+ ta, ok := c.RemoteAddr().(*net.UDPAddr)
-+ if ok {
-+ clientAddr = ta.AddrPort().Addr()
-+ }
-+
-+ firstRead := true
-+ for {
-+ var streamAcceptTimeout time.Duration
-+ if firstRead {
-+ firstRead = false
-+ streamAcceptTimeout = quicFirstReadTimeout
-+ } else {
-+ streamAcceptTimeout = idleTimeout
-+ }
-+ streamAcceptCtx, cancelStreamAccept := context.WithTimeout(connCtx, streamAcceptTimeout)
-+ stream, err := c.AcceptStream(streamAcceptCtx)
-+ cancelStreamAccept()
-+ if err != nil {
-+ return
-+ }
-+
-+ // Handle stream.
-+ // For doq, one stream, one query.
-+ go func() {
-+ defer stream.Close()
-+
-+ // Avoid fragmentation attack.
-+ stream.SetReadDeadline(time.Now().Add(streamReadTimeout))
-+ req, _, err := dnsutils.ReadMsgFromTCP(stream)
-+ if err != nil {
-+ return
-+ }
-+ queryMeta := QueryMeta{
-+ ClientAddr: clientAddr,
-+ ServerName: c.ConnectionState().TLS.ServerName,
-+ }
-+
-+ resp := h.Handle(connCtx, req, queryMeta, pool.PackTCPBuffer)
-+ if resp == nil {
-+ return
-+ }
-+ if _, err := stream.Write(*resp); err != nil {
-+ logger.Warn("failed to write response", zap.Stringer("client", c.RemoteAddr()), zap.Error(err))
-+ }
-+ }()
-+ }
-+ }()
-+ }
-+}
-diff --git a/plugin/enabled_plugins.go b/plugin/enabled_plugins.go
-index 199587c..0f7531b 100644
---- a/plugin/enabled_plugins.go
-+++ b/plugin/enabled_plugins.go
-@@ -21,12 +21,11 @@ package plugin
-
- // data providers
- import (
-+ // data provider
- _ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider/domain_set"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider/ip_set"
--)
-
--// matches
--import (
-+ // matcher
- _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/client_ip"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/cname"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/env"
-@@ -39,10 +38,8 @@ import (
- _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/random"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/rcode"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/resp_ip"
--)
-
--// executables
--import (
-+ // executable
- _ "github.com/IrineSistiana/mosdns/v5/plugin/executable/arbitrary"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/executable/black_hole"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/executable/cache"
-@@ -62,16 +59,13 @@ import (
- _ "github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence/fallback"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/executable/sleep"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/executable/ttl"
--)
-
--// other
--import (
-- _ "github.com/IrineSistiana/mosdns/v5/plugin/mark" // executable and matcher
--)
-+ // executable and matcher
-+ _ "github.com/IrineSistiana/mosdns/v5/plugin/mark"
-
--// servers
--import (
-+ // server
- _ "github.com/IrineSistiana/mosdns/v5/plugin/server/http_server"
-+ _ "github.com/IrineSistiana/mosdns/v5/plugin/server/quic_server"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/server/tcp_server"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/server/udp_server"
- )
-diff --git a/plugin/server/quic_server/quic_server.go b/plugin/server/quic_server/quic_server.go
-new file mode 100644
-index 0000000..8a5a4c1
---- /dev/null
-+++ b/plugin/server/quic_server/quic_server.go
-@@ -0,0 +1,120 @@
-+/*
-+ * Copyright (C) 2020-2022, IrineSistiana
-+ *
-+ * This file is part of mosdns.
-+ *
-+ * mosdns is free software: you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License as published by
-+ * the Free Software Foundation, either version 3 of the License, or
-+ * (at your option) any later version.
-+ *
-+ * mosdns is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-+ * GNU General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU General Public License
-+ * along with this program. If not, see .
-+ */
-+
-+package quic_server
-+
-+import (
-+ "crypto/tls"
-+ "errors"
-+ "fmt"
-+ "net"
-+ "time"
-+
-+ "github.com/IrineSistiana/mosdns/v5/coremain"
-+ "github.com/IrineSistiana/mosdns/v5/pkg/server"
-+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
-+ "github.com/IrineSistiana/mosdns/v5/plugin/server/server_utils"
-+ "github.com/quic-go/quic-go"
-+)
-+
-+const PluginType = "quic_server"
-+
-+func init() {
-+ coremain.RegNewPluginFunc(PluginType, Init, func() any { return new(Args) })
-+}
-+
-+type Args struct {
-+ Entry string `yaml:"entry"`
-+ Listen string `yaml:"listen"`
-+ Cert string `yaml:"cert"`
-+ Key string `yaml:"key"`
-+ IdleTimeout int `yaml:"idle_timeout"`
-+}
-+
-+func (a *Args) init() {
-+ utils.SetDefaultNum(&a.IdleTimeout, 30)
-+}
-+
-+type QuicServer struct {
-+ args *Args
-+
-+ l *quic.Listener
-+}
-+
-+func (s *QuicServer) Close() error {
-+ return s.l.Close()
-+}
-+
-+func Init(bp *coremain.BP, args any) (any, error) {
-+ return StartServer(bp, args.(*Args))
-+}
-+
-+func StartServer(bp *coremain.BP, args *Args) (*QuicServer, error) {
-+ dh, err := server_utils.NewHandler(bp, args.Entry)
-+ if err != nil {
-+ return nil, fmt.Errorf("failed to init dns handler, %w", err)
-+ }
-+
-+ // Init tls
-+ if len(args.Key) == 0 || len(args.Cert) == 0 {
-+ return nil, errors.New("quic server requires a tls certificate")
-+ }
-+ tlsConfig := new(tls.Config)
-+ if err := server.LoadCert(tlsConfig, args.Cert, args.Key); err != nil {
-+ return nil, fmt.Errorf("failed to read tls cert, %w", err)
-+ }
-+ tlsConfig.NextProtos = []string{"doq"}
-+
-+ uc, err := net.ListenPacket("udp", args.Listen)
-+ if err != nil {
-+ return nil, fmt.Errorf("failed to listen socket, %w", err)
-+ }
-+
-+ idleTimeout := time.Duration(args.IdleTimeout) * time.Second
-+
-+ quicConfig := &quic.Config{
-+ MaxIdleTimeout: idleTimeout,
-+ InitialStreamReceiveWindow: 4 * 1024,
-+ MaxStreamReceiveWindow: 4 * 1024,
-+ InitialConnectionReceiveWindow: 8 * 1024,
-+ MaxConnectionReceiveWindow: 16 * 1024,
-+ Allow0RTT: false,
-+ }
-+
-+ qt := &quic.Transport{
-+ Conn: uc,
-+ }
-+
-+ quicListener, err := qt.Listen(tlsConfig, quicConfig)
-+ if err != nil {
-+ qt.Close()
-+ return nil, fmt.Errorf("failed to listen quic, %w", err)
-+ }
-+
-+ go func() {
-+ defer quicListener.Close()
-+ serverOpts := server.DoQServerOpts{Logger: bp.L(), IdleTimeout: idleTimeout}
-+ err := server.ServeDoQ(quicListener, dh, serverOpts)
-+ bp.M().GetSafeClose().SendCloseSignal(err)
-+ }()
-+ return &QuicServer{
-+ args: args,
-+ l: quicListener,
-+ }, nil
-+}
---
-2.34.8
-
diff --git a/mosdns/patches/121-query_context-add-QueryMeta.patch b/mosdns/patches/121-query_context-add-QueryMeta.patch
deleted file mode 100644
index c288818b3..000000000
--- a/mosdns/patches/121-query_context-add-QueryMeta.patch
+++ /dev/null
@@ -1,266 +0,0 @@
-From 65bf1f77a56fe481cacf3a1cada155b66949578f Mon Sep 17 00:00:00 2001
-From: Irine Sistiana <49315432+IrineSistiana@users.noreply.github.com>
-Date: Fri, 22 Sep 2023 16:10:24 +0800
-Subject: [PATCH 5/9] query_context: add QueryMeta
-
----
- pkg/query_context/client_addr.go | 38 -------------------
- pkg/query_context/context.go | 23 ++++++++---
- pkg/server_handler/entry_handler.go | 5 +--
- .../dual_selector/dual_selector_test.go | 9 +++--
- plugin/executable/ipset/ipset_test.go | 11 +++---
- plugin/executable/sequence/sequence_test.go | 5 ++-
- plugin/matcher/client_ip/client_ip_matcher.go | 4 +-
- 7 files changed, 34 insertions(+), 61 deletions(-)
- delete mode 100644 pkg/query_context/client_addr.go
-
-diff --git a/pkg/query_context/client_addr.go b/pkg/query_context/client_addr.go
-deleted file mode 100644
-index 7793fe6..0000000
---- a/pkg/query_context/client_addr.go
-+++ /dev/null
-@@ -1,38 +0,0 @@
--/*
-- * Copyright (C) 2020-2022, IrineSistiana
-- *
-- * This file is part of mosdns.
-- *
-- * mosdns is free software: you can redistribute it and/or modify
-- * it under the terms of the GNU General Public License as published by
-- * the Free Software Foundation, either version 3 of the License, or
-- * (at your option) any later version.
-- *
-- * mosdns is distributed in the hope that it will be useful,
-- * but WITHOUT ANY WARRANTY; without even the implied warranty of
-- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- * GNU General Public License for more details.
-- *
-- * You should have received a copy of the GNU General Public License
-- * along with this program. If not, see .
-- */
--
--package query_context
--
--import (
-- "net/netip"
--)
--
--var clientAddrKey = RegKey()
--
--func SetClientAddr(qCtx *Context, addr *netip.Addr) {
-- qCtx.StoreValue(clientAddrKey, addr)
--}
--
--func GetClientAddr(qCtx *Context) (*netip.Addr, bool) {
-- v, ok := qCtx.GetValue(clientAddrKey)
-- if !ok {
-- return nil, false
-- }
-- return v.(*netip.Addr), true
--}
-diff --git a/pkg/query_context/context.go b/pkg/query_context/context.go
-index d3e67ae..9fa3fd7 100644
---- a/pkg/query_context/context.go
-+++ b/pkg/query_context/context.go
-@@ -20,11 +20,13 @@
- package query_context
-
- import (
-+ "sync/atomic"
-+ "time"
-+
-+ "github.com/IrineSistiana/mosdns/v5/pkg/server"
- "github.com/miekg/dns"
- "go.uber.org/zap"
- "go.uber.org/zap/zapcore"
-- "sync/atomic"
-- "time"
- )
-
- // Context is a query context that pass through plugins
-@@ -34,6 +36,7 @@ import (
- type Context struct {
- startTime time.Time // when was this Context created
- q *dns.Msg
-+ queryMeta QueryMeta
-
- // id for this Context. Not for the dns query. This id is mainly for logging.
- id uint32
-@@ -48,14 +51,17 @@ type Context struct {
-
- var contextUid atomic.Uint32
-
-+type QueryMeta = server.QueryMeta
-+
- // NewContext creates a new query Context.
- // q is the query dns msg. It cannot be nil, or NewContext will panic.
--func NewContext(q *dns.Msg) *Context {
-+func NewContext(q *dns.Msg, qm QueryMeta) *Context {
- if q == nil {
- panic("handler: query msg is nil")
- }
- ctx := &Context{
- q: q,
-+ queryMeta: qm,
- id: contextUid.Add(1),
- startTime: time.Now(),
- }
-@@ -68,6 +74,11 @@ func (ctx *Context) Q() *dns.Msg {
- return ctx.q
- }
-
-+// QueryMeta returns the meta data of the query.
-+func (ctx *Context) QueryMeta() QueryMeta {
-+ return ctx.queryMeta
-+}
-+
- // R returns the response. It might be nil.
- func (ctx *Context) R() *dns.Msg {
- return ctx.r
-@@ -164,8 +175,8 @@ func (ctx *Context) DeleteMark(m uint32) {
- func (ctx *Context) MarshalLogObject(encoder zapcore.ObjectEncoder) error {
- encoder.AddUint32("uqid", ctx.id)
-
-- if addr, ok := GetClientAddr(ctx); ok && addr.IsValid() {
-- zap.Stringer("client", addr).AddTo(encoder)
-+ if clientAddr := ctx.queryMeta.ClientAddr; clientAddr.IsValid() {
-+ zap.Stringer("client", clientAddr).AddTo(encoder)
- }
-
- q := ctx.Q()
-@@ -180,7 +191,7 @@ func (ctx *Context) MarshalLogObject(encoder zapcore.ObjectEncoder) error {
- if r := ctx.R(); r != nil {
- encoder.AddInt("rcode", r.Rcode)
- }
-- encoder.AddDuration("elapsed", time.Now().Sub(ctx.StartTime()))
-+ encoder.AddDuration("elapsed", time.Since(ctx.StartTime()))
- return nil
- }
-
-diff --git a/pkg/server_handler/entry_handler.go b/pkg/server_handler/entry_handler.go
-index 9e3a386..c12d852 100644
---- a/pkg/server_handler/entry_handler.go
-+++ b/pkg/server_handler/entry_handler.go
-@@ -79,10 +79,7 @@ func (h *EntryHandler) Handle(ctx context.Context, q *dns.Msg, qInfo server.Quer
- defer cancel()
-
- // exec entry
-- qCtx := query_context.NewContext(q)
-- if qInfo.ClientAddr.IsValid() {
-- query_context.SetClientAddr(qCtx, &qInfo.ClientAddr)
-- }
-+ qCtx := query_context.NewContext(q, qInfo)
- err := h.opts.Entry.Exec(ctx, qCtx)
- respMsg := qCtx.R()
- if err != nil {
-diff --git a/plugin/executable/dual_selector/dual_selector_test.go b/plugin/executable/dual_selector/dual_selector_test.go
-index 6a5ae92..524e739 100644
---- a/plugin/executable/dual_selector/dual_selector_test.go
-+++ b/plugin/executable/dual_selector/dual_selector_test.go
-@@ -21,14 +21,15 @@ package dual_selector
-
- import (
- "context"
-+ "net"
-+ "testing"
-+ "time"
-+
- "github.com/IrineSistiana/mosdns/v5/coremain"
- "github.com/IrineSistiana/mosdns/v5/pkg/query_context"
- "github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence"
- "github.com/miekg/dns"
- "go.uber.org/zap"
-- "net"
-- "testing"
-- "time"
- )
-
- type dummyNext struct {
-@@ -158,7 +159,7 @@ func TestSelector_Exec(t *testing.T) {
-
- q := new(dns.Msg)
- q.SetQuestion("example.", tt.qtype)
-- qCtx := query_context.NewContext(q)
-+ qCtx := query_context.NewContext(q, query_context.QueryMeta{})
- cw := sequence.NewChainWalker([]*sequence.ChainNode{{E: tt.next}}, nil)
- if err := s.Exec(context.Background(), qCtx, cw); (err != nil) != tt.wantErr {
- t.Errorf("Exec() error = %v, wantErr %v", err, tt.wantErr)
-diff --git a/plugin/executable/ipset/ipset_test.go b/plugin/executable/ipset/ipset_test.go
-index cb92eb2..c5ad508 100644
---- a/plugin/executable/ipset/ipset_test.go
-+++ b/plugin/executable/ipset/ipset_test.go
-@@ -24,15 +24,16 @@ package ipset
- import (
- "context"
- "fmt"
-- "github.com/IrineSistiana/mosdns/v5/pkg/query_context"
-- "github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence"
-- "github.com/miekg/dns"
-- "github.com/vishvananda/netlink"
- "math/rand"
- "net"
- "os"
- "strconv"
- "testing"
-+
-+ "github.com/IrineSistiana/mosdns/v5/pkg/query_context"
-+ "github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence"
-+ "github.com/miekg/dns"
-+ "github.com/vishvananda/netlink"
- )
-
- func skipTest(t *testing.T) {
-@@ -85,7 +86,7 @@ func Test_ipset(t *testing.T) {
- r.Answer = append(r.Answer, &dns.A{A: net.ParseIP("127.0.0.2")})
- r.Answer = append(r.Answer, &dns.AAAA{AAAA: net.ParseIP("::1")})
- r.Answer = append(r.Answer, &dns.AAAA{AAAA: net.ParseIP("::2")})
-- qCtx := query_context.NewContext(q)
-+ qCtx := query_context.NewContext(q, query_context.QueryMeta{})
- qCtx.SetResponse(r)
- if err := p.Exec(context.Background(), qCtx); err != nil {
- t.Fatal(err)
-diff --git a/plugin/executable/sequence/sequence_test.go b/plugin/executable/sequence/sequence_test.go
-index ea7704d..16b1360 100644
---- a/plugin/executable/sequence/sequence_test.go
-+++ b/plugin/executable/sequence/sequence_test.go
-@@ -22,10 +22,11 @@ package sequence
- import (
- "context"
- "errors"
-+ "testing"
-+
- "github.com/IrineSistiana/mosdns/v5/coremain"
- "github.com/IrineSistiana/mosdns/v5/pkg/query_context"
- "github.com/miekg/dns"
-- "testing"
- )
-
- type dummy struct {
-@@ -186,7 +187,7 @@ func Test_sequence_Exec(t *testing.T) {
- if err != nil {
- t.Fatal(err)
- }
-- qCtx := query_context.NewContext(new(dns.Msg))
-+ qCtx := query_context.NewContext(new(dns.Msg), query_context.QueryMeta{})
- if err := s.Exec(context.Background(), qCtx); (err != nil) != tt.wantErr {
- t.Errorf("Exec() error = %v, wantErr %v", err, tt.wantErr)
- }
-diff --git a/plugin/matcher/client_ip/client_ip_matcher.go b/plugin/matcher/client_ip/client_ip_matcher.go
-index 357df9b..b308b5d 100644
---- a/plugin/matcher/client_ip/client_ip_matcher.go
-+++ b/plugin/matcher/client_ip/client_ip_matcher.go
-@@ -39,9 +39,9 @@ func QuickSetup(bq sequence.BQ, s string) (sequence.Matcher, error) {
- }
-
- func matchClientAddr(qCtx *query_context.Context, m netlist.Matcher) (bool, error) {
-- addr, _ := query_context.GetClientAddr(qCtx)
-+ addr := qCtx.QueryMeta().ClientAddr
- if !addr.IsValid() {
- return false, nil
- }
-- return m.Match(*addr), nil
-+ return m.Match(addr), nil
- }
---
-2.34.8
-
diff --git a/mosdns/patches/122-add-new-string_exp-matcher.patch b/mosdns/patches/122-add-new-string_exp-matcher.patch
deleted file mode 100644
index 9c699dfa0..000000000
--- a/mosdns/patches/122-add-new-string_exp-matcher.patch
+++ /dev/null
@@ -1,291 +0,0 @@
-From 71145e797f3748b2b608d7f2e0319339fbd41f5b Mon Sep 17 00:00:00 2001
-From: Irine Sistiana <49315432+IrineSistiana@users.noreply.github.com>
-Date: Fri, 22 Sep 2023 17:35:01 +0800
-Subject: [PATCH 6/9] add new string_exp matcher
-
----
- plugin/enabled_plugins.go | 1 +
- plugin/matcher/string_exp/string_exp.go | 184 +++++++++++++++++++
- plugin/matcher/string_exp/string_exp_test.go | 67 +++++++
- 3 files changed, 252 insertions(+)
- create mode 100644 plugin/matcher/string_exp/string_exp.go
- create mode 100644 plugin/matcher/string_exp/string_exp_test.go
-
-diff --git a/plugin/enabled_plugins.go b/plugin/enabled_plugins.go
-index 0f7531b..dfb311b 100644
---- a/plugin/enabled_plugins.go
-+++ b/plugin/enabled_plugins.go
-@@ -38,6 +38,7 @@ import (
- _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/random"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/rcode"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/resp_ip"
-+ _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/string_exp"
-
- // executable
- _ "github.com/IrineSistiana/mosdns/v5/plugin/executable/arbitrary"
-diff --git a/plugin/matcher/string_exp/string_exp.go b/plugin/matcher/string_exp/string_exp.go
-new file mode 100644
-index 0000000..692f4e3
---- /dev/null
-+++ b/plugin/matcher/string_exp/string_exp.go
-@@ -0,0 +1,184 @@
-+/*
-+ * Copyright (C) 2020-2022, IrineSistiana
-+ *
-+ * This file is part of mosdns.
-+ *
-+ * mosdns is free software: you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License as published by
-+ * the Free Software Foundation, either version 3 of the License, or
-+ * (at your option) any later version.
-+ *
-+ * mosdns is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-+ * GNU General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU General Public License
-+ * along with this program. If not, see .
-+ */
-+
-+package string_exp
-+
-+import (
-+ "context"
-+ "errors"
-+ "fmt"
-+ "os"
-+ "regexp"
-+ "strings"
-+
-+ "github.com/IrineSistiana/mosdns/v5/pkg/query_context"
-+ "github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence"
-+)
-+
-+const PluginType = "string_exp"
-+
-+func init() {
-+ sequence.MustRegMatchQuickSetup(PluginType, QuickSetup)
-+}
-+
-+var _ sequence.Matcher = (*Matcher)(nil)
-+
-+type Matcher struct {
-+ getStr GetStrFunc
-+ m StringMatcher
-+}
-+
-+type StringMatcher interface {
-+ MatchStr(s string) bool
-+}
-+
-+type GetStrFunc func(qCtx *query_context.Context) string
-+
-+func (m *Matcher) Match(_ context.Context, qCtx *query_context.Context) (bool, error) {
-+ return m.match(qCtx), nil
-+}
-+
-+func (m *Matcher) match(qCtx *query_context.Context) bool {
-+ return m.m.MatchStr(m.getStr(qCtx))
-+}
-+
-+func NewMatcher(f GetStrFunc, sm StringMatcher) *Matcher {
-+ m := &Matcher{
-+ getStr: f,
-+ m: sm,
-+ }
-+ return m
-+}
-+
-+// Format: "scr_string_name op [string]..."
-+// scr_string_name = {url_path|server_name|$env_key}
-+// op = {zl|eq|prefix|suffix|contains|regexp}
-+func QuickSetupFromStr(s string) (sequence.Matcher, error) {
-+ sf := strings.Fields(s)
-+ if len(sf) < 2 {
-+ return nil, errors.New("not enough args")
-+ }
-+ srcStrName := sf[0]
-+ op := sf[1]
-+ args := sf[2:]
-+
-+ var sm StringMatcher
-+ switch op {
-+ case "zl":
-+ sm = opZl{}
-+ case "eq":
-+ m := make(map[string]struct{})
-+ for _, s := range args {
-+ m[s] = struct{}{}
-+ }
-+ sm = &opEq{m: m}
-+ case "regexp":
-+ var exps []*regexp.Regexp
-+ for _, s := range args {
-+ exp, err := regexp.Compile(s)
-+ if err != nil {
-+ return nil, fmt.Errorf("invalid reg expression, %w", err)
-+ }
-+ exps = append(exps, exp)
-+ }
-+ sm = &opRegExp{exp: exps}
-+ case "prefix":
-+ sm = &opF{s: args, f: strings.HasPrefix}
-+ case "suffix":
-+ sm = &opF{s: args, f: strings.HasSuffix}
-+ case "contains":
-+ sm = &opF{s: args, f: strings.Contains}
-+ default:
-+ return nil, fmt.Errorf("invalid operator %s", op)
-+ }
-+
-+ var gf GetStrFunc
-+ if strings.HasPrefix(srcStrName, "$") {
-+ // Env
-+ envKey := strings.TrimPrefix(srcStrName, "$")
-+ gf = func(_ *query_context.Context) string {
-+ return os.Getenv(envKey)
-+ }
-+ } else {
-+ switch srcStrName {
-+ case "url_path":
-+ gf = getUrlPath
-+ case "server_name":
-+ gf = getServerName
-+ default:
-+ return nil, fmt.Errorf("invalid src string name %s", srcStrName)
-+ }
-+ }
-+ return NewMatcher(gf, sm), nil
-+}
-+
-+// QuickSetup returns a sequence.ExecQuickSetupFunc.
-+func QuickSetup(_ sequence.BQ, s string) (sequence.Matcher, error) {
-+ return QuickSetupFromStr(s)
-+}
-+
-+type opZl struct{}
-+
-+func (op opZl) MatchStr(s string) bool {
-+ return len(s) == 0
-+}
-+
-+type opEq struct {
-+ m map[string]struct{}
-+}
-+
-+func (op *opEq) MatchStr(s string) bool {
-+ _, ok := op.m[s]
-+ return ok
-+}
-+
-+type opF struct {
-+ s []string
-+ f func(s, arg string) bool
-+}
-+
-+func (op *opF) MatchStr(s string) bool {
-+ for _, sub := range op.s {
-+ if op.f(s, sub) {
-+ return true
-+ }
-+ }
-+ return false
-+}
-+
-+type opRegExp struct {
-+ exp []*regexp.Regexp
-+}
-+
-+func (op *opRegExp) MatchStr(s string) bool {
-+ for _, exp := range op.exp {
-+ if exp.MatchString(s) {
-+ return true
-+ }
-+ }
-+ return false
-+}
-+
-+func getUrlPath(qCtx *query_context.Context) string {
-+ return qCtx.QueryMeta().UrlPath
-+}
-+
-+func getServerName(qCtx *query_context.Context) string {
-+ return qCtx.QueryMeta().ServerName
-+}
-diff --git a/plugin/matcher/string_exp/string_exp_test.go b/plugin/matcher/string_exp/string_exp_test.go
-new file mode 100644
-index 0000000..9140191
---- /dev/null
-+++ b/plugin/matcher/string_exp/string_exp_test.go
-@@ -0,0 +1,67 @@
-+/*
-+ * Copyright (C) 2020-2022, IrineSistiana
-+ *
-+ * This file is part of mosdns.
-+ *
-+ * mosdns is free software: you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License as published by
-+ * the Free Software Foundation, either version 3 of the License, or
-+ * (at your option) any later version.
-+ *
-+ * mosdns is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-+ * GNU General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU General Public License
-+ * along with this program. If not, see .
-+ */
-+
-+package string_exp
-+
-+import (
-+ "context"
-+ "os"
-+ "testing"
-+
-+ "github.com/IrineSistiana/mosdns/v5/pkg/query_context"
-+ "github.com/miekg/dns"
-+ "github.com/stretchr/testify/require"
-+)
-+
-+func TestMatcher_Match(t *testing.T) {
-+ r := require.New(t)
-+ q := new(dns.Msg)
-+ qc := query_context.NewContext(q, query_context.QueryMeta{UrlPath: "/dns-query", ServerName: "a.b.c"})
-+ os.Setenv("STRING_EXP_TEST", "abc")
-+
-+ doTest := func(arg string, want bool) {
-+ t.Helper()
-+ urlMatcher, err := QuickSetupFromStr(arg)
-+ r.NoError(err)
-+ got, err := urlMatcher.Match(context.Background(), qc)
-+ r.NoError(err)
-+ r.Equal(want, got)
-+ }
-+
-+ doTest("url_path zl", false)
-+ doTest("url_path eq /dns-query", true)
-+ doTest("url_path eq /123 /dns-query /abc", true)
-+ doTest("url_path eq /123 /abc", false)
-+ doTest("url_path contains abc dns def", true)
-+ doTest("url_path contains abc def", false)
-+ doTest("url_path prefix abc /dns def", true)
-+ doTest("url_path prefix abc def", false)
-+ doTest("url_path suffix abc query def", true)
-+ doTest("url_path suffix abc def", false)
-+ doTest("url_path regexp ^/dns-query$", true)
-+ doTest("url_path regexp ^abc", false)
-+
-+ doTest("server_name eq abc a.b.c def", true)
-+ doTest("server_name eq abc def", false)
-+
-+ doTest("$STRING_EXP_TEST eq 123 abc def", true)
-+ doTest("$STRING_EXP_TEST eq 123 def", false)
-+ doTest("$STRING_EXP_TEST_NOT_EXIST eq 123 abc def", false)
-+ doTest("$STRING_EXP_TEST_NOT_EXIST zl", true)
-+}
---
-2.34.8
-
diff --git a/mosdns/patches/123-add-plugin-rate_limiter.patch b/mosdns/patches/123-add-plugin-rate_limiter.patch
deleted file mode 100644
index 56c50ac1b..000000000
--- a/mosdns/patches/123-add-plugin-rate_limiter.patch
+++ /dev/null
@@ -1,297 +0,0 @@
-From 11436dd9cde412f83d1bfbd06b4163445c52bb12 Mon Sep 17 00:00:00 2001
-From: Irine Sistiana <49315432+IrineSistiana@users.noreply.github.com>
-Date: Fri, 22 Sep 2023 20:55:49 +0800
-Subject: [PATCH 7/9] add plugin rate_limiter
-
----
- go.mod | 1 +
- go.sum | 2 +
- pkg/rate_limiter/rate_limiter.go | 145 ++++++++++++++++++
- plugin/enabled_plugins.go | 1 +
- .../executable/rate_limiter/rate_limiter.go | 85 ++++++++++
- 5 files changed, 234 insertions(+)
- create mode 100644 pkg/rate_limiter/rate_limiter.go
- create mode 100644 plugin/executable/rate_limiter/rate_limiter.go
-
-diff --git a/go.mod b/go.mod
-index 7c2b96a..aea0c99 100644
---- a/go.mod
-+++ b/go.mod
-@@ -63,6 +63,7 @@ require (
- golang.org/x/crypto v0.13.0 // indirect
- golang.org/x/mod v0.12.0 // indirect
- golang.org/x/text v0.13.0 // indirect
-+ golang.org/x/time v0.3.0 // indirect
- golang.org/x/tools v0.13.0 // indirect
- gopkg.in/ini.v1 v1.67.0 // indirect
- gopkg.in/yaml.v3 v3.0.1 // indirect
-diff --git a/go.sum b/go.sum
-index dd20043..d2b393f 100644
---- a/go.sum
-+++ b/go.sum
-@@ -413,6 +413,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
- golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
- golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
- golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
-+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
- golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
- golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
- golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-diff --git a/pkg/rate_limiter/rate_limiter.go b/pkg/rate_limiter/rate_limiter.go
-new file mode 100644
-index 0000000..30fa516
---- /dev/null
-+++ b/pkg/rate_limiter/rate_limiter.go
-@@ -0,0 +1,145 @@
-+package rate_limiter
-+
-+import (
-+ "io"
-+ "net/netip"
-+ "sync"
-+ "time"
-+
-+ "golang.org/x/time/rate"
-+)
-+
-+type RateLimiter interface {
-+ Allow(addr netip.Addr) bool
-+ io.Closer
-+}
-+
-+type limiter struct {
-+ limit rate.Limit
-+ burst int
-+ mask4 int
-+ mask6 int
-+
-+ closeOnce sync.Once
-+ closeNotify chan struct{}
-+ m sync.Mutex
-+ tables map[netip.Addr]*limiterEntry
-+}
-+
-+type limiterEntry struct {
-+ l *rate.Limiter
-+ lastSeen time.Time
-+ sync.Once
-+}
-+
-+// limit and burst should be greater than zero.
-+// If gcInterval is <= 0, it will be automatically chosen between 2~10s.
-+// In this case, if the token refill time (burst/limit) is greater than 10s,
-+// the actual average qps limit may be higher than expected.
-+// If mask is zero or greater than 32/128. The default is 32/48.
-+// If mask is negative, the masks will be 0.
-+func NewRateLimiter(limit rate.Limit, burst int, gcInterval time.Duration, mask4, mask6 int) RateLimiter {
-+ if mask4 > 32 || mask4 == 0 {
-+ mask4 = 32
-+ }
-+ if mask4 < 0 {
-+ mask4 = 0
-+ }
-+
-+ if mask6 > 128 || mask6 == 0 {
-+ mask6 = 48
-+ }
-+ if mask6 < 0 {
-+ mask6 = 0
-+ }
-+
-+ if gcInterval <= 0 {
-+ if limit <= 0 || burst <= 0 {
-+ gcInterval = time.Second * 2
-+ } else {
-+ refillSec := float64(burst) / float64(limit)
-+ if refillSec < 2 {
-+ refillSec = 2
-+ }
-+ if refillSec > 10 {
-+ refillSec = 10
-+ }
-+ gcInterval = time.Duration(refillSec) * time.Second
-+ }
-+ }
-+
-+ l := &limiter{
-+ limit: limit,
-+ burst: burst,
-+ mask4: mask4,
-+ mask6: mask6,
-+ closeNotify: make(chan struct{}),
-+ tables: make(map[netip.Addr]*limiterEntry),
-+ }
-+ go l.gcLoop(gcInterval)
-+ return l
-+}
-+
-+func (l *limiter) Allow(a netip.Addr) bool {
-+ a = l.applyMask(a)
-+ now := time.Now()
-+ l.m.Lock()
-+ e, ok := l.tables[a]
-+ if !ok {
-+ e = &limiterEntry{
-+ l: rate.NewLimiter(l.limit, l.burst),
-+ lastSeen: now,
-+ }
-+ l.tables[a] = e
-+ }
-+ e.lastSeen = now
-+ clientLimiter := e.l
-+ l.m.Unlock()
-+ return clientLimiter.AllowN(now, 1)
-+}
-+
-+func (l *limiter) Close() error {
-+ l.closeOnce.Do(func() {
-+ close(l.closeNotify)
-+ })
-+ return nil
-+}
-+
-+func (l *limiter) gcLoop(gcInterval time.Duration) {
-+ ticker := time.NewTicker(gcInterval)
-+ defer ticker.Stop()
-+
-+ for {
-+ select {
-+ case <-l.closeNotify:
-+ return
-+ case now := <-ticker.C:
-+ l.doGc(now, gcInterval)
-+ }
-+ }
-+}
-+
-+func (l *limiter) doGc(now time.Time, gcInterval time.Duration) {
-+ l.m.Lock()
-+ defer l.m.Unlock()
-+
-+ for a, e := range l.tables {
-+ if now.Sub(e.lastSeen) > gcInterval {
-+ delete(l.tables, a)
-+ }
-+ }
-+}
-+
-+func (l *limiter) applyMask(a netip.Addr) netip.Addr {
-+ switch {
-+ case a.Is4():
-+ m, _ := a.Prefix(l.mask4)
-+ return m.Addr()
-+ case a.Is4In6():
-+ m, _ := netip.AddrFrom4(a.As4()).Prefix(l.mask4)
-+ return m.Addr()
-+ default:
-+ m, _ := a.Prefix(l.mask6)
-+ return m.Addr()
-+ }
-+}
-diff --git a/plugin/enabled_plugins.go b/plugin/enabled_plugins.go
-index dfb311b..d72ed07 100644
---- a/plugin/enabled_plugins.go
-+++ b/plugin/enabled_plugins.go
-@@ -54,6 +54,7 @@ import (
- _ "github.com/IrineSistiana/mosdns/v5/plugin/executable/metrics_collector"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/executable/nftset"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/executable/query_summary"
-+ _ "github.com/IrineSistiana/mosdns/v5/plugin/executable/rate_limiter"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/executable/redirect"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/executable/reverse_lookup"
- _ "github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence"
-diff --git a/plugin/executable/rate_limiter/rate_limiter.go b/plugin/executable/rate_limiter/rate_limiter.go
-new file mode 100644
-index 0000000..241f947
---- /dev/null
-+++ b/plugin/executable/rate_limiter/rate_limiter.go
-@@ -0,0 +1,85 @@
-+/*
-+ * Copyright (C) 2020-2022, IrineSistiana
-+ *
-+ * This file is part of mosdns.
-+ *
-+ * mosdns is free software: you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License as published by
-+ * the Free Software Foundation, either version 3 of the License, or
-+ * (at your option) any later version.
-+ *
-+ * mosdns is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-+ * GNU General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU General Public License
-+ * along with this program. If not, see .
-+ */
-+
-+package rate_limiter
-+
-+import (
-+ "context"
-+
-+ "github.com/IrineSistiana/mosdns/v5/coremain"
-+ "github.com/IrineSistiana/mosdns/v5/pkg/query_context"
-+ "github.com/IrineSistiana/mosdns/v5/pkg/rate_limiter"
-+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
-+ "github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence"
-+ "github.com/miekg/dns"
-+ "golang.org/x/time/rate"
-+)
-+
-+const PluginType = "rate_limiter"
-+
-+func init() {
-+ coremain.RegNewPluginFunc(PluginType, Init, func() any { return new(Args) })
-+}
-+
-+type Args struct {
-+ Qps float64 `yaml:"qps"`
-+ Burst int `yaml:"burst"`
-+ Mask4 int `yaml:"mask4"`
-+ Mask6 int `yaml:"mask6"`
-+}
-+
-+func (args *Args) init() {
-+ utils.SetDefaultUnsignNum(&args.Qps, 20)
-+ utils.SetDefaultUnsignNum(&args.Burst, 40)
-+ utils.SetDefaultUnsignNum(&args.Mask4, 32)
-+ utils.SetDefaultUnsignNum(&args.Mask4, 48)
-+}
-+
-+var _ sequence.Executable = (*RateLimiter)(nil)
-+
-+type RateLimiter struct {
-+ l rate_limiter.RateLimiter
-+}
-+
-+func Init(_ *coremain.BP, args any) (any, error) {
-+ return New(*(args.(*Args))), nil
-+}
-+
-+func New(args Args) *RateLimiter {
-+ args.init()
-+ l := rate_limiter.NewRateLimiter(rate.Limit(args.Qps), args.Burst, 0, args.Mask4, args.Mask6)
-+ return &RateLimiter{l: l}
-+}
-+
-+func (s *RateLimiter) Exec(ctx context.Context, qCtx *query_context.Context) error {
-+ clientAddr := qCtx.QueryMeta().ClientAddr
-+ if clientAddr.IsValid() {
-+ if !s.l.Allow(clientAddr) {
-+ qCtx.SetResponse(refuse(qCtx.Q()))
-+ }
-+ }
-+ return nil
-+}
-+
-+func refuse(q *dns.Msg) *dns.Msg {
-+ r := new(dns.Msg)
-+ r.SetReply(q)
-+ r.Rcode = dns.RcodeRefused
-+ return r
-+}
---
-2.34.8
-
diff --git a/mosdns/patches/124-transport-fixed-eol-pipelineConn-was-t-closed.patch b/mosdns/patches/124-transport-fixed-eol-pipelineConn-was-t-closed.patch
deleted file mode 100644
index cfdf38edc..000000000
--- a/mosdns/patches/124-transport-fixed-eol-pipelineConn-was-t-closed.patch
+++ /dev/null
@@ -1,158 +0,0 @@
-From f81a617d6bc3ad05bd9c9edd343083f4b4c09cd4 Mon Sep 17 00:00:00 2001
-From: Irine Sistiana <49315432+IrineSistiana@users.noreply.github.com>
-Date: Sat, 23 Sep 2023 08:28:10 +0800
-Subject: [PATCH 8/9] transport: fixed eol pipelineConn was't closed
-
-when calling PipelineTransport.Close()
----
- pkg/upstream/transport/pipeline.go | 54 ++++++++++++++-----------
- pkg/upstream/transport/pipeline_test.go | 2 +-
- 2 files changed, 31 insertions(+), 25 deletions(-)
-
-diff --git a/pkg/upstream/transport/pipeline.go b/pkg/upstream/transport/pipeline.go
-index 10c3d23..70f9c8d 100644
---- a/pkg/upstream/transport/pipeline.go
-+++ b/pkg/upstream/transport/pipeline.go
-@@ -33,10 +33,11 @@ import (
- type PipelineTransport struct {
- PipelineOpts
-
-- m sync.Mutex // protect following fields
-- closed bool
-- r *rand.Rand
-- conns []*pipelineConn
-+ m sync.Mutex // protect following fields
-+ closed bool
-+ r *rand.Rand
-+ activeConns []*pipelineConn
-+ conns map[*pipelineConn]struct{}
- }
-
- type PipelineOpts struct {
-@@ -66,6 +67,7 @@ func NewPipelineTransport(opt PipelineOpts) *PipelineTransport {
- return &PipelineTransport{
- PipelineOpts: opt,
- r: rand.New(rand.NewSource(time.Now().Unix())),
-+ conns: make(map[*pipelineConn]struct{}),
- }
- }
-
-@@ -73,13 +75,13 @@ func (t *PipelineTransport) ExchangeContext(ctx context.Context, m *dns.Msg) (*d
- const maxAttempt = 3
- attempt := 0
- for {
-- conn, allocatedQid, isNewConn, wg, err := t.getPipelineConn()
-+ pc, allocatedQid, isNewConn, err := t.getPipelineConn()
- if err != nil {
- return nil, err
- }
-
-- r, err := conn.exchangePipeline(ctx, m, allocatedQid)
-- wg.Done()
-+ r, err := pc.dc.exchangePipeline(ctx, m, allocatedQid)
-+ pc.wg.Done()
-
- if err != nil {
- // Reused connection may not stable.
-@@ -103,7 +105,7 @@ func (t *PipelineTransport) Close() error {
- return nil
- }
- t.closed = true
-- for _, conn := range t.conns {
-+ for conn := range t.conns {
- conn.dc.closeWithErr(errClosedTransport)
- }
- return nil
-@@ -113,10 +115,9 @@ func (t *PipelineTransport) Close() error {
- // Caller must call wg.Done() after dnsConn.exchangePipeline.
- // The returned dnsConn is ready to serve queries.
- func (t *PipelineTransport) getPipelineConn() (
-- dc *dnsConn,
-+ pc *pipelineConn,
- allocatedQid uint16,
- isNewConn bool,
-- wg *sync.WaitGroup,
- err error,
- ) {
- t.m.Lock()
-@@ -128,21 +129,19 @@ func (t *PipelineTransport) getPipelineConn() (
-
- pci, pc := t.pickPipelineConnLocked()
-
-- // Dail a new connection if (conn pool is empty), or
-- // (the picked conn is busy, and we are allowed to dail more connections).
-+ // Dial a new connection if (conn pool is empty), or
-+ // (the picked conn is busy, and we are allowed to dial more connections).
- maxConn := t.MaxConn
- if maxConn <= 0 {
- maxConn = defaultPipelineMaxConns
- }
-- if pc == nil || (pc.dc.queueLen() > pipelineBusyQueueLen && len(t.conns) < maxConn) {
-- dc = newDnsConn(t.IOOpts)
-+ if pc == nil || (pc.dc.queueLen() > pipelineBusyQueueLen && len(t.activeConns) < maxConn) {
-+ dc := newDnsConn(t.IOOpts)
- pc = newPipelineConn(dc)
- isNewConn = true
-- pci = sliceAdd(&t.conns, pc)
-- } else {
-- dc = pc.dc
-+ pci = sliceAdd(&t.activeConns, pc)
-+ t.conns[pc] = struct{}{}
- }
-- wg = &pc.wg
-
- pc.wg.Add(1)
- pc.servedLocked++
-@@ -152,13 +151,20 @@ func (t *PipelineTransport) getPipelineConn() (
- // This connection has served too many queries.
- // Note: the connection should be closed only after all its queries finished.
- // We can't close it here. Some queries may still on that connection.
-- sliceDel(&t.conns, pci)
-+ sliceDel(&t.activeConns, pci) // remove from active conns
-+ }
-+ t.m.Unlock()
-+
-+ if eol {
-+ // Cleanup when all queries is finished.
- go func() {
-- wg.Wait()
-- dc.closeWithErr(errEOL)
-+ pc.wg.Wait()
-+ pc.dc.closeWithErr(errEOL)
-+ t.m.Lock()
-+ delete(t.conns, pc)
-+ t.m.Unlock()
- }()
- }
-- t.m.Unlock()
- return
- }
-
-@@ -167,9 +173,9 @@ func (t *PipelineTransport) getPipelineConn() (
- // Require holding PipelineTransport.m.
- func (t *PipelineTransport) pickPipelineConnLocked() (int, *pipelineConn) {
- for {
-- pci, pc := sliceRandGet(t.conns, t.r)
-+ pci, pc := sliceRandGet(t.activeConns, t.r)
- if pc != nil && pc.dc.isClosed() { // closed conn, delete it and retry
-- sliceDel(&t.conns, pci)
-+ sliceDel(&t.activeConns, pci)
- continue
- }
- return pci, pc // conn pool is empty or we got a pc
-diff --git a/pkg/upstream/transport/pipeline_test.go b/pkg/upstream/transport/pipeline_test.go
-index 653d779..c595288 100644
---- a/pkg/upstream/transport/pipeline_test.go
-+++ b/pkg/upstream/transport/pipeline_test.go
-@@ -86,7 +86,7 @@ func testPipelineTransport(t *testing.T, ioOpts IOOpts) {
- wg.Wait()
-
- pt.m.Lock()
-- pl := len(pt.conns)
-+ pl := len(pt.activeConns)
- pt.m.Unlock()
- if pl > po.MaxConn {
- t.Fatalf("max %d active conn, but got %d active conn(s)", po.MaxConn, pl)
---
-2.34.8
-