OSDN Git Service

v2raya: backport upstream fixes
authorTianling Shen <cnsztl@immortalwrt.org>
Sun, 6 Nov 2022 09:50:26 +0000 (17:50 +0800)
committerTianling Shen <cnsztl@immortalwrt.org>
Sun, 6 Nov 2022 09:50:26 +0000 (17:50 +0800)
Signed-off-by: Tianling Shen <cnsztl@immortalwrt.org>
v2raya/patches/014-fix-seed-cannot-be-read-from-vless-sharing-link-and-add-m.patch [new file with mode: 0644]
v2raya/patches/015-fix-a-problem-that-supervisor-cannot-exit-normally.patch [new file with mode: 0644]
v2raya/patches/016-fix-unexpected-exit-does-not-apply-stop-steps.patch [new file with mode: 0644]
v2raya/patches/017-optimize-reduce-disk-writes.patch [new file with mode: 0644]
v2raya/patches/018-fix-do-not-rollback-closed-transaction.patch [new file with mode: 0644]

diff --git a/v2raya/patches/014-fix-seed-cannot-be-read-from-vless-sharing-link-and-add-m.patch b/v2raya/patches/014-fix-seed-cannot-be-read-from-vless-sharing-link-and-add-m.patch
new file mode 100644 (file)
index 0000000..ba50306
--- /dev/null
@@ -0,0 +1,105 @@
+From 5db722b22b39642280572a62b149d4e1efa21ce3 Mon Sep 17 00:00:00 2001
+From: mzz2017 <mzz@tuta.io>
+Date: Mon, 8 Aug 2022 22:30:36 +0800
+Subject: [PATCH] fix: seed cannot be read from vless sharing-link and add
+ missing sni field. #616
+
+---
+ service/core/serverObj/v2ray.go | 24 +++++++++++-------------
+ 1 file changed, 11 insertions(+), 13 deletions(-)
+
+--- a/core/serverObj/v2ray.go
++++ b/core/serverObj/v2ray.go
+@@ -12,7 +12,6 @@ import (
+       "time"
+       jsoniter "github.com/json-iterator/go"
+-      "github.com/tidwall/gjson"
+       "github.com/v2rayA/v2rayA/common"
+       "github.com/v2rayA/v2rayA/core/coreObj"
+       "github.com/v2rayA/v2rayA/core/v2ray/service"
+@@ -39,6 +38,7 @@ type V2Ray struct {
+       Net           string `json:"net"`
+       Type          string `json:"type"`
+       Host          string `json:"host"`
++      SNI           string `json:"sni"`
+       Path          string `json:"path"`
+       TLS           string `json:"tls"`
+       Flow          string `json:"flow,omitempty"`
+@@ -69,7 +69,8 @@ func ParseVlessURL(vless string) (data *
+               ID:            u.User.String(),
+               Net:           u.Query().Get("type"),
+               Type:          u.Query().Get("headerType"),
+-              Host:          u.Query().Get("sni"),
++              Host:          u.Query().Get("host"),
++              SNI:           u.Query().Get("sni"),
+               Path:          u.Query().Get("path"),
+               TLS:           u.Query().Get("security"),
+               Flow:          u.Query().Get("flow"),
+@@ -86,16 +87,13 @@ func ParseVlessURL(vless string) (data *
+       if data.Type == "" {
+               data.Type = "none"
+       }
+-      if data.Host == "" {
+-              data.Host = u.Query().Get("host")
+-      }
+       if data.TLS == "" {
+               data.TLS = "none"
+       }
+       if data.Flow == "" {
+               data.Flow = "xtls-rprx-direct"
+       }
+-      if data.Type == "mkcp" || data.Type == "kcp" {
++      if data.Net == "mkcp" || data.Net == "kcp" {
+               data.Path = u.Query().Get("seed")
+       }
+       return data, nil
+@@ -145,6 +143,7 @@ func ParseVmessURL(vmess string) (data *
+               if aid == "" {
+                       aid = q.Get("aid")
+               }
++              sni := q.Get("sni")
+               info = V2Ray{
+                       ID:            subMatch[1],
+                       Add:           subMatch[2],
+@@ -152,6 +151,7 @@ func ParseVmessURL(vmess string) (data *
+                       Ps:            ps,
+                       Host:          obfsParam,
+                       Path:          path,
++                      SNI:           sni,
+                       Net:           obfs,
+                       Aid:           aid,
+                       TLS:           map[string]string{"1": "tls"}[q.Get("tls")],
+@@ -165,12 +165,6 @@ func ParseVmessURL(vmess string) (data *
+               if err != nil {
+                       return
+               }
+-              if info.Host == "" {
+-                      sni := gjson.Get(raw, "sni")
+-                      if sni.Exists() {
+-                              info.Host = sni.String()
+-                      }
+-              }
+       }
+       // correct the wrong vmess as much as possible
+       if strings.HasPrefix(info.Host, "/") && info.Path == "" {
+@@ -328,7 +322,9 @@ func (v *V2Ray) Configuration(info Prior
+                               core.StreamSettings.TLSSettings.AllowInsecure = true
+                       }
+                       // SNI
+-                      if v.Host != "" {
++                      if v.SNI != "" {
++                              core.StreamSettings.TLSSettings.ServerName = v.SNI
++                      } else if v.Host != "" {
+                               core.StreamSettings.TLSSettings.ServerName = v.Host
+                       }
+                       // Alpn
+@@ -345,6 +341,8 @@ func (v *V2Ray) Configuration(info Prior
+                       // SNI
+                       if v.Host != "" {
+                               core.StreamSettings.XTLSSettings.ServerName = v.Host
++                      } else if v.Host != "" {
++                              core.StreamSettings.TLSSettings.ServerName = v.Host
+                       }
+                       if v.AllowInsecure {
+                               core.StreamSettings.XTLSSettings.AllowInsecure = true
diff --git a/v2raya/patches/015-fix-a-problem-that-supervisor-cannot-exit-normally.patch b/v2raya/patches/015-fix-a-problem-that-supervisor-cannot-exit-normally.patch
new file mode 100644 (file)
index 0000000..5447dc0
--- /dev/null
@@ -0,0 +1,100 @@
+From 3f78422f81f3abc2668fc3938b31d213bfe4dfff Mon Sep 17 00:00:00 2001
+From: mzz2017 <mzz@tuta.io>
+Date: Sun, 28 Aug 2022 17:54:36 +0800
+Subject: [PATCH] fix: a problem that supervisor cannot exit normally
+
+---
+ service/core/specialMode/infra/handle.go     | 11 ++++++----
+ service/core/specialMode/infra/supervisor.go | 22 ++++++++------------
+ 2 files changed, 16 insertions(+), 17 deletions(-)
+
+--- a/core/specialMode/infra/handle.go
++++ b/core/specialMode/infra/handle.go
+@@ -127,10 +127,13 @@ func (interfaceHandle *handle) handleRec
+       return results, msg
+ }
+-func packetFilter(portCache *portCache, pPacket *gopacket.Packet, whitelistDnsServers *v2router.GeoIPMatcher) (m *dnsmessage.Message, pSAddr, pSPort, pDAddr, pDPort *gopacket.Endpoint) {
+-      packet := *pPacket
+-      trans := packet.TransportLayer()
++func packetFilter(portCache *portCache, packet gopacket.Packet, whitelistDnsServers *v2router.GeoIPMatcher) (m *dnsmessage.Message, pSAddr, pSPort, pDAddr, pDPort *gopacket.Endpoint) {
++      //跳过非网络层的包
++      if packet.NetworkLayer() == nil {
++              return
++      }
+       //跳过非传输层的包
++      trans := packet.TransportLayer()
+       if trans == nil {
+               return
+       }
+@@ -180,7 +183,7 @@ func packetFilter(portCache *portCache,
+ }
+ func (interfaceHandle *handle) handlePacket(packet gopacket.Packet, ifname string, whitelistDnsServers *v2router.GeoIPMatcher, whitelistDomains *strmatcher.MatcherGroup) {
+-      m, sAddr, sPort, dAddr, dPort := packetFilter(interfaceHandle.portCache, &packet, whitelistDnsServers)
++      m, sAddr, sPort, dAddr, dPort := packetFilter(interfaceHandle.portCache, packet, whitelistDnsServers)
+       if m == nil {
+               return
+       }
+--- a/core/specialMode/infra/supervisor.go
++++ b/core/specialMode/infra/supervisor.go
+@@ -9,7 +9,6 @@ import (
+       v2router "github.com/v2rayA/v2ray-lib/router"
+       "github.com/v2rayA/v2rayA/pkg/util/log"
+       "sync"
+-      "time"
+ )
+ type DnsSupervisor struct {
+@@ -70,7 +69,7 @@ func (d *DnsSupervisor) DeleteHandles(if
+       }
+       close(d.handles[ifname].done)
+       delete(d.handles, ifname)
+-      log.Trace("DnsSupervisor:%v closed", ifname)
++      log.Trace("DnsSupervisor:%v deleted", ifname)
+       return
+ }
+@@ -81,28 +80,24 @@ func (d *DnsSupervisor) Run(ifname strin
+       d.inner.Lock()
+       handle, ok := d.handles[ifname]
+       if !ok {
++              d.inner.Unlock()
+               return fmt.Errorf("Run: %v not exsits", ifname)
+       }
+       if handle.running {
++              d.inner.Unlock()
+               return fmt.Errorf("Run: %v is running", ifname)
+       }
+       handle.running = true
+       log.Trace("[DnsSupervisor] " + ifname + ": running")
+-      pkgsrc := gopacket.NewPacketSource(handle, layers.LayerTypeEthernet)
++      // we only decode UDP packets
++      pkgsrc := gopacket.NewPacketSource(handle, layers.LayerTypeDNS)
+       pkgsrc.NoCopy = true
++      //pkgsrc.Lazy = true
+       d.inner.Unlock()
+       packets := pkgsrc.Packets()
+       go func() {
+-              for {
+-                      //心跳包,防止内存泄漏
+-                      packets <- gopacket.NewPacket(nil, layers.LinkTypeEthernet, gopacket.DecodeOptions{})
+-                      select {
+-                      case <-handle.done:
+-                              return
+-                      default:
+-                              time.Sleep(2 * time.Second)
+-                      }
+-              }
++              <-handle.done
++              packets <- gopacket.NewPacket(nil, layers.LinkTypeEthernet, pkgsrc.DecodeOptions)
+       }()
+ out:
+       for packet := range packets {
+@@ -113,5 +108,6 @@ out:
+               }
+               go handle.handlePacket(packet, ifname, whitelistDnsServers, whitelistDomains)
+       }
++      log.Trace("DnsSupervisor:%v closed", ifname)
+       return
+ }
diff --git a/v2raya/patches/016-fix-unexpected-exit-does-not-apply-stop-steps.patch b/v2raya/patches/016-fix-unexpected-exit-does-not-apply-stop-steps.patch
new file mode 100644 (file)
index 0000000..a945dbd
--- /dev/null
@@ -0,0 +1,52 @@
+From 153b72ed623876ad73b731c2ec2344e9057d3c35 Mon Sep 17 00:00:00 2001
+From: mzz2017 <mzz@tuta.io>
+Date: Wed, 21 Sep 2022 16:50:24 +0800
+Subject: [PATCH] fix: unexpected exit does not apply stop steps
+
+---
+ service/core/v2ray/process.go        | 4 ++--
+ service/core/v2ray/processManager.go | 8 +++-----
+ 2 files changed, 5 insertions(+), 7 deletions(-)
+
+--- a/core/v2ray/process.go
++++ b/core/v2ray/process.go
+@@ -35,7 +35,7 @@ type Process struct {
+       tag2WhichIndex map[string]int
+ }
+-func NewProcess(tmpl *Template, prestart func() error, poststart func() error) (process *Process, err error) {
++func NewProcess(tmpl *Template, prestart func() error, poststart func() error, stopfunc func(p *Process)) (process *Process, err error) {
+       process = &Process{
+               template: tmpl,
+       }
+@@ -111,7 +111,7 @@ func NewProcess(tmpl *Template, prestart
+                       // canceled by v2rayA
+                       return
+               }
+-              defer ProcessManager.Stop(false)
++              defer stopfunc(process)
+               var t []string
+               if p != nil {
+                       if p.Success() {
+--- a/core/v2ray/processManager.go
++++ b/core/v2ray/processManager.go
+@@ -245,16 +245,14 @@ func (m *CoreProcessManager) Start(t *Te
+               return m.beforeStart(t)
+       }, func() error {
+               return m.afterStart(t)
++      }, func(p *Process) {
++              m.p = p
++              ProcessManager.Stop(false)
+       })
+       if err != nil {
+               return err
+       }
+       m.p = process
+-      defer func() {
+-              if err != nil {
+-                      m.stop(true)
+-              }
+-      }()
+       configure.SetRunning(true)
+       return nil
diff --git a/v2raya/patches/017-optimize-reduce-disk-writes.patch b/v2raya/patches/017-optimize-reduce-disk-writes.patch
new file mode 100644 (file)
index 0000000..5437570
--- /dev/null
@@ -0,0 +1,336 @@
+From 00366b224b2e28861b80f677e8aa604c5d08dae3 Mon Sep 17 00:00:00 2001
+From: Kelo <meetkelo@outlook.com>
+Date: Sat, 29 Oct 2022 16:27:26 +0800
+Subject: [PATCH] optimize: reduce disk writes
+
+---
+ service/db/boltdb.go  | 43 +++++++++++++++++++++++++++++++----
+ service/db/listOp.go  | 48 +++++++++++++++++++++------------------
+ service/db/plainOp.go | 52 ++++++++++++++++++++++++-------------------
+ service/db/setOp.go   | 20 +++++++++--------
+ 4 files changed, 105 insertions(+), 58 deletions(-)
+
+--- a/db/boltdb.go
++++ b/db/boltdb.go
+@@ -1,13 +1,14 @@
+ package db
+ import (
+-      "go.etcd.io/bbolt"
+-      "github.com/v2rayA/v2rayA/conf"
+-      "github.com/v2rayA/v2rayA/pkg/util/copyfile"
+-      "github.com/v2rayA/v2rayA/pkg/util/log"
+       "os"
+       "path/filepath"
+       "sync"
++
++      "github.com/v2rayA/v2rayA/conf"
++      "github.com/v2rayA/v2rayA/pkg/util/copyfile"
++      "github.com/v2rayA/v2rayA/pkg/util/log"
++      "go.etcd.io/bbolt"
+ )
+ var once sync.Once
+@@ -46,3 +47,37 @@ func DB() *bbolt.DB {
+       once.Do(initDB)
+       return db
+ }
++
++// The function should return a dirty flag.
++// If the dirty flag is true and there is no error then the transaction is commited.
++// Otherwise, the transaction is rolled back.
++func Transaction(db *bbolt.DB, fn func(*bbolt.Tx) (bool, error)) error {
++      tx, err := db.Begin(true)
++      if err != nil {
++              return err
++      }
++      defer tx.Rollback()
++      dirty, err := fn(tx)
++      if err != nil {
++              _ = tx.Rollback()
++              return err
++      }
++      if !dirty {
++              return nil
++      }
++      return tx.Commit()
++}
++
++// If the bucket does not exist, the dirty flag is setted
++func CreateBucketIfNotExists(tx *bbolt.Tx, name []byte, dirty *bool) (*bbolt.Bucket, error) {
++      bkt := tx.Bucket(name)
++      if bkt != nil {
++              return bkt, nil
++      }
++      bkt, err := tx.CreateBucket(name)
++      if err != nil {
++              return nil, err
++      }
++      *dirty = true
++      return bkt, nil
++}
+--- a/db/listOp.go
++++ b/db/listOp.go
+@@ -2,13 +2,14 @@ package db
+ import (
+       "fmt"
+-      "go.etcd.io/bbolt"
+-      jsoniter "github.com/json-iterator/go"
+-      "github.com/tidwall/gjson"
+-      "github.com/tidwall/sjson"
+       "reflect"
+       "sort"
+       "strconv"
++
++      jsoniter "github.com/json-iterator/go"
++      "github.com/tidwall/gjson"
++      "github.com/tidwall/sjson"
++      "go.etcd.io/bbolt"
+ )
+ func ListSet(bucket string, key string, index int, val interface{}) (err error) {
+@@ -31,20 +32,21 @@ func ListSet(bucket string, key string,
+ }
+ func ListGet(bucket string, key string, index int) (b []byte, err error) {
+-      err = DB().Update(func(tx *bbolt.Tx) error {
+-              if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
+-                      return err
++      err = Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
++              dirty := false
++              if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
++                      return dirty, err
+               } else {
+                       v := bkt.Get([]byte(key))
+                       if v == nil {
+-                              return fmt.Errorf("ListGet: can't get element from an empty list")
++                              return dirty, fmt.Errorf("ListGet: can't get element from an empty list")
+                       }
+                       r := gjson.GetBytes(v, strconv.Itoa(index))
+                       if r.Exists() {
+                               b = []byte(r.Raw)
+-                              return nil
++                              return dirty, nil
+                       } else {
+-                              return fmt.Errorf("ListGet: no such element")
++                              return dirty, fmt.Errorf("ListGet: no such element")
+                       }
+               }
+       })
+@@ -79,24 +81,25 @@ func ListAppend(bucket string, key strin
+ }
+ func ListGetAll(bucket string, key string) (list [][]byte, err error) {
+-      err = DB().Update(func(tx *bbolt.Tx) error {
+-              if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
+-                      return err
++      err = Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
++              dirty := false
++              if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
++                      return dirty, err
+               } else {
+                       b := bkt.Get([]byte(key))
+                       if b == nil {
+-                              return nil
++                              return dirty, nil
+                       }
+                       parsed := gjson.ParseBytes(b)
+                       if !parsed.IsArray() {
+-                              return fmt.Errorf("ListGetAll: is not array")
++                              return dirty, fmt.Errorf("ListGetAll: is not array")
+                       }
+                       results := parsed.Array()
+                       for _, r := range results {
+                               list = append(list, []byte(r.Raw))
+                       }
+               }
+-              return nil
++              return dirty, nil
+       })
+       return list, err
+ }
+@@ -143,21 +146,22 @@ func ListRemove(bucket, key string, inde
+ }
+ func ListLen(bucket string, key string) (length int, err error) {
+-      err = DB().Update(func(tx *bbolt.Tx) error {
+-              if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
+-                      return err
++      err = Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
++              dirty := false
++              if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
++                      return dirty, err
+               } else {
+                       b := bkt.Get([]byte(key))
+                       if b == nil {
+-                              return nil
++                              return dirty, nil
+                       }
+                       parsed := gjson.ParseBytes(b)
+                       if !parsed.IsArray() {
+-                              return fmt.Errorf("ListLen: is not array")
++                              return dirty, fmt.Errorf("ListLen: is not array")
+                       }
+                       length = len(parsed.Array())
+               }
+-              return nil
++              return dirty, nil
+       })
+       return length, err
+ }
+--- a/db/plainOp.go
++++ b/db/plainOp.go
+@@ -2,50 +2,54 @@ package db
+ import (
+       "fmt"
+-      "go.etcd.io/bbolt"
++
+       jsoniter "github.com/json-iterator/go"
+       "github.com/v2rayA/v2rayA/common"
+       "github.com/v2rayA/v2rayA/pkg/util/log"
++      "go.etcd.io/bbolt"
+ )
+ func Get(bucket string, key string, val interface{}) (err error) {
+-      return DB().Update(func(tx *bbolt.Tx) error {
+-              if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
+-                      return err
++      return Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
++              dirty := false
++              if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
++                      return dirty, err
+               } else {
+                       if v := bkt.Get([]byte(key)); v == nil {
+-                              return fmt.Errorf("Get: key is not found")
++                              return dirty, fmt.Errorf("Get: key is not found")
+                       } else {
+-                              return jsoniter.Unmarshal(v, val)
++                              return dirty, jsoniter.Unmarshal(v, val)
+                       }
+               }
+       })
+ }
+ func GetRaw(bucket string, key string) (b []byte, err error) {
+-      err = DB().Update(func(tx *bbolt.Tx) error {
+-              if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
+-                      return err
++      err = Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
++              dirty := false
++              if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
++                      return dirty, err
+               } else {
+                       v := bkt.Get([]byte(key))
+                       if v == nil {
+-                              return fmt.Errorf("GetRaw: key is not found")
++                              return dirty, fmt.Errorf("GetRaw: key is not found")
+                       }
+                       b = common.BytesCopy(v)
+-                      return nil
++                      return dirty, nil
+               }
+       })
+       return b, err
+ }
+ func Exists(bucket string, key string) (exists bool) {
+-      if err := DB().Update(func(tx *bbolt.Tx) error {
+-              if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
+-                      return err
++      if err := Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
++              dirty := false
++              if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
++                      return dirty, err
+               } else {
+                       v := bkt.Get([]byte(key))
+                       exists = v != nil
+-                      return nil
++                      return dirty, nil
+               }
+       }); err != nil {
+               log.Warn("%v", err)
+@@ -55,23 +59,25 @@ func Exists(bucket string, key string) (
+ }
+ func GetBucketLen(bucket string) (length int, err error) {
+-      err = DB().Update(func(tx *bbolt.Tx) error {
+-              if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
+-                      return err
++      err = Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
++              dirty := false
++              if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
++                      return dirty, err
+               } else {
+                       length = bkt.Stats().KeyN
+               }
+-              return nil
++              return dirty, nil
+       })
+       return length, err
+ }
+ func GetBucketKeys(bucket string) (keys []string, err error) {
+-      err = DB().Update(func(tx *bbolt.Tx) error {
+-              if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
+-                      return err
++      err = Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
++              dirty := false
++              if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
++                      return dirty, err
+               } else {
+-                      return bkt.ForEach(func(k, v []byte) error {
++                      return dirty, bkt.ForEach(func(k, v []byte) error {
+                               keys = append(keys, string(k))
+                               return nil
+                       })
+--- a/db/setOp.go
++++ b/db/setOp.go
+@@ -4,8 +4,9 @@ import (
+       "bytes"
+       "crypto/sha256"
+       "encoding/gob"
+-      "go.etcd.io/bbolt"
++
+       "github.com/v2rayA/v2rayA/common"
++      "go.etcd.io/bbolt"
+ )
+ type set map[[32]byte]interface{}
+@@ -28,26 +29,27 @@ func toSha256(val interface{}) (hash [32
+ }
+ func setOp(bucket string, key string, f func(m set) (readonly bool, err error)) (err error) {
+-      return DB().Update(func(tx *bbolt.Tx) error {
+-              if bkt, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
+-                      return err
++      return Transaction(DB(), func(tx *bbolt.Tx) (bool, error) {
++              dirty := false
++              if bkt, err := CreateBucketIfNotExists(tx, []byte(bucket), &dirty); err != nil {
++                      return dirty, err
+               } else {
+                       var m set
+                       v := bkt.Get([]byte(key))
+                       if v == nil {
+                               m = make(set)
+                       } else if err := gob.NewDecoder(bytes.NewReader(v)).Decode(&m); err != nil {
+-                              return err
++                              return dirty, err
+                       }
+                       if readonly, err := f(m); err != nil {
+-                              return err
++                              return dirty, err
+                       } else if readonly {
+-                              return nil
++                              return dirty, nil
+                       }
+                       if b, err := common.ToBytes(m); err != nil {
+-                              return err
++                              return dirty, err
+                       } else {
+-                              return bkt.Put([]byte(key), b)
++                              return true, bkt.Put([]byte(key), b)
+                       }
+               }
+       })
diff --git a/v2raya/patches/018-fix-do-not-rollback-closed-transaction.patch b/v2raya/patches/018-fix-do-not-rollback-closed-transaction.patch
new file mode 100644 (file)
index 0000000..b9815f4
--- /dev/null
@@ -0,0 +1,27 @@
+From 451912074ba1ba4000c66874876bc0a6b64cb5da Mon Sep 17 00:00:00 2001
+From: Kelo <meetkelo@outlook.com>
+Date: Sun, 30 Oct 2022 16:49:22 +0800
+Subject: [PATCH] fix: do not rollback closed transaction
+
+---
+ service/db/boltdb.go | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/db/boltdb.go
++++ b/db/boltdb.go
+@@ -56,14 +56,13 @@ func Transaction(db *bbolt.DB, fn func(*
+       if err != nil {
+               return err
+       }
+-      defer tx.Rollback()
+       dirty, err := fn(tx)
+       if err != nil {
+               _ = tx.Rollback()
+               return err
+       }
+       if !dirty {
+-              return nil
++              return tx.Rollback()
+       }
+       return tx.Commit()
+ }