Update On Fri Mar 20 20:03:30 CET 2026

This commit is contained in:
github-action[bot]
2026-03-20 20:03:30 +01:00
parent 7a11c8b589
commit 6f0b8b4a85
306 changed files with 18701 additions and 1246 deletions
+1
View File
@@ -1304,3 +1304,4 @@ Update On Mon Mar 16 20:17:53 CET 2026
Update On Tue Mar 17 20:16:34 CET 2026
Update On Wed Mar 18 20:13:21 CET 2026
Update On Thu Mar 19 20:11:34 CET 2026
Update On Fri Mar 20 20:03:22 CET 2026
@@ -53,7 +53,7 @@ jobs:
- { goos: linux, goarch: mipsle, gomips: softfloat, output: mipsle-softfloat }
- { goos: linux, goarch: mips64, output: mips64 }
- { goos: linux, goarch: mips64le, output: mips64le, debian: mips64el, rpm: mips64el }
- { goos: linux, goarch: loong64, output: loong64-abi1, abi: '1', debian: loongarch64, rpm: loongarch64 }
- { goos: linux, goarch: loong64, output: loong64-abi1, abi: '1', debian: loongarch64, rpm: loongarch64, goversion: 'custom' }
- { goos: linux, goarch: loong64, output: loong64-abi2, abi: '2', debian: loong64, rpm: loong64 }
- { goos: linux, goarch: riscv64, output: riscv64, debian: riscv64, rpm: riscv64 }
- { goos: linux, goarch: s390x, output: s390x, debian: s390x, rpm: s390x }
@@ -158,14 +158,14 @@ jobs:
- uses: actions/checkout@v5
- name: Set up Go
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.abi != '1' }}
if: ${{ matrix.jobs.goversion == '' }}
uses: actions/setup-go@v6
with:
go-version: '1.26'
check-latest: true # Always check for the latest patch release
- name: Set up Go
if: ${{ matrix.jobs.goversion != '' && matrix.jobs.abi != '1' }}
if: ${{ matrix.jobs.goversion != '' && matrix.jobs.goversion != 'custom' }}
uses: actions/setup-go@v6
with:
go-version: ${{ matrix.jobs.goversion }}
@@ -54,7 +54,7 @@ type SudokuHTTPMaskOptions struct {
Mode string `proxy:"mode,omitempty"`
TLS bool `proxy:"tls,omitempty"`
Host string `proxy:"host,omitempty"`
PathRoot string `proxy:"path_root,omitempty"`
PathRoot string `proxy:"path-root,omitempty"`
Multiplex string `proxy:"multiplex,omitempty"`
}
@@ -27,8 +27,7 @@ type Trojan struct {
hexPassword [trojan.KeyLength]byte
// for gun mux
gunConfig *gun.Config
gunTransport *gun.TransportWrap
gunTransport *gun.Transport
realityConfig *tlsC.RealityConfig
echConfig *ech.Config
@@ -178,7 +177,7 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Con
var c net.Conn
// gun transport
if t.gunTransport != nil {
c, err = gun.StreamGunWithTransport(t.gunTransport, t.gunConfig)
c, err = t.gunTransport.Dial()
} else {
c, err = t.dialer.DialContext(ctx, "tcp", t.addr)
}
@@ -206,7 +205,7 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata)
var c net.Conn
// grpc transport
if t.gunTransport != nil {
c, err = gun.StreamGunWithTransport(t.gunTransport, t.gunConfig)
c, err = t.gunTransport.Dial()
} else {
c, err = t.dialer.DialContext(ctx, "tcp", t.addr)
}
@@ -317,13 +316,14 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
Reality: t.realityConfig,
}
t.gunTransport = gun.NewHTTP2Client(dialFn, tlsConfig)
t.gunConfig = &gun.Config{
ServiceName: option.GrpcOpts.GrpcServiceName,
UserAgent: option.GrpcOpts.GrpcUserAgent,
Host: option.SNI,
gunConfig := &gun.Config{
ServiceName: option.GrpcOpts.GrpcServiceName,
UserAgent: option.GrpcOpts.GrpcUserAgent,
Host: option.SNI,
PingInterval: option.GrpcOpts.PingInterval,
}
t.gunTransport = gun.NewTransport(dialFn, tlsConfig, gunConfig)
}
return t, nil
@@ -33,8 +33,7 @@ type Vless struct {
encryption *encryption.ClientInstance
// for gun mux
gunConfig *gun.Config
gunTransport *gun.TransportWrap
gunTransport *gun.Transport
realityConfig *tlsC.RealityConfig
echConfig *ech.Config
@@ -234,7 +233,7 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn
var c net.Conn
// gun transport
if v.gunTransport != nil {
c, err = gun.StreamGunWithTransport(v.gunTransport, v.gunConfig)
c, err = v.gunTransport.Dial()
} else {
c, err = v.dialer.DialContext(ctx, "tcp", v.addr)
}
@@ -260,7 +259,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (
var c net.Conn
// gun transport
if v.gunTransport != nil {
c, err = gun.StreamGunWithTransport(v.gunTransport, v.gunConfig)
c, err = v.gunTransport.Dial()
} else {
c, err = v.dialer.DialContext(ctx, "tcp", v.addr)
}
@@ -431,9 +430,10 @@ func NewVless(option VlessOption) (*Vless, error) {
}
gunConfig := &gun.Config{
ServiceName: option.GrpcOpts.GrpcServiceName,
UserAgent: option.GrpcOpts.GrpcUserAgent,
Host: option.ServerName,
ServiceName: option.GrpcOpts.GrpcServiceName,
UserAgent: option.GrpcOpts.GrpcUserAgent,
Host: option.ServerName,
PingInterval: option.GrpcOpts.PingInterval,
}
if option.ServerName == "" {
gunConfig.Host = v.addr
@@ -457,9 +457,7 @@ func NewVless(option VlessOption) (*Vless, error) {
}
}
v.gunConfig = gunConfig
v.gunTransport = gun.NewHTTP2Client(dialFn, tlsConfig)
v.gunTransport = gun.NewTransport(dialFn, tlsConfig, gunConfig)
}
return v, nil
@@ -34,8 +34,7 @@ type Vmess struct {
option *VmessOption
// for gun mux
gunConfig *gun.Config
gunTransport *gun.TransportWrap
gunTransport *gun.Transport
realityConfig *tlsC.RealityConfig
echConfig *ech.Config
@@ -86,6 +85,7 @@ type HTTP2Options struct {
type GrpcOptions struct {
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
GrpcUserAgent string `proxy:"grpc-user-agent,omitempty"`
PingInterval int `proxy:"ping-interval,omitempty"`
}
type WSOptions struct {
@@ -295,7 +295,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn
var c net.Conn
// gun transport
if v.gunTransport != nil {
c, err = gun.StreamGunWithTransport(v.gunTransport, v.gunConfig)
c, err = v.gunTransport.Dial()
} else {
c, err = v.dialer.DialContext(ctx, "tcp", v.addr)
}
@@ -318,7 +318,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (
var c net.Conn
// gun transport
if v.gunTransport != nil {
c, err = gun.StreamGunWithTransport(v.gunTransport, v.gunConfig)
c, err = v.gunTransport.Dial()
} else {
c, err = v.dialer.DialContext(ctx, "tcp", v.addr)
}
@@ -437,9 +437,10 @@ func NewVmess(option VmessOption) (*Vmess, error) {
}
gunConfig := &gun.Config{
ServiceName: option.GrpcOpts.GrpcServiceName,
UserAgent: option.GrpcOpts.GrpcUserAgent,
Host: option.ServerName,
ServiceName: option.GrpcOpts.GrpcServiceName,
UserAgent: option.GrpcOpts.GrpcUserAgent,
Host: option.ServerName,
PingInterval: option.GrpcOpts.PingInterval,
}
if option.ServerName == "" {
gunConfig.Host = v.addr
@@ -463,9 +464,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
}
}
v.gunConfig = gunConfig
v.gunTransport = gun.NewHTTP2Client(dialFn, tlsConfig)
v.gunTransport = gun.NewTransport(dialFn, tlsConfig, gunConfig)
}
return v, nil
@@ -19,8 +19,6 @@ type Fallback struct {
testUrl string
selected string
expectedStatus string
Hidden bool
Icon string
}
func (f *Fallback) Now() string {
@@ -90,8 +88,8 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
"testUrl": f.testUrl,
"expectedStatus": f.expectedStatus,
"fixed": f.selected,
"hidden": f.Hidden,
"icon": f.Icon,
"hidden": f.Hidden(),
"icon": f.Icon(),
})
}
@@ -163,6 +161,8 @@ func NewFallback(option *GroupCommonOption, providers []P.ProxyProvider) *Fallba
GroupBase: NewGroupBase(GroupBaseOption{
Name: option.Name,
Type: C.Fallback,
Hidden: option.Hidden,
Icon: option.Icon,
Filter: option.Filter,
ExcludeFilter: option.ExcludeFilter,
ExcludeType: option.ExcludeType,
@@ -173,7 +173,5 @@ func NewFallback(option *GroupCommonOption, providers []P.ProxyProvider) *Fallba
disableUDP: option.DisableUDP,
testUrl: option.URL,
expectedStatus: option.ExpectedStatus,
Hidden: option.Hidden,
Icon: option.Icon,
}
}
@@ -22,6 +22,8 @@ import (
type GroupBase struct {
*outbound.Base
hidden bool
icon string
filterRegs []*regexp2.Regexp
excludeFilterRegs []*regexp2.Regexp
excludeTypeArray []string
@@ -30,7 +32,7 @@ type GroupBase struct {
failedTimes int
failedTime time.Time
failedTesting atomic.Bool
TestTimeout int
testTimeout int
maxFailedTimes int
// for GetProxies
@@ -42,6 +44,8 @@ type GroupBase struct {
type GroupBaseOption struct {
Name string
Type C.AdapterType
Hidden bool
Icon string
Filter string
ExcludeFilter string
ExcludeType string
@@ -74,17 +78,19 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
gb := &GroupBase{
Base: outbound.NewBase(outbound.BaseOption{Name: opt.Name, Type: opt.Type}),
hidden: opt.Hidden,
icon: opt.Icon,
filterRegs: filterRegs,
excludeFilterRegs: excludeFilterRegs,
excludeTypeArray: excludeTypeArray,
providers: opt.Providers,
failedTesting: atomic.NewBool(false),
TestTimeout: opt.TestTimeout,
testTimeout: opt.TestTimeout,
maxFailedTimes: opt.MaxFailedTimes,
}
if gb.TestTimeout == 0 {
gb.TestTimeout = 5000
if gb.testTimeout == 0 {
gb.testTimeout = 5000
}
if gb.maxFailedTimes == 0 {
gb.maxFailedTimes = 5
@@ -93,6 +99,14 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
return gb
}
func (gb *GroupBase) Hidden() bool {
return gb.hidden
}
func (gb *GroupBase) Icon() string {
return gb.icon
}
func (gb *GroupBase) Touch() {
for _, pd := range gb.providers {
pd.Touch()
@@ -265,7 +279,7 @@ func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error, fn func(
log.Debugln("ProxyGroup: %s first failed", gb.Name())
gb.failedTime = time.Now()
} else {
if time.Since(gb.failedTime) > time.Duration(gb.TestTimeout)*time.Millisecond {
if time.Since(gb.failedTime) > time.Duration(gb.testTimeout)*time.Millisecond {
gb.failedTimes = 0
return
}
@@ -27,8 +27,6 @@ type LoadBalance struct {
strategyFn strategyFn
testUrl string
expectedStatus string
Hidden bool
Icon string
}
var errStrategy = errors.New("unsupported strategy")
@@ -234,8 +232,8 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
"all": all,
"testUrl": lb.testUrl,
"expectedStatus": lb.expectedStatus,
"hidden": lb.Hidden,
"icon": lb.Icon,
"hidden": lb.Hidden(),
"icon": lb.Icon(),
})
}
@@ -267,6 +265,8 @@ func NewLoadBalance(option *GroupCommonOption, providers []P.ProxyProvider, stra
GroupBase: NewGroupBase(GroupBaseOption{
Name: option.Name,
Type: C.LoadBalance,
Hidden: option.Hidden,
Icon: option.Icon,
Filter: option.Filter,
ExcludeFilter: option.ExcludeFilter,
ExcludeType: option.ExcludeType,
@@ -278,7 +278,5 @@ func NewLoadBalance(option *GroupCommonOption, providers []P.ProxyProvider, stra
disableUDP: option.DisableUDP,
testUrl: option.URL,
expectedStatus: option.ExpectedStatus,
Hidden: option.Hidden,
Icon: option.Icon,
}, nil
}
@@ -14,8 +14,6 @@ type Selector struct {
disableUDP bool
selected string
testUrl string
Hidden bool
Icon string
}
// DialContext implements C.ProxyAdapter
@@ -68,8 +66,8 @@ func (s *Selector) MarshalJSON() ([]byte, error) {
"now": s.Now(),
"all": all,
"testUrl": url,
"hidden": s.Hidden,
"icon": s.Icon,
"hidden": s.Hidden(),
"icon": s.Icon(),
})
}
@@ -121,6 +119,8 @@ func NewSelector(option *GroupCommonOption, providers []P.ProxyProvider) *Select
GroupBase: NewGroupBase(GroupBaseOption{
Name: option.Name,
Type: C.Selector,
Hidden: option.Hidden,
Icon: option.Icon,
Filter: option.Filter,
ExcludeFilter: option.ExcludeFilter,
ExcludeType: option.ExcludeType,
@@ -131,7 +131,5 @@ func NewSelector(option *GroupCommonOption, providers []P.ProxyProvider) *Select
selected: "COMPATIBLE",
disableUDP: option.DisableUDP,
testUrl: option.URL,
Hidden: option.Hidden,
Icon: option.Icon,
}
}
@@ -29,8 +29,6 @@ type URLTest struct {
expectedStatus string
tolerance uint16
disableUDP bool
Hidden bool
Icon string
fastNode C.Proxy
fastSingle *singledo.Single[C.Proxy]
}
@@ -180,8 +178,8 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
"testUrl": u.testUrl,
"expectedStatus": u.expectedStatus,
"fixed": u.selected,
"hidden": u.Hidden,
"icon": u.Icon,
"hidden": u.Hidden(),
"icon": u.Icon(),
})
}
@@ -215,6 +213,8 @@ func NewURLTest(option *GroupCommonOption, providers []P.ProxyProvider, options
GroupBase: NewGroupBase(GroupBaseOption{
Name: option.Name,
Type: C.URLTest,
Hidden: option.Hidden,
Icon: option.Icon,
Filter: option.Filter,
ExcludeFilter: option.ExcludeFilter,
ExcludeType: option.ExcludeType,
@@ -226,8 +226,6 @@ func NewURLTest(option *GroupCommonOption, providers []P.ProxyProvider, options
disableUDP: option.DisableUDP,
testUrl: option.URL,
expectedStatus: option.ExpectedStatus,
Hidden: option.Hidden,
Icon: option.Icon,
}
for _, option := range options {
@@ -16,6 +16,9 @@ type ProxyGroup interface {
Now() string
Touch()
Hidden() bool
Icon() string
URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (mp map[string]uint16, err error)
}
@@ -18,8 +18,8 @@ var (
type healthCheckSchema struct {
Enable bool `provider:"enable"`
URL string `provider:"url"`
Interval int `provider:"interval"`
URL string `provider:"url,omitempty"`
Interval int `provider:"interval,omitempty"`
TestTimeout int `provider:"timeout,omitempty"`
Lazy bool `provider:"lazy,omitempty"`
ExpectedStatus string `provider:"expected-status,omitempty"`
@@ -669,6 +669,7 @@ proxies: # socks5
grpc-opts:
grpc-service-name: "example"
# grpc-user-agent: "grpc-go/1.36.0"
# ping-interval: 0 # 默认关闭,单位为秒
# ip-version: ipv4
# vless
@@ -759,6 +760,7 @@ proxies: # socks5
grpc-opts:
grpc-service-name: "grpc"
# grpc-user-agent: "grpc-go/1.36.0"
# ping-interval: 0 # 默认关闭,单位为秒
reality-opts:
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
@@ -830,6 +832,7 @@ proxies: # socks5
grpc-opts:
grpc-service-name: "example"
# grpc-user-agent: "grpc-go/1.36.0"
# ping-interval: 0 # 默认关闭,单位为秒
- name: trojan-ws
server: server
@@ -1098,7 +1101,7 @@ proxies: # socks5
mode: legacy # 可选:legacy(默认)、streamsplit-stream)、poll、auto(先 stream 再 poll)、wsWebSocket 隧道)
# tls: true # 可选:仅在 mode 为 stream/poll/auto/ws 时生效;true 强制 https/wssfalse 强制 http/ws(不会根据端口自动推断)
# host: "" # 可选:覆盖 Host/SNI(支持 example.com 或 example.com:443);仅在 mode 为 stream/poll/auto/ws 时生效
# path_root: "" # 可选:HTTP 隧道端点一级路径前缀(双方需一致),例如 "aabbcc" 或 "/aabbcc/" => /aabbcc/session、/aabbcc/stream、/aabbcc/api/v1/upload、/aabbcc/ws
# path-root: "" # 可选:HTTP 隧道端点一级路径前缀(双方需一致),例如 "aabbcc" 或 "/aabbcc/" => /aabbcc/session、/aabbcc/stream、/aabbcc/api/v1/upload、/aabbcc/ws
# multiplex: off # 可选:off(默认)、auto(复用底层 HTTP 连接,减少建链 RTT)、on(Sudoku mux 单隧道多目标;仅在 mode=stream/poll/auto 生效;ws 强制 off
#
# 向后兼容旧写法:
@@ -1677,7 +1680,7 @@ listeners:
httpmask:
disable: false # true 禁用所有 HTTP 伪装/隧道
mode: legacy # 可选:legacy(默认)、streamsplit-stream)、poll、auto(先 stream 再 poll)、wsWebSocket 隧道)
# path_root: "" # 可选:HTTP 隧道端点一级路径前缀(双方需一致),例如 "aabbcc" 或 "/aabbcc/" => /aabbcc/session、/aabbcc/stream、/aabbcc/api/v1/upload、/aabbcc/ws
# path-root: "" # 可选:HTTP 隧道端点一级路径前缀(双方需一致),例如 "aabbcc" 或 "/aabbcc/" => /aabbcc/session、/aabbcc/stream、/aabbcc/api/v1/upload、/aabbcc/ws
#
# 可选:当启用 HTTPMask 且识别到“像 HTTP 但不符合 tunnel/auth”的请求时,将原始字节透传给 fallback(常用于与其他服务共端口):
# fallback: "127.0.0.1:80"
@@ -23,6 +23,7 @@ require (
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/http v0.1.0
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604
github.com/metacubex/mhurl v0.1.0
github.com/metacubex/mlkem v0.1.0
github.com/metacubex/quic-go v0.59.1-0.20260213014310-4df8f0de5b56
github.com/metacubex/randv2 v0.2.0
@@ -107,6 +107,8 @@ github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
github.com/metacubex/mhurl v0.1.0 h1:ZdW4Zxe3j3uJ89gNytOazHu6kbHn5owutN/VfXOI8GE=
github.com/metacubex/mhurl v0.1.0/go.mod h1:2qpQImCbXoUs6GwJrjuEXKelPyoimsIXr07eNKZdS00=
github.com/metacubex/mlkem v0.1.0 h1:wFClitonSFcmipzzQvax75beLQU+D7JuC+VK1RzSL8I=
github.com/metacubex/mlkem v0.1.0/go.mod h1:amhaXZVeYNShuy9BILcR7P0gbeo/QLZsnqCdL8U2PDQ=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
@@ -35,7 +35,7 @@ type SudokuOption struct {
type SudokuHTTPMaskOptions struct {
Disable bool `inbound:"disable,omitempty"`
Mode string `inbound:"mode,omitempty"`
PathRoot string `inbound:"path_root,omitempty"`
PathRoot string `inbound:"path-root,omitempty"`
}
func (o SudokuOption) Equal(config C.InboundConfig) bool {
@@ -148,6 +148,7 @@ func handleConn(conn net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions
bufConn := N.NewBufferedConn(conn)
head, err := bufConn.Peek(1)
if err != nil {
conn.Close()
return
}
@@ -4,9 +4,10 @@ import (
"bytes"
"errors"
"net"
"net/url"
"github.com/metacubex/mihomo/transport/socks5"
"github.com/metacubex/mhurl"
)
type packet struct {
@@ -48,7 +49,7 @@ func (c *packet) InAddr() net.Addr {
}
func ParseSSURL(s string) (addr, cipher, password string, err error) {
u, err := url.Parse(s)
u, err := mhurl.Parse(s) // we need multiple hosts url supports
if err != nil {
return
}
@@ -0,0 +1,22 @@
package shadowsocks
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
func TestParseSSURL(t *testing.T) {
for _, test := range []struct{ method, passwd, hosts string }{
{method: "aes-256-gcm", passwd: "password", hosts: ":1000,:2000,:3000"},
{method: "aes-256-gcm", passwd: "password", hosts: "127.0.0.1:1000,127.0.0.1:2000,127.0.0.1:3000"},
{method: "aes-256-gcm", passwd: "password", hosts: "[::1]:1000,[::1]:2000,[::1]:3000"},
} {
addr, cipher, password, err := ParseSSURL(fmt.Sprintf("ss://%s:%s@%s", test.method, test.passwd, test.hosts))
require.NoError(t, err)
require.Equal(t, test.hosts, addr)
require.Equal(t, test.method, cipher)
require.Equal(t, test.passwd, password)
}
}
@@ -4,7 +4,6 @@ import (
"context"
"errors"
"net"
"net/url"
"strings"
"github.com/metacubex/mihomo/adapter/inbound"
@@ -19,6 +18,7 @@ import (
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
"github.com/metacubex/http"
"github.com/metacubex/mhurl"
vmess "github.com/metacubex/sing-vmess"
"github.com/metacubex/sing/common"
"github.com/metacubex/sing/common/metadata"
@@ -230,7 +230,7 @@ func HandleVmess(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition)
}
func ParseVmessURL(s string) (addr, username, password string, err error) {
u, err := url.Parse(s)
u, err := mhurl.Parse(s) // we need multiple hosts url supports
if err != nil {
return
}
@@ -0,0 +1,22 @@
package sing_vmess
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
func TestParseVmessURL(t *testing.T) {
for _, test := range []struct{ username, passwd, hosts string }{
{username: "username", passwd: "password", hosts: ":1000,:2000,:3000"},
{username: "username", passwd: "password", hosts: "127.0.0.1:1000,127.0.0.1:2000,127.0.0.1:3000"},
{username: "username", passwd: "password", hosts: "[::1]:1000,[::1]:2000,[::1]:3000"},
} {
addr, username, password, err := ParseVmessURL(fmt.Sprintf("vmess://%s:%s@%s", test.username, test.passwd, test.hosts))
require.NoError(t, err)
require.Equal(t, test.hosts, addr)
require.Equal(t, test.username, username)
require.Equal(t, test.passwd, password)
}
}
@@ -33,8 +33,8 @@ var (
)
var defaultHeader = http.Header{
"content-type": []string{"application/grpc"},
"user-agent": []string{"grpc-go/1.36.0"},
"Content-Type": []string{"application/grpc"},
"User-Agent": []string{"grpc-go/1.36.0"},
}
type DialFn = func(ctx context.Context, network, addr string) (net.Conn, error)
@@ -59,9 +59,10 @@ type Conn struct {
}
type Config struct {
ServiceName string
UserAgent string
Host string
ServiceName string
UserAgent string
Host string
PingInterval int
}
func (g *Conn) initReader() {
@@ -246,7 +247,7 @@ func (g *Conn) SetDeadline(t time.Time) error {
return nil
}
func NewHTTP2Client(dialFn DialFn, tlsConfig *vmess.TLSConfig) *TransportWrap {
func NewTransport(dialFn DialFn, tlsConfig *vmess.TLSConfig, gunCfg *Config) *Transport {
dialFunc := func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
defer cancel()
@@ -288,14 +289,16 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *vmess.TLSConfig) *TransportWrap {
DialTLSContext: dialFunc,
AllowHTTP: false,
DisableCompression: true,
ReadIdleTimeout: time.Duration(gunCfg.PingInterval) * time.Second, // If zero, no health check is performed
PingTimeout: 0,
}
ctx, cancel := context.WithCancel(context.Background())
wrap := &TransportWrap{
Http2Transport: transport,
ctx: ctx,
cancel: cancel,
wrap := &Transport{
transport: transport,
cfg: gunCfg,
ctx: ctx,
cancel: cancel,
}
return wrap
}
@@ -307,18 +310,18 @@ func ServiceNameToPath(serviceName string) string {
return "/" + serviceName + "/Tun"
}
func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, error) {
func (t *Transport) Dial() (net.Conn, error) {
serviceName := "GunService"
if cfg.ServiceName != "" {
serviceName = cfg.ServiceName
if t.cfg.ServiceName != "" {
serviceName = t.cfg.ServiceName
}
path := ServiceNameToPath(serviceName)
reader, writer := io.Pipe()
header := defaultHeader.Clone()
if cfg.UserAgent != "" {
header.Set("User-Agent", cfg.UserAgent)
if t.cfg.UserAgent != "" {
header.Set("User-Agent", t.cfg.UserAgent)
}
request := &http.Request{
@@ -326,17 +329,17 @@ func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, er
Body: reader,
URL: &url.URL{
Scheme: "https",
Host: cfg.Host,
Host: t.cfg.Host,
Path: path,
// for unescape path
Opaque: "//" + cfg.Host + path,
Opaque: "//" + t.cfg.Host + path,
},
Proto: "HTTP/2",
ProtoMajor: 2,
ProtoMinor: 0,
Header: header,
}
request = request.WithContext(transport.ctx)
request = request.WithContext(t.ctx)
conn := &Conn{
initFn: func() (io.ReadCloser, NetAddr, error) {
@@ -348,7 +351,7 @@ func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, er
},
}
request = request.WithContext(httptrace.WithClientTrace(request.Context(), trace))
response, err := transport.RoundTrip(request)
response, err := t.transport.RoundTrip(request)
if err != nil {
return nil, nAddr, err
}
@@ -361,13 +364,13 @@ func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, er
return conn, nil
}
func StreamGunWithConn(conn net.Conn, tlsConfig *vmess.TLSConfig, cfg *Config) (net.Conn, error) {
func StreamGunWithConn(conn net.Conn, tlsConfig *vmess.TLSConfig, gunCfg *Config) (net.Conn, error) {
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
return conn, nil
}
transport := NewHTTP2Client(dialFn, tlsConfig)
c, err := StreamGunWithTransport(transport, cfg)
transport := NewTransport(dialFn, tlsConfig, gunCfg)
c, err := transport.Dial()
if err != nil {
return nil, err
}
@@ -10,17 +10,18 @@ import (
"github.com/metacubex/http"
)
type TransportWrap struct {
*http.Http2Transport
type Transport struct {
transport *http.Http2Transport
cfg *Config
ctx context.Context
cancel context.CancelFunc
closeOnce sync.Once
}
func (tw *TransportWrap) Close() error {
tw.closeOnce.Do(func() {
tw.cancel()
CloseTransport(tw.Http2Transport)
func (t *Transport) Close() error {
t.closeOnce.Do(func() {
t.cancel()
CloseHttp2Transport(t.transport)
})
return nil
}
@@ -44,7 +44,7 @@ func closeClientConn(cc *http.Http2ClientConn) { // like forceCloseConn() in htt
_ = cc.Close()
}
func CloseTransport(tr *http.Http2Transport) {
func CloseHttp2Transport(tr *http.Http2Transport) {
connPool := transportConnPool(tr)
p := (*clientConnPool)((*efaceWords)(unsafe.Pointer(&connPool)).data)
p.mu.Lock()
@@ -5,6 +5,7 @@ import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"errors"
@@ -12,6 +13,7 @@ import (
"io"
"net"
"sync"
"sync/atomic"
"golang.org/x/crypto/chacha20poly1305"
)
@@ -55,13 +57,15 @@ type RecordConn struct {
recvAEADEpoch uint32
// Send direction state.
sendEpoch uint32
sendSeq uint64
sendBytes int64
sendEpoch uint32
sendSeq uint64
sendBytes int64
sendEpochUpdates uint32
// Receive direction state.
recvEpoch uint32
recvSeq uint64
recvEpoch uint32
recvSeq uint64
recvInitialized bool
readBuf bytes.Buffer
@@ -105,6 +109,9 @@ func NewRecordConn(conn net.Conn, method string, baseSend, baseRecv []byte) (*Re
}
rc := &RecordConn{Conn: conn, method: method}
rc.keys = recordKeys{baseSend: cloneBytes(baseSend), baseRecv: cloneBytes(baseRecv)}
if err := rc.resetTrafficState(); err != nil {
return nil, err
}
return rc, nil
}
@@ -127,11 +134,9 @@ func (c *RecordConn) Rekey(baseSend, baseRecv []byte) error {
defer c.writeMu.Unlock()
c.keys = recordKeys{baseSend: cloneBytes(baseSend), baseRecv: cloneBytes(baseRecv)}
c.sendEpoch = 0
c.sendSeq = 0
c.sendBytes = 0
c.recvEpoch = 0
c.recvSeq = 0
if err := c.resetTrafficState(); err != nil {
return err
}
c.readBuf.Reset()
c.sendAEAD = nil
@@ -141,6 +146,21 @@ func (c *RecordConn) Rekey(baseSend, baseRecv []byte) error {
return nil
}
func (c *RecordConn) resetTrafficState() error {
sendEpoch, sendSeq, err := randomRecordCounters()
if err != nil {
return fmt.Errorf("initialize record counters: %w", err)
}
c.sendEpoch = sendEpoch
c.sendSeq = sendSeq
c.sendBytes = 0
c.sendEpochUpdates = 0
c.recvEpoch = 0
c.recvSeq = 0
c.recvInitialized = false
return nil
}
func normalizeAEADMethod(method string) string {
switch method {
case "", "chacha20-poly1305":
@@ -166,6 +186,44 @@ func cloneBytes(b []byte) []byte {
return append([]byte(nil), b...)
}
func randomRecordCounters() (uint32, uint64, error) {
epoch, err := randomNonZeroUint32()
if err != nil {
return 0, 0, err
}
seq, err := randomNonZeroUint64()
if err != nil {
return 0, 0, err
}
return epoch, seq, nil
}
func randomNonZeroUint32() (uint32, error) {
var b [4]byte
for {
if _, err := io.ReadFull(rand.Reader, b[:]); err != nil {
return 0, err
}
v := binary.BigEndian.Uint32(b[:])
if v != 0 && v != ^uint32(0) {
return v, nil
}
}
}
func randomNonZeroUint64() (uint64, error) {
var b [8]byte
for {
if _, err := io.ReadFull(rand.Reader, b[:]); err != nil {
return 0, err
}
v := binary.BigEndian.Uint64(b[:])
if v != 0 && v != ^uint64(0) {
return v, nil
}
}
}
func (c *RecordConn) newAEADFor(base []byte, epoch uint32) (cipher.AEAD, error) {
if c.method == "none" {
return nil, nil
@@ -209,17 +267,49 @@ func deriveEpochKey(base []byte, epoch uint32, method string) []byte {
return mac.Sum(nil)
}
func (c *RecordConn) maybeBumpSendEpochLocked(addedPlain int) {
if KeyUpdateAfterBytes <= 0 || c.method == "none" {
return
func (c *RecordConn) maybeBumpSendEpochLocked(addedPlain int) error {
ku := atomic.LoadInt64(&KeyUpdateAfterBytes)
if ku <= 0 || c.method == "none" {
return nil
}
c.sendBytes += int64(addedPlain)
threshold := KeyUpdateAfterBytes * int64(c.sendEpoch+1)
threshold := ku * int64(c.sendEpochUpdates+1)
if c.sendBytes < threshold {
return
return nil
}
c.sendEpoch++
c.sendSeq = 0
c.sendEpochUpdates++
nextSeq, err := randomNonZeroUint64()
if err != nil {
return fmt.Errorf("rotate record seq: %w", err)
}
c.sendSeq = nextSeq
return nil
}
func (c *RecordConn) validateRecvPosition(epoch uint32, seq uint64) error {
if !c.recvInitialized {
return nil
}
if epoch < c.recvEpoch {
return fmt.Errorf("replayed epoch: got %d want >=%d", epoch, c.recvEpoch)
}
if epoch == c.recvEpoch && seq != c.recvSeq {
return fmt.Errorf("out of order: epoch=%d got=%d want=%d", epoch, seq, c.recvSeq)
}
if epoch > c.recvEpoch {
const maxJump = 8
if epoch-c.recvEpoch > maxJump {
return fmt.Errorf("epoch jump too large: got=%d want<=%d", epoch-c.recvEpoch, maxJump)
}
}
return nil
}
func (c *RecordConn) markRecvPosition(epoch uint32, seq uint64) {
c.recvEpoch = epoch
c.recvSeq = seq + 1
c.recvInitialized = true
}
func (c *RecordConn) Write(p []byte) (int, error) {
@@ -282,7 +372,9 @@ func (c *RecordConn) Write(p []byte) (int, error) {
}
total += n
c.maybeBumpSendEpochLocked(n)
if err := c.maybeBumpSendEpochLocked(n); err != nil {
return total, err
}
}
return total, nil
}
@@ -324,31 +416,17 @@ func (c *RecordConn) Read(p []byte) (int, error) {
epoch := binary.BigEndian.Uint32(header[:4])
seq := binary.BigEndian.Uint64(header[4:])
if epoch < c.recvEpoch {
return 0, fmt.Errorf("replayed epoch: got %d want >=%d", epoch, c.recvEpoch)
}
if epoch == c.recvEpoch && seq != c.recvSeq {
return 0, fmt.Errorf("out of order: epoch=%d got=%d want=%d", epoch, seq, c.recvSeq)
}
if epoch > c.recvEpoch {
const maxJump = 8
if epoch-c.recvEpoch > maxJump {
return 0, fmt.Errorf("epoch jump too large: got=%d want<=%d", epoch-c.recvEpoch, maxJump)
}
c.recvEpoch = epoch
c.recvSeq = 0
if seq != 0 {
return 0, fmt.Errorf("out of order: epoch advanced to %d but seq=%d", epoch, seq)
}
if err := c.validateRecvPosition(epoch, seq); err != nil {
return 0, err
}
if c.recvAEAD == nil || c.recvAEADEpoch != c.recvEpoch {
a, err := c.newAEADFor(c.keys.baseRecv, c.recvEpoch)
if c.recvAEAD == nil || c.recvAEADEpoch != epoch {
a, err := c.newAEADFor(c.keys.baseRecv, epoch)
if err != nil {
return 0, err
}
c.recvAEAD = a
c.recvAEADEpoch = c.recvEpoch
c.recvAEADEpoch = epoch
}
aead := c.recvAEAD
@@ -356,7 +434,7 @@ func (c *RecordConn) Read(p []byte) (int, error) {
if err != nil {
return 0, fmt.Errorf("decryption failed: epoch=%d seq=%d: %w", epoch, seq, err)
}
c.recvSeq++
c.markRecvPosition(epoch, seq)
c.readBuf.Write(plaintext)
return c.readBuf.Read(p)
@@ -0,0 +1,86 @@
package crypto
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"io"
"net"
"testing"
"time"
)
type captureConn struct {
bytes.Buffer
}
func (c *captureConn) Read(_ []byte) (int, error) { return 0, io.EOF }
func (c *captureConn) Write(p []byte) (int, error) { return c.Buffer.Write(p) }
func (c *captureConn) Close() error { return nil }
func (c *captureConn) LocalAddr() net.Addr { return nil }
func (c *captureConn) RemoteAddr() net.Addr { return nil }
func (c *captureConn) SetDeadline(time.Time) error { return nil }
func (c *captureConn) SetReadDeadline(time.Time) error { return nil }
func (c *captureConn) SetWriteDeadline(time.Time) error { return nil }
type replayConn struct {
reader *bytes.Reader
}
func (c *replayConn) Read(p []byte) (int, error) { return c.reader.Read(p) }
func (c *replayConn) Write(p []byte) (int, error) { return len(p), nil }
func (c *replayConn) Close() error { return nil }
func (c *replayConn) LocalAddr() net.Addr { return nil }
func (c *replayConn) RemoteAddr() net.Addr { return nil }
func (c *replayConn) SetDeadline(time.Time) error { return nil }
func (c *replayConn) SetReadDeadline(time.Time) error { return nil }
func (c *replayConn) SetWriteDeadline(time.Time) error { return nil }
func TestRecordConn_FirstFrameUsesRandomizedCounters(t *testing.T) {
pskSend := sha256.Sum256([]byte("record-send"))
pskRecv := sha256.Sum256([]byte("record-recv"))
raw := &captureConn{}
writer, err := NewRecordConn(raw, "chacha20-poly1305", pskSend[:], pskRecv[:])
if err != nil {
t.Fatalf("new writer: %v", err)
}
if writer.sendEpoch == 0 || writer.sendSeq == 0 {
t.Fatalf("expected non-zero randomized counters, got epoch=%d seq=%d", writer.sendEpoch, writer.sendSeq)
}
want := []byte("record prefix camouflage")
if _, err := writer.Write(want); err != nil {
t.Fatalf("write: %v", err)
}
wire := raw.Bytes()
if len(wire) < 2+recordHeaderSize {
t.Fatalf("short frame: %d", len(wire))
}
bodyLen := int(binary.BigEndian.Uint16(wire[:2]))
if bodyLen != len(wire)-2 {
t.Fatalf("body len mismatch: got %d want %d", bodyLen, len(wire)-2)
}
epoch := binary.BigEndian.Uint32(wire[2:6])
seq := binary.BigEndian.Uint64(wire[6:14])
if epoch == 0 || seq == 0 {
t.Fatalf("wire header still starts from zero: epoch=%d seq=%d", epoch, seq)
}
reader, err := NewRecordConn(&replayConn{reader: bytes.NewReader(wire)}, "chacha20-poly1305", pskRecv[:], pskSend[:])
if err != nil {
t.Fatalf("new reader: %v", err)
}
got := make([]byte, len(want))
if _, err := io.ReadFull(reader, got); err != nil {
t.Fatalf("read: %v", err)
}
if !bytes.Equal(got, want) {
t.Fatalf("plaintext mismatch: got %q want %q", got, want)
}
}
@@ -0,0 +1,345 @@
package sudoku
import (
"bytes"
"crypto/ecdh"
"crypto/rand"
"encoding/hex"
"fmt"
"net"
"time"
"github.com/metacubex/mihomo/transport/sudoku/crypto"
httpmaskobfs "github.com/metacubex/mihomo/transport/sudoku/obfs/httpmask"
sudokuobfs "github.com/metacubex/mihomo/transport/sudoku/obfs/sudoku"
)
const earlyKIPHandshakeTTL = 60 * time.Second
type EarlyCodecConfig struct {
PSK string
AEAD string
EnablePureDownlink bool
PaddingMin int
PaddingMax int
}
type EarlyClientState struct {
RequestPayload []byte
cfg EarlyCodecConfig
table *sudokuobfs.Table
nonce [kipHelloNonceSize]byte
ephemeral *ecdh.PrivateKey
sessionC2S []byte
sessionS2C []byte
responseSet bool
}
type EarlyServerState struct {
ResponsePayload []byte
UserHash string
cfg EarlyCodecConfig
table *sudokuobfs.Table
sessionC2S []byte
sessionS2C []byte
}
type ReplayAllowFunc func(userHash string, nonce [kipHelloNonceSize]byte, now time.Time) bool
type earlyMemoryConn struct {
reader *bytes.Reader
write bytes.Buffer
}
func newEarlyMemoryConn(readBuf []byte) *earlyMemoryConn {
return &earlyMemoryConn{reader: bytes.NewReader(readBuf)}
}
func (c *earlyMemoryConn) Read(p []byte) (int, error) {
if c == nil || c.reader == nil {
return 0, net.ErrClosed
}
return c.reader.Read(p)
}
func (c *earlyMemoryConn) Write(p []byte) (int, error) {
if c == nil {
return 0, net.ErrClosed
}
return c.write.Write(p)
}
func (c *earlyMemoryConn) Close() error { return nil }
func (c *earlyMemoryConn) LocalAddr() net.Addr { return earlyDummyAddr("local") }
func (c *earlyMemoryConn) RemoteAddr() net.Addr { return earlyDummyAddr("remote") }
func (c *earlyMemoryConn) SetDeadline(time.Time) error { return nil }
func (c *earlyMemoryConn) SetReadDeadline(time.Time) error { return nil }
func (c *earlyMemoryConn) SetWriteDeadline(time.Time) error { return nil }
func (c *earlyMemoryConn) Written() []byte { return append([]byte(nil), c.write.Bytes()...) }
type earlyDummyAddr string
func (a earlyDummyAddr) Network() string { return string(a) }
func (a earlyDummyAddr) String() string { return string(a) }
func buildEarlyClientObfsConn(raw net.Conn, cfg EarlyCodecConfig, table *sudokuobfs.Table) net.Conn {
base := sudokuobfs.NewConn(raw, table, cfg.PaddingMin, cfg.PaddingMax, false)
if cfg.EnablePureDownlink {
return base
}
packed := sudokuobfs.NewPackedConn(raw, table, cfg.PaddingMin, cfg.PaddingMax)
return newDirectionalConn(raw, packed, base)
}
func buildEarlyServerObfsConn(raw net.Conn, cfg EarlyCodecConfig, table *sudokuobfs.Table) net.Conn {
uplink := sudokuobfs.NewConn(raw, table, cfg.PaddingMin, cfg.PaddingMax, false)
if cfg.EnablePureDownlink {
return uplink
}
packed := sudokuobfs.NewPackedConn(raw, table, cfg.PaddingMin, cfg.PaddingMax)
return newDirectionalConn(raw, uplink, packed, packed.Flush)
}
func NewEarlyClientState(cfg EarlyCodecConfig, table *sudokuobfs.Table, userHash [kipHelloUserHashSize]byte, feats uint32) (*EarlyClientState, error) {
if table == nil {
return nil, fmt.Errorf("nil table")
}
curve := ecdh.X25519()
ephemeral, err := curve.GenerateKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("ecdh generate failed: %w", err)
}
var nonce [kipHelloNonceSize]byte
if _, err := rand.Read(nonce[:]); err != nil {
return nil, fmt.Errorf("nonce generate failed: %w", err)
}
var clientPub [kipHelloPubSize]byte
copy(clientPub[:], ephemeral.PublicKey().Bytes())
hello := &KIPClientHello{
Timestamp: time.Now(),
UserHash: userHash,
Nonce: nonce,
ClientPub: clientPub,
Features: feats,
}
mem := newEarlyMemoryConn(nil)
obfsConn := buildEarlyClientObfsConn(mem, cfg, table)
pskC2S, pskS2C := derivePSKDirectionalBases(cfg.PSK)
rc, err := crypto.NewRecordConn(obfsConn, cfg.AEAD, pskC2S, pskS2C)
if err != nil {
return nil, fmt.Errorf("client early crypto setup failed: %w", err)
}
if err := WriteKIPMessage(rc, KIPTypeClientHello, hello.EncodePayload()); err != nil {
return nil, fmt.Errorf("write early client hello failed: %w", err)
}
return &EarlyClientState{
RequestPayload: mem.Written(),
cfg: cfg,
table: table,
nonce: nonce,
ephemeral: ephemeral,
}, nil
}
func (s *EarlyClientState) ProcessResponse(payload []byte) error {
if s == nil {
return fmt.Errorf("nil client state")
}
mem := newEarlyMemoryConn(payload)
obfsConn := buildEarlyClientObfsConn(mem, s.cfg, s.table)
pskC2S, pskS2C := derivePSKDirectionalBases(s.cfg.PSK)
rc, err := crypto.NewRecordConn(obfsConn, s.cfg.AEAD, pskC2S, pskS2C)
if err != nil {
return fmt.Errorf("client early crypto setup failed: %w", err)
}
msg, err := ReadKIPMessage(rc)
if err != nil {
return fmt.Errorf("read early server hello failed: %w", err)
}
if msg.Type != KIPTypeServerHello {
return fmt.Errorf("unexpected early handshake message: %d", msg.Type)
}
sh, err := DecodeKIPServerHelloPayload(msg.Payload)
if err != nil {
return fmt.Errorf("decode early server hello failed: %w", err)
}
if sh.Nonce != s.nonce {
return fmt.Errorf("early handshake nonce mismatch")
}
shared, err := x25519SharedSecret(s.ephemeral, sh.ServerPub[:])
if err != nil {
return fmt.Errorf("ecdh failed: %w", err)
}
s.sessionC2S, s.sessionS2C, err = deriveSessionDirectionalBases(s.cfg.PSK, shared, s.nonce)
if err != nil {
return fmt.Errorf("derive session keys failed: %w", err)
}
s.responseSet = true
return nil
}
func (s *EarlyClientState) WrapConn(raw net.Conn) (net.Conn, error) {
if s == nil {
return nil, fmt.Errorf("nil client state")
}
if !s.responseSet {
return nil, fmt.Errorf("early handshake not completed")
}
obfsConn := buildEarlyClientObfsConn(raw, s.cfg, s.table)
rc, err := crypto.NewRecordConn(obfsConn, s.cfg.AEAD, s.sessionC2S, s.sessionS2C)
if err != nil {
return nil, fmt.Errorf("setup client session crypto failed: %w", err)
}
return rc, nil
}
func (s *EarlyClientState) Ready() bool {
return s != nil && s.responseSet
}
func NewHTTPMaskClientEarlyHandshake(cfg EarlyCodecConfig, table *sudokuobfs.Table, userHash [kipHelloUserHashSize]byte, feats uint32) (*httpmaskobfs.ClientEarlyHandshake, error) {
state, err := NewEarlyClientState(cfg, table, userHash, feats)
if err != nil {
return nil, err
}
return &httpmaskobfs.ClientEarlyHandshake{
RequestPayload: state.RequestPayload,
HandleResponse: state.ProcessResponse,
Ready: state.Ready,
WrapConn: state.WrapConn,
}, nil
}
func ProcessEarlyClientPayload(cfg EarlyCodecConfig, tables []*sudokuobfs.Table, payload []byte, allowReplay ReplayAllowFunc) (*EarlyServerState, error) {
if len(payload) == 0 {
return nil, fmt.Errorf("empty early payload")
}
if len(tables) == 0 {
return nil, fmt.Errorf("no tables configured")
}
var firstErr error
for _, table := range tables {
state, err := processEarlyClientPayloadForTable(cfg, table, payload, allowReplay)
if err == nil {
return state, nil
}
if firstErr == nil {
firstErr = err
}
}
if firstErr == nil {
firstErr = fmt.Errorf("early handshake probe failed")
}
return nil, firstErr
}
func processEarlyClientPayloadForTable(cfg EarlyCodecConfig, table *sudokuobfs.Table, payload []byte, allowReplay ReplayAllowFunc) (*EarlyServerState, error) {
mem := newEarlyMemoryConn(payload)
obfsConn := buildEarlyServerObfsConn(mem, cfg, table)
pskC2S, pskS2C := derivePSKDirectionalBases(cfg.PSK)
rc, err := crypto.NewRecordConn(obfsConn, cfg.AEAD, pskS2C, pskC2S)
if err != nil {
return nil, err
}
msg, err := ReadKIPMessage(rc)
if err != nil {
return nil, err
}
if msg.Type != KIPTypeClientHello {
return nil, fmt.Errorf("unexpected handshake message: %d", msg.Type)
}
ch, err := DecodeKIPClientHelloPayload(msg.Payload)
if err != nil {
return nil, err
}
if absInt64(time.Now().Unix()-ch.Timestamp.Unix()) > int64(earlyKIPHandshakeTTL.Seconds()) {
return nil, fmt.Errorf("time skew/replay")
}
userHash := hex.EncodeToString(ch.UserHash[:])
if allowReplay != nil && !allowReplay(userHash, ch.Nonce, time.Now()) {
return nil, fmt.Errorf("replay detected")
}
curve := ecdh.X25519()
serverEphemeral, err := curve.GenerateKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("ecdh generate failed: %w", err)
}
shared, err := x25519SharedSecret(serverEphemeral, ch.ClientPub[:])
if err != nil {
return nil, fmt.Errorf("ecdh failed: %w", err)
}
sessionC2S, sessionS2C, err := deriveSessionDirectionalBases(cfg.PSK, shared, ch.Nonce)
if err != nil {
return nil, fmt.Errorf("derive session keys failed: %w", err)
}
var serverPub [kipHelloPubSize]byte
copy(serverPub[:], serverEphemeral.PublicKey().Bytes())
serverHello := &KIPServerHello{
Nonce: ch.Nonce,
ServerPub: serverPub,
SelectedFeats: ch.Features & KIPFeatAll,
}
respMem := newEarlyMemoryConn(nil)
respObfs := buildEarlyServerObfsConn(respMem, cfg, table)
respConn, err := crypto.NewRecordConn(respObfs, cfg.AEAD, pskS2C, pskC2S)
if err != nil {
return nil, fmt.Errorf("server early crypto setup failed: %w", err)
}
if err := WriteKIPMessage(respConn, KIPTypeServerHello, serverHello.EncodePayload()); err != nil {
return nil, fmt.Errorf("write early server hello failed: %w", err)
}
return &EarlyServerState{
ResponsePayload: respMem.Written(),
UserHash: userHash,
cfg: cfg,
table: table,
sessionC2S: sessionC2S,
sessionS2C: sessionS2C,
}, nil
}
func (s *EarlyServerState) WrapConn(raw net.Conn) (net.Conn, error) {
if s == nil {
return nil, fmt.Errorf("nil server state")
}
obfsConn := buildEarlyServerObfsConn(raw, s.cfg, s.table)
rc, err := crypto.NewRecordConn(obfsConn, s.cfg.AEAD, s.sessionS2C, s.sessionC2S)
if err != nil {
return nil, fmt.Errorf("setup server session crypto failed: %w", err)
}
return rc, nil
}
func NewHTTPMaskServerEarlyHandshake(cfg EarlyCodecConfig, tables []*sudokuobfs.Table, allowReplay ReplayAllowFunc) *httpmaskobfs.TunnelServerEarlyHandshake {
return &httpmaskobfs.TunnelServerEarlyHandshake{
Prepare: func(payload []byte) (*httpmaskobfs.PreparedServerEarlyHandshake, error) {
state, err := ProcessEarlyClientPayload(cfg, tables, payload, allowReplay)
if err != nil {
return nil, err
}
return &httpmaskobfs.PreparedServerEarlyHandshake{
ResponsePayload: state.ResponsePayload,
WrapConn: state.WrapConn,
UserHash: state.UserHash,
}, nil
},
}
}
@@ -337,6 +337,9 @@ func ServerHandshake(rawConn net.Conn, cfg *ProtocolConfig) (net.Conn, *Handshak
if err := cfg.Validate(); err != nil {
return nil, nil, fmt.Errorf("invalid config: %w", err)
}
if userHash, ok := httpmask.EarlyHandshakeUserHash(rawConn); ok {
return rawConn, &HandshakeMeta{UserHash: userHash}, nil
}
handshakeTimeout := time.Duration(cfg.HandshakeTimeoutSeconds) * time.Second
if handshakeTimeout <= 0 {
@@ -14,6 +14,30 @@ type HTTPMaskTunnelServer struct {
ts *httpmask.TunnelServer
}
func newHTTPMaskEarlyCodecConfig(cfg *ProtocolConfig, psk string) EarlyCodecConfig {
return EarlyCodecConfig{
PSK: psk,
AEAD: cfg.AEADMethod,
EnablePureDownlink: cfg.EnablePureDownlink,
PaddingMin: cfg.PaddingMin,
PaddingMax: cfg.PaddingMax,
}
}
func newClientHTTPMaskEarlyHandshake(cfg *ProtocolConfig) (*httpmask.ClientEarlyHandshake, error) {
table, err := pickClientTable(cfg)
if err != nil {
return nil, err
}
return NewHTTPMaskClientEarlyHandshake(
newHTTPMaskEarlyCodecConfig(cfg, ClientAEADSeed(cfg.Key)),
table,
kipUserHashFromKey(cfg.Key),
KIPFeatAll,
)
}
func NewHTTPMaskTunnelServer(cfg *ProtocolConfig) *HTTPMaskTunnelServer {
return newHTTPMaskTunnelServer(cfg, false)
}
@@ -35,6 +59,11 @@ func newHTTPMaskTunnelServer(cfg *ProtocolConfig, passThroughOnReject bool) *HTT
Mode: cfg.HTTPMaskMode,
PathRoot: cfg.HTTPMaskPathRoot,
AuthKey: ServerAEADSeed(cfg.Key),
EarlyHandshake: NewHTTPMaskServerEarlyHandshake(
newHTTPMaskEarlyCodecConfig(cfg, ServerAEADSeed(cfg.Key)),
cfg.tableCandidates(),
globalHandshakeReplay.allow,
),
// When upstream fallback is enabled, preserve rejected HTTP requests for the caller.
PassThroughOnReject: passThroughOnReject,
})
@@ -101,14 +130,25 @@ func DialHTTPMaskTunnel(ctx context.Context, serverAddress string, cfg *Protocol
default:
return nil, fmt.Errorf("http-mask-mode=%q does not use http tunnel", cfg.HTTPMaskMode)
}
var (
earlyHandshake *httpmask.ClientEarlyHandshake
err error
)
if upgrade != nil {
earlyHandshake, err = newClientHTTPMaskEarlyHandshake(cfg)
if err != nil {
return nil, err
}
}
return httpmask.DialTunnel(ctx, serverAddress, httpmask.TunnelDialOptions{
Mode: cfg.HTTPMaskMode,
TLSEnabled: cfg.HTTPMaskTLSEnabled,
HostOverride: cfg.HTTPMaskHost,
PathRoot: cfg.HTTPMaskPathRoot,
AuthKey: ClientAEADSeed(cfg.Key),
Upgrade: upgrade,
Multiplex: cfg.HTTPMaskMultiplex,
DialContext: dial,
Mode: cfg.HTTPMaskMode,
TLSEnabled: cfg.HTTPMaskTLSEnabled,
HostOverride: cfg.HTTPMaskHost,
PathRoot: cfg.HTTPMaskPathRoot,
AuthKey: ClientAEADSeed(cfg.Key),
EarlyHandshake: earlyHandshake,
Upgrade: upgrade,
Multiplex: cfg.HTTPMaskMultiplex,
DialContext: dial,
})
}
@@ -389,6 +389,68 @@ func TestHTTPMaskTunnel_WS_TCPRoundTrip(t *testing.T) {
}
}
func TestHTTPMaskTunnel_EarlyHandshake_TCPRoundTrip(t *testing.T) {
modes := []string{"stream", "poll", "ws"}
for _, mode := range modes {
t.Run(mode, func(t *testing.T) {
key := "tunnel-early-" + mode
target := "1.1.1.1:80"
serverCfg := newTunnelTestTable(t, key)
serverCfg.HTTPMaskMode = mode
addr, stop, errCh := startTunnelServer(t, serverCfg, func(s *ServerSession) error {
if s.Type != SessionTypeTCP {
return fmt.Errorf("unexpected session type: %v", s.Type)
}
if s.Target != target {
return fmt.Errorf("target mismatch: %s", s.Target)
}
_, _ = s.Conn.Write([]byte("ok"))
return nil
})
defer stop()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
clientCfg := *serverCfg
clientCfg.ServerAddress = addr
handshakeCfg := clientCfg
handshakeCfg.DisableHTTPMask = true
tunnelConn, err := DialHTTPMaskTunnel(ctx, clientCfg.ServerAddress, &clientCfg, (&net.Dialer{}).DialContext, func(raw net.Conn) (net.Conn, error) {
return ClientHandshake(raw, &handshakeCfg)
})
if err != nil {
t.Fatalf("dial tunnel: %v", err)
}
defer tunnelConn.Close()
addrBuf, err := EncodeAddress(target)
if err != nil {
t.Fatalf("encode addr: %v", err)
}
if err := WriteKIPMessage(tunnelConn, KIPTypeOpenTCP, addrBuf); err != nil {
t.Fatalf("write addr: %v", err)
}
buf := make([]byte, 2)
if _, err := io.ReadFull(tunnelConn, buf); err != nil {
t.Fatalf("read: %v", err)
}
if string(buf) != "ok" {
t.Fatalf("unexpected payload: %q", buf)
}
stop()
for err := range errCh {
t.Fatalf("server error: %v", err)
}
})
}
}
func TestHTTPMaskTunnel_Validation(t *testing.T) {
cfg := DefaultConfig()
cfg.Key = "k"
@@ -0,0 +1,174 @@
package httpmask
import (
"encoding/base64"
"errors"
"fmt"
"net"
"net/url"
"strings"
)
const (
tunnelEarlyDataQueryKey = "ed"
tunnelEarlyDataHeader = "X-Sudoku-Early"
)
type ClientEarlyHandshake struct {
RequestPayload []byte
HandleResponse func(payload []byte) error
Ready func() bool
WrapConn func(raw net.Conn) (net.Conn, error)
}
type TunnelServerEarlyHandshake struct {
Prepare func(payload []byte) (*PreparedServerEarlyHandshake, error)
}
type PreparedServerEarlyHandshake struct {
ResponsePayload []byte
WrapConn func(raw net.Conn) (net.Conn, error)
UserHash string
}
type earlyHandshakeMeta interface {
HTTPMaskEarlyHandshakeUserHash() string
}
type earlyHandshakeConn struct {
net.Conn
userHash string
}
func (c *earlyHandshakeConn) HTTPMaskEarlyHandshakeUserHash() string {
if c == nil {
return ""
}
return c.userHash
}
func wrapEarlyHandshakeConn(conn net.Conn, userHash string) net.Conn {
if conn == nil {
return nil
}
return &earlyHandshakeConn{Conn: conn, userHash: userHash}
}
func EarlyHandshakeUserHash(conn net.Conn) (string, bool) {
if conn == nil {
return "", false
}
v, ok := conn.(earlyHandshakeMeta)
if !ok {
return "", false
}
return v.HTTPMaskEarlyHandshakeUserHash(), true
}
type authorizeResponse struct {
token string
earlyPayload []byte
}
func isTunnelTokenByte(c byte) bool {
return (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '-' ||
c == '_'
}
func parseAuthorizeResponse(body []byte) (*authorizeResponse, error) {
s := strings.TrimSpace(string(body))
idx := strings.Index(s, "token=")
if idx < 0 {
return nil, errors.New("missing token")
}
s = s[idx+len("token="):]
if s == "" {
return nil, errors.New("empty token")
}
var b strings.Builder
for i := 0; i < len(s); i++ {
c := s[i]
if isTunnelTokenByte(c) {
b.WriteByte(c)
continue
}
break
}
token := b.String()
if token == "" {
return nil, errors.New("empty token")
}
out := &authorizeResponse{token: token}
if earlyLine := findAuthorizeField(body, "ed="); earlyLine != "" {
decoded, err := base64.RawURLEncoding.DecodeString(earlyLine)
if err != nil {
return nil, fmt.Errorf("decode early authorize payload failed: %w", err)
}
out.earlyPayload = decoded
}
return out, nil
}
func findAuthorizeField(body []byte, prefix string) string {
for _, line := range strings.Split(strings.TrimSpace(string(body)), "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, prefix) {
return strings.TrimSpace(strings.TrimPrefix(line, prefix))
}
}
return ""
}
func setEarlyDataQuery(rawURL string, payload []byte) (string, error) {
if len(payload) == 0 {
return rawURL, nil
}
u, err := url.Parse(rawURL)
if err != nil {
return "", err
}
q := u.Query()
q.Set(tunnelEarlyDataQueryKey, base64.RawURLEncoding.EncodeToString(payload))
u.RawQuery = q.Encode()
return u.String(), nil
}
func parseEarlyDataQuery(u *url.URL) ([]byte, error) {
if u == nil {
return nil, nil
}
val := strings.TrimSpace(u.Query().Get(tunnelEarlyDataQueryKey))
if val == "" {
return nil, nil
}
return base64.RawURLEncoding.DecodeString(val)
}
func applyEarlyHandshakeOrUpgrade(raw net.Conn, opts TunnelDialOptions) (net.Conn, error) {
out := raw
if opts.EarlyHandshake != nil && opts.EarlyHandshake.WrapConn != nil && (opts.EarlyHandshake.Ready == nil || opts.EarlyHandshake.Ready()) {
wrapped, err := opts.EarlyHandshake.WrapConn(raw)
if err != nil {
return nil, err
}
if wrapped != nil {
out = wrapped
}
return out, nil
}
if opts.Upgrade != nil {
wrapped, err := opts.Upgrade(raw)
if err != nil {
return nil, err
}
if wrapped != nil {
out = wrapped
}
}
return out, nil
}
@@ -72,6 +72,10 @@ type TunnelDialOptions struct {
// AuthKey enables short-term HMAC auth for HTTP tunnel requests (anti-probing).
// When set (non-empty), each HTTP request carries an Authorization bearer token derived from AuthKey.
AuthKey string
// EarlyHandshake folds the protocol handshake into the HTTP/WS setup round trip.
// When the server accepts the early payload, DialTunnel returns a conn that is already post-handshake.
// When the server does not echo early data, DialTunnel falls back to Upgrade.
EarlyHandshake *ClientEarlyHandshake
// Upgrade optionally wraps the raw tunnel conn and/or writes a small prelude before DialTunnel returns.
// It is called with the raw tunnel conn; if it returns a non-nil conn, that conn is returned by DialTunnel.
Upgrade func(raw net.Conn) (net.Conn, error)
@@ -225,30 +229,11 @@ func canonicalHeaderHost(urlHost, scheme string) string {
}
func parseTunnelToken(body []byte) (string, error) {
s := strings.TrimSpace(string(body))
idx := strings.Index(s, "token=")
if idx < 0 {
return "", errors.New("missing token")
resp, err := parseAuthorizeResponse(body)
if err != nil {
return "", err
}
s = s[idx+len("token="):]
if s == "" {
return "", errors.New("empty token")
}
// Token is base64.RawURLEncoding (A-Z a-z 0-9 - _). Strip any trailing bytes (e.g. from CDN compression).
var b strings.Builder
for i := 0; i < len(s); i++ {
c := s[i]
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' {
b.WriteByte(c)
continue
}
break
}
token := b.String()
if token == "" {
return "", errors.New("empty token")
}
return token, nil
return resp.token, nil
}
type httpClientTarget struct {
@@ -353,6 +338,13 @@ func dialSessionWithClient(ctx context.Context, client *http.Client, target http
auth := newTunnelAuth(opts.AuthKey, 0)
authorizeURL := (&url.URL{Scheme: target.scheme, Host: target.urlHost, Path: joinPathRoot(opts.PathRoot, "/session")}).String()
if opts.EarlyHandshake != nil && len(opts.EarlyHandshake.RequestPayload) > 0 {
var err error
authorizeURL, err = setEarlyDataQuery(authorizeURL, opts.EarlyHandshake.RequestPayload)
if err != nil {
return nil, err
}
}
var bodyBytes []byte
for attempt := 0; ; attempt++ {
@@ -410,13 +402,19 @@ func dialSessionWithClient(ctx context.Context, client *http.Client, target http
break
}
token, err := parseTunnelToken(bodyBytes)
authResp, err := parseAuthorizeResponse(bodyBytes)
if err != nil {
return nil, fmt.Errorf("%s authorize failed: %q", mode, strings.TrimSpace(string(bodyBytes)))
}
token := authResp.token
if token == "" {
return nil, fmt.Errorf("%s authorize empty token", mode)
}
if opts.EarlyHandshake != nil && len(authResp.earlyPayload) > 0 && opts.EarlyHandshake.HandleResponse != nil {
if err := opts.EarlyHandshake.HandleResponse(authResp.earlyPayload); err != nil {
return nil, err
}
}
pushURL := (&url.URL{Scheme: target.scheme, Host: target.urlHost, Path: joinPathRoot(opts.PathRoot, "/api/v1/upload"), RawQuery: "token=" + url.QueryEscape(token)}).String()
pullURL := (&url.URL{Scheme: target.scheme, Host: target.urlHost, Path: joinPathRoot(opts.PathRoot, "/stream"), RawQuery: "token=" + url.QueryEscape(token)}).String()
@@ -671,16 +669,10 @@ func dialStreamSplitWithClient(ctx context.Context, client *http.Client, target
if c == nil {
return nil, fmt.Errorf("failed to build stream split conn")
}
outConn := net.Conn(c)
if opts.Upgrade != nil {
upgraded, err := opts.Upgrade(c)
if err != nil {
_ = c.Close()
return nil, err
}
if upgraded != nil {
outConn = upgraded
}
outConn, err := applyEarlyHandshakeOrUpgrade(c, opts)
if err != nil {
_ = c.Close()
return nil, err
}
return outConn, nil
}
@@ -694,16 +686,10 @@ func dialStreamSplit(ctx context.Context, serverAddress string, opts TunnelDialO
if c == nil {
return nil, fmt.Errorf("failed to build stream split conn")
}
outConn := net.Conn(c)
if opts.Upgrade != nil {
upgraded, err := opts.Upgrade(c)
if err != nil {
_ = c.Close()
return nil, err
}
if upgraded != nil {
outConn = upgraded
}
outConn, err := applyEarlyHandshakeOrUpgrade(c, opts)
if err != nil {
_ = c.Close()
return nil, err
}
return outConn, nil
}
@@ -1120,16 +1106,10 @@ func dialPollWithClient(ctx context.Context, client *http.Client, target httpCli
if c == nil {
return nil, fmt.Errorf("failed to build poll conn")
}
outConn := net.Conn(c)
if opts.Upgrade != nil {
upgraded, err := opts.Upgrade(c)
if err != nil {
_ = c.Close()
return nil, err
}
if upgraded != nil {
outConn = upgraded
}
outConn, err := applyEarlyHandshakeOrUpgrade(c, opts)
if err != nil {
_ = c.Close()
return nil, err
}
return outConn, nil
}
@@ -1143,16 +1123,10 @@ func dialPoll(ctx context.Context, serverAddress string, opts TunnelDialOptions)
if c == nil {
return nil, fmt.Errorf("failed to build poll conn")
}
outConn := net.Conn(c)
if opts.Upgrade != nil {
upgraded, err := opts.Upgrade(c)
if err != nil {
_ = c.Close()
return nil, err
}
if upgraded != nil {
outConn = upgraded
}
outConn, err := applyEarlyHandshakeOrUpgrade(c, opts)
if err != nil {
_ = c.Close()
return nil, err
}
return outConn, nil
}
@@ -1528,6 +1502,8 @@ type TunnelServerOptions struct {
PullReadTimeout time.Duration
// SessionTTL is a best-effort TTL to prevent leaked sessions. 0 uses a conservative default.
SessionTTL time.Duration
// EarlyHandshake optionally folds the protocol handshake into the initial HTTP/WS round trip.
EarlyHandshake *TunnelServerEarlyHandshake
}
type TunnelServer struct {
@@ -1538,6 +1514,7 @@ type TunnelServer struct {
pullReadTimeout time.Duration
sessionTTL time.Duration
earlyHandshake *TunnelServerEarlyHandshake
mu sync.Mutex
sessions map[string]*tunnelSession
@@ -1570,6 +1547,7 @@ func NewTunnelServer(opts TunnelServerOptions) *TunnelServer {
passThroughOnReject: opts.PassThroughOnReject,
pullReadTimeout: timeout,
sessionTTL: ttl,
earlyHandshake: opts.EarlyHandshake,
sessions: make(map[string]*tunnelSession),
}
}
@@ -1925,9 +1903,12 @@ func (s *TunnelServer) handleStream(rawConn net.Conn, req *httpRequestHeader, he
switch strings.ToUpper(req.method) {
case http.MethodGet:
// Stream split-session: GET /session (no token) => token + start tunnel on a server-side pipe.
if token == "" && path == "/session" {
return s.sessionAuthorize(rawConn)
earlyPayload, err := parseEarlyDataQuery(u)
if err != nil {
return rejectOrReply(http.StatusBadRequest, "bad request")
}
return s.sessionAuthorize(rawConn, earlyPayload)
}
// Stream split-session: GET /stream?token=... => downlink poll.
if token != "" && path == "/stream" {
@@ -2045,10 +2026,18 @@ func writeSimpleHTTPResponse(w io.Writer, code int, body string) error {
func writeTokenHTTPResponse(w io.Writer, token string) error {
token = strings.TrimRight(token, "\r\n")
// Use application/octet-stream to avoid CDN auto-compression (e.g. brotli) breaking clients that expect a plain token string.
return writeTokenHTTPResponseWithEarlyData(w, token, nil)
}
func writeTokenHTTPResponseWithEarlyData(w io.Writer, token string, earlyPayload []byte) error {
token = strings.TrimRight(token, "\r\n")
body := "token=" + token
if len(earlyPayload) > 0 {
body += "\ned=" + base64.RawURLEncoding.EncodeToString(earlyPayload)
}
_, err := io.WriteString(w,
fmt.Sprintf("HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nCache-Control: no-store\r\nPragma: no-cache\r\nContent-Length: %d\r\nConnection: close\r\n\r\ntoken=%s",
len("token=")+len(token), token))
fmt.Sprintf("HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nCache-Control: no-store\r\nPragma: no-cache\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s",
len(body), body))
return err
}
@@ -2088,7 +2077,11 @@ func (s *TunnelServer) handlePoll(rawConn net.Conn, req *httpRequestHeader, head
switch strings.ToUpper(req.method) {
case http.MethodGet:
if token == "" && path == "/session" {
return s.sessionAuthorize(rawConn)
earlyPayload, err := parseEarlyDataQuery(u)
if err != nil {
return rejectOrReply(http.StatusBadRequest, "bad request")
}
return s.sessionAuthorize(rawConn, earlyPayload)
}
if token != "" && path == "/stream" {
if s.passThroughOnReject && !s.sessionHas(token) {
@@ -2128,7 +2121,7 @@ func (s *TunnelServer) handlePoll(rawConn net.Conn, req *httpRequestHeader, head
}
}
func (s *TunnelServer) sessionAuthorize(rawConn net.Conn) (HandleResult, net.Conn, error) {
func (s *TunnelServer) sessionAuthorize(rawConn net.Conn, earlyPayload []byte) (HandleResult, net.Conn, error) {
token, err := newSessionToken()
if err != nil {
_ = writeSimpleHTTPResponse(rawConn, http.StatusInternalServerError, "internal error")
@@ -2137,6 +2130,37 @@ func (s *TunnelServer) sessionAuthorize(rawConn net.Conn) (HandleResult, net.Con
}
c1, c2 := newHalfPipe()
outConn := net.Conn(c1)
var responsePayload []byte
var userHash string
if len(earlyPayload) > 0 && s.earlyHandshake != nil && s.earlyHandshake.Prepare != nil {
prepared, err := s.earlyHandshake.Prepare(earlyPayload)
if err != nil {
_ = c1.Close()
_ = c2.Close()
if s.passThroughOnReject {
return HandlePassThrough, newRejectedPreBufferedConn(rawConn, nil), nil
}
_ = writeSimpleHTTPResponse(rawConn, http.StatusNotFound, "not found")
_ = rawConn.Close()
return HandleDone, nil, nil
}
responsePayload = prepared.ResponsePayload
userHash = prepared.UserHash
if prepared.WrapConn != nil {
wrapped, err := prepared.WrapConn(c1)
if err != nil {
_ = c1.Close()
_ = c2.Close()
_ = writeSimpleHTTPResponse(rawConn, http.StatusInternalServerError, "internal error")
_ = rawConn.Close()
return HandleDone, nil, nil
}
if wrapped != nil {
outConn = wrapEarlyHandshakeConn(wrapped, userHash)
}
}
}
s.mu.Lock()
s.sessions[token] = &tunnelSession{conn: c2, lastActive: time.Now()}
@@ -2144,9 +2168,9 @@ func (s *TunnelServer) sessionAuthorize(rawConn net.Conn) (HandleResult, net.Con
go s.reapLater(token)
_ = writeTokenHTTPResponse(rawConn, token)
_ = writeTokenHTTPResponseWithEarlyData(rawConn, token, responsePayload)
_ = rawConn.Close()
return HandleStartTunnel, c1, nil
return HandleStartTunnel, outConn, nil
}
func newSessionToken() (string, error) {
@@ -2,6 +2,7 @@ package httpmask
import (
"context"
"encoding/base64"
"fmt"
"io"
mrand "math/rand"
@@ -115,6 +116,16 @@ func dialWS(ctx context.Context, serverAddress string, opts TunnelDialOptions) (
Host: urlHost,
Path: joinPathRoot(opts.PathRoot, "/ws"),
}
if opts.EarlyHandshake != nil && len(opts.EarlyHandshake.RequestPayload) > 0 {
rawURL, err := setEarlyDataQuery(u.String(), opts.EarlyHandshake.RequestPayload)
if err != nil {
return nil, err
}
u, err = url.Parse(rawURL)
if err != nil {
return nil, err
}
}
header := make(stdhttp.Header)
applyWSHeaders(header, headerHost)
@@ -132,6 +143,16 @@ func dialWS(ctx context.Context, serverAddress string, opts TunnelDialOptions) (
d := ws.Dialer{
Host: headerHost,
Header: ws.HandshakeHeaderHTTP(header),
OnHeader: func(key, value []byte) error {
if !strings.EqualFold(string(key), tunnelEarlyDataHeader) || opts.EarlyHandshake == nil || opts.EarlyHandshake.HandleResponse == nil {
return nil
}
decoded, err := base64.RawURLEncoding.DecodeString(strings.TrimSpace(string(value)))
if err != nil {
return err
}
return opts.EarlyHandshake.HandleResponse(decoded)
},
NetDial: func(dialCtx context.Context, network, addr string) (net.Conn, error) {
if addr == urlHost {
addr = dialAddr
@@ -161,16 +182,10 @@ func dialWS(ctx context.Context, serverAddress string, opts TunnelDialOptions) (
}
wsConn := newWSStreamConn(conn, ws.StateClientSide)
if opts.Upgrade == nil {
return wsConn, nil
}
upgraded, err := opts.Upgrade(wsConn)
upgraded, err := applyEarlyHandshakeOrUpgrade(wsConn, opts)
if err != nil {
_ = wsConn.Close()
return nil, err
}
if upgraded != nil {
return upgraded, nil
}
return wsConn, nil
return upgraded, nil
}
@@ -1,6 +1,7 @@
package httpmask
import (
"encoding/base64"
"net"
"net/http"
"net/url"
@@ -63,15 +64,46 @@ func (s *TunnelServer) handleWS(rawConn net.Conn, req *httpRequestHeader, header
return rejectOrReply(http.StatusNotFound, "not found")
}
earlyPayload, err := parseEarlyDataQuery(u)
if err != nil {
return rejectOrReply(http.StatusBadRequest, "bad request")
}
var prepared *PreparedServerEarlyHandshake
if len(earlyPayload) > 0 && s.earlyHandshake != nil && s.earlyHandshake.Prepare != nil {
prepared, err = s.earlyHandshake.Prepare(earlyPayload)
if err != nil {
return rejectOrReply(http.StatusNotFound, "not found")
}
}
prefix := make([]byte, 0, len(headerBytes)+len(buffered))
prefix = append(prefix, headerBytes...)
prefix = append(prefix, buffered...)
wsConnRaw := newPreBufferedConn(rawConn, prefix)
if _, err := ws.Upgrade(wsConnRaw); err != nil {
upgrader := ws.Upgrader{}
if prepared != nil && len(prepared.ResponsePayload) > 0 {
upgrader.OnBeforeUpgrade = func() (ws.HandshakeHeader, error) {
h := http.Header{}
h.Set(tunnelEarlyDataHeader, base64.RawURLEncoding.EncodeToString(prepared.ResponsePayload))
return ws.HandshakeHeaderHTTP(h), nil
}
}
if _, err := upgrader.Upgrade(wsConnRaw); err != nil {
_ = rawConn.Close()
return HandleDone, nil, nil
}
return HandleStartTunnel, newWSStreamConn(wsConnRaw, ws.StateServerSide), nil
outConn := net.Conn(newWSStreamConn(wsConnRaw, ws.StateServerSide))
if prepared != nil && prepared.WrapConn != nil {
wrapped, err := prepared.WrapConn(outConn)
if err != nil {
_ = outConn.Close()
return HandleDone, nil, nil
}
if wrapped != nil {
outConn = wrapEarlyHandshakeConn(wrapped, prepared.UserHash)
}
}
return HandleStartTunnel, outConn, nil
}
@@ -11,35 +11,35 @@ import (
)
const (
// 每次从 RNG 获取批量随机数的缓存大小,减少 RNG 函数调用开销
RngBatchSize = 128
packedProtectedPrefixBytes = 14
)
// 1. 使用 12字节->16组 的块处理优化 Write (减少循环开销)
// 2. 使用整数阈值随机概率判断 Padding,与纯 Sudoku 保持流量特征一致
// 3. Read 使用 copy 移动避免底层数组泄漏
// PackedConn encodes traffic with the packed Sudoku layout while preserving
// the same padding model as the regular connection.
type PackedConn struct {
net.Conn
table *Table
reader *bufio.Reader
// 读缓冲
// Read-side buffers.
rawBuf []byte
pendingData []byte // 解码后尚未被 Read 取走的字节
pendingData []byte
// 写缓冲与状态
// Write-side state.
writeMu sync.Mutex
writeBuf []byte
bitBuf uint64 // 暂存的位数据
bitCount int // 暂存的位数
bitBuf uint64
bitCount int
// 读状态
// Read-side bit accumulator.
readBitBuf uint64
readBits int
// 随机数与填充控制 - 使用整数阈值随机,与 Conn 一致
// Padding selection matches Conn's threshold-based model.
rng *rand.Rand
paddingThreshold uint64 // 与 Conn 保持一致的随机概率模型
paddingThreshold uint64
padMarker byte
padPool []byte
}
@@ -95,7 +95,6 @@ func NewPackedConn(c net.Conn, table *Table, pMin, pMax int) *PackedConn {
return pc
}
// maybeAddPadding 内联辅助:根据概率阈值插入 padding
func (pc *PackedConn) maybeAddPadding(out []byte) []byte {
if shouldPad(pc.rng, pc.paddingThreshold) {
out = append(out, pc.getPaddingByte())
@@ -103,7 +102,73 @@ func (pc *PackedConn) maybeAddPadding(out []byte) []byte {
return out
}
// Write 极致优化版 - 批量处理 12 字节
func (pc *PackedConn) appendGroup(out []byte, group byte) []byte {
out = pc.maybeAddPadding(out)
return append(out, pc.encodeGroup(group))
}
func (pc *PackedConn) appendForcedPadding(out []byte) []byte {
return append(out, pc.getPaddingByte())
}
func (pc *PackedConn) nextProtectedPrefixGap() int {
return 1 + pc.rng.Intn(2)
}
func (pc *PackedConn) writeProtectedPrefix(out []byte, p []byte) ([]byte, int) {
if len(p) == 0 {
return out, 0
}
limit := len(p)
if limit > packedProtectedPrefixBytes {
limit = packedProtectedPrefixBytes
}
for padCount := 0; padCount < 1+pc.rng.Intn(2); padCount++ {
out = pc.appendForcedPadding(out)
}
gap := pc.nextProtectedPrefixGap()
effective := 0
for i := 0; i < limit; i++ {
pc.bitBuf = (pc.bitBuf << 8) | uint64(p[i])
pc.bitCount += 8
for pc.bitCount >= 6 {
pc.bitCount -= 6
group := byte(pc.bitBuf >> pc.bitCount)
if pc.bitCount == 0 {
pc.bitBuf = 0
} else {
pc.bitBuf &= (1 << pc.bitCount) - 1
}
out = pc.appendGroup(out, group&0x3F)
}
effective++
if effective >= gap {
out = pc.appendForcedPadding(out)
effective = 0
gap = pc.nextProtectedPrefixGap()
}
}
return out, limit
}
func (pc *PackedConn) drainPendingData(dst []byte) int {
n := copy(dst, pc.pendingData)
if n == len(pc.pendingData) {
pc.pendingData = pc.pendingData[:0]
return n
}
remaining := len(pc.pendingData) - n
copy(pc.pendingData, pc.pendingData[n:])
pc.pendingData = pc.pendingData[:remaining]
return n
}
func (pc *PackedConn) Write(p []byte) (int, error) {
if len(p) == 0 {
return 0, nil
@@ -112,20 +177,19 @@ func (pc *PackedConn) Write(p []byte) (int, error) {
pc.writeMu.Lock()
defer pc.writeMu.Unlock()
// 1. 预分配内存,避免 append 导致的多次扩容
// 预估:原数据 * 1.5 (4/3 + padding 余量)
needed := len(p)*3/2 + 32
if cap(pc.writeBuf) < needed {
pc.writeBuf = make([]byte, 0, needed)
}
out := pc.writeBuf[:0]
i := 0
var prefixN int
out, prefixN = pc.writeProtectedPrefix(out, p)
i := prefixN
n := len(p)
// 2. 头部对齐处理 (Slow Path)
for pc.bitCount > 0 && i < n {
out = pc.maybeAddPadding(out)
b := p[i]
i++
pc.bitBuf = (pc.bitBuf << 8) | uint64(b)
@@ -138,14 +202,11 @@ func (pc *PackedConn) Write(p []byte) (int, error) {
} else {
pc.bitBuf &= (1 << pc.bitCount) - 1
}
out = pc.maybeAddPadding(out)
out = append(out, pc.encodeGroup(group&0x3F))
out = pc.appendGroup(out, group&0x3F)
}
}
// 3. 极速批量处理 (Fast Path) - 每次处理 12 字节 → 生成 16 个编码组
for i+11 < n {
// 处理 4 组,每组 3 字节
for batch := 0; batch < 4; batch++ {
b1, b2, b3 := p[i], p[i+1], p[i+2]
i += 3
@@ -155,19 +216,13 @@ func (pc *PackedConn) Write(p []byte) (int, error) {
g3 := ((b2 & 0x0F) << 2) | ((b3 >> 6) & 0x03)
g4 := b3 & 0x3F
// 每个组之前都有概率插入 padding
out = pc.maybeAddPadding(out)
out = append(out, pc.encodeGroup(g1))
out = pc.maybeAddPadding(out)
out = append(out, pc.encodeGroup(g2))
out = pc.maybeAddPadding(out)
out = append(out, pc.encodeGroup(g3))
out = pc.maybeAddPadding(out)
out = append(out, pc.encodeGroup(g4))
out = pc.appendGroup(out, g1)
out = pc.appendGroup(out, g2)
out = pc.appendGroup(out, g3)
out = pc.appendGroup(out, g4)
}
}
// 4. 处理剩余的 3 字节块
for i+2 < n {
b1, b2, b3 := p[i], p[i+1], p[i+2]
i += 3
@@ -177,17 +232,12 @@ func (pc *PackedConn) Write(p []byte) (int, error) {
g3 := ((b2 & 0x0F) << 2) | ((b3 >> 6) & 0x03)
g4 := b3 & 0x3F
out = pc.maybeAddPadding(out)
out = append(out, pc.encodeGroup(g1))
out = pc.maybeAddPadding(out)
out = append(out, pc.encodeGroup(g2))
out = pc.maybeAddPadding(out)
out = append(out, pc.encodeGroup(g3))
out = pc.maybeAddPadding(out)
out = append(out, pc.encodeGroup(g4))
out = pc.appendGroup(out, g1)
out = pc.appendGroup(out, g2)
out = pc.appendGroup(out, g3)
out = pc.appendGroup(out, g4)
}
// 5. 尾部处理 (Tail Path) - 处理剩余的 1 或 2 个字节
for ; i < n; i++ {
b := p[i]
pc.bitBuf = (pc.bitBuf << 8) | uint64(b)
@@ -200,35 +250,28 @@ func (pc *PackedConn) Write(p []byte) (int, error) {
} else {
pc.bitBuf &= (1 << pc.bitCount) - 1
}
out = pc.maybeAddPadding(out)
out = append(out, pc.encodeGroup(group&0x3F))
out = pc.appendGroup(out, group&0x3F)
}
}
// 6. 处理残留位
if pc.bitCount > 0 {
out = pc.maybeAddPadding(out)
group := byte(pc.bitBuf << (6 - pc.bitCount))
pc.bitBuf = 0
pc.bitCount = 0
out = append(out, pc.encodeGroup(group&0x3F))
out = pc.appendGroup(out, group&0x3F)
out = append(out, pc.padMarker)
}
// 尾部可能添加 padding
out = pc.maybeAddPadding(out)
// 发送数据
if len(out) > 0 {
_, err := pc.Conn.Write(out)
pc.writeBuf = out[:0]
return len(p), err
return len(p), writeFull(pc.Conn, out)
}
pc.writeBuf = out[:0]
return len(p), nil
}
// Flush 处理最后不足 6 bit 的情况
func (pc *PackedConn) Flush() error {
pc.writeMu.Lock()
defer pc.writeMu.Unlock()
@@ -243,38 +286,34 @@ func (pc *PackedConn) Flush() error {
out = append(out, pc.padMarker)
}
// 尾部随机添加 padding
out = pc.maybeAddPadding(out)
if len(out) > 0 {
_, err := pc.Conn.Write(out)
pc.writeBuf = out[:0]
return err
return writeFull(pc.Conn, out)
}
return nil
}
// Read 优化版:减少切片操作,避免内存泄漏
func (pc *PackedConn) Read(p []byte) (int, error) {
// 1. 优先返回待处理区的数据
if len(pc.pendingData) > 0 {
n := copy(p, pc.pendingData)
if n == len(pc.pendingData) {
pc.pendingData = pc.pendingData[:0]
} else {
// 优化:移动剩余数据到数组头部,避免切片指向中间导致内存泄漏
remaining := len(pc.pendingData) - n
copy(pc.pendingData, pc.pendingData[n:])
pc.pendingData = pc.pendingData[:remaining]
func writeFull(w io.Writer, b []byte) error {
for len(b) > 0 {
n, err := w.Write(b)
if err != nil {
return err
}
return n, nil
b = b[n:]
}
return nil
}
func (pc *PackedConn) Read(p []byte) (int, error) {
if len(pc.pendingData) > 0 {
return pc.drainPendingData(p), nil
}
// 2. 循环读取直到解出数据或出错
for {
nr, rErr := pc.reader.Read(pc.rawBuf)
if nr > 0 {
// 缓存频繁访问的变量
rBuf := pc.readBitBuf
rBits := pc.readBits
padMarker := pc.padMarker
@@ -324,24 +363,13 @@ func (pc *PackedConn) Read(p []byte) (int, error) {
}
}
// 3. 返回解码后的数据 - 优化:避免底层数组泄漏
n := copy(p, pc.pendingData)
if n == len(pc.pendingData) {
pc.pendingData = pc.pendingData[:0]
} else {
remaining := len(pc.pendingData) - n
copy(pc.pendingData, pc.pendingData[n:])
pc.pendingData = pc.pendingData[:remaining]
}
return n, nil
return pc.drainPendingData(p), nil
}
// getPaddingByte 从 Pool 中随机取 Padding 字节
func (pc *PackedConn) getPaddingByte() byte {
return pc.padPool[pc.rng.Intn(len(pc.padPool))]
}
// encodeGroup 编码 6-bit 组
func (pc *PackedConn) encodeGroup(group byte) byte {
return pc.table.layout.encodeGroup(group)
}
@@ -0,0 +1,91 @@
package sudoku
import (
"bytes"
"io"
"math/rand"
"net"
"testing"
"time"
)
type mockConn struct {
readBuf []byte
writeBuf []byte
}
func (c *mockConn) Read(p []byte) (int, error) {
if len(c.readBuf) == 0 {
return 0, io.EOF
}
n := copy(p, c.readBuf)
c.readBuf = c.readBuf[n:]
return n, nil
}
func (c *mockConn) Write(p []byte) (int, error) {
c.writeBuf = append(c.writeBuf, p...)
return len(p), nil
}
func (c *mockConn) Close() error { return nil }
func (c *mockConn) LocalAddr() net.Addr { return nil }
func (c *mockConn) RemoteAddr() net.Addr { return nil }
func (c *mockConn) SetDeadline(time.Time) error { return nil }
func (c *mockConn) SetReadDeadline(time.Time) error { return nil }
func (c *mockConn) SetWriteDeadline(time.Time) error { return nil }
func TestPackedConn_ProtectedPrefixPadding(t *testing.T) {
table := NewTable("packed-prefix-seed", "prefer_ascii")
mock := &mockConn{}
writer := NewPackedConn(mock, table, 0, 0)
writer.rng = rand.New(rand.NewSource(1))
payload := bytes.Repeat([]byte{0}, 32)
if _, err := writer.Write(payload); err != nil {
t.Fatalf("write: %v", err)
}
wire := append([]byte(nil), mock.writeBuf...)
if len(wire) < 20 {
t.Fatalf("wire too short: %d", len(wire))
}
firstHint := -1
nonHintCount := 0
maxHintRun := 0
currentHintRun := 0
for i, b := range wire[:20] {
if table.layout.isHint(b) {
if firstHint == -1 {
firstHint = i
}
currentHintRun++
if currentHintRun > maxHintRun {
maxHintRun = currentHintRun
}
continue
}
nonHintCount++
currentHintRun = 0
}
if firstHint < 1 || firstHint > 2 {
t.Fatalf("expected 1-2 leading padding bytes, first hint index=%d", firstHint)
}
if nonHintCount < 6 {
t.Fatalf("expected dense prefix padding, got only %d non-hint bytes in first 20", nonHintCount)
}
if maxHintRun > 3 {
t.Fatalf("prefix still exposes long hint run: %d", maxHintRun)
}
reader := NewPackedConn(&mockConn{readBuf: wire}, table, 0, 0)
decoded := make([]byte, len(payload))
if _, err := io.ReadFull(reader, decoded); err != nil {
t.Fatalf("read back: %v", err)
}
if !bytes.Equal(decoded, payload) {
t.Fatalf("roundtrip mismatch")
}
}
@@ -131,34 +131,45 @@ func (c *Client) resetHealthCheckTimer() {
c.healthCheckTimer.Reset(DefaultHealthCheckTimeout)
}
func (c *Client) dial(ctx context.Context, request *http.Request, conn *httpConn, pipeReader *io.PipeReader, pipeWriter *io.PipeWriter) {
func (c *Client) roundTrip(request *http.Request, conn *httpConn) {
c.startOnce.Do(c.start)
trace := &httptrace.ClientTrace{
GotConn: func(connInfo httptrace.GotConnInfo) {
conn.SetLocalAddr(connInfo.Conn.LocalAddr())
conn.SetRemoteAddr(connInfo.Conn.RemoteAddr())
},
}
request = request.WithContext(httptrace.WithClientTrace(ctx, trace))
response, err := c.roundTripper.RoundTrip(request)
if err != nil {
_ = pipeWriter.CloseWithError(err)
_ = pipeReader.CloseWithError(err)
conn.setUp(nil, err)
} else if response.StatusCode != http.StatusOK {
_ = response.Body.Close()
err = fmt.Errorf("unexpected status code: %d", response.StatusCode)
_ = pipeWriter.CloseWithError(err)
_ = pipeReader.CloseWithError(err)
conn.setUp(nil, err)
} else {
c.resetHealthCheckTimer()
conn.setUp(response.Body, nil)
pipeReader, pipeWriter := io.Pipe()
request.Body = pipeReader
*conn = httpConn{
writer: pipeWriter,
created: make(chan struct{}),
}
ctx, cancel := context.WithCancel(c.ctx) // requestCtx must alive during conn not closed
conn.cancelFn = cancel // cancel ctx when conn closed
go func() {
timeout := time.AfterFunc(C.DefaultTCPTimeout, cancel) // only cancel when RoundTrip timeout
defer timeout.Stop() // RoundTrip already returned, stop the timer
trace := &httptrace.ClientTrace{
GotConn: func(connInfo httptrace.GotConnInfo) {
conn.SetLocalAddr(connInfo.Conn.LocalAddr())
conn.SetRemoteAddr(connInfo.Conn.RemoteAddr())
},
}
request = request.WithContext(httptrace.WithClientTrace(ctx, trace))
response, err := c.roundTripper.RoundTrip(request)
if err != nil {
_ = pipeWriter.CloseWithError(err)
_ = pipeReader.CloseWithError(err)
conn.setUp(nil, err)
} else if response.StatusCode != http.StatusOK {
_ = response.Body.Close()
err = fmt.Errorf("unexpected status code: %d", response.StatusCode)
_ = pipeWriter.CloseWithError(err)
_ = pipeReader.CloseWithError(err)
conn.setUp(nil, err)
} else {
c.resetHealthCheckTimer()
conn.setUp(response.Body, nil)
}
}()
}
func (c *Client) Dial(ctx context.Context, host string) (net.Conn, error) {
pipeReader, pipeWriter := io.Pipe()
request := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{
@@ -166,23 +177,16 @@ func (c *Client) Dial(ctx context.Context, host string) (net.Conn, error) {
Host: host,
},
Header: make(http.Header),
Body: pipeReader,
Host: host,
}
request.Header.Add("User-Agent", TCPUserAgent)
request.Header.Add("Proxy-Authorization", c.auth)
conn := &tcpConn{
httpConn: httpConn{
writer: pipeWriter,
created: make(chan struct{}),
},
}
go c.dial(ctx, request, &conn.httpConn, pipeReader, pipeWriter)
conn := &tcpConn{}
c.roundTrip(request, &conn.httpConn)
return conn, nil
}
func (c *Client) ListenPacket(ctx context.Context) (net.PacketConn, error) {
pipeReader, pipeWriter := io.Pipe()
request := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{
@@ -190,25 +194,16 @@ func (c *Client) ListenPacket(ctx context.Context) (net.PacketConn, error) {
Host: UDPMagicAddress,
},
Header: make(http.Header),
Body: pipeReader,
Host: UDPMagicAddress,
}
request.Header.Add("User-Agent", UDPUserAgent)
request.Header.Add("Proxy-Authorization", c.auth)
conn := &clientPacketConn{
packetConn: packetConn{
httpConn: httpConn{
writer: pipeWriter,
created: make(chan struct{}),
},
},
}
go c.dial(ctx, request, &conn.httpConn, pipeReader, pipeWriter)
conn := &clientPacketConn{}
c.roundTrip(request, &conn.httpConn)
return conn, nil
}
func (c *Client) ListenICMP(ctx context.Context) (*IcmpConn, error) {
pipeReader, pipeWriter := io.Pipe()
request := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{
@@ -216,18 +211,12 @@ func (c *Client) ListenICMP(ctx context.Context) (*IcmpConn, error) {
Host: ICMPMagicAddress,
},
Header: make(http.Header),
Body: pipeReader,
Host: ICMPMagicAddress,
}
request.Header.Add("User-Agent", ICMPUserAgent)
request.Header.Add("Proxy-Authorization", c.auth)
conn := &IcmpConn{
httpConn{
writer: pipeWriter,
created: make(chan struct{}),
},
}
go c.dial(ctx, request, &conn.httpConn, pipeReader, pipeWriter)
conn := &IcmpConn{}
c.roundTrip(request, &conn.httpConn)
return conn, nil
}
@@ -11,7 +11,7 @@ func forceCloseAllConnections(roundTripper RoundTripper) {
roundTripper.CloseIdleConnections()
switch tr := roundTripper.(type) {
case *http.Http2Transport:
gun.CloseTransport(tr)
gun.CloseHttp2Transport(tr)
case *http3.Transport:
_ = tr.Close()
}
@@ -97,6 +97,7 @@ type httpConn struct {
body io.ReadCloser
created chan struct{}
createErr error
cancelFn func()
gun.NetAddr
// deadlines
@@ -125,6 +126,9 @@ func (h *httpConn) Close() error {
if h.body != nil {
errorArr = append(errorArr, h.body.Close())
}
if h.cancelFn != nil {
h.cancelFn()
}
return errors.Join(errorArr...)
}
@@ -53,6 +53,7 @@ require (
github.com/metacubex/hpke v0.1.0 // indirect
github.com/metacubex/http v0.1.0 // indirect
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 // indirect
github.com/metacubex/mhurl v0.1.0 // indirect
github.com/metacubex/mihomo v1.7.0 // indirect
github.com/metacubex/mlkem v0.1.0 // indirect
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
@@ -102,6 +102,8 @@ github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
github.com/metacubex/mhurl v0.1.0 h1:ZdW4Zxe3j3uJ89gNytOazHu6kbHn5owutN/VfXOI8GE=
github.com/metacubex/mhurl v0.1.0/go.mod h1:2qpQImCbXoUs6GwJrjuEXKelPyoimsIXr07eNKZdS00=
github.com/metacubex/mlkem v0.1.0 h1:wFClitonSFcmipzzQvax75beLQU+D7JuC+VK1RzSL8I=
github.com/metacubex/mlkem v0.1.0/go.mod h1:amhaXZVeYNShuy9BILcR7P0gbeo/QLZsnqCdL8U2PDQ=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
@@ -58,6 +58,7 @@ require (
github.com/metacubex/hpke v0.1.0 // indirect
github.com/metacubex/http v0.1.0 // indirect
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 // indirect
github.com/metacubex/mhurl v0.1.0 // indirect
github.com/metacubex/mlkem v0.1.0 // indirect
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
github.com/metacubex/qpack v0.6.0 // indirect
@@ -102,6 +102,8 @@ github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
github.com/metacubex/mhurl v0.1.0 h1:ZdW4Zxe3j3uJ89gNytOazHu6kbHn5owutN/VfXOI8GE=
github.com/metacubex/mhurl v0.1.0/go.mod h1:2qpQImCbXoUs6GwJrjuEXKelPyoimsIXr07eNKZdS00=
github.com/metacubex/mlkem v0.1.0 h1:wFClitonSFcmipzzQvax75beLQU+D7JuC+VK1RzSL8I=
github.com/metacubex/mlkem v0.1.0/go.mod h1:amhaXZVeYNShuy9BILcR7P0gbeo/QLZsnqCdL8U2PDQ=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
@@ -68,8 +68,11 @@ func QueryProxyGroupNames(excludeNotSelectable bool) []string {
}
for _, p := range proxies {
if _, ok := p.Adapter().(outboundgroup.ProxyGroup); ok {
if g, ok := p.Adapter().(outboundgroup.ProxyGroup); ok {
if !excludeNotSelectable || p.Type() == C.Selector {
if g.Hidden() {
continue
}
result = append(result, p.Name())
}
}
+4 -6
View File
@@ -19,8 +19,6 @@ type Fallback struct {
testUrl string
selected string
expectedStatus string
Hidden bool
Icon string
}
func (f *Fallback) Now() string {
@@ -90,8 +88,8 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
"testUrl": f.testUrl,
"expectedStatus": f.expectedStatus,
"fixed": f.selected,
"hidden": f.Hidden,
"icon": f.Icon,
"hidden": f.Hidden(),
"icon": f.Icon(),
})
}
@@ -163,6 +161,8 @@ func NewFallback(option *GroupCommonOption, providers []P.ProxyProvider) *Fallba
GroupBase: NewGroupBase(GroupBaseOption{
Name: option.Name,
Type: C.Fallback,
Hidden: option.Hidden,
Icon: option.Icon,
Filter: option.Filter,
ExcludeFilter: option.ExcludeFilter,
ExcludeType: option.ExcludeType,
@@ -173,7 +173,5 @@ func NewFallback(option *GroupCommonOption, providers []P.ProxyProvider) *Fallba
disableUDP: option.DisableUDP,
testUrl: option.URL,
expectedStatus: option.ExpectedStatus,
Hidden: option.Hidden,
Icon: option.Icon,
}
}
+19 -5
View File
@@ -22,6 +22,8 @@ import (
type GroupBase struct {
*outbound.Base
hidden bool
icon string
filterRegs []*regexp2.Regexp
excludeFilterRegs []*regexp2.Regexp
excludeTypeArray []string
@@ -30,7 +32,7 @@ type GroupBase struct {
failedTimes int
failedTime time.Time
failedTesting atomic.Bool
TestTimeout int
testTimeout int
maxFailedTimes int
// for GetProxies
@@ -42,6 +44,8 @@ type GroupBase struct {
type GroupBaseOption struct {
Name string
Type C.AdapterType
Hidden bool
Icon string
Filter string
ExcludeFilter string
ExcludeType string
@@ -74,17 +78,19 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
gb := &GroupBase{
Base: outbound.NewBase(outbound.BaseOption{Name: opt.Name, Type: opt.Type}),
hidden: opt.Hidden,
icon: opt.Icon,
filterRegs: filterRegs,
excludeFilterRegs: excludeFilterRegs,
excludeTypeArray: excludeTypeArray,
providers: opt.Providers,
failedTesting: atomic.NewBool(false),
TestTimeout: opt.TestTimeout,
testTimeout: opt.TestTimeout,
maxFailedTimes: opt.MaxFailedTimes,
}
if gb.TestTimeout == 0 {
gb.TestTimeout = 5000
if gb.testTimeout == 0 {
gb.testTimeout = 5000
}
if gb.maxFailedTimes == 0 {
gb.maxFailedTimes = 5
@@ -93,6 +99,14 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
return gb
}
func (gb *GroupBase) Hidden() bool {
return gb.hidden
}
func (gb *GroupBase) Icon() string {
return gb.icon
}
func (gb *GroupBase) Touch() {
for _, pd := range gb.providers {
pd.Touch()
@@ -265,7 +279,7 @@ func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error, fn func(
log.Debugln("ProxyGroup: %s first failed", gb.Name())
gb.failedTime = time.Now()
} else {
if time.Since(gb.failedTime) > time.Duration(gb.TestTimeout)*time.Millisecond {
if time.Since(gb.failedTime) > time.Duration(gb.testTimeout)*time.Millisecond {
gb.failedTimes = 0
return
}
@@ -27,8 +27,6 @@ type LoadBalance struct {
strategyFn strategyFn
testUrl string
expectedStatus string
Hidden bool
Icon string
}
var errStrategy = errors.New("unsupported strategy")
@@ -234,8 +232,8 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
"all": all,
"testUrl": lb.testUrl,
"expectedStatus": lb.expectedStatus,
"hidden": lb.Hidden,
"icon": lb.Icon,
"hidden": lb.Hidden(),
"icon": lb.Icon(),
})
}
@@ -267,6 +265,8 @@ func NewLoadBalance(option *GroupCommonOption, providers []P.ProxyProvider, stra
GroupBase: NewGroupBase(GroupBaseOption{
Name: option.Name,
Type: C.LoadBalance,
Hidden: option.Hidden,
Icon: option.Icon,
Filter: option.Filter,
ExcludeFilter: option.ExcludeFilter,
ExcludeType: option.ExcludeType,
@@ -278,7 +278,5 @@ func NewLoadBalance(option *GroupCommonOption, providers []P.ProxyProvider, stra
disableUDP: option.DisableUDP,
testUrl: option.URL,
expectedStatus: option.ExpectedStatus,
Hidden: option.Hidden,
Icon: option.Icon,
}, nil
}
+4 -6
View File
@@ -14,8 +14,6 @@ type Selector struct {
disableUDP bool
selected string
testUrl string
Hidden bool
Icon string
}
// DialContext implements C.ProxyAdapter
@@ -68,8 +66,8 @@ func (s *Selector) MarshalJSON() ([]byte, error) {
"now": s.Now(),
"all": all,
"testUrl": url,
"hidden": s.Hidden,
"icon": s.Icon,
"hidden": s.Hidden(),
"icon": s.Icon(),
})
}
@@ -121,6 +119,8 @@ func NewSelector(option *GroupCommonOption, providers []P.ProxyProvider) *Select
GroupBase: NewGroupBase(GroupBaseOption{
Name: option.Name,
Type: C.Selector,
Hidden: option.Hidden,
Icon: option.Icon,
Filter: option.Filter,
ExcludeFilter: option.ExcludeFilter,
ExcludeType: option.ExcludeType,
@@ -131,7 +131,5 @@ func NewSelector(option *GroupCommonOption, providers []P.ProxyProvider) *Select
selected: "COMPATIBLE",
disableUDP: option.DisableUDP,
testUrl: option.URL,
Hidden: option.Hidden,
Icon: option.Icon,
}
}
+4 -6
View File
@@ -29,8 +29,6 @@ type URLTest struct {
expectedStatus string
tolerance uint16
disableUDP bool
Hidden bool
Icon string
fastNode C.Proxy
fastSingle *singledo.Single[C.Proxy]
}
@@ -180,8 +178,8 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
"testUrl": u.testUrl,
"expectedStatus": u.expectedStatus,
"fixed": u.selected,
"hidden": u.Hidden,
"icon": u.Icon,
"hidden": u.Hidden(),
"icon": u.Icon(),
})
}
@@ -215,6 +213,8 @@ func NewURLTest(option *GroupCommonOption, providers []P.ProxyProvider, options
GroupBase: NewGroupBase(GroupBaseOption{
Name: option.Name,
Type: C.URLTest,
Hidden: option.Hidden,
Icon: option.Icon,
Filter: option.Filter,
ExcludeFilter: option.ExcludeFilter,
ExcludeType: option.ExcludeType,
@@ -226,8 +226,6 @@ func NewURLTest(option *GroupCommonOption, providers []P.ProxyProvider, options
disableUDP: option.DisableUDP,
testUrl: option.URL,
expectedStatus: option.ExpectedStatus,
Hidden: option.Hidden,
Icon: option.Icon,
}
for _, option := range options {
+3
View File
@@ -16,6 +16,9 @@ type ProxyGroup interface {
Now() string
Touch()
Hidden() bool
Icon() string
URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (mp map[string]uint16, err error)
}
+2 -1
View File
@@ -23,6 +23,7 @@ require (
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/http v0.1.0
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604
github.com/metacubex/mhurl v0.1.0
github.com/metacubex/mlkem v0.1.0
github.com/metacubex/quic-go v0.59.1-0.20260213014310-4df8f0de5b56
github.com/metacubex/randv2 v0.2.0
@@ -43,7 +44,6 @@ require (
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f
github.com/mroth/weightedrand/v2 v2.1.0
github.com/openacid/low v0.1.21
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
github.com/samber/lo v1.53.0
github.com/sirupsen/logrus v1.9.4
github.com/stretchr/testify v1.11.1
@@ -104,6 +104,7 @@ require (
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
+2
View File
@@ -107,6 +107,8 @@ github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
github.com/metacubex/mhurl v0.1.0 h1:ZdW4Zxe3j3uJ89gNytOazHu6kbHn5owutN/VfXOI8GE=
github.com/metacubex/mhurl v0.1.0/go.mod h1:2qpQImCbXoUs6GwJrjuEXKelPyoimsIXr07eNKZdS00=
github.com/metacubex/mlkem v0.1.0 h1:wFClitonSFcmipzzQvax75beLQU+D7JuC+VK1RzSL8I=
github.com/metacubex/mlkem v0.1.0/go.mod h1:amhaXZVeYNShuy9BILcR7P0gbeo/QLZsnqCdL8U2PDQ=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
+3 -2
View File
@@ -4,9 +4,10 @@ import (
"bytes"
"errors"
"net"
"net/url"
"github.com/metacubex/mihomo/transport/socks5"
"github.com/metacubex/mhurl"
)
type packet struct {
@@ -48,7 +49,7 @@ func (c *packet) InAddr() net.Addr {
}
func ParseSSURL(s string) (addr, cipher, password string, err error) {
u, err := url.Parse(s)
u, err := mhurl.Parse(s) // we need multiple hosts url supports
if err != nil {
return
}
@@ -0,0 +1,22 @@
package shadowsocks
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
func TestParseSSURL(t *testing.T) {
for _, test := range []struct{ method, passwd, hosts string }{
{method: "aes-256-gcm", passwd: "password", hosts: ":1000,:2000,:3000"},
{method: "aes-256-gcm", passwd: "password", hosts: "127.0.0.1:1000,127.0.0.1:2000,127.0.0.1:3000"},
{method: "aes-256-gcm", passwd: "password", hosts: "[::1]:1000,[::1]:2000,[::1]:3000"},
} {
addr, cipher, password, err := ParseSSURL(fmt.Sprintf("ss://%s:%s@%s", test.method, test.passwd, test.hosts))
require.NoError(t, err)
require.Equal(t, test.hosts, addr)
require.Equal(t, test.method, cipher)
require.Equal(t, test.passwd, password)
}
}
-2
View File
@@ -506,8 +506,6 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
}
}
//l.openAndroidHotspot(tunOptions)
if !l.options.AutoDetectInterface {
resolver.ResetConnection()
}
@@ -4,7 +4,6 @@ package sing_tun
import (
"errors"
"runtime"
"sync"
"github.com/metacubex/mihomo/component/process"
@@ -13,8 +12,6 @@ import (
"github.com/metacubex/mihomo/log"
"github.com/metacubex/sing-tun"
"github.com/sagernet/netlink"
"golang.org/x/sys/unix"
)
type packageManagerCallback struct{}
@@ -78,25 +75,3 @@ func init() {
process.DefaultPackageNameResolver = findPackageName
}
}
func (l *Listener) openAndroidHotspot(tunOptions tun.Options) {
if runtime.GOOS == "android" && tunOptions.AutoRoute {
priority := 9000
if len(tunOptions.ExcludedRanges()) > 0 {
priority++
}
if tunOptions.InterfaceMonitor.AndroidVPNEnabled() {
priority++
}
it := netlink.NewRule()
it.Priority = priority
it.IifName = tunOptions.Name
it.Table = 254 //main
it.Family = unix.AF_INET
it.SuppressPrefixlen = 0
err := netlink.RuleAdd(it)
if err != nil {
log.Warnln("[TUN] add AndroidHotspot rule error")
}
}
}
@@ -9,4 +9,3 @@ import (
func (l *Listener) buildAndroidRules(tunOptions *tun.Options) error {
return nil
}
func (l *Listener) openAndroidHotspot(tunOptions tun.Options) {}
+2 -2
View File
@@ -4,7 +4,6 @@ import (
"context"
"errors"
"net"
"net/url"
"strings"
"github.com/metacubex/mihomo/adapter/inbound"
@@ -19,6 +18,7 @@ import (
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
"github.com/metacubex/http"
"github.com/metacubex/mhurl"
vmess "github.com/metacubex/sing-vmess"
"github.com/metacubex/sing/common"
"github.com/metacubex/sing/common/metadata"
@@ -230,7 +230,7 @@ func HandleVmess(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition)
}
func ParseVmessURL(s string) (addr, username, password string, err error) {
u, err := url.Parse(s)
u, err := mhurl.Parse(s) // we need multiple hosts url supports
if err != nil {
return
}
@@ -0,0 +1,22 @@
package sing_vmess
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
func TestParseVmessURL(t *testing.T) {
for _, test := range []struct{ username, passwd, hosts string }{
{username: "username", passwd: "password", hosts: ":1000,:2000,:3000"},
{username: "username", passwd: "password", hosts: "127.0.0.1:1000,127.0.0.1:2000,127.0.0.1:3000"},
{username: "username", passwd: "password", hosts: "[::1]:1000,[::1]:2000,[::1]:3000"},
} {
addr, username, password, err := ParseVmessURL(fmt.Sprintf("vmess://%s:%s@%s", test.username, test.passwd, test.hosts))
require.NoError(t, err)
require.Equal(t, test.hosts, addr)
require.Equal(t, test.username, username)
require.Equal(t, test.passwd, password)
}
}
+3 -3
View File
@@ -1692,7 +1692,7 @@ dependencies = [
"windows-registry 0.5.3",
"windows-sys 0.60.2",
"winreg 0.55.0",
"zip 8.2.0",
"zip 8.3.0",
"zip-extensions",
]
@@ -12802,9 +12802,9 @@ dependencies = [
[[package]]
name = "zip"
version = "8.2.0"
version = "8.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b680f2a0cd479b4cff6e1233c483fdead418106eae419dc60200ae9850f6d004"
checksum = "4a243cfad17427fc077f529da5a95abe4e94fd2bfdb601611870a6557cc67657"
dependencies = [
"aes",
"bzip2",
@@ -11,7 +11,7 @@
"build": "tsc"
},
"dependencies": {
"@tanstack/react-query": "5.91.2",
"@tanstack/react-query": "5.91.3",
"@tauri-apps/api": "2.10.1",
"ahooks": "3.9.6",
"dayjs": "1.11.20",
@@ -43,7 +43,7 @@
"country-emoji": "1.5.6",
"dayjs": "1.11.20",
"framer-motion": "12.38.0",
"i18next": "25.8.19",
"i18next": "25.8.20",
"jotai": "2.18.1",
"json-schema": "0.4.0",
"material-react-table": "3.2.1",
@@ -71,7 +71,7 @@
"@emotion/react": "11.14.0",
"@iconify/json": "2.2.452",
"@monaco-editor/react": "4.7.0",
"@tanstack/react-query": "5.91.2",
"@tanstack/react-query": "5.91.3",
"@tanstack/react-router": "1.167.5",
"@tanstack/react-router-devtools": "1.166.9",
"@tanstack/router-plugin": "1.166.14",
+2 -2
View File
@@ -5,7 +5,7 @@
"mihomo_alpha": "alpha-dd4eb63",
"clash_rs": "v0.9.6",
"clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.9.6-alpha+sha.489d5d9"
"clash_rs_alpha": "0.9.6-alpha+sha.b17ba0a"
},
"arch_template": {
"mihomo": {
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-rs-armv7-unknown-linux-gnueabihf"
}
},
"updated_at": "2026-03-18T22:23:38.730Z"
"updated_at": "2026-03-19T22:22:44.922Z"
}
+1 -1
View File
@@ -80,7 +80,7 @@
"prettier-plugin-ember-template-tag": "2.1.3",
"prettier-plugin-tailwindcss": "0.7.2",
"prettier-plugin-toml": "2.0.6",
"stylelint": "17.4.0",
"stylelint": "17.5.0",
"stylelint-config-html": "1.1.0",
"stylelint-config-recess-order": "7.7.0",
"stylelint-config-standard": "40.0.0",
+107 -82
View File
@@ -95,26 +95,26 @@ importers:
specifier: 2.0.6
version: 2.0.6(prettier@3.8.1)
stylelint:
specifier: 17.4.0
version: 17.4.0(typescript@5.9.3)
specifier: 17.5.0
version: 17.5.0(typescript@5.9.3)
stylelint-config-html:
specifier: 1.1.0
version: 1.1.0(postcss-html@1.8.1)(stylelint@17.4.0(typescript@5.9.3))
version: 1.1.0(postcss-html@1.8.1)(stylelint@17.5.0(typescript@5.9.3))
stylelint-config-recess-order:
specifier: 7.7.0
version: 7.7.0(stylelint-order@8.1.1(stylelint@17.4.0(typescript@5.9.3)))(stylelint@17.4.0(typescript@5.9.3))
version: 7.7.0(stylelint-order@8.1.1(stylelint@17.5.0(typescript@5.9.3)))(stylelint@17.5.0(typescript@5.9.3))
stylelint-config-standard:
specifier: 40.0.0
version: 40.0.0(stylelint@17.4.0(typescript@5.9.3))
version: 40.0.0(stylelint@17.5.0(typescript@5.9.3))
stylelint-declaration-block-no-ignored-properties:
specifier: 3.0.0
version: 3.0.0(stylelint@17.4.0(typescript@5.9.3))
version: 3.0.0(stylelint@17.5.0(typescript@5.9.3))
stylelint-order:
specifier: 8.1.1
version: 8.1.1(stylelint@17.4.0(typescript@5.9.3))
version: 8.1.1(stylelint@17.5.0(typescript@5.9.3))
stylelint-scss:
specifier: 7.0.0
version: 7.0.0(stylelint@17.4.0(typescript@5.9.3))
version: 7.0.0(stylelint@17.5.0(typescript@5.9.3))
tailwindcss:
specifier: 4.2.2
version: 4.2.2
@@ -128,8 +128,8 @@ importers:
frontend/interface:
dependencies:
'@tanstack/react-query':
specifier: 5.91.2
version: 5.91.2(react@19.2.4)
specifier: 5.91.3
version: 5.91.3(react@19.2.4)
'@tauri-apps/api':
specifier: 2.10.1
version: 2.10.1
@@ -261,8 +261,8 @@ importers:
specifier: 12.38.0
version: 12.38.0(@emotion/is-prop-valid@1.3.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
i18next:
specifier: 25.8.19
version: 25.8.19(typescript@5.9.3)
specifier: 25.8.20
version: 25.8.20(typescript@5.9.3)
jotai:
specifier: 2.18.1
version: 2.18.1(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.4)
@@ -301,7 +301,7 @@ importers:
version: 8.2.0(e309558cb1df3652b39c2e76ceb9cee4)
react-i18next:
specifier: 15.7.4
version: 15.7.4(i18next@25.8.19(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
version: 15.7.4(i18next@25.8.20(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
react-markdown:
specifier: 10.1.0
version: 10.1.0(@types/react@19.2.14)(react@19.2.4)
@@ -340,8 +340,8 @@ importers:
specifier: 4.7.0
version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@tanstack/react-query':
specifier: 5.91.2
version: 5.91.2(react@19.2.4)
specifier: 5.91.3
version: 5.91.3(react@19.2.4)
'@tanstack/react-router':
specifier: 1.167.5
version: 1.167.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -500,7 +500,7 @@ importers:
version: 6.0.0(react@19.2.4)
react-i18next:
specifier: 15.7.4
version: 15.7.4(i18next@25.8.19(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
version: 15.7.4(i18next@25.8.20(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
react-use:
specifier: 17.6.0
version: 17.6.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -594,14 +594,14 @@ importers:
specifier: 1.1.1
version: 1.1.1
tar:
specifier: 7.5.11
version: 7.5.11
specifier: 7.5.12
version: 7.5.12
telegram:
specifier: 2.26.22
version: 2.26.22
undici:
specifier: 7.24.4
version: 7.24.4
specifier: 7.24.5
version: 7.24.5
yargs:
specifier: 18.0.0
version: 18.0.0
@@ -3852,8 +3852,8 @@ packages:
'@tanstack/query-core@5.91.2':
resolution: {integrity: sha512-Uz2pTgPC1mhqrrSGg18RKCWT/pkduAYtxbcyIyKBhw7dTWjXZIzqmpzO2lBkyWr4hlImQgpu1m1pei3UnkFRWw==}
'@tanstack/react-query@5.91.2':
resolution: {integrity: sha512-GClLPzbM57iFXv+FlvOUL56XVe00PxuTaVEyj1zAObhRiKF008J5vedmaq7O6ehs+VmPHe8+PUQhMuEyv8d9wQ==}
'@tanstack/react-query@5.91.3':
resolution: {integrity: sha512-D8jsCexxS5crZxAeiH6VlLHOUzmHOxeW5c11y8rZu0c34u/cy18hUKQXA/gn1Ila3ZIFzP+Pzv76YnliC0EtZQ==}
peerDependencies:
react: ^18 || ^19
@@ -4575,6 +4575,10 @@ packages:
resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
engines: {node: '>=12'}
ansi-regex@6.2.2:
resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
engines: {node: '>=12'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
@@ -4959,15 +4963,6 @@ packages:
typescript:
optional: true
cosmiconfig@9.0.0:
resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==}
engines: {node: '>=14'}
peerDependencies:
typescript: '>=4.9.5'
peerDependenciesMeta:
typescript:
optional: true
cosmiconfig@9.0.1:
resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==}
engines: {node: '>=14'}
@@ -5010,6 +5005,10 @@ packages:
resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
css-tree@3.2.1:
resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
css-what@6.1.0:
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
engines: {node: '>= 6'}
@@ -5528,6 +5527,10 @@ packages:
resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==}
engines: {node: '>=18'}
get-east-asian-width@1.5.0:
resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
engines: {node: '>=18'}
get-nonce@1.0.1:
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
engines: {node: '>=6'}
@@ -5568,8 +5571,8 @@ packages:
resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==}
engines: {node: '>=18'}
globby@16.1.0:
resolution: {integrity: sha512-+A4Hq7m7Ze592k9gZRy4gJ27DrXRNnC1vPjxTt1qQxEY8RxagBkBxivkCwg7FxSTG0iLLEMaUx13oOr0R2/qcQ==}
globby@16.1.1:
resolution: {integrity: sha512-dW7vl+yiAJSp6aCekaVnVJxurRv7DCOLyXqEG3RYMYUg7AuJ2jCqPkZTA8ooqC2vtnkaMcV5WfFBMuEnTu1OQg==}
engines: {node: '>=20'}
globjoin@0.1.4:
@@ -5661,8 +5664,8 @@ packages:
hyphenate-style-name@1.1.0:
resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==}
i18next@25.8.19:
resolution: {integrity: sha512-Szzho7sXdv9Do6TvZIyYu+H+94xCrh60cq01DKcYH/gDjpcHimrQApUEQycxw37U7guwbTEJ07CWScGGbgs96w==}
i18next@25.8.20:
resolution: {integrity: sha512-xjo9+lbX/P1tQt3xpO2rfJiBppNfUnNIPKgCvNsTKsvTOCro1Qr/geXVg1N47j5ScOSaXAPq8ET93raK3Rr06A==}
peerDependencies:
typescript: ^5
peerDependenciesMeta:
@@ -6169,6 +6172,9 @@ packages:
mdn-data@2.25.0:
resolution: {integrity: sha512-T2LPsjgUE/tgMmRXREVmwsux89DwWfNjiynOeXuLd2mX6jphGQ2YE3Ukz7LQ2VOFKiVZU/Ee1GqzHiipZCjymw==}
mdn-data@2.27.1:
resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
memorystream@0.3.1:
resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
engines: {node: '>= 0.10.0'}
@@ -6177,8 +6183,8 @@ packages:
resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==}
engines: {node: '>=18'}
meow@14.0.0:
resolution: {integrity: sha512-JhC3R1f6dbspVtmF3vKjAWz1EVIvwFrGGPLSdU6rK79xBwHWTuHoLnRX/t1/zHS1Ch1Y2UtIrih7DAHuH9JFJA==}
meow@14.1.0:
resolution: {integrity: sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==}
engines: {node: '>=20'}
merge2@1.4.1:
@@ -7339,6 +7345,10 @@ packages:
resolution: {integrity: sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==}
engines: {node: '>=20'}
string-width@8.2.0:
resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==}
engines: {node: '>=20'}
stringify-entities@4.0.4:
resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
@@ -7350,6 +7360,10 @@ packages:
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
engines: {node: '>=12'}
strip-ansi@7.2.0:
resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
engines: {node: '>=12'}
strip-bom@3.0.0:
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
engines: {node: '>=4'}
@@ -7411,8 +7425,8 @@ packages:
peerDependencies:
stylelint: ^16.8.2 || ^17.0.0
stylelint@17.4.0:
resolution: {integrity: sha512-3kQ2/cHv3Zt8OBg+h2B8XCx9evEABQIrv4hh3uXahGz/ZEHrTR80zxBiK2NfXNaSoyBzxO1pjsz1Vhdzwn5XSw==}
stylelint@17.5.0:
resolution: {integrity: sha512-o/NS6zhsPZFmgUm5tXX4pVNg1XDOZSlucLdf2qow/lVn4JIyzZIQ5b3kad1ugqUj3GSIgr2u5lQw7X8rjqw33g==}
engines: {node: '>=20.19.0'}
hasBin: true
@@ -7482,8 +7496,8 @@ packages:
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
engines: {node: '>=6'}
tar@7.5.11:
resolution: {integrity: sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==}
tar@7.5.12:
resolution: {integrity: sha512-9TsuLcdhOn4XztcQqhNyq1KOwOOED/3k58JAvtULiYqbO8B/0IBAAIE1hj0Svmm58k27TmcigyDI0deMlgG3uw==}
engines: {node: '>=18'}
telegram@2.26.22:
@@ -7612,8 +7626,8 @@ packages:
resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==}
engines: {node: '>=14.0'}
undici@7.24.4:
resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==}
undici@7.24.5:
resolution: {integrity: sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==}
engines: {node: '>=20.18.1'}
unicode-canonical-property-names-ecmascript@2.0.1:
@@ -11362,7 +11376,7 @@ snapshots:
'@tanstack/query-core@5.91.2': {}
'@tanstack/react-query@5.91.2(react@19.2.4)':
'@tanstack/react-query@5.91.3(react@19.2.4)':
dependencies:
'@tanstack/query-core': 5.91.2
react: 19.2.4
@@ -12201,6 +12215,8 @@ snapshots:
ansi-regex@6.0.1: {}
ansi-regex@6.2.2: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
@@ -12565,15 +12581,6 @@ snapshots:
optionalDependencies:
typescript: 5.9.3
cosmiconfig@9.0.0(typescript@5.9.3):
dependencies:
env-paths: 2.2.1
import-fresh: 3.3.0
js-yaml: 4.1.1
parse-json: 5.2.0
optionalDependencies:
typescript: 5.9.3
cosmiconfig@9.0.1(typescript@5.9.3):
dependencies:
env-paths: 2.2.1
@@ -12622,6 +12629,11 @@ snapshots:
mdn-data: 2.12.2
source-map-js: 1.2.1
css-tree@3.2.1:
dependencies:
mdn-data: 2.27.1
source-map-js: 1.2.1
css-what@6.1.0: {}
cssesc@3.0.0: {}
@@ -13135,6 +13147,8 @@ snapshots:
get-east-asian-width@1.4.0: {}
get-east-asian-width@1.5.0: {}
get-nonce@1.0.1: {}
get-tsconfig@4.10.1:
@@ -13182,7 +13196,7 @@ snapshots:
globals@17.4.0: {}
globby@16.1.0:
globby@16.1.1:
dependencies:
'@sindresorhus/merge-streams': 4.0.0
fast-glob: 3.3.3
@@ -13301,7 +13315,7 @@ snapshots:
hyphenate-style-name@1.1.0: {}
i18next@25.8.19(typescript@5.9.3):
i18next@25.8.20(typescript@5.9.3):
dependencies:
'@babel/runtime': 7.28.6
optionalDependencies:
@@ -13787,11 +13801,13 @@ snapshots:
mdn-data@2.25.0: {}
mdn-data@2.27.1: {}
memorystream@0.3.1: {}
meow@13.2.0: {}
meow@14.0.0: {}
meow@14.1.0: {}
merge2@1.4.1: {}
@@ -14530,11 +14546,11 @@ snapshots:
dependencies:
react: 19.2.4
react-i18next@15.7.4(i18next@25.8.19(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3):
react-i18next@15.7.4(i18next@25.8.20(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3):
dependencies:
'@babel/runtime': 7.28.3
html-parse-stringify: 3.0.1
i18next: 25.8.19(typescript@5.9.3)
i18next: 25.8.20(typescript@5.9.3)
react: 19.2.4
optionalDependencies:
react-dom: 19.2.4(react@19.2.4)
@@ -15060,6 +15076,11 @@ snapshots:
get-east-asian-width: 1.4.0
strip-ansi: 7.1.0
string-width@8.2.0:
dependencies:
get-east-asian-width: 1.5.0
strip-ansi: 7.2.0
stringify-entities@4.0.4:
dependencies:
character-entities-html4: 2.1.0
@@ -15073,6 +15094,10 @@ snapshots:
dependencies:
ansi-regex: 6.0.1
strip-ansi@7.2.0:
dependencies:
ansi-regex: 6.2.2
strip-bom@3.0.0: {}
strip-json-comments@3.1.1: {}
@@ -15087,36 +15112,36 @@ snapshots:
dependencies:
inline-style-parser: 0.2.3
stylelint-config-html@1.1.0(postcss-html@1.8.1)(stylelint@17.4.0(typescript@5.9.3)):
stylelint-config-html@1.1.0(postcss-html@1.8.1)(stylelint@17.5.0(typescript@5.9.3)):
dependencies:
postcss-html: 1.8.1
stylelint: 17.4.0(typescript@5.9.3)
stylelint: 17.5.0(typescript@5.9.3)
stylelint-config-recess-order@7.7.0(stylelint-order@8.1.1(stylelint@17.4.0(typescript@5.9.3)))(stylelint@17.4.0(typescript@5.9.3)):
stylelint-config-recess-order@7.7.0(stylelint-order@8.1.1(stylelint@17.5.0(typescript@5.9.3)))(stylelint@17.5.0(typescript@5.9.3)):
dependencies:
stylelint: 17.4.0(typescript@5.9.3)
stylelint-order: 8.1.1(stylelint@17.4.0(typescript@5.9.3))
stylelint: 17.5.0(typescript@5.9.3)
stylelint-order: 8.1.1(stylelint@17.5.0(typescript@5.9.3))
stylelint-config-recommended@18.0.0(stylelint@17.4.0(typescript@5.9.3)):
stylelint-config-recommended@18.0.0(stylelint@17.5.0(typescript@5.9.3)):
dependencies:
stylelint: 17.4.0(typescript@5.9.3)
stylelint: 17.5.0(typescript@5.9.3)
stylelint-config-standard@40.0.0(stylelint@17.4.0(typescript@5.9.3)):
stylelint-config-standard@40.0.0(stylelint@17.5.0(typescript@5.9.3)):
dependencies:
stylelint: 17.4.0(typescript@5.9.3)
stylelint-config-recommended: 18.0.0(stylelint@17.4.0(typescript@5.9.3))
stylelint: 17.5.0(typescript@5.9.3)
stylelint-config-recommended: 18.0.0(stylelint@17.5.0(typescript@5.9.3))
stylelint-declaration-block-no-ignored-properties@3.0.0(stylelint@17.4.0(typescript@5.9.3)):
stylelint-declaration-block-no-ignored-properties@3.0.0(stylelint@17.5.0(typescript@5.9.3)):
dependencies:
stylelint: 17.4.0(typescript@5.9.3)
stylelint: 17.5.0(typescript@5.9.3)
stylelint-order@8.1.1(stylelint@17.4.0(typescript@5.9.3)):
stylelint-order@8.1.1(stylelint@17.5.0(typescript@5.9.3)):
dependencies:
postcss: 8.5.8
postcss-sorting: 10.0.0(postcss@8.5.8)
stylelint: 17.4.0(typescript@5.9.3)
stylelint: 17.5.0(typescript@5.9.3)
stylelint-scss@7.0.0(stylelint@17.4.0(typescript@5.9.3)):
stylelint-scss@7.0.0(stylelint@17.5.0(typescript@5.9.3)):
dependencies:
css-tree: 3.1.0
is-plain-object: 5.0.0
@@ -15126,9 +15151,9 @@ snapshots:
postcss-resolve-nested-selector: 0.1.6
postcss-selector-parser: 7.1.1
postcss-value-parser: 4.2.0
stylelint: 17.4.0(typescript@5.9.3)
stylelint: 17.5.0(typescript@5.9.3)
stylelint@17.4.0(typescript@5.9.3):
stylelint@17.5.0(typescript@5.9.3):
dependencies:
'@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
@@ -15138,15 +15163,15 @@ snapshots:
'@csstools/selector-resolve-nested': 4.0.0(postcss-selector-parser@7.1.1)
'@csstools/selector-specificity': 6.0.0(postcss-selector-parser@7.1.1)
colord: 2.9.3
cosmiconfig: 9.0.0(typescript@5.9.3)
cosmiconfig: 9.0.1(typescript@5.9.3)
css-functions-list: 3.3.3
css-tree: 3.1.0
css-tree: 3.2.1
debug: 4.4.3
fast-glob: 3.3.3
fastest-levenshtein: 1.0.16
file-entry-cache: 11.1.2
global-modules: 2.0.0
globby: 16.1.0
globby: 16.1.1
globjoin: 0.1.4
html-tags: 5.1.0
ignore: 7.0.5
@@ -15154,7 +15179,7 @@ snapshots:
imurmurhash: 0.1.4
is-plain-object: 5.0.0
mathml-tag-names: 4.0.0
meow: 14.0.0
meow: 14.1.0
micromatch: 4.0.8
normalize-path: 3.0.0
picocolors: 1.1.1
@@ -15162,7 +15187,7 @@ snapshots:
postcss-safe-parser: 7.0.1(postcss@8.5.8)
postcss-selector-parser: 7.1.1
postcss-value-parser: 4.2.0
string-width: 8.1.1
string-width: 8.2.0
supports-hyperlinks: 4.4.0
svg-tags: 1.0.0
table: 6.9.0
@@ -15235,7 +15260,7 @@ snapshots:
tapable@2.3.0: {}
tar@7.5.11:
tar@7.5.12:
dependencies:
'@isaacs/fs-minipass': 4.0.1
chownr: 3.0.0
@@ -15375,7 +15400,7 @@ snapshots:
dependencies:
'@fastify/busboy': 2.1.1
undici@7.24.4: {}
undici@7.24.5: {}
unicode-canonical-property-names-ecmascript@2.0.1: {}
+2 -2
View File
@@ -22,9 +22,9 @@
"fs-extra": "11.3.4",
"octokit": "5.0.5",
"picocolors": "1.1.1",
"tar": "7.5.11",
"tar": "7.5.12",
"telegram": "2.26.22",
"undici": "7.24.4",
"undici": "7.24.5",
"yargs": "18.0.0"
}
}
+4
View File
@@ -240,6 +240,7 @@ type serverConfigMasqueradeFile struct {
type serverConfigMasqueradeProxy struct {
URL string `mapstructure:"url"`
RewriteHost bool `mapstructure:"rewriteHost"`
XForwarded bool `mapstructure:"xForwarded"`
Insecure bool `mapstructure:"insecure"`
}
@@ -852,6 +853,9 @@ func (c *serverConfig) fillMasqHandler(hyConfig *server.Config) error {
if !c.Masquerade.Proxy.RewriteHost {
r.Out.Host = r.In.Host
}
if c.Masquerade.Proxy.XForwarded {
r.SetXForwarded()
}
},
Transport: transport,
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
+1
View File
@@ -172,6 +172,7 @@ func TestServerConfig(t *testing.T) {
Proxy: serverConfigMasqueradeProxy{
URL: "https://some.site.net",
RewriteHost: true,
XForwarded: true,
Insecure: true,
},
String: serverConfigMasqueradeString{
+1
View File
@@ -133,6 +133,7 @@ masquerade:
proxy:
url: https://some.site.net
rewriteHost: true
xForwarded: true
insecure: true
string:
content: aint nothin here
+13 -6
View File
@@ -10,15 +10,24 @@ include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=mac80211
ifdef CONFIG_LINUX_6_12
PKG_VERSION:=6.18.7
PKG_HASH:=623e5cf46ca8e81fd413f4f465e2580a0143e24929f9c22ce1ba7c34f2872989
PKG_SOURCE_URL:=https://github.com/openwrt/backports/releases/download/backports-v$(PKG_VERSION)
PKG_SOURCE:=backports-$(PKG_VERSION).tar.zst
PATCH_DIR:=./patches-6.18
else
PKG_VERSION:=6.6.15
PKG_HASH:=3bbc461121134fda9089c084a5eed577d05e7837a157edf9a3797937172a3ece
PKG_SOURCE_URL:=http://mirror2.openwrt.org/sources/
PKG_SOURCE:=backports-$(PKG_VERSION).tar.xz
PATCH_DIR:=./patches
endif
PKG_RELEASE:=1
PKG_LICENSE:=GPL-2.0-only
PKG_LICENSE_FILES:=COPYING
PKG_SOURCE_URL:=http://mirror2.openwrt.org/sources/
PKG_HASH:=3bbc461121134fda9089c084a5eed577d05e7837a157edf9a3797937172a3ece
PKG_SOURCE:=backports-$(PKG_VERSION).tar.xz
PKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/$(if $(BUILD_VARIANT),$(PKG_NAME)-$(BUILD_VARIANT)/)backports-$(PKG_VERSION)
PKG_BUILD_PARALLEL:=1
@@ -333,9 +342,7 @@ endif
ifeq ($(strip $(CONFIG_EXTERNAL_KERNEL_TREE)),"")
ifeq ($(strip $(CONFIG_KERNEL_GIT_CLONE_URI)),"")
define Build/Configure
cmp $(PKG_BUILD_DIR)/include/linux/ath9k_platform.h $(LINUX_DIR)/include/linux/ath9k_platform.h
cmp $(PKG_BUILD_DIR)/include/linux/ath5k_platform.h $(LINUX_DIR)/include/linux/ath5k_platform.h
cmp $(PKG_BUILD_DIR)/include/linux/rt2x00_platform.h $(LINUX_DIR)/include/linux/rt2x00_platform.h
endef
endif
endif
@@ -0,0 +1,10 @@
--- a/drivers/net/wireless/ath/Kconfig
+++ b/drivers/net/wireless/ath/Kconfig
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: ISC
config ATH_COMMON
- tristate
+ tristate "ath.ko"
depends on m
config WLAN_VENDOR_ATH
@@ -0,0 +1,31 @@
--- a/drivers/net/wireless/ath/Makefile
+++ b/drivers/net/wireless/ath/Makefile
@@ -16,10 +16,10 @@ ath-objs := main.o \
regd.o \
hw.o \
key.o \
+ debug.o \
dfs_pattern_detector.o \
dfs_pri_detector.o
-ath-$(CPTCFG_ATH_DEBUG) += debug.o
ath-$(CPTCFG_ATH_TRACEPOINTS) += trace.o
CFLAGS_trace.o := -I$(src)
--- a/drivers/net/wireless/ath/ath.h
+++ b/drivers/net/wireless/ath/ath.h
@@ -321,14 +321,7 @@ void _ath_dbg(struct ath_common *common,
#endif /* CPTCFG_ATH_DEBUG */
/** Returns string describing opmode, or NULL if unknown mode. */
-#ifdef CPTCFG_ATH_DEBUG
const char *ath_opmode_to_string(enum nl80211_iftype opmode);
-#else
-static inline const char *ath_opmode_to_string(enum nl80211_iftype opmode)
-{
- return "UNKNOWN";
-}
-#endif
extern const char *ath_bus_type_strings[];
static inline const char *ath_bus_type_to_string(enum ath_bus_type bustype)
@@ -0,0 +1,92 @@
--- a/drivers/net/wireless/ath/regd.c
+++ b/drivers/net/wireless/ath/regd.c
@@ -24,6 +24,7 @@
#include "regd_common.h"
static int __ath_regd_init(struct ath_regulatory *reg);
+static struct reg_dmn_pair_mapping *ath_get_regpair(int regdmn);
/*
* This is a set of common rules used by our world regulatory domains.
@@ -116,6 +117,9 @@ static const struct ieee80211_regdomain
static bool dynamic_country_user_possible(struct ath_regulatory *reg)
{
+ if (IS_ENABLED(CPTCFG_ATH_USER_REGD))
+ return true;
+
if (IS_ENABLED(CPTCFG_ATH_REG_DYNAMIC_USER_CERT_TESTING))
return true;
@@ -188,6 +192,8 @@ static bool dynamic_country_user_possibl
static bool ath_reg_dyn_country_user_allow(struct ath_regulatory *reg)
{
+ if (IS_ENABLED(CPTCFG_ATH_USER_REGD))
+ return true;
if (!IS_ENABLED(CPTCFG_ATH_REG_DYNAMIC_USER_REG_HINTS))
return false;
if (!dynamic_country_user_possible(reg))
@@ -345,6 +351,9 @@ ath_reg_apply_beaconing_flags(struct wip
struct ieee80211_channel *ch;
unsigned int i;
+ if (IS_ENABLED(CPTCFG_ATH_USER_REGD))
+ return;
+
for (band = 0; band < NUM_NL80211_BANDS; band++) {
if (!wiphy->bands[band])
continue;
@@ -379,6 +388,9 @@ ath_reg_apply_ir_flags(struct wiphy *wip
{
struct ieee80211_supported_band *sband;
+ if (IS_ENABLED(CPTCFG_ATH_USER_REGD))
+ return;
+
sband = wiphy->bands[NL80211_BAND_2GHZ];
if (!sband)
return;
@@ -408,6 +420,9 @@ static void ath_reg_apply_radar_flags(st
struct ieee80211_channel *ch;
unsigned int i;
+ if (IS_ENABLED(CPTCFG_ATH_USER_REGD))
+ return;
+
if (!wiphy->bands[NL80211_BAND_5GHZ])
return;
@@ -640,6 +655,10 @@ ath_regd_init_wiphy(struct ath_regulator
const struct ieee80211_regdomain *regd;
wiphy->reg_notifier = reg_notifier;
+
+ if (IS_ENABLED(CPTCFG_ATH_USER_REGD))
+ return 0;
+
wiphy->regulatory_flags |= REGULATORY_STRICT_REG |
REGULATORY_CUSTOM_REG;
--- a/drivers/net/wireless/ath/Kconfig
+++ b/drivers/net/wireless/ath/Kconfig
@@ -24,6 +24,9 @@ config WLAN_VENDOR_ATH
if WLAN_VENDOR_ATH
+config ATH_USER_REGD
+ bool "Do not enforce EEPROM regulatory restrictions"
+
config ATH_DEBUG
bool "Atheros wireless debugging"
help
--- a/local-symbols
+++ b/local-symbols
@@ -91,6 +91,7 @@ ADM8211=
ATH_COMMON=
WLAN_VENDOR_ATH=
ATH_DEBUG=
+ATH_USER_REGD=
ATH_TRACEPOINTS=
ATH_REG_DYNAMIC_USER_REG_HINTS=
ATH_REG_DYNAMIC_USER_CERT_TESTING=
@@ -0,0 +1,84 @@
--- a/drivers/net/wireless/ath/regd.c
+++ b/drivers/net/wireless/ath/regd.c
@@ -44,7 +44,8 @@ static struct reg_dmn_pair_mapping *ath_
NL80211_RRF_NO_OFDM)
/* We allow IBSS on these on a case by case basis by regulatory domain */
-#define ATH_5GHZ_5150_5350 REG_RULE(5150-10, 5350+10, 80, 0, 30,\
+#define ATH_5GHZ_5150_5350 REG_RULE(5150-10, 5240+10, 80, 0, 30, 0),\
+ REG_RULE(5260-10, 5350+10, 80, 0, 30,\
NL80211_RRF_NO_IR)
#define ATH_5GHZ_5470_5850 REG_RULE(5470-10, 5850+10, 80, 0, 30,\
NL80211_RRF_NO_IR)
@@ -62,57 +63,56 @@ static struct reg_dmn_pair_mapping *ath_
#define ATH_5GHZ_NO_MIDBAND ATH_5GHZ_5150_5350, \
ATH_5GHZ_5725_5850
+#define REGD_RULES(...) \
+ .reg_rules = { __VA_ARGS__ }, \
+ .n_reg_rules = ARRAY_SIZE(((struct ieee80211_reg_rule[]) { __VA_ARGS__ }))
+
/* Can be used for:
* 0x60, 0x61, 0x62 */
static const struct ieee80211_regdomain ath_world_regdom_60_61_62 = {
- .n_reg_rules = 5,
.alpha2 = "99",
- .reg_rules = {
+ REGD_RULES(
ATH_2GHZ_ALL,
ATH_5GHZ_ALL,
- }
+ )
};
/* Can be used by 0x63 and 0x65 */
static const struct ieee80211_regdomain ath_world_regdom_63_65 = {
- .n_reg_rules = 4,
.alpha2 = "99",
- .reg_rules = {
+ REGD_RULES(
ATH_2GHZ_CH01_11,
ATH_2GHZ_CH12_13,
ATH_5GHZ_NO_MIDBAND,
- }
+ )
};
/* Can be used by 0x64 only */
static const struct ieee80211_regdomain ath_world_regdom_64 = {
- .n_reg_rules = 3,
.alpha2 = "99",
- .reg_rules = {
+ REGD_RULES(
ATH_2GHZ_CH01_11,
ATH_5GHZ_NO_MIDBAND,
- }
+ )
};
/* Can be used by 0x66 and 0x69 */
static const struct ieee80211_regdomain ath_world_regdom_66_69 = {
- .n_reg_rules = 3,
.alpha2 = "99",
- .reg_rules = {
+ REGD_RULES(
ATH_2GHZ_CH01_11,
ATH_5GHZ_ALL,
- }
+ )
};
/* Can be used by 0x67, 0x68, 0x6A and 0x6C */
static const struct ieee80211_regdomain ath_world_regdom_67_68_6A_6C = {
- .n_reg_rules = 4,
.alpha2 = "99",
- .reg_rules = {
+ REGD_RULES(
ATH_2GHZ_CH01_11,
ATH_2GHZ_CH12_13,
ATH_5GHZ_ALL,
- }
+ )
};
static bool dynamic_country_user_possible(struct ath_regulatory *reg)
@@ -0,0 +1,19 @@
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -3335,6 +3335,8 @@ void regulatory_hint_country_ie(struct w
enum environment_cap env = ENVIRON_ANY;
struct regulatory_request *request = NULL, *lr;
+ return;
+
/* IE len must be evenly divisible by 2 */
if (country_ie_len & 0x01)
return;
@@ -3584,6 +3586,7 @@ static bool is_wiphy_all_set_reg_flag(en
void regulatory_hint_disconnect(void)
{
+ return;
/* Restore of regulatory settings is not required when wiphy(s)
* ignore IE from connected access point but clearance of beacon hints
* is required when wiphy(s) supports beacon hints.
@@ -0,0 +1,26 @@
--- a/drivers/net/wireless/ath/regd_common.h
+++ b/drivers/net/wireless/ath/regd_common.h
@@ -32,6 +32,7 @@ enum EnumRd {
FCC2_WORLD = 0x21,
FCC2_ETSIC = 0x22,
FCC6_WORLD = 0x23,
+ FCC3_FCCA_2 = 0x2A,
FRANCE_RES = 0x31,
FCC3_FCCA = 0x3A,
FCC3_WORLD = 0x3B,
@@ -173,6 +174,7 @@ static struct reg_dmn_pair_mapping regDo
{FCC2_WORLD, CTL_FCC, CTL_ETSI},
{FCC2_ETSIC, CTL_FCC, CTL_ETSI},
{FCC3_FCCA, CTL_FCC, CTL_FCC},
+ {FCC3_FCCA_2, CTL_FCC, CTL_FCC},
{FCC3_WORLD, CTL_FCC, CTL_ETSI},
{FCC3_ETSIC, CTL_FCC, CTL_ETSI},
{FCC4_FCCA, CTL_FCC, CTL_FCC},
@@ -486,6 +488,7 @@ static struct country_code_to_enum_rd al
{CTRY_UAE, NULL1_WORLD, "AE"},
{CTRY_UNITED_KINGDOM, ETSI1_WORLD, "GB"},
{CTRY_UNITED_STATES, FCC3_FCCA, "US"},
+ {CTRY_UNITED_STATES, FCC3_FCCA_2, "US"},
{CTRY_UNITED_STATES2, FCC3_FCCA, "US"},
{CTRY_UNITED_STATES3, FCC3_FCCA, "US"},
/* This "PS" is for US public safety actually... to support this we
@@ -0,0 +1,51 @@
--- a/drivers/net/wireless/ath/regd.c
+++ b/drivers/net/wireless/ath/regd.c
@@ -115,6 +115,16 @@ static const struct ieee80211_regdomain
)
};
+static u16 ath_regd_get_eepromRD(struct ath_regulatory *reg)
+{
+ return reg->current_rd & ~WORLDWIDE_ROAMING_FLAG;
+}
+
+static bool is_default_regd(struct ath_regulatory *reg)
+{
+ return ath_regd_get_eepromRD(reg) == CTRY_DEFAULT;
+}
+
static bool dynamic_country_user_possible(struct ath_regulatory *reg)
{
if (IS_ENABLED(CPTCFG_ATH_USER_REGD))
@@ -123,6 +133,9 @@ static bool dynamic_country_user_possibl
if (IS_ENABLED(CPTCFG_ATH_REG_DYNAMIC_USER_CERT_TESTING))
return true;
+ if (is_default_regd(reg))
+ return true;
+
switch (reg->country_code) {
case CTRY_UNITED_STATES:
case CTRY_JAPAN1:
@@ -208,11 +221,6 @@ static inline bool is_wwr_sku(u16 regd)
(regd == WORLD));
}
-static u16 ath_regd_get_eepromRD(struct ath_regulatory *reg)
-{
- return reg->current_rd & ~WORLDWIDE_ROAMING_FLAG;
-}
-
bool ath_is_world_regd(struct ath_regulatory *reg)
{
return is_wwr_sku(ath_regd_get_eepromRD(reg));
@@ -659,6 +667,9 @@ ath_regd_init_wiphy(struct ath_regulator
if (IS_ENABLED(CPTCFG_ATH_USER_REGD))
return 0;
+ if (is_default_regd(reg))
+ return 0;
+
wiphy->regulatory_flags |= REGULATORY_STRICT_REG |
REGULATORY_CUSTOM_REG;
@@ -0,0 +1,56 @@
--- a/drivers/net/wireless/ath/ath5k/pci.c
+++ b/drivers/net/wireless/ath/ath5k/pci.c
@@ -20,6 +20,7 @@
#include <linux/pci.h>
#include <linux/etherdevice.h>
#include <linux/module.h>
+#include <linux/ath5k_platform.h>
#include "../ath.h"
#include "ath5k.h"
#include "debug.h"
@@ -73,7 +74,7 @@ static void ath5k_pci_read_cachesize(str
}
/*
- * Read from eeprom
+ * Read from eeprom or platform_data
*/
static bool
ath5k_pci_eeprom_read(struct ath_common *common, u32 offset, u16 *data)
@@ -81,6 +82,19 @@ ath5k_pci_eeprom_read(struct ath_common
struct ath5k_hw *ah = common->ah;
u32 status, timeout;
+ struct ath5k_platform_data *pdata = NULL;
+
+ if (ah->pdev)
+ pdata = ah->pdev->dev.platform_data;
+
+ if (pdata && pdata->eeprom_data && pdata->eeprom_data[61] == AR5K_EEPROM_MAGIC_VALUE) {
+ if (offset >= ATH5K_PLAT_EEP_MAX_WORDS)
+ return false;
+
+ *data = pdata->eeprom_data[offset];
+ return true;
+ }
+
/*
* Initialize EEPROM access
*/
@@ -124,6 +138,16 @@ static int ath5k_pci_eeprom_read_mac(str
u16 data;
int octet;
+ struct ath5k_platform_data *pdata = NULL;
+
+ if (ah->pdev)
+ pdata = ah->pdev->dev.platform_data;
+
+ if (pdata && pdata->macaddr) {
+ memcpy(mac, pdata->macaddr, ETH_ALEN);
+ return 0;
+ }
+
AR5K_EEPROM_READ(0x20, data);
for (offset = 0x1f, octet = 0, total = 0; offset >= 0x1d; offset--) {
@@ -0,0 +1,47 @@
--- a/drivers/net/wireless/ath/ath10k/Kconfig
+++ b/drivers/net/wireless/ath/ath10k/Kconfig
@@ -94,6 +94,12 @@ config ATH10K_TRACING
help
Select this to ath10k use tracing infrastructure.
+config ATH10K_THERMAL
+ bool "Atheros ath10k thermal monitoring support"
+ depends on THERMAL
+ ---help---
+ Select this to ath10k use hwmon for thermal measurement.
+
config ATH10K_DFS_CERTIFIED
bool "Atheros DFS support for certified platforms"
depends on ATH10K && CFG80211_CERTIFICATION_ONUS
--- a/drivers/net/wireless/ath/ath10k/Makefile
+++ b/drivers/net/wireless/ath/ath10k/Makefile
@@ -18,7 +18,7 @@ ath10k_core-y += mac.o \
ath10k_core-$(CPTCFG_ATH10K_SPECTRAL) += spectral.o
ath10k_core-$(CPTCFG_NL80211_TESTMODE) += testmode.o
ath10k_core-$(CPTCFG_ATH10K_TRACING) += trace.o
-ath10k_core-$(CONFIG_THERMAL) += thermal.o
+ath10k_core-$(CPTCFG_ATH10K_THERMAL) += thermal.o
ath10k_core-$(CPTCFG_ATH10K_LEDS) += leds.o
ath10k_core-$(CPTCFG_MAC80211_DEBUGFS) += debugfs_sta.o
ath10k_core-$(CONFIG_PM) += wow.o
--- a/drivers/net/wireless/ath/ath10k/thermal.h
+++ b/drivers/net/wireless/ath/ath10k/thermal.h
@@ -25,7 +25,7 @@ struct ath10k_thermal {
int temperature;
};
-#if IS_REACHABLE(CONFIG_THERMAL)
+#if IS_REACHABLE(CPTCFG_ATH10K_THERMAL)
int ath10k_thermal_register(struct ath10k *ar);
void ath10k_thermal_unregister(struct ath10k *ar);
void ath10k_thermal_event_temperature(struct ath10k *ar, int temperature);
--- a/local-symbols
+++ b/local-symbols
@@ -151,6 +151,7 @@ ATH10K_DEBUG=
ATH10K_DEBUGFS=
ATH10K_LEDS=
ATH10K_SPECTRAL=
+ATH10K_THERMAL=
ATH10K_TRACING=
ATH10K_DFS_CERTIFIED=
WCN36XX=
@@ -0,0 +1,33 @@
From: Sven Eckelmann <sven@open-mesh.com>
Date: Tue, 18 Nov 2014 12:29:28 +0100
Subject: [PATCH] ath10k: Don't initialize devices asynchronously
OpenWrt requires all PHYs to be initialized to create the configuration files
during bootup. ath10k violates this because it delays the creation of the PHY
to a not well defined point in the future.
Forcing the work to be done immediately works around this problem but may also
delay the boot when firmware images cannot be found.
Signed-off-by: Sven Eckelmann <sven@open-mesh.com>
---
--- a/drivers/net/wireless/ath/ath10k/core.c
+++ b/drivers/net/wireless/ath/ath10k/core.c
@@ -3583,6 +3583,16 @@ int ath10k_core_register(struct ath10k *
queue_work(ar->workqueue, &ar->register_work);
+ /* OpenWrt requires all PHYs to be initialized to create the
+ * configuration files during bootup. ath10k violates this
+ * because it delays the creation of the PHY to a not well defined
+ * point in the future.
+ *
+ * Forcing the work to be done immediately works around this problem
+ * but may also delay the boot when firmware images cannot be found.
+ */
+ flush_workqueue(ar->workqueue);
+
return 0;
}
EXPORT_SYMBOL(ath10k_core_register);
@@ -0,0 +1,37 @@
--- a/drivers/net/wireless/ath/ath10k/mac.c
+++ b/drivers/net/wireless/ath/ath10k/mac.c
@@ -9970,6 +9970,21 @@ static int ath10k_mac_init_rd(struct ath
return 0;
}
+#ifdef CPTCFG_MAC80211_LEDS
+static const struct ieee80211_tpt_blink ath10k_tpt_blink[] = {
+ { .throughput = 0 * 1024, .blink_time = 334 },
+ { .throughput = 1 * 1024, .blink_time = 260 },
+ { .throughput = 2 * 1024, .blink_time = 220 },
+ { .throughput = 5 * 1024, .blink_time = 190 },
+ { .throughput = 10 * 1024, .blink_time = 170 },
+ { .throughput = 25 * 1024, .blink_time = 150 },
+ { .throughput = 54 * 1024, .blink_time = 130 },
+ { .throughput = 120 * 1024, .blink_time = 110 },
+ { .throughput = 265 * 1024, .blink_time = 80 },
+ { .throughput = 586 * 1024, .blink_time = 50 },
+};
+#endif
+
int ath10k_mac_register(struct ath10k *ar)
{
static const u32 cipher_suites[] = {
@@ -10332,6 +10347,12 @@ int ath10k_mac_register(struct ath10k *a
ar->hw->weight_multiplier = ATH10K_AIRTIME_WEIGHT_MULTIPLIER;
+#ifdef CPTCFG_MAC80211_LEDS
+ ieee80211_create_tpt_led_trigger(ar->hw,
+ IEEE80211_TPT_LEDTRIG_FL_RADIO, ath10k_tpt_blink,
+ ARRAY_SIZE(ath10k_tpt_blink));
+#endif
+
ret = ieee80211_register_hw(ar->hw);
if (ret) {
ath10k_err(ar, "failed to register ieee80211: %d\n", ret);
@@ -0,0 +1,51 @@
From 79c9d7aabae1d1da9eea97d83b61e1517a8a2221 Mon Sep 17 00:00:00 2001
From: Mathias Kresin <dev@kresin.me>
Date: Fri, 22 Jun 2018 18:59:44 +0200
Subject: [PATCH] ath10k: use tpt LED trigger by default
Use the tpt LED trigger for each created phy led. Ths way LEDs attached
to the ath10k GPIO pins are indicating the phy status and blink on
traffic.
Signed-off-by: Mathias Kresin <dev@kresin.me>
---
drivers/net/wireless/ath/ath10k/core.h | 4 ++++
drivers/net/wireless/ath/ath10k/leds.c | 4 +---
drivers/net/wireless/ath/ath10k/mac.c | 2 +-
3 files changed, 6 insertions(+), 4 deletions(-)
--- a/drivers/net/wireless/ath/ath10k/core.h
+++ b/drivers/net/wireless/ath/ath10k/core.h
@@ -1320,6 +1320,10 @@ struct ath10k {
s32 tx_power_2g_limit;
s32 tx_power_5g_limit;
+#ifdef CPTCFG_MAC80211_LEDS
+ const char *led_default_trigger;
+#endif
+
/* must be last */
u8 drv_priv[] __aligned(sizeof(void *));
};
--- a/drivers/net/wireless/ath/ath10k/leds.c
+++ b/drivers/net/wireless/ath/ath10k/leds.c
@@ -69,7 +69,7 @@ int ath10k_leds_register(struct ath10k *
ar->leds.cdev.name = ar->leds.label;
ar->leds.cdev.brightness_set_blocking = ath10k_leds_set_brightness_blocking;
- ar->leds.cdev.default_trigger = ar->leds.wifi_led.default_trigger;
+ ar->leds.cdev.default_trigger = ar->led_default_trigger;
ret = led_classdev_register(wiphy_dev(ar->hw->wiphy), &ar->leds.cdev);
if (ret)
--- a/drivers/net/wireless/ath/ath10k/mac.c
+++ b/drivers/net/wireless/ath/ath10k/mac.c
@@ -10348,7 +10348,7 @@ int ath10k_mac_register(struct ath10k *a
ar->hw->weight_multiplier = ATH10K_AIRTIME_WEIGHT_MULTIPLIER;
#ifdef CPTCFG_MAC80211_LEDS
- ieee80211_create_tpt_led_trigger(ar->hw,
+ ar->led_default_trigger = ieee80211_create_tpt_led_trigger(ar->hw,
IEEE80211_TPT_LEDTRIG_FL_RADIO, ath10k_tpt_blink,
ARRAY_SIZE(ath10k_tpt_blink));
#endif
@@ -0,0 +1,101 @@
From: Sven Eckelmann <seckelmann@datto.com>
Date: Wed, 28 Nov 2018 16:16:27 +0100
Subject: ath10k: adjust tx power reduction for US regulatory domain
FCC allows maximum antenna gain of 6 dBi. 15.247(b)(4):
> (4) The conducted output power limit
> specified in paragraph (b) of this section
> is based on the use of antennas
> with directional gains that do not exceed
> 6 dBi. Except as shown in paragraph
> (c) of this section, if transmitting
> antennas of directional gain greater
> than 6 dBi are used, the conducted
> output power from the intentional radiator
> shall be reduced below the stated
> values in paragraphs (b)(1), (b)(2),
> and (b)(3) of this section, as appropriate,
> by the amount in dB that the
> directional gain of the antenna exceeds
> 6 dBi.
https://www.gpo.gov/fdsys/pkg/CFR-2013-title47-vol1/pdf/CFR-2013-title47-vol1-sec15-247.pdf
Signed-off-by: Sven Eckelmann <seckelmann@datto.com>
Forwarded: no
--- a/drivers/net/wireless/ath/ath10k/mac.c
+++ b/drivers/net/wireless/ath/ath10k/mac.c
@@ -1051,6 +1051,40 @@ static inline int ath10k_vdev_delete_syn
return 0;
}
+static u32 ath10k_get_max_antenna_gain(struct ath10k *ar,
+ u32 ch_max_antenna_gain)
+{
+ u32 max_antenna_gain;
+
+ if (ar->dfs_detector && ar->dfs_detector->region == NL80211_DFS_FCC) {
+ /* FCC allows maximum antenna gain of 6 dBi. 15.247(b)(4):
+ *
+ * > (4) The conducted output power limit
+ * > specified in paragraph (b) of this section
+ * > is based on the use of antennas
+ * > with directional gains that do not exceed
+ * > 6 dBi. Except as shown in paragraph
+ * > (c) of this section, if transmitting
+ * > antennas of directional gain greater
+ * > than 6 dBi are used, the conducted
+ * > output power from the intentional radiator
+ * > shall be reduced below the stated
+ * > values in paragraphs (b)(1), (b)(2),
+ * > and (b)(3) of this section, as appropriate,
+ * > by the amount in dB that the
+ * > directional gain of the antenna exceeds
+ * > 6 dBi.
+ *
+ * https://www.gpo.gov/fdsys/pkg/CFR-2013-title47-vol1/pdf/CFR-2013-title47-vol1-sec15-247.pdf
+ */
+ max_antenna_gain = 6;
+ } else {
+ max_antenna_gain = 0;
+ }
+
+ return max(ch_max_antenna_gain, max_antenna_gain);
+}
+
static int ath10k_monitor_vdev_start(struct ath10k *ar, int vdev_id)
{
struct cfg80211_chan_def *chandef = NULL;
@@ -1083,7 +1117,8 @@ static int ath10k_monitor_vdev_start(str
arg.channel.min_power = 0;
arg.channel.max_power = channel->max_power * 2;
arg.channel.max_reg_power = channel->max_reg_power * 2;
- arg.channel.max_antenna_gain = channel->max_antenna_gain;
+ arg.channel.max_antenna_gain = ath10k_get_max_antenna_gain(ar,
+ channel->max_antenna_gain);
reinit_completion(&ar->vdev_setup_done);
reinit_completion(&ar->vdev_delete_done);
@@ -1529,7 +1564,8 @@ static int ath10k_vdev_start_restart(str
arg.channel.min_power = 0;
arg.channel.max_power = chandef->chan->max_power * 2;
arg.channel.max_reg_power = chandef->chan->max_reg_power * 2;
- arg.channel.max_antenna_gain = chandef->chan->max_antenna_gain;
+ arg.channel.max_antenna_gain = ath10k_get_max_antenna_gain(ar,
+ chandef->chan->max_antenna_gain);
if (arvif->vdev_type == WMI_VDEV_TYPE_AP) {
arg.ssid = arvif->u.ap.ssid;
@@ -3460,7 +3496,8 @@ static int ath10k_update_channel_list(st
ch->min_power = 0;
ch->max_power = channel->max_power * 2;
ch->max_reg_power = channel->max_reg_power * 2;
- ch->max_antenna_gain = channel->max_antenna_gain;
+ ch->max_antenna_gain = ath10k_get_max_antenna_gain(ar,
+ channel->max_antenna_gain);
ch->reg_class_id = 0; /* FIXME */
/* FIXME: why use only legacy modes, why not any
@@ -0,0 +1,37 @@
From 22fb5991a44c78ff18ec0082dc90c809356eb893 Mon Sep 17 00:00:00 2001
From: Ansuel Smith <ansuelsmth@gmail.com>
Date: Sun, 27 Sep 2020 19:23:35 +0200
Subject: [PATCH 1/2] ath10k: Try to get mac-address from dts
Most of embedded device that have the ath10k wifi integrated store the
mac-address in nvmem partitions. Try to fetch the mac-address using the
standard 'of_get_mac_address' than in all the check also try to fetch the
address using the nvmem api searching for a defined 'mac-address' cell.
Mac-address defined in the dts have priority than any other address found.
Tested-on: QCA9984 hw1.0 PCI 10.4
Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
---
drivers/net/wireless/ath/ath10k/core.c | 10 ++++++++++
1 file changed, 10 insertions(+)
--- a/drivers/net/wireless/ath/ath10k/core.c
+++ b/drivers/net/wireless/ath/ath10k/core.c
@@ -10,6 +10,7 @@
#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/of.h>
+#include <linux/of_net.h>
#include <linux/property.h>
#include <linux/dmi.h>
#include <linux/ctype.h>
@@ -3455,6 +3456,8 @@ static int ath10k_core_probe_fw(struct a
device_get_mac_address(ar->dev, ar->mac_addr);
+ of_get_mac_address(ar->dev->of_node, ar->mac_addr);
+
ret = ath10k_core_init_firmware_features(ar);
if (ret) {
ath10k_err(ar, "fatal problem with firmware features: %d\n",
@@ -0,0 +1,28 @@
From f7d6edafe4358e3880a26775cfde4cd5c71ba063 Mon Sep 17 00:00:00 2001
From: David Bauer <mail@david-bauer.net>
Date: Wed, 5 Jul 2023 01:30:29 +0200
Subject: [PATCH] ath10k: always use mac80211 loss detection
ath10k does not report excessive loss in case of broken block-ack
sessions. The loss is communicated to the host-os, but ath10k does not
trigger a low-ack events by itself.
The mac80211 framework for loss detection however detects this
circumstance well in case of ath10k. So use it regardless of ath10k's
own loss detection mechanism.
Signed-off-by: David Bauer <mail@david-bauer.net>
---
drivers/net/wireless/ath/ath10k/mac.c | 1 -
1 file changed, 1 deletion(-)
--- a/drivers/net/wireless/ath/ath10k/mac.c
+++ b/drivers/net/wireless/ath/ath10k/mac.c
@@ -10140,7 +10140,6 @@ int ath10k_mac_register(struct ath10k *a
ieee80211_hw_set(ar->hw, CHANCTX_STA_CSA);
ieee80211_hw_set(ar->hw, QUEUE_CONTROL);
ieee80211_hw_set(ar->hw, SUPPORTS_TX_FRAG);
- ieee80211_hw_set(ar->hw, REPORTS_LOW_ACK);
if (!test_bit(ATH10K_FLAG_RAW_MODE, &ar->dev_flags))
ieee80211_hw_set(ar->hw, SW_CRYPTO_CONTROL);
@@ -0,0 +1,64 @@
--- a/drivers/net/wireless/ath/ath10k/htt.h
+++ b/drivers/net/wireless/ath/ath10k/htt.h
@@ -236,7 +236,11 @@ enum htt_rx_ring_flags {
};
#define HTT_RX_RING_SIZE_MIN 128
+#ifndef CONFIG_ATH10K_SMALLBUFFERS
#define HTT_RX_RING_SIZE_MAX 2048
+#else
+#define HTT_RX_RING_SIZE_MAX 512
+#endif
#define HTT_RX_RING_SIZE HTT_RX_RING_SIZE_MAX
#define HTT_RX_RING_FILL_LEVEL (((HTT_RX_RING_SIZE) / 2) - 1)
#define HTT_RX_RING_FILL_LEVEL_DUAL_MAC (HTT_RX_RING_SIZE - 1)
--- a/drivers/net/wireless/ath/ath10k/pci.c
+++ b/drivers/net/wireless/ath/ath10k/pci.c
@@ -133,7 +133,11 @@ static const struct ce_attr pci_host_ce_
.flags = CE_ATTR_FLAGS,
.src_nentries = 0,
.src_sz_max = 2048,
+#ifndef CONFIG_ATH10K_SMALLBUFFERS
.dest_nentries = 512,
+#else
+ .dest_nentries = 128,
+#endif
.recv_cb = ath10k_pci_htt_htc_rx_cb,
},
@@ -142,7 +146,11 @@ static const struct ce_attr pci_host_ce_
.flags = CE_ATTR_FLAGS,
.src_nentries = 0,
.src_sz_max = 2048,
+#ifndef CONFIG_ATH10K_SMALLBUFFERS
.dest_nentries = 128,
+#else
+ .dest_nentries = 64,
+#endif
.recv_cb = ath10k_pci_htc_rx_cb,
},
@@ -169,7 +177,11 @@ static const struct ce_attr pci_host_ce_
.flags = CE_ATTR_FLAGS,
.src_nentries = 0,
.src_sz_max = 512,
+#ifndef CONFIG_ATH10K_SMALLBUFFERS
.dest_nentries = 512,
+#else
+ .dest_nentries = 128,
+#endif
.recv_cb = ath10k_pci_htt_rx_cb,
},
@@ -194,7 +206,11 @@ static const struct ce_attr pci_host_ce_
.flags = CE_ATTR_FLAGS,
.src_nentries = 0,
.src_sz_max = 2048,
+#ifndef CONFIG_ATH10K_SMALLBUFFERS
.dest_nentries = 128,
+#else
+ .dest_nentries = 96,
+#endif
.recv_cb = ath10k_pci_pktlog_rx_cb,
},
@@ -0,0 +1,70 @@
From: Zhi-Jun You <hujy652@gmail.com>
Date: Wed, 6 Aug 2025 15:00:05 +0800
Subject: [RFC PATCH] wifi: ath10k: support flush_sta method
When a STA is marked as no longer authorized, if the driver doesn't
implement flush_sta(), mac80211 calls ieee80211_flush_queues() to
flush hardware queues to avoid sending unencrypted frames.
This has became a problem for ath10k because ieee80211_flush_queues()
will stop all traffic and call ath10k_flush, which waits until the
whole HW queue is empty. In a busy environment this will trigger a
timeout warning and stalls other STAs.
Fix this by implementing flush_sta method using WMI command to flush
frames of a specific STA.
Flushed frames will be marked as discard in tx complete indication.
ops->flush_sta will be set to NULL if htt.disable_tx_comp is set to
true.
Tested-on: QCA9984 hw1.0 PCI 10.4-3.9.0.2-00157
Signed-off-by: Zhi-Jun You <hujy652@gmail.com>
Tested-by: Florian Maurer <maurer@fh-aachen.de>
---
drivers/net/wireless/ath/ath10k/mac.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
--- a/drivers/net/wireless/ath/ath10k/mac.c
+++ b/drivers/net/wireless/ath/ath10k/mac.c
@@ -8180,6 +8180,20 @@ static void ath10k_flush(struct ieee8021
mutex_unlock(&ar->conf_mutex);
}
+static void ath10k_mac_op_flush_sta(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ struct ath10k_vif *arvif = (void *)vif->drv_priv;
+ struct ath10k *ar = hw->priv;
+ u32 bitmap = 0xFFFFFFFF;
+ int ret = 0;
+
+ ret = ath10k_wmi_peer_flush(ar, arvif->vdev_id, sta->addr, bitmap);
+ if (ret)
+ ath10k_warn(ar, "failed to flush sta (sta %pM)\n",
+ sta->addr);
+}
+
/* TODO: Implement this function properly
* For now it is needed to reply to Probe Requests in IBSS mode.
* Probably we need this information from FW.
@@ -9532,6 +9546,7 @@ static const struct ieee80211_ops ath10k
.set_rts_threshold = ath10k_set_rts_threshold,
.set_frag_threshold = ath10k_mac_op_set_frag_threshold,
.flush = ath10k_flush,
+ .flush_sta = ath10k_mac_op_flush_sta,
.tx_last_beacon = ath10k_tx_last_beacon,
.set_antenna = ath10k_set_antenna,
.get_antenna = ath10k_get_antenna,
@@ -10353,6 +10368,9 @@ int ath10k_mac_register(struct ath10k *a
if (!ar->hw_params.hw_ops->set_coverage_class)
ar->ops->set_coverage_class = NULL;
+ if (ar->htt.disable_tx_comp)
+ ar->ops->flush_sta = NULL;
+
ret = ath_regd_init(&ar->ath_common.regulatory, ar->hw->wiphy,
ath10k_reg_notifier);
if (ret) {
@@ -0,0 +1,164 @@
From 534a5f99d589cfa6b244b4433c192b6a278a67ff Mon Sep 17 00:00:00 2001
From: Robert Marko <robimarko@gmail.com>
Date: Sat, 5 Nov 2022 20:15:40 +0100
Subject: [PATCH] wifi: ath11k: use unique QRTR instance ID
Currently, trying to use AHB + PCI/MHI cards or multiple PCI/MHI cards
will cause a clash in the QRTR instance node ID and prevent the driver
from talking via QMI to the card and thus initializing it with:
[ 9.836329] ath11k c000000.wifi: host capability request failed: 1 90
[ 9.842047] ath11k c000000.wifi: failed to send qmi host cap: -22
So, in order to allow for this combination of cards, especially AHB + PCI
cards like IPQ8074 + QCN9074 (Used by me and tested on) set the desired
QRTR instance ID offset by calculating a unique one based on PCI domain
and bus ID-s and writing it to bits 7-0 of BHI_ERRDBG2 MHI register by
using the SBL state callback that is added as part of the series.
We also have to make sure that new QRTR offset is added on top of the
default QRTR instance ID-s that are currently used in the driver.
This finally allows using AHB + PCI or multiple PCI cards on the same
system.
Since this is not supported on QCA6390 and like, its limited to QCN9074
which is known to support changing QRTR instance ID.
Before:
root@OpenWrt:/# qrtr-lookup
Service Version Instance Node Port
1054 1 0 7 1 <unknown>
69 1 2 7 3 ATH10k WLAN firmware service
After:
root@OpenWrt:/# qrtr-lookup
Service Version Instance Node Port
1054 1 0 7 1 <unknown>
69 1 2 7 3 ATH10k WLAN firmware service
15 1 0 8 1 Test service
69 1 8 8 2 ATH10k WLAN firmware service
Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.5.0.1-01208-QCAHKSWPL_SILICONZ-1
Tested-on: QCN9074 hw1.0 PCI WLAN.HK.2.5.0.1-01208-QCAHKSWPL_SILICONZ-1
Signed-off-by: Robert Marko <robimarko@gmail.com>
---
drivers/net/wireless/ath/ath11k/mhi.c | 49 ++++++++++++++++++---------
drivers/net/wireless/ath/ath11k/mhi.h | 3 ++
drivers/net/wireless/ath/ath11k/pci.c | 9 ++++-
3 files changed, 44 insertions(+), 17 deletions(-)
--- a/drivers/net/wireless/ath/ath11k/mhi.c
+++ b/drivers/net/wireless/ath/ath11k/mhi.c
@@ -239,6 +239,34 @@ static void ath11k_mhi_op_runtime_put(st
{
}
+static int ath11k_mhi_op_read_reg(struct mhi_controller *mhi_cntrl,
+ void __iomem *addr,
+ u32 *out)
+{
+ *out = readl(addr);
+
+ return 0;
+}
+
+static void ath11k_mhi_op_write_reg(struct mhi_controller *mhi_cntrl,
+ void __iomem *addr,
+ u32 val)
+{
+ writel(val, addr);
+}
+
+static void ath11k_mhi_qrtr_instance_set(struct mhi_controller *mhi_cntrl)
+{
+ struct ath11k_base *ab = dev_get_drvdata(mhi_cntrl->cntrl_dev);
+
+ if (ab->hw_rev == ATH11K_HW_QCN9074_HW10) {
+ ath11k_mhi_op_write_reg(mhi_cntrl,
+ mhi_cntrl->bhi + BHI_ERRDBG2,
+ FIELD_PREP(QRTR_INSTANCE_MASK,
+ ab->qmi.service_ins_id - ab->hw_params.qmi_service_ins_id));
+ }
+}
+
static char *ath11k_mhi_op_callback_to_str(enum mhi_callback reason)
{
switch (reason) {
@@ -260,6 +288,8 @@ static char *ath11k_mhi_op_callback_to_s
return "MHI_CB_FATAL_ERROR";
case MHI_CB_BW_REQ:
return "MHI_CB_BW_REQ";
+ case MHI_CB_EE_SBL_MODE:
+ return "MHI_CB_EE_SBL_MODE";
default:
return "UNKNOWN";
}
@@ -290,6 +320,9 @@ static void ath11k_mhi_op_status_cb(stru
queue_work(ab->workqueue_aux, &ab->reset_work);
break;
+ case MHI_CB_EE_SBL_MODE:
+ ath11k_mhi_qrtr_instance_set(mhi_cntrl);
+ break;
default:
break;
}
@@ -297,22 +330,6 @@ static void ath11k_mhi_op_status_cb(stru
ab_pci->mhi_pre_cb = cb;
}
-static int ath11k_mhi_op_read_reg(struct mhi_controller *mhi_cntrl,
- void __iomem *addr,
- u32 *out)
-{
- *out = readl(addr);
-
- return 0;
-}
-
-static void ath11k_mhi_op_write_reg(struct mhi_controller *mhi_cntrl,
- void __iomem *addr,
- u32 val)
-{
- writel(val, addr);
-}
-
static int ath11k_mhi_read_addr_from_dt(struct mhi_controller *mhi_ctrl)
{
struct device_node *np;
--- a/drivers/net/wireless/ath/ath11k/mhi.h
+++ b/drivers/net/wireless/ath/ath11k/mhi.h
@@ -17,6 +17,9 @@
#define MHICTRL 0x38
#define MHICTRL_RESET_MASK 0x2
+#define BHI_ERRDBG2 0x38
+#define QRTR_INSTANCE_MASK GENMASK(7, 0)
+
int ath11k_mhi_start(struct ath11k_pci *ar_pci);
void ath11k_mhi_stop(struct ath11k_pci *ar_pci, bool is_suspend);
int ath11k_mhi_register(struct ath11k_pci *ar_pci);
--- a/drivers/net/wireless/ath/ath11k/pci.c
+++ b/drivers/net/wireless/ath/ath11k/pci.c
@@ -394,13 +394,20 @@ static void ath11k_pci_sw_reset(struct a
static void ath11k_pci_init_qmi_ce_config(struct ath11k_base *ab)
{
struct ath11k_qmi_ce_cfg *cfg = &ab->qmi.ce_cfg;
+ struct ath11k_pci *ab_pci = ath11k_pci_priv(ab);
+ struct pci_bus *bus = ab_pci->pdev->bus;
cfg->tgt_ce = ab->hw_params.target_ce_config;
cfg->tgt_ce_len = ab->hw_params.target_ce_count;
cfg->svc_to_ce_map = ab->hw_params.svc_to_ce_map;
cfg->svc_to_ce_map_len = ab->hw_params.svc_to_ce_map_len;
- ab->qmi.service_ins_id = ab->hw_params.qmi_service_ins_id;
+
+ if (ab->hw_rev == ATH11K_HW_QCN9074_HW10) {
+ ab->qmi.service_ins_id = ab->hw_params.qmi_service_ins_id +
+ (((pci_domain_nr(bus) & 0xF) << 4) | (bus->number & 0xF));
+ } else
+ ab->qmi.service_ins_id = ab->hw_params.qmi_service_ins_id;
ath11k_ce_get_shadow_config(ab, &cfg->shadow_reg_v2,
&cfg->shadow_reg_v2_len);
@@ -0,0 +1,69 @@
From 88f17c87ddc9ee7467acdc322d383e5a443a55ab Mon Sep 17 00:00:00 2001
From: Christian Marangi <ansuelsmth@gmail.com>
Date: Mon, 8 Dec 2025 20:50:47 +0100
Subject: [PATCH 1/2] wifi: ath11k: fix wrong usage of resource_size() causing
firmware panic
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
On converting to the of_reserved_mem_region_to_resource() helper with
commit 900730dc4705 ("wifi: ath: Use
of_reserved_mem_region_to_resource() for "memory-region"") a logic error
was introduced in the ath11k_core_coldboot_cal_support() if condition.
The original code checked for hremote_node presence and skipped
ath11k_core_coldboot_cal_support() in the other switch case but now
everything is driven entirely on the values of the resource struct.
resource_size() (in this case) is wrongly assumed to return a size of
zero if the passed resource struct is init to zero. This is not the case
as a resource struct should be always init with correct values (or at
best set the end value to -1 to signal it's not configured)
(the return value of resource_size() for a resource struct with start
and end set to zero is 1)
On top of this, using resource_size() to check if a resource struct is
initialized or not is generally wrong and other measure should be used
instead.
To better handle this, use the DEFINE_RES macro to initialize the
resource struct and set the IORESOURCE_UNSET flag by default.
Replace the resource_size() check with checking for the resource struct
flags and check if it's IORESOURCE_UNSET.
This change effectively restore the original logic and restore correct
loading of the ath11k firmware (restoring correct functionality of
Wi-Fi)
Cc: stable@vger.kernel.org
Fixes: 900730dc4705 ("wifi: ath: Use of_reserved_mem_region_to_resource() for "memory-region"")
Link: https://lore.kernel.org/all/20251207215359.28895-1-ansuelsmth@gmail.com/T/#m990492684913c5a158ff0e5fc90697d8ad95351b
Cc: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
drivers/net/wireless/ath/ath11k/qmi.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
--- a/drivers/net/wireless/ath/ath11k/qmi.c
+++ b/drivers/net/wireless/ath/ath11k/qmi.c
@@ -2039,8 +2039,8 @@ static int ath11k_qmi_alloc_target_mem_c
static int ath11k_qmi_assign_target_mem_chunk(struct ath11k_base *ab)
{
+ struct resource res = DEFINE_RES_NAMED(0, 0, NULL, IORESOURCE_UNSET);
struct device *dev = ab->dev;
- struct resource res = {};
u32 host_ddr_sz;
int i, idx, ret;
@@ -2086,7 +2086,7 @@ static int ath11k_qmi_assign_target_mem_
}
if (ath11k_core_coldboot_cal_support(ab)) {
- if (resource_size(&res)) {
+ if (res.flags != IORESOURCE_UNSET) {
ab->qmi.target_mem[idx].paddr =
res.start + host_ddr_sz;
ab->qmi.target_mem[idx].iaddr =
@@ -0,0 +1,62 @@
From 824dde8652815aa67b4e2bf2d8a9455a8ef82b8f Mon Sep 17 00:00:00 2001
From: Ziyang Huang <hzyitc@outlook.com>
Date: Thu, 29 Jun 2023 06:12:45 +0000
Subject: [PATCH] wifi: ath11k: Support setting bdf-addr and caldb-addr via DT
Signed-off-by: Ziyang Huang <hzyitc@outlook.com>
---
drivers/net/wireless/ath/ath11k/qmi.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
--- a/drivers/net/wireless/ath/ath11k/qmi.c
+++ b/drivers/net/wireless/ath/ath11k/qmi.c
@@ -2042,6 +2042,7 @@ static int ath11k_qmi_assign_target_mem_
struct resource res = DEFINE_RES_NAMED(0, 0, NULL, IORESOURCE_UNSET);
struct device *dev = ab->dev;
u32 host_ddr_sz;
+ u32 addr;
int i, idx, ret;
for (i = 0, idx = 0; i < ab->qmi.mem_seg_count; i++) {
@@ -2073,7 +2074,9 @@ static int ath11k_qmi_assign_target_mem_
idx++;
break;
case BDF_MEM_REGION_TYPE:
- ab->qmi.target_mem[idx].paddr = ab->hw_params.bdf_addr;
+ if (of_property_read_u32(dev->of_node, "qcom,bdf-addr", &addr))
+ addr = ab->hw_params.bdf_addr;
+ ab->qmi.target_mem[idx].paddr = addr;
ab->qmi.target_mem[idx].iaddr = NULL;
ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size;
ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type;
@@ -2095,8 +2098,9 @@ static int ath11k_qmi_assign_target_mem_
if (!ab->qmi.target_mem[idx].iaddr)
return -EIO;
} else {
- ab->qmi.target_mem[idx].paddr =
- ATH11K_QMI_CALDB_ADDRESS;
+ if (of_property_read_u32(dev->of_node, "qcom,caldb-addr", &addr))
+ addr = ATH11K_QMI_CALDB_ADDRESS;
+ ab->qmi.target_mem[idx].paddr = addr;
ab->qmi.target_mem[idx].iaddr = NULL;
}
} else {
@@ -2302,6 +2306,7 @@ static int ath11k_qmi_load_file_target_m
struct qmi_wlanfw_bdf_download_resp_msg_v01 resp;
struct qmi_txn txn;
const u8 *temp = data;
+ u32 addr;
void __iomem *bdf_addr = NULL;
int ret = 0;
u32 remaining = len;
@@ -2313,7 +2318,9 @@ static int ath11k_qmi_load_file_target_m
memset(&resp, 0, sizeof(resp));
if (ab->hw_params.fixed_bdf_addr) {
- bdf_addr = ioremap(ab->hw_params.bdf_addr, ab->hw_params.fw.board_size);
+ if(of_property_read_u32(ab->dev->of_node, "qcom,bdf-addr", &addr))
+ addr = ab->hw_params.bdf_addr;
+ bdf_addr = ioremap(addr, ab->hw_params.fw.board_size);
if (!bdf_addr) {
ath11k_warn(ab, "qmi ioremap error for bdf_addr\n");
ret = -EIO;
@@ -0,0 +1,65 @@
From b5ade0e0e1c1622a85fbfd2c93b41caff479f305 Mon Sep 17 00:00:00 2001
From: Florian Maurer <f.maurer@outlook.de>
Date: Fri, 3 Oct 2025 12:56:13 +0200
Subject: [PATCH] ath11k: add ath11k_mac_op_flush_sta to properly flush pending
packets
When a STA is marked as no longer authorized, if the driver doesn't
implement flush_sta(), mac80211 calls ieee80211_flush_queues() to
flush hardware queues to avoid sending unencrypted frames.
This has became a problem for ath11k because ieee80211_flush_queues()
will stop all traffic and call ath11k_flush, which waits until the
whole HW queue is empty. In a busy environment this will trigger a
timeout warning and stalls other STAs.
Fix this by implementing flush_sta method using WMI command to flush
frames of a specific STA.
Flushed frames will be marked as discard in tx complete indication.
warning print "ath11k c000000.wifi: failed to flush transmit queue 0"
was observed on various openwrt devices, and is fixed through this patch.
Tested-by: Florian Maurer <f.maurer@outlook.de>
Tested-by: Flole <flole@flole.de>
Co-developed-by: Benjamin Berg <benjamin@sipsolutions.net>
Signed-off-by: Benjamin Berg <benjamin@sipsolutions.net>
Signed-off-by: Florian Maurer <f.maurer@outlook.de>
---
drivers/net/wireless/ath/ath11k/mac.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
--- a/drivers/net/wireless/ath/ath11k/mac.c
+++ b/drivers/net/wireless/ath/ath11k/mac.c
@@ -8330,6 +8330,23 @@ static void ath11k_mac_op_flush(struct i
ath11k_mac_flush_tx_complete(ar);
}
+static void ath11k_mac_op_flush_sta(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ struct ath11k_vif *arvif = (void *)vif->drv_priv;
+ struct ath11k *ar = hw->priv;
+ struct peer_flush_params params = {
+ .peer_tid_bitmap = 0xFF,
+ .vdev_id = arvif->vdev_id,
+ };
+ int ret;
+
+ ret = ath11k_wmi_send_peer_flush_tids_cmd(ar, sta->addr, &params);
+ if (ret)
+ ath11k_warn(ar->ab, "failed to flush sta %pM: %d\n", sta->addr, ret);
+}
+
static bool
ath11k_mac_has_single_legacy_rate(struct ath11k *ar,
enum nl80211_band band,
@@ -9920,6 +9937,7 @@ static const struct ieee80211_ops ath11k
.set_bitrate_mask = ath11k_mac_op_set_bitrate_mask,
.get_survey = ath11k_mac_op_get_survey,
.flush = ath11k_mac_op_flush,
+ .flush_sta = ath11k_mac_op_flush_sta,
.sta_statistics = ath11k_mac_op_sta_statistics,
CFG80211_TESTMODE_CMD(ath11k_tm_cmd)
@@ -0,0 +1,66 @@
From 703d6551f71e7290619d6effe2a25a64e10538b7 Mon Sep 17 00:00:00 2001
From: Robert Marko <robimarko@gmail.com>
Date: Thu, 15 Dec 2022 12:20:52 +0100
Subject: [PATCH] ath11k: control thermal support via symbol
Currently, thermal support will get built if CONFIG_THERMAL is reachable,
however this is not suitable for OpenWrt as with ALL_KMODS being set to y
ATH11K_THERMAL wont get selected and so hwmon and thermal kmods wont get
pulled in resulting in a build-failure.
So, to avoid that, lets do what is already done for ath10k and add a
config symbol into backports for enabling thermal support.
Signed-off-by: Robert Marko <robimarko@gmail.com>
---
drivers/net/wireless/ath/ath11k/Kconfig | 7 +++++++
drivers/net/wireless/ath/ath11k/Makefile | 2 +-
drivers/net/wireless/ath/ath11k/thermal.h | 2 +-
local-symbols | 1 +
4 files changed, 10 insertions(+), 2 deletions(-)
--- a/drivers/net/wireless/ath/ath11k/Kconfig
+++ b/drivers/net/wireless/ath/ath11k/Kconfig
@@ -62,3 +62,10 @@ config ATH11K_SPECTRAL
Enable ath11k spectral scan support
Say Y to enable access to the FFT/spectral data via debugfs.
+
+config ATH11K_THERMAL
+ bool "ath11k thermal sensors and throttling support"
+ depends on ATH11K
+ depends on THERMAL
+ help
+ Enable ath11k thermal sensors and throttling support.
--- a/drivers/net/wireless/ath/ath11k/Makefile
+++ b/drivers/net/wireless/ath/ath11k/Makefile
@@ -24,7 +24,7 @@ ath11k-y += core.o \
ath11k-$(CPTCFG_ATH11K_DEBUGFS) += debugfs.o debugfs_htt_stats.o debugfs_sta.o
ath11k-$(CPTCFG_NL80211_TESTMODE) += testmode.o
ath11k-$(CPTCFG_ATH11K_TRACING) += trace.o
-ath11k-$(CONFIG_THERMAL) += thermal.o
+ath11k-$(CPTCFG_ATH11K_THERMAL) += thermal.o
ath11k-$(CPTCFG_ATH11K_SPECTRAL) += spectral.o
ath11k-$(CONFIG_PM) += wow.o
ath11k-$(CONFIG_DEV_COREDUMP) += coredump.o
--- a/drivers/net/wireless/ath/ath11k/thermal.h
+++ b/drivers/net/wireless/ath/ath11k/thermal.h
@@ -26,7 +26,7 @@ struct ath11k_thermal {
int temperature;
};
-#if IS_REACHABLE(CONFIG_THERMAL)
+#if IS_REACHABLE(CPTCFG_ATH11K_THERMAL)
int ath11k_thermal_register(struct ath11k_base *ab);
void ath11k_thermal_unregister(struct ath11k_base *ab);
int ath11k_thermal_set_throttling(struct ath11k *ar, u32 throttle_state);
--- a/local-symbols
+++ b/local-symbols
@@ -163,6 +163,7 @@ ATH11K_DEBUG=
ATH11K_DEBUGFS=
ATH11K_TRACING=
ATH11K_SPECTRAL=
+ATH11K_THERMAL=
ATH12K=
ATH12K_AHB=
ATH12K_DEBUG=
@@ -0,0 +1,75 @@
From fb1c40c225cbc413d82c872dd8c8af3469b2b921 Mon Sep 17 00:00:00 2001
From: Robert Marko <robimarko@gmail.com>
Date: Fri, 16 Dec 2022 17:17:52 +0100
Subject: [PATCH] ath11k: support setting FW memory mode via DT
ath11k is really memory intensive for devices with less that 1GB of RAM,
so lets allow saving a significant amount of memory by setting the FW to
Mode-1 via DTS for devices that need it.
However the drawback is reduced number of VDEV-s and peers which is a
reasonable tradeoff.
Mode-2 allows for further reduction, but it has further restrictions.
While we are here, lets add a print to be able to easily determine what
FW memory mode is being used.
Signed-off-by: Robert Marko <robimarko@gmail.com>
---
drivers/net/wireless/ath/ath11k/core.c | 28 ++++++++++++++++++++++++--
1 file changed, 26 insertions(+), 2 deletions(-)
--- a/drivers/net/wireless/ath/ath11k/core.c
+++ b/drivers/net/wireless/ath/ath11k/core.c
@@ -39,7 +39,7 @@ bool ath11k_ftm_mode;
module_param_named(ftm_mode, ath11k_ftm_mode, bool, 0444);
MODULE_PARM_DESC(ftm_mode, "Boots up in factory test mode");
-static const struct ath11k_hw_params ath11k_hw_params[] = {
+static struct ath11k_hw_params ath11k_hw_params[] = {
{
.hw_rev = ATH11K_HW_IPQ8074,
.name = "ipq8074 hw2.0",
@@ -2521,7 +2521,8 @@ static void ath11k_core_reset(struct wor
static int ath11k_init_hw_params(struct ath11k_base *ab)
{
const struct ath11k_hw_params *hw_params = NULL;
- int i;
+ u32 fw_mem_mode;
+ int i, ret;
for (i = 0; i < ARRAY_SIZE(ath11k_hw_params); i++) {
hw_params = &ath11k_hw_params[i];
@@ -2537,7 +2538,31 @@ static int ath11k_init_hw_params(struct
ab->hw_params = *hw_params;
+ ret = of_property_read_u32(ab->dev->of_node,
+ "qcom,ath11k-fw-memory-mode",
+ &fw_mem_mode);
+ if (!ret) {
+ if (fw_mem_mode == 0) {
+ ab->hw_params.fw_mem_mode = 0;
+ ab->hw_params.num_vdevs = 16 + 1;
+ ab->hw_params.num_peers = 512;
+ }
+ else if (fw_mem_mode == 1) {
+ ab->hw_params.fw_mem_mode = 1;
+ ab->hw_params.num_vdevs = 8;
+ ab->hw_params.num_peers = 128;
+ } else if (fw_mem_mode == 2) {
+ ab->hw_params.fw_mem_mode = 2;
+ ab->hw_params.num_vdevs = 8;
+ ab->hw_params.num_peers = 128;
+ ab->hw_params.coldboot_cal_mm = false;
+ ab->hw_params.coldboot_cal_ftm = false;
+ } else
+ ath11k_info(ab, "Unsupported FW memory mode: %u\n", fw_mem_mode);
+ }
+
ath11k_info(ab, "%s\n", ab->hw_params.name);
+ ath11k_info(ab, "FW memory mode: %d\n", ab->hw_params.fw_mem_mode);
return 0;
}
@@ -0,0 +1,332 @@
From abdd0985a36189ef2cc0e393b027276e86137ace Mon Sep 17 00:00:00 2001
From: Aditya Kumar Singh <quic_adisi@quicinc.com>
Date: Tue, 11 Apr 2023 20:08:49 +0200
Subject: [PATCH] ath11k: remove intersection support for regulatory rules
Currently, regulatory rules from new country settings is intersected with
rules from default country settings(during initialisation) in order to prevent
users to bypass their default country settings such as power limits, channel
flags, etc.
However, the country setting in the BDF will take higher higher precendence
and FW will protect it. Therefore, there is no need to handle intersection
on the driver side now.
Remove regulatory rules intersection logic support.
Signed-off-by: Aditya Kumar Singh <quic_adisi@quicinc.com>
---
drivers/net/wireless/ath/ath11k/reg.c | 168 +++-----------------------
drivers/net/wireless/ath/ath11k/reg.h | 2 +-
drivers/net/wireless/ath/ath11k/wmi.c | 24 +---
3 files changed, 16 insertions(+), 178 deletions(-)
--- a/drivers/net/wireless/ath/ath11k/reg.c
+++ b/drivers/net/wireless/ath/ath11k/reg.c
@@ -353,134 +353,6 @@ static u32 ath11k_map_fw_phy_flags(u32 p
return flags;
}
-static bool
-ath11k_reg_can_intersect(struct ieee80211_reg_rule *rule1,
- struct ieee80211_reg_rule *rule2)
-{
- u32 start_freq1, end_freq1;
- u32 start_freq2, end_freq2;
-
- start_freq1 = rule1->freq_range.start_freq_khz;
- start_freq2 = rule2->freq_range.start_freq_khz;
-
- end_freq1 = rule1->freq_range.end_freq_khz;
- end_freq2 = rule2->freq_range.end_freq_khz;
-
- if ((start_freq1 >= start_freq2 &&
- start_freq1 < end_freq2) ||
- (start_freq2 > start_freq1 &&
- start_freq2 < end_freq1))
- return true;
-
- /* TODO: Should we restrict intersection feasibility
- * based on min bandwidth of the intersected region also,
- * say the intersected rule should have a min bandwidth
- * of 20MHz?
- */
-
- return false;
-}
-
-static void ath11k_reg_intersect_rules(struct ieee80211_reg_rule *rule1,
- struct ieee80211_reg_rule *rule2,
- struct ieee80211_reg_rule *new_rule)
-{
- u32 start_freq1, end_freq1;
- u32 start_freq2, end_freq2;
- u32 freq_diff, max_bw;
-
- start_freq1 = rule1->freq_range.start_freq_khz;
- start_freq2 = rule2->freq_range.start_freq_khz;
-
- end_freq1 = rule1->freq_range.end_freq_khz;
- end_freq2 = rule2->freq_range.end_freq_khz;
-
- new_rule->freq_range.start_freq_khz = max_t(u32, start_freq1,
- start_freq2);
- new_rule->freq_range.end_freq_khz = min_t(u32, end_freq1, end_freq2);
-
- freq_diff = new_rule->freq_range.end_freq_khz -
- new_rule->freq_range.start_freq_khz;
- max_bw = min_t(u32, rule1->freq_range.max_bandwidth_khz,
- rule2->freq_range.max_bandwidth_khz);
- new_rule->freq_range.max_bandwidth_khz = min_t(u32, max_bw, freq_diff);
-
- new_rule->power_rule.max_antenna_gain =
- min_t(u32, rule1->power_rule.max_antenna_gain,
- rule2->power_rule.max_antenna_gain);
-
- new_rule->power_rule.max_eirp = min_t(u32, rule1->power_rule.max_eirp,
- rule2->power_rule.max_eirp);
-
- /* Use the flags of both the rules */
- new_rule->flags = rule1->flags | rule2->flags;
-
- if ((rule1->flags & NL80211_RRF_PSD) && (rule2->flags & NL80211_RRF_PSD))
- new_rule->psd = min_t(s8, rule1->psd, rule2->psd);
- else
- new_rule->flags &= ~NL80211_RRF_PSD;
-
- /* To be safe, lts use the max cac timeout of both rules */
- new_rule->dfs_cac_ms = max_t(u32, rule1->dfs_cac_ms,
- rule2->dfs_cac_ms);
-}
-
-static struct ieee80211_regdomain *
-ath11k_regd_intersect(struct ieee80211_regdomain *default_regd,
- struct ieee80211_regdomain *curr_regd)
-{
- u8 num_old_regd_rules, num_curr_regd_rules, num_new_regd_rules;
- struct ieee80211_reg_rule *old_rule, *curr_rule, *new_rule;
- struct ieee80211_regdomain *new_regd = NULL;
- u8 i, j, k;
-
- num_old_regd_rules = default_regd->n_reg_rules;
- num_curr_regd_rules = curr_regd->n_reg_rules;
- num_new_regd_rules = 0;
-
- /* Find the number of intersecting rules to allocate new regd memory */
- for (i = 0; i < num_old_regd_rules; i++) {
- old_rule = default_regd->reg_rules + i;
- for (j = 0; j < num_curr_regd_rules; j++) {
- curr_rule = curr_regd->reg_rules + j;
-
- if (ath11k_reg_can_intersect(old_rule, curr_rule))
- num_new_regd_rules++;
- }
- }
-
- if (!num_new_regd_rules)
- return NULL;
-
- new_regd = kzalloc(sizeof(*new_regd) + (num_new_regd_rules *
- sizeof(struct ieee80211_reg_rule)),
- GFP_ATOMIC);
-
- if (!new_regd)
- return NULL;
-
- /* We set the new country and dfs region directly and only trim
- * the freq, power, antenna gain by intersecting with the
- * default regdomain. Also MAX of the dfs cac timeout is selected.
- */
- new_regd->n_reg_rules = num_new_regd_rules;
- memcpy(new_regd->alpha2, curr_regd->alpha2, sizeof(new_regd->alpha2));
- new_regd->dfs_region = curr_regd->dfs_region;
- new_rule = new_regd->reg_rules;
-
- for (i = 0, k = 0; i < num_old_regd_rules; i++) {
- old_rule = default_regd->reg_rules + i;
- for (j = 0; j < num_curr_regd_rules; j++) {
- curr_rule = curr_regd->reg_rules + j;
-
- if (ath11k_reg_can_intersect(old_rule, curr_rule))
- ath11k_reg_intersect_rules(old_rule, curr_rule,
- (new_rule + k++));
- }
- }
- return new_regd;
-}
-
static const char *
ath11k_reg_get_regdom_str(enum nl80211_dfs_regions dfs_region)
{
@@ -631,11 +503,11 @@ ath11k_reg_ap_pwr_convert(enum ieee80211
struct ieee80211_regdomain *
ath11k_reg_build_regd(struct ath11k_base *ab,
- struct cur_regulatory_info *reg_info, bool intersect,
+ struct cur_regulatory_info *reg_info,
enum wmi_vdev_type vdev_type,
enum ieee80211_ap_reg_power power_type)
{
- struct ieee80211_regdomain *tmp_regd, *default_regd, *new_regd = NULL;
+ struct ieee80211_regdomain *new_regd = NULL;
struct cur_reg_rule *reg_rule, *reg_rule_6ghz;
u8 i = 0, j = 0, k = 0;
u8 num_rules;
@@ -678,26 +550,26 @@ ath11k_reg_build_regd(struct ath11k_base
}
if (!num_rules)
- goto ret;
+ return new_regd;
/* Add max additional rules to accommodate weather radar band */
if (reg_info->dfs_region == ATH11K_DFS_REG_ETSI)
num_rules += 2;
- tmp_regd = kzalloc(sizeof(*tmp_regd) +
+ new_regd = kzalloc(sizeof(*new_regd) +
(num_rules * sizeof(struct ieee80211_reg_rule)),
GFP_ATOMIC);
- if (!tmp_regd)
- goto ret;
+ if (!new_regd)
+ return new_regd;
- memcpy(tmp_regd->alpha2, reg_info->alpha2, REG_ALPHA2_LEN + 1);
+ memcpy(new_regd->alpha2, reg_info->alpha2, REG_ALPHA2_LEN + 1);
memcpy(alpha2, reg_info->alpha2, REG_ALPHA2_LEN + 1);
alpha2[2] = '\0';
- tmp_regd->dfs_region = ath11k_map_fw_dfs_region(reg_info->dfs_region);
+ new_regd->dfs_region = ath11k_map_fw_dfs_region(reg_info->dfs_region);
ath11k_dbg(ab, ATH11K_DBG_REG,
"Country %s, CFG Regdomain %s FW Regdomain %d, num_reg_rules %d\n",
- alpha2, ath11k_reg_get_regdom_str(tmp_regd->dfs_region),
+ alpha2, ath11k_reg_get_regdom_str(new_regd->dfs_region),
reg_info->dfs_region, num_rules);
/* Update reg_rules[] below. Firmware is expected to
* send these rules in order(2 GHz rules first and then 5 GHz)
@@ -736,7 +608,7 @@ ath11k_reg_build_regd(struct ath11k_base
flags |= ath11k_map_fw_reg_flags(reg_rule->flags);
flags |= ath11k_map_fw_phy_flags(reg_info->phybitmap);
- ath11k_reg_update_rule(tmp_regd->reg_rules + i,
+ ath11k_reg_update_rule(new_regd->reg_rules + i,
reg_rule->start_freq,
reg_rule->end_freq, max_bw,
reg_rule->ant_gain, reg_rule->reg_power,
@@ -751,7 +623,7 @@ ath11k_reg_build_regd(struct ath11k_base
reg_info->dfs_region == ATH11K_DFS_REG_ETSI &&
(reg_rule->end_freq > ETSI_WEATHER_RADAR_BAND_LOW &&
reg_rule->start_freq < ETSI_WEATHER_RADAR_BAND_HIGH)){
- ath11k_reg_update_weather_radar_band(ab, tmp_regd,
+ ath11k_reg_update_weather_radar_band(ab, new_regd,
reg_rule, &i,
flags, max_bw);
continue;
@@ -762,37 +634,20 @@ ath11k_reg_build_regd(struct ath11k_base
"\t%d. (%d - %d @ %d) (%d, %d) (%d ms) (FLAGS %d) (%d, %d)\n",
i + 1, reg_rule->start_freq, reg_rule->end_freq,
max_bw, reg_rule->ant_gain, reg_rule->reg_power,
- tmp_regd->reg_rules[i].dfs_cac_ms, flags,
+ new_regd->reg_rules[i].dfs_cac_ms, flags,
reg_rule->psd_flag, reg_rule->psd_eirp);
} else {
ath11k_dbg(ab, ATH11K_DBG_REG,
"\t%d. (%d - %d @ %d) (%d, %d) (%d ms) (FLAGS %d)\n",
i + 1, reg_rule->start_freq, reg_rule->end_freq,
max_bw, reg_rule->ant_gain, reg_rule->reg_power,
- tmp_regd->reg_rules[i].dfs_cac_ms,
+ new_regd->reg_rules[i].dfs_cac_ms,
flags);
}
}
- tmp_regd->n_reg_rules = i;
+ new_regd->n_reg_rules = i;
- if (intersect) {
- default_regd = ab->default_regd[reg_info->phy_id];
-
- /* Get a new regd by intersecting the received regd with
- * our default regd.
- */
- new_regd = ath11k_regd_intersect(default_regd, tmp_regd);
- kfree(tmp_regd);
- if (!new_regd) {
- ath11k_warn(ab, "Unable to create intersected regdomain\n");
- goto ret;
- }
- } else {
- new_regd = tmp_regd;
- }
-
-ret:
return new_regd;
}
@@ -844,17 +699,6 @@ void ath11k_regd_update_chan_list_work(s
}
}
-static bool ath11k_reg_is_world_alpha(char *alpha)
-{
- if (alpha[0] == '0' && alpha[1] == '0')
- return true;
-
- if (alpha[0] == 'n' && alpha[1] == 'a')
- return true;
-
- return false;
-}
-
static enum wmi_vdev_type ath11k_reg_get_ar_vdev_type(struct ath11k *ar)
{
struct ath11k_vif *arvif;
@@ -877,7 +721,6 @@ int ath11k_reg_handle_chan_list(struct a
enum ieee80211_ap_reg_power power_type)
{
struct ieee80211_regdomain *regd;
- bool intersect = false;
int pdev_idx;
struct ath11k *ar;
enum wmi_vdev_type vdev_type;
@@ -929,24 +772,14 @@ int ath11k_reg_handle_chan_list(struct a
(char *)reg_info->alpha2, 2))
goto retfail;
- /* Intersect new rules with default regd if a new country setting was
- * requested, i.e a default regd was already set during initialization
- * and the regd coming from this event has a valid country info.
- */
- if (ab->default_regd[pdev_idx] &&
- !ath11k_reg_is_world_alpha((char *)
- ab->default_regd[pdev_idx]->alpha2) &&
- !ath11k_reg_is_world_alpha((char *)reg_info->alpha2))
- intersect = true;
-
ar = ab->pdevs[pdev_idx].ar;
vdev_type = ath11k_reg_get_ar_vdev_type(ar);
ath11k_dbg(ab, ATH11K_DBG_WMI,
- "wmi handle chan list power type %d vdev type %d intersect %d\n",
- power_type, vdev_type, intersect);
+ "wmi handle chan list power type %d vdev type %d\n",
+ power_type, vdev_type);
- regd = ath11k_reg_build_regd(ab, reg_info, intersect, vdev_type, power_type);
+ regd = ath11k_reg_build_regd(ab, reg_info, vdev_type, power_type);
if (!regd) {
ath11k_warn(ab, "failed to build regd from reg_info\n");
goto fallback;
--- a/drivers/net/wireless/ath/ath11k/reg.h
+++ b/drivers/net/wireless/ath/ath11k/reg.h
@@ -36,7 +36,7 @@ void ath11k_regd_update_work(struct work
void ath11k_regd_update_chan_list_work(struct work_struct *work);
struct ieee80211_regdomain *
ath11k_reg_build_regd(struct ath11k_base *ab,
- struct cur_regulatory_info *reg_info, bool intersect,
+ struct cur_regulatory_info *reg_info,
enum wmi_vdev_type vdev_type,
enum ieee80211_ap_reg_power power_type);
int ath11k_regd_update(struct ath11k *ar);
@@ -0,0 +1,26 @@
From a3be23672b4a81256d275af31afc6edcce5c5a26 Mon Sep 17 00:00:00 2001
From: Mantas Pucka <mantas@8devices.com>
Date: Mon, 22 Jan 2024 11:38:28 +0200
Subject: [PATCH] wifi: ath11k: disable coldboot for ipq6018
Coldboot calibration does not work at the moment and causes failure during
wifi startup.
Signed-off-by: Mantas Pucka <mantas@8devices.com>
---
drivers/net/wireless/ath/ath11k/core.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
--- a/drivers/net/wireless/ath/ath11k/core.c
+++ b/drivers/net/wireless/ath/ath11k/core.c
@@ -173,8 +173,8 @@ static struct ath11k_hw_params ath11k_hw
.supports_shadow_regs = false,
.idle_ps = false,
.supports_sta_ps = false,
- .coldboot_cal_mm = true,
- .coldboot_cal_ftm = true,
+ .coldboot_cal_mm = false,
+ .coldboot_cal_ftm = false,
.cbcal_restart_fw = true,
.fw_mem_mode = 0,
.num_vdevs = 16 + 1,
@@ -0,0 +1,27 @@
From b2d16b688ce04b67f2033f90f49f4add7ebd3fe8 Mon Sep 17 00:00:00 2001
From: George Moussalem <george.moussalem@outlook.com>
Date: Tue, 10 Jun 2025 14:34:37 +0400
Subject: [PATCH] wifi: ath11k: disable coldboot calibration for ipq5018
Coldboot calibration does not work causes the firmware to crash during
wifi startup. So let's disable coldboot calibration until a solution is
found.
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
drivers/net/wireless/ath/ath11k/core.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
--- a/drivers/net/wireless/ath/ath11k/core.c
+++ b/drivers/net/wireless/ath/ath11k/core.c
@@ -700,8 +700,8 @@ static struct ath11k_hw_params ath11k_hw
.supports_suspend = false,
.hal_params = &ath11k_hw_hal_params_ipq8074,
.single_pdev_only = false,
- .coldboot_cal_mm = true,
- .coldboot_cal_ftm = true,
+ .coldboot_cal_mm = false,
+ .coldboot_cal_ftm = false,
.cbcal_restart_fw = true,
.fix_l1ss = true,
.supports_dynamic_smps_6ghz = false,
@@ -0,0 +1,153 @@
From: Ziyang Huang <hzyitc@outlook.com>
Date: Thu, 2 May 2024 00:14:31 +0800
Subject: [PATCH] wifi: ath11k: fix remapped ce accessing issue on 64bit OS
On 64bit OS, when ab->mem_ce is lower than or 4G far away from ab->mem,
u32 is not enough to store the offsets, which makes ath11k_ahb_read32()
and ath11k_ahb_write32() access incorrect address and causes Data Abort
Exception.
Let's use the high bits of offsets to decide where to access, which is
similar as ath11k_pci_get_window_start() done. In the future, we can merge
these functions for unified regs accessing.
Signed-off-by: Ziyang Huang <hzyitc@outlook.com>
---
--- a/drivers/net/wireless/ath/ath11k/ahb.c
+++ b/drivers/net/wireless/ath/ath11k/ahb.c
@@ -198,12 +198,18 @@ static const struct ath11k_pci_ops ath11
static inline u32 ath11k_ahb_read32(struct ath11k_base *ab, u32 offset)
{
- return ioread32(ab->mem + offset);
+ if ((offset & ATH11K_REG_TYPE_MASK) == ATH11K_REG_TYPE_CE)
+ return ioread32(ab->mem_ce + FIELD_GET(ATH11K_REG_OFFSET_MASK, offset));
+ else
+ return ioread32(ab->mem + FIELD_GET(ATH11K_REG_OFFSET_MASK, offset));
}
static inline void ath11k_ahb_write32(struct ath11k_base *ab, u32 offset, u32 value)
{
- iowrite32(value, ab->mem + offset);
+ if ((offset & ATH11K_REG_TYPE_MASK) == ATH11K_REG_TYPE_CE)
+ iowrite32(value, ab->mem_ce + FIELD_GET(ATH11K_REG_OFFSET_MASK, offset));
+ else
+ iowrite32(value, ab->mem + FIELD_GET(ATH11K_REG_OFFSET_MASK, offset));
}
static void ath11k_ahb_kill_tasklets(struct ath11k_base *ab)
@@ -275,9 +281,9 @@ static void ath11k_ahb_ce_irq_enable(str
const struct ce_ie_addr *ce_ie_addr = ab->hw_params.ce_ie_addr;
u32 ie1_reg_addr, ie2_reg_addr, ie3_reg_addr;
- ie1_reg_addr = ce_ie_addr->ie1_reg_addr + ATH11K_CE_OFFSET(ab);
- ie2_reg_addr = ce_ie_addr->ie2_reg_addr + ATH11K_CE_OFFSET(ab);
- ie3_reg_addr = ce_ie_addr->ie3_reg_addr + ATH11K_CE_OFFSET(ab);
+ ie1_reg_addr = ce_ie_addr->ie1_reg_addr;
+ ie2_reg_addr = ce_ie_addr->ie2_reg_addr;
+ ie3_reg_addr = ce_ie_addr->ie3_reg_addr;
ce_attr = &ab->hw_params.host_ce_config[ce_id];
if (ce_attr->src_nentries)
@@ -296,9 +302,9 @@ static void ath11k_ahb_ce_irq_disable(st
const struct ce_ie_addr *ce_ie_addr = ab->hw_params.ce_ie_addr;
u32 ie1_reg_addr, ie2_reg_addr, ie3_reg_addr;
- ie1_reg_addr = ce_ie_addr->ie1_reg_addr + ATH11K_CE_OFFSET(ab);
- ie2_reg_addr = ce_ie_addr->ie2_reg_addr + ATH11K_CE_OFFSET(ab);
- ie3_reg_addr = ce_ie_addr->ie3_reg_addr + ATH11K_CE_OFFSET(ab);
+ ie1_reg_addr = ce_ie_addr->ie1_reg_addr;
+ ie2_reg_addr = ce_ie_addr->ie2_reg_addr;
+ ie3_reg_addr = ce_ie_addr->ie3_reg_addr;
ce_attr = &ab->hw_params.host_ce_config[ce_id];
if (ce_attr->src_nentries)
--- a/drivers/net/wireless/ath/ath11k/hal.c
+++ b/drivers/net/wireless/ath/ath11k/hal.c
@@ -1274,20 +1274,16 @@ static int ath11k_hal_srng_create_config
s->reg_start[1] = HAL_SEQ_WCSS_UMAC_TCL_REG + HAL_TCL_STATUS_RING_HP;
s = &hal->srng_config[HAL_CE_SRC];
- s->reg_start[0] = HAL_SEQ_WCSS_UMAC_CE0_SRC_REG(ab) + HAL_CE_DST_RING_BASE_LSB +
- ATH11K_CE_OFFSET(ab);
- s->reg_start[1] = HAL_SEQ_WCSS_UMAC_CE0_SRC_REG(ab) + HAL_CE_DST_RING_HP +
- ATH11K_CE_OFFSET(ab);
+ s->reg_start[0] = HAL_SEQ_WCSS_UMAC_CE0_SRC_REG(ab) + HAL_CE_DST_RING_BASE_LSB;
+ s->reg_start[1] = HAL_SEQ_WCSS_UMAC_CE0_SRC_REG(ab) + HAL_CE_DST_RING_HP;
s->reg_size[0] = HAL_SEQ_WCSS_UMAC_CE1_SRC_REG(ab) -
HAL_SEQ_WCSS_UMAC_CE0_SRC_REG(ab);
s->reg_size[1] = HAL_SEQ_WCSS_UMAC_CE1_SRC_REG(ab) -
HAL_SEQ_WCSS_UMAC_CE0_SRC_REG(ab);
s = &hal->srng_config[HAL_CE_DST];
- s->reg_start[0] = HAL_SEQ_WCSS_UMAC_CE0_DST_REG(ab) + HAL_CE_DST_RING_BASE_LSB +
- ATH11K_CE_OFFSET(ab);
- s->reg_start[1] = HAL_SEQ_WCSS_UMAC_CE0_DST_REG(ab) + HAL_CE_DST_RING_HP +
- ATH11K_CE_OFFSET(ab);
+ s->reg_start[0] = HAL_SEQ_WCSS_UMAC_CE0_DST_REG(ab) + HAL_CE_DST_RING_BASE_LSB;
+ s->reg_start[1] = HAL_SEQ_WCSS_UMAC_CE0_DST_REG(ab) + HAL_CE_DST_RING_HP;
s->reg_size[0] = HAL_SEQ_WCSS_UMAC_CE1_DST_REG(ab) -
HAL_SEQ_WCSS_UMAC_CE0_DST_REG(ab);
s->reg_size[1] = HAL_SEQ_WCSS_UMAC_CE1_DST_REG(ab) -
@@ -1295,9 +1291,8 @@ static int ath11k_hal_srng_create_config
s = &hal->srng_config[HAL_CE_DST_STATUS];
s->reg_start[0] = HAL_SEQ_WCSS_UMAC_CE0_DST_REG(ab) +
- HAL_CE_DST_STATUS_RING_BASE_LSB + ATH11K_CE_OFFSET(ab);
- s->reg_start[1] = HAL_SEQ_WCSS_UMAC_CE0_DST_REG(ab) + HAL_CE_DST_STATUS_RING_HP +
- ATH11K_CE_OFFSET(ab);
+ HAL_CE_DST_STATUS_RING_BASE_LSB;
+ s->reg_start[1] = HAL_SEQ_WCSS_UMAC_CE0_DST_REG(ab) + HAL_CE_DST_STATUS_RING_HP;
s->reg_size[0] = HAL_SEQ_WCSS_UMAC_CE1_DST_REG(ab) -
HAL_SEQ_WCSS_UMAC_CE0_DST_REG(ab);
s->reg_size[1] = HAL_SEQ_WCSS_UMAC_CE1_DST_REG(ab) -
--- a/drivers/net/wireless/ath/ath11k/hw.c
+++ b/drivers/net/wireless/ath/ath11k/hw.c
@@ -2268,9 +2268,9 @@ const struct ce_ie_addr ath11k_ce_ie_add
};
const struct ce_ie_addr ath11k_ce_ie_addr_ipq5018 = {
- .ie1_reg_addr = CE_HOST_IPQ5018_IE_ADDRESS - HAL_IPQ5018_CE_WFSS_REG_BASE,
- .ie2_reg_addr = CE_HOST_IPQ5018_IE_2_ADDRESS - HAL_IPQ5018_CE_WFSS_REG_BASE,
- .ie3_reg_addr = CE_HOST_IPQ5018_IE_3_ADDRESS - HAL_IPQ5018_CE_WFSS_REG_BASE,
+ .ie1_reg_addr = ATH11K_REG_TYPE_CE + CE_HOST_IPQ5018_IE_ADDRESS - HAL_IPQ5018_CE_WFSS_REG_BASE,
+ .ie2_reg_addr = ATH11K_REG_TYPE_CE + CE_HOST_IPQ5018_IE_2_ADDRESS - HAL_IPQ5018_CE_WFSS_REG_BASE,
+ .ie3_reg_addr = ATH11K_REG_TYPE_CE + CE_HOST_IPQ5018_IE_3_ADDRESS - HAL_IPQ5018_CE_WFSS_REG_BASE,
};
const struct ce_remap ath11k_ce_remap_ipq5018 = {
@@ -2801,13 +2801,13 @@ const struct ath11k_hw_regs ipq5018_regs
.hal_reo_status_hp = 0x00003070,
/* WCSS relative address */
- .hal_seq_wcss_umac_ce0_src_reg = 0x08400000
+ .hal_seq_wcss_umac_ce0_src_reg = ATH11K_REG_TYPE_CE + 0x08400000
- HAL_IPQ5018_CE_WFSS_REG_BASE,
- .hal_seq_wcss_umac_ce0_dst_reg = 0x08401000
+ .hal_seq_wcss_umac_ce0_dst_reg = ATH11K_REG_TYPE_CE + 0x08401000
- HAL_IPQ5018_CE_WFSS_REG_BASE,
- .hal_seq_wcss_umac_ce1_src_reg = 0x08402000
+ .hal_seq_wcss_umac_ce1_src_reg = ATH11K_REG_TYPE_CE + 0x08402000
- HAL_IPQ5018_CE_WFSS_REG_BASE,
- .hal_seq_wcss_umac_ce1_dst_reg = 0x08403000
+ .hal_seq_wcss_umac_ce1_dst_reg = ATH11K_REG_TYPE_CE + 0x08403000
- HAL_IPQ5018_CE_WFSS_REG_BASE,
/* WBM Idle address */
--- a/drivers/net/wireless/ath/ath11k/hw.h
+++ b/drivers/net/wireless/ath/ath11k/hw.h
@@ -81,7 +81,12 @@
#define ATH11K_M3_FILE "m3.bin"
#define ATH11K_REGDB_FILE_NAME "regdb.bin"
-#define ATH11K_CE_OFFSET(ab) (ab->mem_ce - ab->mem)
+#define ATH11K_REG_TYPE_MASK GENMASK(31, 28)
+#define ATH11K_REG_TYPE(x) FIELD_PREP_CONST(ATH11K_REG_TYPE_MASK, x)
+#define ATH11K_REG_TYPE_NORMAL ATH11K_REG_TYPE(0)
+#define ATH11K_REG_TYPE_DP ATH11K_REG_TYPE(1)
+#define ATH11K_REG_TYPE_CE ATH11K_REG_TYPE(2)
+#define ATH11K_REG_OFFSET_MASK GENMASK(27, 0)
enum ath11k_hw_rate_cck {
ATH11K_HW_RATE_CCK_LP_11M = 0,
@@ -0,0 +1,120 @@
From: George Moussalem <george.moussalem@outlook.com>
Date: Wed, 27 Oct 2024 16:34:11 +0400
Subject: [PATCH] wifi: ath11k: add hw params for QCN6122
Add QCN6122 platform support.
QCN6122 is a PCIe based solution that is attached to and enumerated
by the WPSS (Wireless Processor SubSystem) Q6 processor.
Though it is a PCIe device, since it is not attached to APSS processor
(Application Processor SubSystem), APSS will be unaware of such a decice
and hence it is registered to the APSS processor as a platform device(AHB).
Because of this hybrid nature, it is called as a hybrid bus device.
As such, QCN6122 is a hybrid bus type device and follows the same codepath
as for WCN6750.
This is a heavily simplified version of below downstream patch:
Download from https://git.codelinaro.org/clo/qsdk/oss/system/feeds/wlan-open/-/blob/NHSS.QSDK.12.4.5.r2/mac80211/patches/232-ath11k-qcn6122-support.patch
Co-developed-by: George Moussalem <george.moussalem@outlook.com>
Signed-off-by: Sowmiya Sree Elavalagan <ssreeela@codeaurora.org>
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
--- a/drivers/net/wireless/ath/ath11k/core.c
+++ b/drivers/net/wireless/ath/ath11k/core.c
@@ -907,6 +907,67 @@ static struct ath11k_hw_params ath11k_hw
.support_dual_stations = true,
.pdev_suspend = false,
},
+ {
+ .hw_rev = ATH11K_HW_QCN6122_HW10,
+ .name = "qcn6122 hw1.0",
+ .fw = {
+ .dir = "QCN6122/hw1.0",
+ .board_size = 256 * 1024,
+ .cal_offset = 128 * 1024,
+ },
+ .hal_params = &ath11k_hw_hal_params_ipq8074,
+ .max_radios = MAX_RADIOS_5018,
+ .bdf_addr = 0x4D200000,
+ .hw_ops = &ipq5018_ops,
+ .hal_desc_sz = sizeof(struct hal_rx_desc_qcn9074),
+ .qmi_service_ins_id = ATH11K_QMI_WLFW_SERVICE_INS_ID_V01_QCN6122,
+ .interface_modes = BIT(NL80211_IFTYPE_STATION) |
+ BIT(NL80211_IFTYPE_AP) |
+ BIT(NL80211_IFTYPE_MESH_POINT),
+ .spectral = {
+ .fft_sz = 2,
+ .fft_pad_sz = 0,
+ .summary_pad_sz = 16,
+ .fft_hdr_len = 24,
+ .max_fft_bins = 1024,
+ },
+ .credit_flow = false,
+ .max_tx_ring = 1,
+ .supports_monitor = true,
+ .supports_shadow_regs = false,
+ .idle_ps = false,
+ .supports_suspend = false,
+ .host_ce_config = ath11k_host_ce_config_qcn9074,
+ .ce_count = CE_CNT_5018,
+ .target_ce_config = ath11k_target_ce_config_wlan_ipq5018,
+ .target_ce_count = TARGET_CE_CNT_5018,
+ .svc_to_ce_map = ath11k_target_service_to_ce_map_wlan_ipq5018,
+ .svc_to_ce_map_len = SVC_CE_MAP_LEN_5018,
+ .single_pdev_only = false,
+ .rxdma1_enable = true,
+ .num_rxdma_per_pdev = RXDMA_PER_PDEV_5018,
+ .rx_mac_buf_ring = false,
+ .vdev_start_delay = false,
+ .htt_peer_map_v2 = true,
+ .coldboot_cal_mm = false,
+ .coldboot_cal_ftm = false,
+ .cbcal_restart_fw = true,
+ .fix_l1ss = true,
+ .alloc_cacheable_memory = true,
+ .m3_fw_support = false,
+ .fixed_bdf_addr = true,
+ .fixed_mem_region = true,
+ .static_window_map = true,
+ .hybrid_bus_type = true,
+ .fw_mem_mode = 1,
+ .supports_sta_ps = false,
+ .dbr_debug_support = true,
+ .bios_sar_capa = NULL,
+ .fixed_fw_mem = false,
+ .support_off_channel_tx = false,
+ .tcl_ring_retry = true,
+ .tx_ring_size = DP_TCL_DATA_RING_SIZE,
+ },
};
static const struct dmi_system_id ath11k_pm_quirk_table[] = {
--- a/drivers/net/wireless/ath/ath11k/core.h
+++ b/drivers/net/wireless/ath/ath11k/core.h
@@ -152,6 +152,7 @@ enum ath11k_hw_rev {
ATH11K_HW_IPQ5018_HW10,
ATH11K_HW_QCA2066_HW21,
ATH11K_HW_QCA6698AQ_HW21,
+ ATH11K_HW_QCN6122_HW10,
};
enum ath11k_firmware_mode {
--- a/drivers/net/wireless/ath/ath11k/qmi.h
+++ b/drivers/net/wireless/ath/ath11k/qmi.h
@@ -22,10 +22,11 @@
#define ATH11K_QMI_WLFW_SERVICE_INS_ID_V01_IPQ8074 0x02
#define ATH11K_QMI_WLFW_SERVICE_INS_ID_V01_QCN9074 0x07
#define ATH11K_QMI_WLFW_SERVICE_INS_ID_V01_WCN6750 0x03
+#define ATH11K_QMI_WLFW_SERVICE_INS_ID_V01_QCN6122 0x40
#define ATH11K_QMI_WLANFW_MAX_TIMESTAMP_LEN_V01 32
#define ATH11K_QMI_RESP_LEN_MAX 8192
#define ATH11K_QMI_WLANFW_MAX_NUM_MEM_SEG_V01 52
-#define ATH11K_QMI_CALDB_SIZE 0x480000
+#define ATH11K_QMI_CALDB_SIZE 0x500000
#define ATH11K_QMI_BDF_EXT_STR_LENGTH 0x20
#define ATH11K_QMI_FW_MEM_REQ_SEGMENT_CNT 5
@@ -0,0 +1,114 @@
From: George Moussalem <george.moussalem@outlook.com>
Date: Wed, 27 Oct 2024 16:34:11 +0400
Subject: [PATCH] wifi: ath11k: add hal regs for QCN6122
Add HAL changes required to support QCN6122. Offsets are similar to those of
WCN6750 but QCN6122 does not use the hal_shadow_base_addr, so add platform
specific ath11k_hw_regs and register them in hw params.
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
--- a/drivers/net/wireless/ath/ath11k/core.c
+++ b/drivers/net/wireless/ath/ath11k/core.c
@@ -920,6 +920,7 @@ static struct ath11k_hw_params ath11k_hw
.bdf_addr = 0x4D200000,
.hw_ops = &ipq5018_ops,
.hal_desc_sz = sizeof(struct hal_rx_desc_qcn9074),
+ .regs = &qcn6122_regs,
.qmi_service_ins_id = ATH11K_QMI_WLFW_SERVICE_INS_ID_V01_QCN6122,
.interface_modes = BIT(NL80211_IFTYPE_STATION) |
BIT(NL80211_IFTYPE_AP) |
--- a/drivers/net/wireless/ath/ath11k/hw.c
+++ b/drivers/net/wireless/ath/ath11k/hw.c
@@ -2822,6 +2822,81 @@ const struct ath11k_hw_regs ipq5018_regs
.hal_wbm1_release_ring_base_lsb = 0x0000097c,
};
+const struct ath11k_hw_regs qcn6122_regs = {
+ /* SW2TCL(x) R0 ring configuration address */
+ .hal_tcl1_ring_base_lsb = 0x00000694,
+ .hal_tcl1_ring_base_msb = 0x00000698,
+ .hal_tcl1_ring_id = 0x0000069c,
+ .hal_tcl1_ring_misc = 0x000006a4,
+ .hal_tcl1_ring_tp_addr_lsb = 0x000006b0,
+ .hal_tcl1_ring_tp_addr_msb = 0x000006b4,
+ .hal_tcl1_ring_consumer_int_setup_ix0 = 0x000006c4,
+ .hal_tcl1_ring_consumer_int_setup_ix1 = 0x000006c8,
+ .hal_tcl1_ring_msi1_base_lsb = 0x000006dc,
+ .hal_tcl1_ring_msi1_base_msb = 0x000006e0,
+ .hal_tcl1_ring_msi1_data = 0x000006e4,
+ .hal_tcl2_ring_base_lsb = 0x000006ec,
+ .hal_tcl_ring_base_lsb = 0x0000079c,
+
+ /* TCL STATUS ring address */
+ .hal_tcl_status_ring_base_lsb = 0x000008a4,
+
+ /* REO2SW(x) R0 ring configuration address */
+ .hal_reo1_ring_base_lsb = 0x000001ec,
+ .hal_reo1_ring_base_msb = 0x000001f0,
+ .hal_reo1_ring_id = 0x000001f4,
+ .hal_reo1_ring_misc = 0x000001fc,
+ .hal_reo1_ring_hp_addr_lsb = 0x00000200,
+ .hal_reo1_ring_hp_addr_msb = 0x00000204,
+ .hal_reo1_ring_producer_int_setup = 0x00000210,
+ .hal_reo1_ring_msi1_base_lsb = 0x00000234,
+ .hal_reo1_ring_msi1_base_msb = 0x00000238,
+ .hal_reo1_ring_msi1_data = 0x0000023c,
+ .hal_reo2_ring_base_lsb = 0x00000244,
+ .hal_reo1_aging_thresh_ix_0 = 0x00000564,
+ .hal_reo1_aging_thresh_ix_1 = 0x00000568,
+ .hal_reo1_aging_thresh_ix_2 = 0x0000056c,
+ .hal_reo1_aging_thresh_ix_3 = 0x00000570,
+
+ /* REO2SW(x) R2 ring pointers (head/tail) address */
+ .hal_reo1_ring_hp = 0x00003028,
+ .hal_reo1_ring_tp = 0x0000302c,
+ .hal_reo2_ring_hp = 0x00003030,
+
+ /* REO2TCL R0 ring configuration address */
+ .hal_reo_tcl_ring_base_lsb = 0x000003fc,
+ .hal_reo_tcl_ring_hp = 0x00003058,
+
+ /* SW2REO ring address */
+ .hal_sw2reo_ring_base_lsb = 0x0000013c,
+ .hal_sw2reo_ring_hp = 0x00003018,
+
+ /* REO CMD ring address */
+ .hal_reo_cmd_ring_base_lsb = 0x000000e4,
+ .hal_reo_cmd_ring_hp = 0x00003010,
+
+ /* REO status address */
+ .hal_reo_status_ring_base_lsb = 0x00000504,
+ .hal_reo_status_hp = 0x00003070,
+
+ /* WCSS relative address */
+ .hal_seq_wcss_umac_ce0_src_reg = 0x01b80000,
+ .hal_seq_wcss_umac_ce0_dst_reg = 0x01b81000,
+ .hal_seq_wcss_umac_ce1_src_reg = 0x01b82000,
+ .hal_seq_wcss_umac_ce1_dst_reg = 0x01b83000,
+
+ /* WBM Idle address */
+ .hal_wbm_idle_link_ring_base_lsb = 0x00000874,
+ .hal_wbm_idle_link_ring_misc = 0x00000884,
+
+ /* SW2WBM release address */
+ .hal_wbm_release_ring_base_lsb = 0x000001ec,
+
+ /* WBM2SW release address */
+ .hal_wbm0_release_ring_base_lsb = 0x00000924,
+ .hal_wbm1_release_ring_base_lsb = 0x0000097c,
+};
+
const struct ath11k_hw_hal_params ath11k_hw_hal_params_ipq8074 = {
.rx_buf_rbm = HAL_RX_BUF_RBM_SW3_BM,
.tcl2wbm_rbm_map = ath11k_hw_tcl2wbm_rbm_map_ipq8074,
--- a/drivers/net/wireless/ath/ath11k/hw.h
+++ b/drivers/net/wireless/ath/ath11k/hw.h
@@ -426,6 +426,7 @@ extern const struct ath11k_hw_regs qcn90
extern const struct ath11k_hw_regs wcn6855_regs;
extern const struct ath11k_hw_regs wcn6750_regs;
extern const struct ath11k_hw_regs ipq5018_regs;
+extern const struct ath11k_hw_regs qcn6122_regs;
static inline const char *ath11k_bd_ie_type_str(enum ath11k_bd_ie_type type)
{

Some files were not shown because too many files have changed in this diff Show More