- Added TproxyNotSkipBr flag for OpenWrt.
- Removed all upstreamed patches.
- Removed deprecated option.
- Re-enable ipv6/nftables auto-detect.
Signed-off-by: Tianling Shen <cnsztl@immortalwrt.org>
include $(TOPDIR)/rules.mk
PKG_NAME:=v2rayA
-PKG_VERSION:=1.5.9.1698.1
-PKG_RELEASE:=4
+PKG_VERSION:=2.0.4
+PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/v2rayA/v2rayA/tar.gz/v$(PKG_VERSION)?
-PKG_HASH:=247a357230c616bf48309c61d119686e4ad56939c05afef584c45051e9dc6220
+PKG_HASH:=fb0c60d95cd208e3306cf9c5488f41fe7cf6697d58dc377e5e7d471e37ce9060
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)/service
PKG_LICENSE:=AGPL-3.0-only
PKG_USE_MIPS16:=0
GO_PKG:=github.com/v2rayA/v2rayA
-GO_PKG_LDFLAGS_X:=$(GO_PKG)/conf.Version=$(PKG_VERSION)
+GO_PKG_LDFLAGS_X:= \
+ $(GO_PKG)/conf.Version=$(PKG_VERSION) \
+ $(GO_PKG)/core/iptables.TproxyNotSkipBr=true
include $(INCLUDE_DIR)/package.mk
include ../../lang/golang/golang-package.mk
URL:=https://codeload.github.com/v2rayA/v2raya-web/tar.gz/v$(PKG_VERSION)?
URL_FILE:=$(WEB_FILE)
FILE:=$(WEB_FILE)
- HASH:=149097a42c3e5fa6f5c3cd46d1bf7ec4546e79ad37c1446b759539e700bd75e2
+ HASH:=39eacb70753b309a0f44ede282ad01b5c6a13d51bed72d0f5563574de9bea0b7
endef
define Build/Prepare
# Make sure your IPv6 network works fine before you turn it on.
# Optional values: auto, on, off.
- option ipv6_support 'on'
+ option ipv6_support 'auto'
# Experimental feature. Make sure you have installed nftables.
# Optional values: auto, on, off.
- option nftables_support 'on'
+ option nftables_support 'auto'
# Optional values: trace, debug, info, warn or error
option log_level 'info'
# v2rayA will pass in the --stage (pre-start, post-start, pre-stop, post-stop) argument.
option plugin_manager ''
- # Specify the certification path instead of automatically generating a self-signed certificate.
- # Example: /etc/v2raya/grpc_certificate.crt,/etc/v2raya/grpc_private.key
- option vless_grpc_inbound_cert_key ''
-
append_env_arg "config" "address" "0.0.0.0:2017"
append_env_arg "config" "config" "/etc/v2raya"
- append_env_arg "config" "ipv6_support" "on"
- append_env_arg "config" "nftables_support" "on"
+ append_env_arg "config" "ipv6_support" "auto"
+ append_env_arg "config" "nftables_support" "auto"
append_env_arg "config" "log_level" "info"
append_env_arg "config" "log_file" "/var/log/v2raya/v2raya.log"
append_env_arg "config" "log_max_days" "3"
append_env_arg "config" "transparent_hook"
append_env_arg "config" "core_hook"
append_env_arg "config" "plugin_manager"
- append_env_arg "config" "vless_grpc_inbound_cert_key"
append_env_bool "config" "log_disable_color"
append_env_bool "config" "log_disable_timestamp"
+++ /dev/null
-From ca6a05273284daa04856a840e64f3936f700b7c3 Mon Sep 17 00:00:00 2001
-From: mzz2017 <mzz@tuta.io>
-Date: Fri, 16 Sep 2022 15:13:11 +0800
-Subject: [PATCH] fix: we should skip interface ppp+ to avoid to break net
-
----
- service/core/iptables/tproxy.go | 6 +++++-
- 1 file changed, 5 insertions(+), 1 deletion(-)
-
---- a/core/iptables/tproxy.go
-+++ b/core/iptables/tproxy.go
-@@ -16,7 +16,7 @@ var Tproxy tproxy
- func (t *tproxy) AddIPWhitelist(cidr string) {
- // avoid duplication
- t.RemoveIPWhitelist(cidr)
-- pos := 5
-+ pos := 7
- if configure.GetSettingNotNil().AntiPollution != configure.AntipollutionClosed {
- pos += 3
- }
-@@ -67,6 +67,8 @@ iptables -w 2 -t mangle -A TP_RULE -j CO
- iptables -w 2 -t mangle -A TP_RULE -m mark --mark 0x40/0xc0 -j RETURN
- iptables -w 2 -t mangle -A TP_RULE -i docker+ -j RETURN
- iptables -w 2 -t mangle -A TP_RULE -i veth+ -j RETURN
-+iptables -w 2 -t mangle -A TP_RULE -i ppp+ -j RETURN
-+iptables -w 2 -t mangle -A TP_RULE -i dn42-+ -j RETURN
- `
- if configure.GetSettingNotNil().AntiPollution != configure.AntipollutionClosed {
- commands += `
-@@ -125,6 +127,8 @@ ip6tables -w 2 -t mangle -A TP_RULE -j C
- ip6tables -w 2 -t mangle -A TP_RULE -m mark --mark 0x40/0xc0 -j RETURN
- ip6tables -w 2 -t mangle -A TP_RULE -i docker+ -j RETURN
- ip6tables -w 2 -t mangle -A TP_RULE -i veth+ -j RETURN
-+ip6tables -w 2 -t mangle -A TP_RULE -i ppp+ -j RETURN
-+ip6tables -w 2 -t mangle -A TP_RULE -i dn42-+ -j RETURN
- `
- if configure.GetSettingNotNil().AntiPollution != configure.AntipollutionClosed {
- commands += `
+++ /dev/null
-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
+++ /dev/null
-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
- }
+++ /dev/null
-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
+++ /dev/null
-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)
- }
- }
- })
+++ /dev/null
-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()
- }
+++ /dev/null
-From 58a6cf270e43ec3eaeef7d1c65de76278dd6d349 Mon Sep 17 00:00:00 2001
-From: mzz2017 <2017@duck.com>
-Date: Mon, 13 Feb 2023 14:42:07 +0800
-Subject: [PATCH] fix: simple-obfs
-
----
- service/pkg/plugin/simpleobfs/http.go | 8 +++++++-
- service/pkg/plugin/simpleobfs/tls.go | 7 +++++++
- 2 files changed, 14 insertions(+), 1 deletion(-)
-
---- a/pkg/plugin/simpleobfs/http.go
-+++ b/pkg/plugin/simpleobfs/http.go
-@@ -12,6 +12,7 @@ import (
- "net"
- "net/http"
- "strings"
-+ "sync"
- )
-
- // HTTPObfs is shadowsocks http simple-obfs implementation
-@@ -24,9 +25,13 @@ type HTTPObfs struct {
- offset int
- firstRequest bool
- firstResponse bool
-+ rMu sync.Mutex
-+ wMu sync.Mutex
- }
-
- func (ho *HTTPObfs) Read(b []byte) (int, error) {
-+ ho.rMu.Lock()
-+ defer ho.rMu.Unlock()
- if ho.buf != nil {
- n := copy(b, ho.buf[ho.offset:])
- ho.offset += n
-@@ -64,6 +69,8 @@ func (ho *HTTPObfs) Read(b []byte) (int,
- }
-
- func (ho *HTTPObfs) Write(b []byte) (int, error) {
-+ ho.wMu.Lock()
-+ defer ho.wMu.Unlock()
- if ho.firstRequest {
- randBytes := make([]byte, 16)
- rand.Read(randBytes)
-@@ -71,7 +78,6 @@ func (ho *HTTPObfs) Write(b []byte) (int
- req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2))
- req.Header.Set("Upgrade", "websocket")
- req.Header.Set("Connection", "Upgrade")
-- req.Host = ho.host
- if ho.port != "80" {
- req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port)
- }
---- a/pkg/plugin/simpleobfs/tls.go
-+++ b/pkg/plugin/simpleobfs/tls.go
-@@ -8,6 +8,7 @@ import (
- "io"
- "math/rand"
- "net"
-+ "sync"
- "time"
- )
-
-@@ -26,6 +27,8 @@ type TLSObfs struct {
- remain int
- firstRequest bool
- firstResponse bool
-+ rMu sync.Mutex
-+ wMu sync.Mutex
- }
-
- func (to *TLSObfs) read(b []byte, discardN int) (int, error) {
-@@ -54,6 +57,8 @@ func (to *TLSObfs) read(b []byte, discar
- }
-
- func (to *TLSObfs) Read(b []byte) (int, error) {
-+ to.rMu.Lock()
-+ defer to.rMu.Unlock()
- if to.remain > 0 {
- length := to.remain
- if length > len(b) {
-@@ -77,6 +82,8 @@ func (to *TLSObfs) Read(b []byte) (int,
- return to.read(b, 3)
- }
- func (to *TLSObfs) Write(b []byte) (int, error) {
-+ to.wMu.Lock()
-+ defer to.wMu.Unlock()
- length := len(b)
- for i := 0; i < length; i += chunkSize {
- end := i + chunkSize
+++ /dev/null
-From d10cf52839e848870df0ea852d9a818ac03e7aa3 Mon Sep 17 00:00:00 2001
-From: cubercsl <2014cais01@gmail.com>
-Date: Thu, 19 Jan 2023 16:43:30 +0800
-Subject: [PATCH 1/5] feat: add nftables support
-
-fix: use iptables-nft if nftables-support is on
-fix: save nft to V2RAYA_CONFIG
-fix: tproxy for ipv6
-chore: small change in table format
----
- service/conf/environmentConfig.go | 1 +
- service/core/iptables/dropSpoofing.go | 4 +-
- service/core/iptables/iptables.go | 7 +-
- service/core/iptables/redirect.go | 142 +++++++++++++++++--
- service/core/iptables/tproxy.go | 195 +++++++++++++++++++++++++-
- service/core/iptables/utils.go | 23 ++-
- service/core/iptables/watcher.go | 1 +
- service/core/v2ray/asset/asset.go | 17 ++-
- service/core/v2ray/transparent.go | 9 +-
- 9 files changed, 367 insertions(+), 32 deletions(-)
-
---- a/conf/environmentConfig.go
-+++ b/conf/environmentConfig.go
-@@ -24,6 +24,7 @@ type Params struct {
- WebDir string `id:"webdir" desc:"v2rayA web files directory. use embedded files if not specify."`
- VlessGrpcInboundCertKey []string `id:"vless-grpc-inbound-cert-key" desc:"Specify the certification path instead of automatically generating a self-signed certificate. Example: /etc/v2raya/grpc_certificate.crt,/etc/v2raya/grpc_private.key"`
- IPV6Support string `id:"ipv6-support" default:"auto" desc:"Optional values: auto, on, off. Make sure your IPv6 network works fine before you turn it on."`
-+ NFTablesSupport string `id:"nftables-support" default:"off" desc:"Optional values: auto, on, off. Experimental feature. Make sure you have installed nftables."`
- PassCheckRoot bool `desc:"Skip privilege checking. Use it only when you cannot start v2raya but confirm you have root privilege"`
- ResetPassword bool `id:"reset-password"`
- LogLevel string `id:"log-level" default:"info" desc:"Optional values: trace, debug, info, warn or error"`
---- a/core/iptables/dropSpoofing.go
-+++ b/core/iptables/dropSpoofing.go
-@@ -34,7 +34,7 @@ ip6tables -w 2 -I FORWARD -j DROP_SPOOFI
- `
- }
- return Setter{
-- Cmds: commands,
-+ Cmds: commands,
- }
- }
-
-@@ -54,6 +54,6 @@ ip6tables -w 2 -X DROP_SPOOFING
- `
- }
- return Setter{
-- Cmds: commands,
-+ Cmds: commands,
- }
- }
---- a/core/iptables/iptables.go
-+++ b/core/iptables/iptables.go
-@@ -1,11 +1,12 @@
- package iptables
-
- import (
-- "github.com/v2rayA/v2rayA/common"
-- "github.com/v2rayA/v2rayA/common/cmds"
- "strings"
- "sync"
- "time"
-+
-+ "github.com/v2rayA/v2rayA/common"
-+ "github.com/v2rayA/v2rayA/common/cmds"
- )
-
- // http://briteming.hatenablog.com/entry/2019/06/18/175518
-@@ -56,6 +57,10 @@ func (c Setter) Run(stopAtError bool) er
- if common.IsDocker() {
- commands = strings.ReplaceAll(commands, "iptables", "iptables-legacy")
- commands = strings.ReplaceAll(commands, "ip6tables", "ip6tables-legacy")
-+ } else if (!cmds.IsCommandValid("iptables") || IsNFTablesSupported()) &&
-+ cmds.IsCommandValid("iptables-nft") {
-+ commands = strings.ReplaceAll(commands, "iptables", "iptables-nft")
-+ commands = strings.ReplaceAll(commands, "ip6tables", "ip6tables-nft")
- }
- var errs []error
- if c.PreFunc != nil {
---- a/core/iptables/redirect.go
-+++ b/core/iptables/redirect.go
-@@ -2,15 +2,34 @@ package iptables
-
- import (
- "fmt"
-- "github.com/v2rayA/v2rayA/common/cmds"
-+ "os"
- "strings"
-+
-+ "github.com/v2rayA/v2rayA/common/cmds"
-+ "github.com/v2rayA/v2rayA/core/v2ray/asset"
- )
-
--type redirect struct{}
-+type redirect interface {
-+ AddIPWhitelist(cidr string)
-+ RemoveIPWhitelist(cidr string)
-+ GetSetupCommands() Setter
-+ GetCleanCommands() Setter
-+}
-+
-+type legacyRedirect struct{}
-+type nftRedirect struct{}
-
- var Redirect redirect
-
--func (r *redirect) AddIPWhitelist(cidr string) {
-+func init() {
-+ if IsNFTablesSupported() {
-+ Redirect = &nftRedirect{}
-+ } else {
-+ Redirect = &legacyRedirect{}
-+ }
-+}
-+
-+func (r *legacyRedirect) AddIPWhitelist(cidr string) {
- // avoid duplication
- r.RemoveIPWhitelist(cidr)
- var commands string
-@@ -22,13 +41,13 @@ func (r *redirect) AddIPWhitelist(cidr s
- cmds.ExecCommands(commands, false)
- }
-
--func (r *redirect) RemoveIPWhitelist(cidr string) {
-+func (r *legacyRedirect) RemoveIPWhitelist(cidr string) {
- var commands string
- commands = fmt.Sprintf(`iptables -w 2 -t mangle -D TP_RULE -d %s -j RETURN`, cidr)
- cmds.ExecCommands(commands, false)
- }
-
--func (r *redirect) GetSetupCommands() Setter {
-+func (r *legacyRedirect) GetSetupCommands() Setter {
- commands := `
- iptables -w 2 -t nat -N TP_OUT
- iptables -w 2 -t nat -N TP_PRE
-@@ -84,11 +103,11 @@ ip6tables -w 2 -t nat -A TP_OUT -j TP_RU
- `
- }
- return Setter{
-- Cmds: commands,
-+ Cmds: commands,
- }
- }
-
--func (r *redirect) GetCleanCommands() Setter {
-+func (r *legacyRedirect) GetCleanCommands() Setter {
- commands := `
- iptables -w 2 -t nat -F TP_OUT
- iptables -w 2 -t nat -D OUTPUT -p tcp -j TP_OUT
-@@ -112,6 +131,113 @@ ip6tables -w 2 -t nat -X TP_RULE
- `
- }
- return Setter{
-- Cmds: commands,
-+ Cmds: commands,
-+ }
-+}
-+
-+func (t *nftRedirect) AddIPWhitelist(cidr string) {
-+ command := fmt.Sprintf("nft add element inet v2raya interface { %s }", cidr)
-+ if !strings.Contains(cidr, ".") {
-+ command = strings.Replace(command, "interface", "interface6", 1)
-+ }
-+ cmds.ExecCommands(command, false)
-+}
-+
-+func (t *nftRedirect) RemoveIPWhitelist(cidr string) {
-+ command := fmt.Sprintf("nft delete element inet v2raya interface { %s }", cidr)
-+ if !strings.Contains(cidr, ".") {
-+ command = strings.Replace(command, "interface", "interface6", 1)
- }
-+ cmds.ExecCommands(command, false)
-+}
-+
-+func (r *nftRedirect) GetSetupCommands() Setter {
-+ // 198.18.0.0/15 and fc00::/7 are reserved for private use but used by fakedns
-+ table := `
-+table inet v2raya {
-+ set whitelist {
-+ type ipv4_addr
-+ flags interval
-+ auto-merge
-+ elements = {
-+ 0.0.0.0/32,
-+ 10.0.0.0/8,
-+ 100.64.0.0/10,
-+ 127.0.0.0/8,
-+ 169.254.0.0/16,
-+ 172.16.0.0/12,
-+ 192.0.0.0/24,
-+ 192.0.2.0/24,
-+ 192.88.99.0/24,
-+ 192.168.0.0/16,
-+ 198.51.100.0/24,
-+ 203.0.113.0/24,
-+ 224.0.0.0/4,
-+ 240.0.0.0/4
-+ }
-+ }
-+
-+ set whitelist6 {
-+ type ipv6_addr
-+ flags interval
-+ auto-merge
-+ elements = {
-+ ::/128,
-+ ::1/128,
-+ 64:ff9b::/96,
-+ 100::/64,
-+ 2001::/32,
-+ 2001:20::/28,
-+ fe80::/10,
-+ ff00::/8
-+ }
-+ }
-+
-+ set interface {
-+ type ipv4_addr
-+ flags interval
-+ auto-merge
-+ }
-+
-+ set interface6 {
-+ type ipv6_addr
-+ flags interval
-+ auto-merge
-+ }
-+
-+ chain tp_rule {
-+ ip daddr @whitelist return
-+ ip daddr @interface return
-+ ip6 daddr @whitelist6 return
-+ ip6 daddr @interface6 return
-+ meta mark & 0x80 == 0x80 return
-+ meta l4proto tcp redirect to :32345
-+ }
-+
-+ chain tp_pre {
-+ type nat hook prerouting priority dstnat - 5
-+ meta nfproto { ipv4, ipv6 } meta l4proto tcp jump tp_rule
-+ }
-+
-+ chain tp_out {
-+ type nat hook output priority -105
-+ meta nfproto { ipv4, ipv6 } meta l4proto tcp jump tp_rule
-+ }
-+}
-+`
-+ if !IsIPv6Supported() {
-+ table = strings.ReplaceAll(table, "meta nfproto { ipv4, ipv6 }", "meta nfproto ipv4")
-+ }
-+
-+ nftablesConf := asset.GetNFTablesConfigPath()
-+ os.WriteFile(nftablesConf, []byte(table), 0644)
-+
-+ command := `nft -f ` + nftablesConf
-+
-+ return Setter{Cmds: command}
-+}
-+
-+func (r *nftRedirect) GetCleanCommands() Setter {
-+ command := `nft delete table inet v2raya`
-+ return Setter{Cmds: command}
- }
---- a/core/iptables/tproxy.go
-+++ b/core/iptables/tproxy.go
-@@ -2,18 +2,36 @@ package iptables
-
- import (
- "fmt"
-+ "os"
-+ "strings"
-+
- "github.com/v2rayA/v2rayA/common/cmds"
-+ "github.com/v2rayA/v2rayA/core/v2ray/asset"
- "github.com/v2rayA/v2rayA/db/configure"
-- "strings"
- )
-
--type tproxy struct {
-- watcher *LocalIPWatcher
-+type tproxy interface {
-+ AddIPWhitelist(cidr string)
-+ RemoveIPWhitelist(cidr string)
-+ GetSetupCommands() Setter
-+ GetCleanCommands() Setter
- }
-
-+type legacyTproxy struct{}
-+
-+type nftTproxy struct{}
-+
- var Tproxy tproxy
-
--func (t *tproxy) AddIPWhitelist(cidr string) {
-+func init() {
-+ if IsNFTablesSupported() {
-+ Tproxy = &nftTproxy{}
-+ } else {
-+ Tproxy = &legacyTproxy{}
-+ }
-+}
-+
-+func (t *legacyTproxy) AddIPWhitelist(cidr string) {
- // avoid duplication
- t.RemoveIPWhitelist(cidr)
- pos := 7
-@@ -30,7 +48,7 @@ func (t *tproxy) AddIPWhitelist(cidr str
- cmds.ExecCommands(commands, false)
- }
-
--func (t *tproxy) RemoveIPWhitelist(cidr string) {
-+func (t *legacyTproxy) RemoveIPWhitelist(cidr string) {
- var commands string
- commands = fmt.Sprintf(`iptables -w 2 -t mangle -D TP_RULE -d %s -j RETURN`, cidr)
- if !strings.Contains(cidr, ".") {
-@@ -40,7 +58,7 @@ func (t *tproxy) RemoveIPWhitelist(cidr
- cmds.ExecCommands(commands, false)
- }
-
--func (t *tproxy) GetSetupCommands() Setter {
-+func (t *legacyTproxy) GetSetupCommands() Setter {
- commands := `
- ip rule add fwmark 0x40/0xc0 table 100
- ip route add local 0.0.0.0/0 dev lo table 100
-@@ -158,7 +176,7 @@ ip6tables -w 2 -t mangle -A TP_MARK -j C
- }
- }
-
--func (t *tproxy) GetCleanCommands() Setter {
-+func (t *legacyTproxy) GetCleanCommands() Setter {
- commands := `
- ip rule del fwmark 0x40/0xc0 table 100
- ip route del local 0.0.0.0/0 dev lo table 100
-@@ -195,3 +213,166 @@ ip6tables -w 2 -t mangle -X TP_MARK
- Cmds: commands,
- }
- }
-+
-+func (t *nftTproxy) AddIPWhitelist(cidr string) {
-+ command := fmt.Sprintf("nft add element inet v2raya interface { %s }", cidr)
-+ if !strings.Contains(cidr, ".") {
-+ command = strings.Replace(command, "interface", "interface6", 1)
-+ }
-+ cmds.ExecCommands(command, false)
-+}
-+
-+func (t *nftTproxy) RemoveIPWhitelist(cidr string) {
-+ command := fmt.Sprintf("nft delete element inet v2raya interface { %s }", cidr)
-+ if !strings.Contains(cidr, ".") {
-+ command = strings.Replace(command, "interface", "interface6", 1)
-+ }
-+ cmds.ExecCommands(command, false)
-+}
-+
-+func (t *nftTproxy) GetSetupCommands() Setter {
-+ // 198.18.0.0/15 and fc00::/7 are reserved for private use but used by fakedns
-+ table := `
-+table inet v2raya {
-+ set whitelist {
-+ type ipv4_addr
-+ flags interval
-+ auto-merge
-+ elements = {
-+ 0.0.0.0/32,
-+ 10.0.0.0/8,
-+ 100.64.0.0/10,
-+ 127.0.0.0/8,
-+ 169.254.0.0/16,
-+ 172.16.0.0/12,
-+ 192.0.0.0/24,
-+ 192.0.2.0/24,
-+ 192.88.99.0/24,
-+ 192.168.0.0/16,
-+ 198.51.100.0/24,
-+ 203.0.113.0/24,
-+ 224.0.0.0/4,
-+ 240.0.0.0/4
-+ }
-+ }
-+
-+ set whitelist6 {
-+ type ipv6_addr
-+ flags interval
-+ auto-merge
-+ elements = {
-+ ::/128,
-+ ::1/128,
-+ 64:ff9b::/96,
-+ 100::/64,
-+ 2001::/32,
-+ 2001:20::/28,
-+ fe80::/10,
-+ ff00::/8
-+ }
-+ }
-+
-+ set interface {
-+ type ipv4_addr
-+ flags interval
-+ auto-merge
-+ }
-+
-+ set interface6 {
-+ type ipv6_addr
-+ flags interval
-+ auto-merge
-+ }
-+
-+ chain tp_out {
-+ meta mark & 0x80 == 0x80 return
-+ meta l4proto { tcp, udp } fib saddr type local fib daddr type != local jump tp_rule
-+ }
-+
-+ chain tp_pre {
-+ iifname "lo" mark & 0xc0 != 0x40 return
-+ meta l4proto { tcp, udp } fib saddr type != local fib daddr type != local jump tp_rule
-+ meta l4proto { tcp, udp } mark & 0xc0 == 0x40 tproxy ip to 127.0.0.1:32345
-+ meta l4proto { tcp, udp } mark & 0xc0 == 0x40 tproxy ip6 to [::1]:32345
-+ }
-+
-+ chain output {
-+ type route hook output priority mangle - 5; policy accept;
-+ meta nfproto { ipv4, ipv6 } jump tp_out
-+ }
-+
-+ chain prerouting {
-+ type filter hook prerouting priority mangle - 5; policy accept;
-+ meta nfproto { ipv4, ipv6 } jump tp_pre
-+ }
-+
-+ chain tp_rule {
-+ meta mark set ct mark
-+ meta mark & 0xc0 == 0x40 return
-+ iifname "docker*" return
-+ iifname "veth*" return
-+ iifname "wg*" return
-+ iifname "ppp*" return
-+ # anti-pollution
-+ ip daddr @interface return
-+ ip daddr @whitelist return
-+ ip6 daddr @interface6 return
-+ ip6 daddr @whitelist6 return
-+ jump tp_mark
-+ }
-+
-+ chain tp_mark {
-+ tcp flags & (fin | syn | rst | ack) == syn meta mark set mark | 0x40
-+ meta l4proto udp ct state new meta mark set mark | 0x40
-+ ct mark set mark
-+ }
-+}
-+`
-+ if configure.GetSettingNotNil().AntiPollution != configure.AntipollutionClosed {
-+ table = strings.ReplaceAll(table, "# anti-pollution", `
-+ meta l4proto { tcp, udp } th dport 53 jump tp_mark
-+ meta mark & 0xc0 == 0x40 return
-+ `)
-+ }
-+
-+ if !IsIPv6Supported() {
-+ // drop ipv6 packets hooks
-+ table = strings.ReplaceAll(table, "meta nfproto { ipv4, ipv6 }", "meta nfproto ipv4")
-+ }
-+
-+ nftablesConf := asset.GetNFTablesConfigPath()
-+ os.WriteFile(nftablesConf, []byte(table), 0644)
-+
-+ command := `
-+ip rule add fwmark 0x40/0xc0 table 100
-+ip route add local 0.0.0.0/0 dev lo table 100
-+`
-+ if IsIPv6Supported() {
-+ command += `
-+ip -6 rule add fwmark 0x40/0xc0 table 100
-+ip -6 route add local ::/0 dev lo table 100
-+`
-+ }
-+
-+ command += `nft -f ` + nftablesConf
-+ return Setter{Cmds: command}
-+}
-+
-+func (t *nftTproxy) GetCleanCommands() Setter {
-+ command := `
-+ip rule del fwmark 0x40/0xc0 table 100
-+ip route del local 0.0.0.0/0 dev lo table 100
-+`
-+ if IsIPv6Supported() {
-+ command += `
-+ip -6 rule del fwmark 0x40/0xc0 table 100
-+ip -6 route del local ::/0 dev lo table 100
-+ `
-+ }
-+
-+ command += `nft delete table inet v2raya`
-+ if !IsIPv6Supported() {
-+ command = strings.Replace(command, "inet", "ip", 1)
-+ }
-+ return Setter{Cmds: command}
-+}
---- a/core/iptables/utils.go
-+++ b/core/iptables/utils.go
-@@ -1,12 +1,13 @@
- package iptables
-
- import (
-+ "net"
-+ "strconv"
-+
- "github.com/v2rayA/v2rayA/common"
- "github.com/v2rayA/v2rayA/common/cmds"
- "github.com/v2rayA/v2rayA/conf"
- "golang.org/x/net/nettest"
-- "net"
-- "strconv"
- )
-
- func IPNet2CIDR(ipnet *net.IPNet) string {
-@@ -44,3 +45,21 @@ func IsIPv6Supported() bool {
- }
- return cmds.IsCommandValid("ip6tables")
- }
-+
-+func IsNFTablesSupported() bool {
-+
-+ switch conf.GetEnvironmentConfig().NFTablesSupport {
-+ // Warning:
-+ // This is an experimental feature for nftables support.
-+ // The default value is "off" for now but may be changed to "auto" in the future
-+ case "on":
-+ return true
-+ case "off":
-+ return false
-+ default:
-+ }
-+ if common.IsDocker() {
-+ return false
-+ }
-+ return cmds.IsCommandValid("nft")
-+}
---- a/core/iptables/watcher.go
-+++ b/core/iptables/watcher.go
-@@ -10,6 +10,7 @@ type LocalIPWatcher struct {
- cidrPool map[string]struct{}
- AddedFunc func(cidr string)
- RemovedFunc func(cidr string)
-+ UpdateFunc func(cidrs []string)
- }
-
- func NewLocalIPWatcher(interval time.Duration, AddedFunc func(cidr string), RemovedFunc func(cidr string)) *LocalIPWatcher {
---- a/core/v2ray/asset/asset.go
-+++ b/core/v2ray/asset/asset.go
-@@ -3,12 +3,6 @@ package asset
- import (
- "errors"
- "fmt"
-- "github.com/adrg/xdg"
-- "github.com/muhammadmuzzammil1998/jsonc"
-- "github.com/v2rayA/v2rayA/common/files"
-- "github.com/v2rayA/v2rayA/conf"
-- "github.com/v2rayA/v2rayA/core/v2ray/where"
-- "github.com/v2rayA/v2rayA/pkg/util/log"
- "io"
- "io/fs"
- "net/http"
-@@ -17,6 +11,13 @@ import (
- "path/filepath"
- "runtime"
- "time"
-+
-+ "github.com/adrg/xdg"
-+ "github.com/muhammadmuzzammil1998/jsonc"
-+ "github.com/v2rayA/v2rayA/common/files"
-+ "github.com/v2rayA/v2rayA/conf"
-+ "github.com/v2rayA/v2rayA/core/v2ray/where"
-+ "github.com/v2rayA/v2rayA/pkg/util/log"
- )
-
- func GetV2rayLocationAssetOverride() string {
-@@ -140,6 +141,10 @@ func GetV2rayConfigDirPath() (p string)
- return conf.GetEnvironmentConfig().V2rayConfigDirectory
- }
-
-+func GetNFTablesConfigPath() (p string) {
-+ return path.Join(conf.GetEnvironmentConfig().Config, "v2raya.nft")
-+}
-+
- func Download(url string, to string) (err error) {
- log.Info("Downloading %v to %v", url, to)
- c := http.Client{Timeout: 90 * time.Second}
---- a/core/v2ray/transparent.go
-+++ b/core/v2ray/transparent.go
-@@ -2,13 +2,14 @@ package v2ray
-
- import (
- "fmt"
-+ "strings"
-+ "time"
-+
- "github.com/v2rayA/v2rayA/conf"
- "github.com/v2rayA/v2rayA/core/iptables"
- "github.com/v2rayA/v2rayA/core/specialMode"
- "github.com/v2rayA/v2rayA/db/configure"
- "github.com/v2rayA/v2rayA/pkg/util/log"
-- "strings"
-- "time"
- )
-
- func deleteTransparentProxyRules() {
-@@ -45,12 +46,12 @@ func writeTransparentProxyRules() (err e
- }
- return fmt.Errorf("not support \"tproxy\" mode of transparent proxy: %w", err)
- }
-- iptables.SetWatcher(&iptables.Tproxy)
-+ iptables.SetWatcher(iptables.Tproxy)
- case configure.TransparentRedirect:
- if err = iptables.Redirect.GetSetupCommands().Run(true); err != nil {
- return fmt.Errorf("not support \"redirect\" mode of transparent proxy: %w", err)
- }
-- iptables.SetWatcher(&iptables.Redirect)
-+ iptables.SetWatcher(iptables.Redirect)
- case configure.TransparentSystemProxy:
- if err = iptables.SystemProxy.GetSetupCommands().Run(true); err != nil {
- return fmt.Errorf("not support \"system proxy\" mode of transparent proxy: %w", err)