💋 Sync 2026-09-20 23:11:08

This commit is contained in:
github-actions[bot] committed 2026-09-20 23:11:08 +08:00
1 parent 8df28edee1
commit ab8e741007
59 files changed
+3502 -928

No files matched your search

+2 -2
View File
@@ -5,8 +5,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=BitSrunLoginGo
PKG_VERSION:=1.6.7
PKG_RELEASE:=6
PKG_VERSION:=1.6.8
PKG_RELEASE:=7
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/Mmx233/BitSrunLoginGo/tar.gz/v$(PKG_VERSION)?
+3 -3
View File
@@ -5,10 +5,10 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=dae
PKG_VERSION:=2026.09.12
PKG_RELEASE:=44
PKG_VERSION:=2026.09.18
PKG_RELEASE:=45
PKG_SOURCE:=dae-src-2026.09.12-187058462a1f.tar.gz
PKG_SOURCE:=dae-src-2026.09.18-dc29e60bab23.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=skip
File diff suppressed because it is too large. Load diff
+7 -12
View File
@@ -1,24 +1,19 @@
--- a/trace/kern/trace.c
+++ b/trace/kern/trace.c
@@ -228,7 +228,7 @@ KPROBE_SKB_AT(1)
@@ -452,7 +452,6 @@ KPROBE_SKB_AT(1)
KPROBE_SKB_AT(2)
KPROBE_SKB_AT(3)
KPROBE_SKB_AT(4)
-KPROBE_SKB_AT(5)
+//KPROBE_SKB_AT(5)
SEC("kprobe/skb_lifetime_termination")
int kprobe_skb_lifetime_termination(struct pt_regs *ctx)
--- a/trace/trace.go
+++ b/trace/trace.go
@@ -212,8 +212,8 @@ func attachBpfToTargets(objs *bpfObjects
kp, err = link.Kprobe(fn, objs.KprobeSkb3, nil)
case 4:
kp, err = link.Kprobe(fn, objs.KprobeSkb4, nil)
- case 5:
- kp, err = link.Kprobe(fn, objs.KprobeSkb5, nil)
+ //case 5:
+ // kp, err = link.Kprobe(fn, objs.KprobeSkb5, nil)
@@ -191,7 +191,6 @@ func attachBpfToTargets(objs *bpfObjects, targets map[string]int) (links []link.Link, err error) {
1: objs.KprobeSkb1,
2: objs.KprobeSkb2,
3: objs.KprobeSkb3,
4: objs.KprobeSkb4,
- 5: objs.KprobeSkb5,
}
if err != nil {
logrus.Debugf("failed to attach kprobe to %s: %+v\n", fn, err)
+3 -3
View File
@@ -5,10 +5,10 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=daed
PKG_VERSION:=2026.09.12
PKG_RELEASE:=55
PKG_VERSION:=2026.09.18
PKG_RELEASE:=56
PKG_SOURCE:=daed-src-2026.09.12-a0181f729855.tar.gz
PKG_SOURCE:=daed-src-2026.09.18-0d4c9d0c95b9.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=skip
@@ -0,0 +1,837 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: kenzok8 <kenzok8@gmail.com>
Date: Sun, 20 Sep 2026 16:59:23 +0800
Subject: [PATCH] Adapt wing to new dae core
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
改动:适配新核心生命周期,并修复空配置停止和重载回滚。
原因:旧接口删除且新控制面交接流程已改变。
---
dae-core/config/marshal.go | 5 +
dae/run.go | 402 +++++++++++++-----
dae/run_handoff_test.go | 33 ++
dae/runtime.go | 6 +-
dae/utils.go | 9 +-
db/node_utils.go | 3 +-
graphql/service/config/mutation_utils.go | 9 +-
graphql/service/node/latency_cache.go | 2 +-
.../service/node/latency_mutation_utils.go | 59 ++-
graphql/service/routing/resolver.go | 2 +-
10 files changed, 415 insertions(+), 115 deletions(-)
create mode 100644 dae/run_handoff_test.go
diff --git a/dae-core/config/marshal.go b/dae-core/config/marshal.go
index f68006a..69a5325 100644
--- a/dae-core/config/marshal.go
+++ b/dae-core/config/marshal.go
@@ -42,6 +42,11 @@ type Marshaller struct {
buf bytes.Buffer
}
+// Bytes returns the serialized section buffer without exposing mutable state.
+func (m *Marshaller) Bytes() []byte {
+ return m.buf.Bytes()
+}
+
func (m *Marshaller) writeLine(depth int, line string) {
if depth < 0 {
depth = 0
diff --git a/dae/run.go b/dae/run.go
index a0a665d..96b579c 100644
--- a/dae/run.go
+++ b/dae/run.go
@@ -6,6 +6,8 @@
package dae
import (
+ "context"
+ "errors"
"fmt"
"net/netip"
"runtime"
@@ -28,6 +30,22 @@ type ReloadMessage struct {
Callback chan<- error
}
+func closeReloadResources(closers ...func() error) error {
+ var errs error
+ for _, closeResource := range closers {
+ if closeResource != nil {
+ errs = errors.Join(errs, closeResource())
+ }
+ }
+ return errs
+}
+
+func notifyReloadCallback(callback chan<- error, err error) {
+ if callback != nil {
+ callback <- err
+ }
+}
+
var ChReloadConfigs = make(chan *ReloadMessage)
var GracefullyExit = make(chan struct{})
var EmptyConfig *daeConfig.Config
@@ -77,88 +95,115 @@ func Run(log *logrus.Logger, conf *daeConfig.Config, externGeoDataDirs []string,
case nil:
break dryLoop
default:
- newConf.Callback <- nil
+ notifyReloadCallback(newConf.Callback, nil)
}
}
return nil
}
// New c.
- c, err = newControlPlane(log, nil, nil, conf, externGeoDataDirs)
+ processSessions := control.NewSessionManager(context.Background())
+ defer func() { _ = processSessions.Close() }()
+ c, err = newControlPlane(context.Background(), log, nil, nil, conf, externGeoDataDirs, false)
if err != nil {
return err
}
+ if err = c.AttachSessionManager(processSessions); err != nil {
+ _ = c.Close()
+ return err
+ }
+ datapathDetached := false
+ if conf == EmptyConfig {
+ if err = c.DetachBpfHooks(); err != nil {
+ _ = c.Close()
+ return fmt.Errorf("detach initial empty datapath: %w", err)
+ }
+ datapathDetached = true
+ }
- // Serve tproxy TCP/UDP server util signals.
var listener *control.Listener
+ if err = control.GetDaeNetns().With(func() error {
+ var listenErr error
+ listener, listenErr = c.Listen(conf.Global.TproxyPort)
+ if listenErr != nil {
+ return listenErr
+ }
+ return listener.ValidateCurrentNetns()
+ }); err != nil {
+ if listener != nil {
+ _ = listener.Close()
+ }
+ _ = c.Close()
+ return fmt.Errorf("listen in dae netns: %w", err)
+ }
+ readyChan := make(chan bool, 1)
+ serveDone := make(chan error, 1)
+ go func() { serveDone <- c.Serve(readyChan, listener) }()
+ if !<-readyChan {
+ serveErr := <-serveDone
+ _ = closeReloadResources(listener.Close, c.Close)
+ if serveErr == nil {
+ serveErr = fmt.Errorf("initial control plane was not ready")
+ }
+ return serveErr
+ }
+ log.Infoln("Ready")
go func() {
- readyChan := make(chan bool, 1)
- go func() {
- <-readyChan
- log.Infoln("Ready")
- }()
- control.GetDaeNetns().With(func() error {
- if listener, err = c.ListenAndServe(readyChan, conf.Global.TproxyPort); err != nil {
- log.Errorln("ListenAndServe:", err)
- }
- return err
- })
- // Exit
+ if serveErr := <-serveDone; serveErr != nil {
+ log.WithError(serveErr).Errorln("control plane serve stopped")
+ }
ChReloadConfigs <- nil
}()
reloading := false
- /* dae-wing start */
- var errReload error
- var chCallback chan<- error
- /* dae-wing end */
loop:
for newReloadMsg := range ChReloadConfigs {
switch newReloadMsg {
case nil:
- /* dae-wing start */
- // We will receive nil after control plane being Closed.
- // We'll judge if we are in a reloading.
- /* dae-wing end */
-
if reloading {
- if listener == nil {
- // Failed to listen. Exit.
- break loop
- }
- // Serve.
+ // The old generation's Serve loop reports its shutdown after the
+ // candidate has already taken over the cloned listener.
reloading = false
- log.Warnln("[Reload] Serve")
- readyChan := make(chan bool, 1)
- go func() {
- if err := c.Serve(readyChan, listener); err != nil {
- log.Errorln("ListenAndServe:", err)
- }
- // Exit
- ChReloadConfigs <- nil
- }()
- <-readyChan
- log.Warnln("[Reload] Finished")
- /* dae-wing start */
- chCallback <- errReload
- /* dae-wing end */
+ continue
} else {
- // Listening error.
break loop
}
default:
// Reload signal.
log.Warnln("[Reload] Received reload signal; prepare to reload")
- /* dae-wing start */
newConf := newReloadMsg.Config
- /* dae-wing end */
-
- // openwrt-daede: dashboard stop reloads to EmptyConfig; detach hooks.
- if newConf == EmptyConfig {
- if e := c.DetachBpfHooks(); e != nil {
- log.Errorln("[Stop] DetachBpfHooks:", e)
+ if datapathDetached && newConf == EmptyConfig {
+ notifyReloadCallback(newReloadMsg.Callback, nil)
+ continue
+ }
+ oldC := c
+ wasStopped := datapathDetached
+ failReload := func(cause, cleanupErr error) error {
+ cause = errors.Join(cause, cleanupErr)
+ if cleanupErr == nil && wasStopped {
+ if detachErr := oldC.DetachBpfHooks(); detachErr != nil {
+ cause = errors.Join(cause, fmt.Errorf("restore stopped datapath: %w", detachErr))
+ cleanupErr = detachErr
+ } else {
+ datapathDetached = true
+ }
}
+ notifyReloadCallback(newReloadMsg.Callback, cause)
+ if cleanupErr != nil {
+ return cause
+ }
+ return nil
}
+ if wasStopped {
+ if restoreErr := oldC.RestoreDatapathForReloadRollback(); restoreErr != nil {
+ if fatalErr := failReload(fmt.Errorf("prepare stopped datapath: %w", restoreErr), nil); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ datapathDetached = false
+ }
+
// New logger.
oldLogOutput := log.Out
log = logrus.New()
@@ -167,65 +212,223 @@ loop:
log.SetOutput(oldLogOutput) // FIXME: THIS IS A HACK.
logrus.SetOutput(oldLogOutput)
- // New control plane.
- obj := c.EjectBpf()
var dnsCache map[string]*control.DnsCache
if conf.Dns.IpVersionPrefer == newConf.Dns.IpVersionPrefer {
// Only keep dns cache when ip version preference not change.
dnsCache = c.CloneDnsCache()
}
- log.Warnln("[Reload] Load new control plane")
- newC, err := newControlPlane(log, obj, dnsCache, newConf, externGeoDataDirs)
+ log.Warnln("[Reload] Prepare new control plane")
+ freshState, err := c.SnapshotFreshDatapathState()
if err != nil {
- /* dae-wing start */
- errReload = err
- /* dae-wing end */
-
- log.WithFields(logrus.Fields{
- "err": err,
- }).Errorln("[Reload] Failed to reload; try to roll back configuration")
- // Load last config back.
- newC, err = newControlPlane(log, obj, dnsCache, conf, externGeoDataDirs)
- if err != nil {
- obj.Close()
- c.Close()
- log.WithFields(logrus.Fields{
- "err": err,
- }).Fatalln("[Reload] Failed to roll back configuration")
+ if fatalErr := failReload(err, nil); fatalErr != nil {
+ return fatalErr
}
- newConf = conf
- log.Errorln("[Reload] Last reload failed; rolled back configuration")
- } else {
- log.Warnln("[Reload] Stopped old control plane")
-
- /* dae-wing start */
- errReload = nil
- /* dae-wing end */
+ continue
}
+ newC, err := newControlPlane(context.Background(), log, freshState, dnsCache, newConf, externGeoDataDirs, true)
+ if err == nil {
+ err = newC.AttachSessionManager(processSessions)
+ }
+ if err != nil {
+ var cleanupErr error
+ if newC != nil {
+ cleanupErr = newC.Close()
+ }
+ if fatalErr := failReload(err, cleanupErr); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ stagedListener, err := listener.Clone()
+ if err != nil {
+ if fatalErr := failReload(fmt.Errorf("clone listener: %w", err), newC.Close()); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ if err = oldC.StopDNSListener(); err != nil {
+ cleanupErr := closeReloadResources(stagedListener.Close, newC.Close, oldC.RestartDNSListener)
+ if fatalErr := failReload(fmt.Errorf("stop old DNS listener: %w", err), cleanupErr); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ dnsStopped := true
+ readyChan := make(chan bool, 1)
+ serveDone := make(chan error, 1)
+ go func() { serveDone <- newC.Serve(readyChan, stagedListener) }()
+ ready := <-readyChan
+ if !ready {
+ serveErr := <-serveDone
+ if serveErr == nil {
+ serveErr = fmt.Errorf("prepared control plane was not ready")
+ }
+ cleanupErr := closeReloadResources(
+ stagedListener.Close,
+ newC.Close,
+ oldC.RestartDNSListener,
+ )
+ dnsStopped = false
+ if fatalErr := failReload(serveErr, cleanupErr); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ hookPrepared := false
+ provisionalOwner := false
+ hookFlipCommitted := false
+ hookSetAdopted := false
+ flowDatapathAdopted := false
+ rollbackCleanupFailed := false
+ rollbackCandidate := func(cause error) error {
+ var rollbackErr error
+ addRollbackErr := func(err error) {
+ if err != nil {
+ rollbackCleanupFailed = true
+ rollbackErr = errors.Join(rollbackErr, err)
+ }
+ }
+ if flowDatapathAdopted {
+ addRollbackErr(oldC.AdoptProcessFlowDatapath(newC))
+ }
+ if hookSetAdopted {
+ if ownerErr := newC.RestorePreparedTCHookSet(oldC); ownerErr != nil {
+ rollbackCleanupFailed = true
+ return errors.Join(cause, rollbackErr, fmt.Errorf("restore TC HookSet owner: %w", ownerErr))
+ }
+ }
+ if hookFlipCommitted {
+ addRollbackErr(newC.RollbackPreparedBpfHookFlip())
+ }
+ if hookPrepared && !hookSetAdopted && !hookFlipCommitted {
+ addRollbackErr(newC.ClearPreparedTCHookHandoff())
+ }
+ if provisionalOwner {
+ newC.UnregisterProvisionalRoutingEpochExecutionOwner()
+ }
+ addRollbackErr(newC.DetachBpfHooks())
+ addRollbackErr(stagedListener.Close())
+ addRollbackErr(newC.Close())
+ if dnsStopped {
+ addRollbackErr(oldC.RestartDNSListener())
+ dnsStopped = false
+ }
+ return errors.Join(cause, rollbackErr)
+ }
+ if err = newC.PrepareTCHookHandoff(oldC); err != nil {
+ rollbackErr := rollbackCandidate(err)
+ if rollbackCleanupFailed {
+ notifyReloadCallback(newReloadMsg.Callback, rollbackErr)
+ return rollbackErr
+ }
+ if fatalErr := failReload(rollbackErr, nil); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ hookPrepared = true
+ if err = newC.RegisterProvisionalRoutingEpochExecutionOwner(); err != nil {
+ rollbackErr := rollbackCandidate(err)
+ if rollbackCleanupFailed {
+ notifyReloadCallback(newReloadMsg.Callback, rollbackErr)
+ return rollbackErr
+ }
+ if fatalErr := failReload(rollbackErr, nil); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ provisionalOwner = true
+ if err = newC.CommitPreparedBpfHookFlip(); err != nil {
+ rollbackErr := rollbackCandidate(err)
+ if rollbackCleanupFailed {
+ notifyReloadCallback(newReloadMsg.Callback, rollbackErr)
+ return rollbackErr
+ }
+ if fatalErr := failReload(rollbackErr, nil); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ hookFlipCommitted = true
+ if err = newC.AdoptPreparedTCHookSet(oldC); err != nil {
+ rollbackErr := rollbackCandidate(err)
+ if rollbackCleanupFailed {
+ notifyReloadCallback(newReloadMsg.Callback, rollbackErr)
+ return rollbackErr
+ }
+ if fatalErr := failReload(rollbackErr, nil); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ hookSetAdopted = true
+ if err = newC.AdoptProcessFlowDatapath(oldC); err != nil {
+ rollbackErr := rollbackCandidate(err)
+ if rollbackCleanupFailed {
+ notifyReloadCallback(newReloadMsg.Callback, rollbackErr)
+ return rollbackErr
+ }
+ if fatalErr := failReload(rollbackErr, nil); fatalErr != nil {
+ return fatalErr
+ }
+ continue
+ }
+ flowDatapathAdopted = true
+ newC.UnregisterProvisionalRoutingEpochExecutionOwner()
+ provisionalOwner = false
+ if err = newC.FinalizePreparedTCHooks(); err != nil {
+ fatalErr := errors.Join(fmt.Errorf("finalize prepared TC hooks: %w", err), closeReloadResources(
+ listener.Close,
+ stagedListener.Close,
+ newC.Close,
+ oldC.Close,
+ ))
+ notifyReloadCallback(newReloadMsg.Callback, fatalErr)
+ return fatalErr
+ }
+ hookPrepared = false
+ hookFlipCommitted = false
+ hookSetAdopted = false
- // Inject bpf objects into the new control plane life-cycle.
- newC.InjectBpf(obj)
-
- // Prepare new context.
- oldC := c
+ oldListener := listener
c = newC
conf = newConf
+ listener = stagedListener
+ datapathDetached = false
+ retireErr := closeReloadResources(oldListener.Close, oldC.Close)
+ if newConf == EmptyConfig {
+ if retireErr != nil {
+ fatalErr := errors.Join(fmt.Errorf("retire previous control plane: %w", retireErr), closeReloadResources(stagedListener.Close, newC.Close))
+ notifyReloadCallback(newReloadMsg.Callback, fatalErr)
+ return fatalErr
+ }
+ if detachErr := newC.DetachBpfHooks(); detachErr != nil {
+ fatalErr := errors.Join(fmt.Errorf("detach stopped datapath: %w", detachErr), closeReloadResources(stagedListener.Close, newC.Close))
+ notifyReloadCallback(newReloadMsg.Callback, fatalErr)
+ return fatalErr
+ }
+ datapathDetached = true
+ } else if retireErr != nil {
+ log.WithError(retireErr).Warnln("[Reload] Failed to close old control plane")
+ }
reloading = true
- /* dae-wing start */
- chCallback = newReloadMsg.Callback
- /* dae-wing end */
-
- // Ready to close.
- oldC.Close()
+ notifyReloadCallback(newReloadMsg.Callback, nil)
+ go func(done <-chan error) {
+ if serveErr := <-done; serveErr != nil {
+ log.WithError(serveErr).Errorln("control plane serve stopped")
+ }
+ ChReloadConfigs <- nil
+ }(serveDone)
}
}
- if e := c.Close(); e != nil {
+ if e := closeReloadResources(listener.Close, c.Close); e != nil {
return fmt.Errorf("close control plane: %w", e)
}
return nil
}
-func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*control.DnsCache, conf *daeConfig.Config, externGeoDataDirs []string) (c *control.ControlPlane, err error) {
+func newControlPlane(ctx context.Context, log *logrus.Logger, bpf interface{}, dnsCache map[string]*control.DnsCache, conf *daeConfig.Config, externGeoDataDirs []string, prepared bool) (c *control.ControlPlane, err error) {
// Print configuration.
if log.IsLevelEnabled(logrus.DebugLevel) {
@@ -239,10 +442,6 @@ func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*c
control.PurgeStaleTCFilters(log)
}
- // Init Direct Dialers.
- direct.InitDirectDialers(conf.Global.FallbackResolver)
- netutils.FallbackDns = netip.MustParseAddrPort(conf.Global.FallbackResolver)
-
if !conf.Global.DisableWaitingNetwork && len(conf.Global.WanInterface) > 0 {
// Wait for network for WAN ready.
if err = waitForNetwork(log); err != nil {
@@ -266,7 +465,10 @@ func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*c
}
// New dae control plane.
- c, err = control.NewControlPlane(
+ directDialers := direct.NewDirectDialers(conf.Global.FallbackResolver)
+ systemDNSResolver := netutils.NewSystemDNSResolver(netip.MustParseAddrPort(conf.Global.FallbackResolver))
+ c, err = control.NewControlPlaneWithContextOptions(
+ ctx,
log,
bpf,
dnsCache,
@@ -276,6 +478,14 @@ func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*c
&conf.Global,
&conf.Dns,
externGeoDataDirs,
+ control.ControlPlaneBuildOptions{
+ DelayDatapathCommit: prepared,
+ DelayDNSListenerStart: prepared,
+ IsReload: prepared || bpf != nil,
+ DirectDialer: directDialers.Symmetric,
+ FullconeDirectDialer: directDialers.Fullcone,
+ SystemDNSResolver: systemDNSResolver,
+ },
)
if err != nil {
return nil, err
diff --git a/dae/run_handoff_test.go b/dae/run_handoff_test.go
new file mode 100644
index 0000000..0ffb2ec
--- /dev/null
+++ b/dae/run_handoff_test.go
@@ -0,0 +1,33 @@
+package dae
+
+import (
+ "errors"
+ "reflect"
+ "testing"
+)
+
+func TestCloseReloadResourcesOrder(t *testing.T) {
+ var got []string
+ err := closeReloadResources(
+ func() error { got = append(got, "old-listener"); return nil },
+ func() error { got = append(got, "staged-listener"); return nil },
+ func() error { got = append(got, "new-control-plane"); return nil },
+ func() error { got = append(got, "old-control-plane"); return nil },
+ )
+ if err != nil {
+ t.Fatalf("closeReloadResources() error = %v", err)
+ }
+ want := []string{"old-listener", "staged-listener", "new-control-plane", "old-control-plane"}
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("close order = %v, want %v", got, want)
+ }
+}
+
+func TestCloseReloadResourcesJoinsErrors(t *testing.T) {
+ one := errors.New("old listener")
+ two := errors.New("old control plane")
+ err := closeReloadResources(func() error { return one }, func() error { return two })
+ if !errors.Is(err, one) || !errors.Is(err, two) {
+ t.Fatalf("joined error = %v, want both close errors", err)
+ }
+}
diff --git a/dae/runtime.go b/dae/runtime.go
index 2ab0d68..af46089 100644
--- a/dae/runtime.go
+++ b/dae/runtime.go
@@ -30,18 +30,16 @@ type RuntimeOverview struct {
}
func GetRuntimeOverview(windowSec int, maxPoints int) (*RuntimeOverview, error) {
- activeTCPConnections := 0
ctl, err := ControlPlane()
+ var snapshot control.RuntimeStatsSnapshot
if err != nil {
if !errors.Is(err, ErrControlPlaneNotInit) {
return nil, err
}
} else {
- activeTCPConnections = ctl.ActiveTCPConnections()
+ snapshot = ctl.SnapshotRuntimeStats(windowSec, maxPoints)
}
- snapshot := control.SnapshotRuntimeStats(activeTCPConnections, control.DefaultUdpEndpointPool.Count(), windowSec, maxPoints)
-
samples := make([]RuntimeTrafficSample, 0, len(snapshot.Samples))
for _, sample := range snapshot.Samples {
samples = append(samples, RuntimeTrafficSample{
diff --git a/dae/utils.go b/dae/utils.go
index dc43cbc..ca61c85 100644
--- a/dae/utils.go
+++ b/dae/utils.go
@@ -32,8 +32,11 @@ var (
EmptyGlobalSection = `global {}`
)
-func NecessaryOutbounds(routing *daeConfig.Routing) (outbounds []string) {
- f := daeConfig.FunctionOrStringToFunction(routing.Fallback)
+func NecessaryOutbounds(routing *daeConfig.Routing) (outbounds []string, err error) {
+ f, err := daeConfig.ParseFunctionOrString(routing.Fallback)
+ if err != nil {
+ return nil, err
+ }
outbounds = append(outbounds, f.Name)
for _, r := range routing.Rules {
outbound := r.Outbound.Name
@@ -42,7 +45,7 @@ func NecessaryOutbounds(routing *daeConfig.Routing) (outbounds []string) {
}
outbounds = append(outbounds, outbound)
}
- return daeCommon.Deduplicate(outbounds)
+ return daeCommon.Deduplicate(outbounds), nil
}
func ParseConfig(globalSection *string, dnsSection *string, routingSection *string) (*daeConfig.Config, error) {
diff --git a/db/node_utils.go b/db/node_utils.go
index b7b43b8..7ca8fc3 100644
--- a/db/node_utils.go
+++ b/db/node_utils.go
@@ -6,6 +6,7 @@
package db
import (
+ "context"
"strings"
"github.com/daeuniverse/dae-wing/common"
@@ -25,7 +26,7 @@ func NewNodeModel(link string, tag *string, subscriptionId *uint) (*Node, error)
}
_tag = *tag
}
- d, err := dialer.NewFromLink(&dialer.GlobalOption{
+ d, err := dialer.NewFromLinkContext(context.Background(), &dialer.GlobalOption{
Log: logrus.StandardLogger(),
}, dialer.InstanceOption{DisableCheck: false}, link, _tag)
if err != nil {
diff --git a/graphql/service/config/mutation_utils.go b/graphql/service/config/mutation_utils.go
index 59f0ebd..c7d6b78 100644
--- a/graphql/service/config/mutation_utils.go
+++ b/graphql/service/config/mutation_utils.go
@@ -298,7 +298,7 @@ func ApplyIfRunning(ctx context.Context) error {
func runLocked(ctx context.Context, noLoad bool) (n int32, err error) {
//// Dry run.
if noLoad {
- ch := make(chan error)
+ ch := make(chan error, 1)
reloadMsg := &dae.ReloadMessage{
Config: dae.EmptyConfig,
Callback: ch,
@@ -384,7 +384,10 @@ func runLocked(ctx context.Context, noLoad bool) (n int32, err error) {
}
/// Fill in necessary groups and nodes.
// Find groups needed by routing.
- outbounds := dae.NecessaryOutbounds(&c.Routing)
+ outbounds, err := dae.NecessaryOutbounds(&c.Routing)
+ if err != nil {
+ return 0, err
+ }
var groups []db.Group
q = d.Model(&db.Group{}).
Where("name in ?", outbounds).
@@ -523,7 +526,7 @@ func runLocked(ctx context.Context, noLoad bool) (n int32, err error) {
readTx = nil
/// Reload with current config.
- chReloadCallback := make(chan error)
+ chReloadCallback := make(chan error, 1)
reloadMsg := &dae.ReloadMessage{
Config: c,
Callback: chReloadCallback,
diff --git a/graphql/service/node/latency_cache.go b/graphql/service/node/latency_cache.go
index ba66794..e0ec91e 100644
--- a/graphql/service/node/latency_cache.go
+++ b/graphql/service/node/latency_cache.go
@@ -162,7 +162,7 @@ func refreshLatencyCache(ctx context.Context) error {
return err
}
- results := testLatencyResultsForNodes(option, nodes)
+ results := testLatencyResultsForNodes(ctx, option, nodes)
storeLatencyResults(results)
if ctl, err := dae.ControlPlane(); err == nil {
diff --git a/graphql/service/node/latency_mutation_utils.go b/graphql/service/node/latency_mutation_utils.go
index 056f9fa..5026b33 100644
--- a/graphql/service/node/latency_mutation_utils.go
+++ b/graphql/service/node/latency_mutation_utils.go
@@ -8,6 +8,8 @@ package node
import (
"context"
"io"
+ "net/netip"
+ "strings"
"sync"
"time"
@@ -15,6 +17,7 @@ import (
"github.com/daeuniverse/dae-wing/dae"
"github.com/daeuniverse/dae-wing/db"
dialer "github.com/daeuniverse/dae/component/outbound/dialer"
+ "github.com/daeuniverse/outbound/netproxy"
"github.com/graph-gophers/graphql-go"
"github.com/sirupsen/logrus"
)
@@ -32,12 +35,12 @@ func TestLatencies(ctx context.Context, ids *[]graphql.ID) ([]*LatencyResolver,
return nil, err
}
- results := testLatencyResultsForNodes(option, nodes)
+ results := testLatencyResultsForNodes(ctx, option, nodes)
storeLatencyResults(results)
return results, nil
}
-func testLatencyResultsForNodes(option *dialer.GlobalOption, nodes []db.Node) []*LatencyResolver {
+func testLatencyResultsForNodes(ctx context.Context, option *dialer.GlobalOption, nodes []db.Node) []*LatencyResolver {
results := make([]*LatencyResolver, len(nodes))
sem := make(chan struct{}, latencyProbeConcurrency)
var wg sync.WaitGroup
@@ -51,7 +54,7 @@ func testLatencyResultsForNodes(option *dialer.GlobalOption, nodes []db.Node) []
defer func() { <-sem }()
node := nodes[index]
- results[index] = testSingleNodeLatency(option, &node)
+ results[index] = testSingleNodeLatency(ctx, option, &node)
}()
}
@@ -93,14 +96,14 @@ func latencyProbeNodes(ctx context.Context, ids *[]graphql.ID) ([]db.Node, error
return nodes, nil
}
-func testSingleNodeLatency(option *dialer.GlobalOption, node *db.Node) *LatencyResolver {
+func testSingleNodeLatency(ctx context.Context, option *dialer.GlobalOption, node *db.Node) *LatencyResolver {
resolver := &LatencyResolver{
NodeID: node.ID,
AliveVal: false,
TestedAtV: time.Now(),
}
- d, err := dialer.NewFromLink(option, dialer.InstanceOption{DisableCheck: false}, node.Link, "")
+ d, err := dialer.NewFromLinkContext(ctx, option, dialer.InstanceOption{DisableCheck: false}, node.Link, "")
if err != nil {
msg := err.Error()
resolver.MessageV = &msg
@@ -108,7 +111,7 @@ func testSingleNodeLatency(option *dialer.GlobalOption, node *db.Node) *LatencyR
}
defer d.Close()
- result, err := d.ProbeLatencyFast()
+ result, err := probeLatencyFast(ctx, d)
if err != nil {
msg := err.Error()
resolver.MessageV = &msg
@@ -128,3 +131,47 @@ func testSingleNodeLatency(option *dialer.GlobalOption, node *db.Node) *LatencyR
}
return resolver
}
+
+func probeLatencyFast(ctx context.Context, d *dialer.Dialer) (*dialer.LatencyProbeResult, error) {
+ opt, err := d.TcpCheckOptionRaw.Option()
+ if err != nil {
+ return nil, err
+ }
+ network, err := netproxy.ParseMagicNetwork(d.TcpCheckOptionRaw.ResolverNetwork)
+ if err != nil {
+ return nil, err
+ }
+ result := &dialer.LatencyProbeResult{CheckedAt: time.Now()}
+ var messages []string
+ for _, probe := range []struct {
+ index int
+ ip netip.Addr
+ }{
+ {index: dialer.IdxTcp4, ip: opt.Ip4},
+ {index: dialer.IdxTcp6, ip: opt.Ip6},
+ } {
+ if !probe.ip.IsValid() {
+ continue
+ }
+ probeCtx, cancel := context.WithTimeout(ctx, 1500*time.Millisecond)
+ started := time.Now()
+ ok, probeErr := d.HttpCheck(probeCtx, probe.index, opt.Url, probe.ip, opt.Method, network.Mark, network.Mptcp)
+ cancel()
+ if ok {
+ latency := time.Since(started)
+ if !result.Alive || latency < result.Latency {
+ result.Latency = latency
+ }
+ result.Alive = true
+ } else if probeErr != nil {
+ messages = append(messages, probeErr.Error())
+ }
+ }
+ if !result.Alive {
+ result.Message = strings.Join(messages, "; ")
+ if result.Message == "" {
+ result.Message = "no latency result"
+ }
+ }
+ return result, nil
+}
diff --git a/graphql/service/routing/resolver.go b/graphql/service/routing/resolver.go
index 7822d64..3ed61aa 100644
--- a/graphql/service/routing/resolver.go
+++ b/graphql/service/routing/resolver.go
@@ -41,7 +41,7 @@ func (r *Resolver) Selected() bool {
return r.Model.Selected
}
-func (r *Resolver) ReferenceGroups() (outbounds []string) {
+func (r *Resolver) ReferenceGroups() (outbounds []string, err error) {
return dae.NecessaryOutbounds(r.DaeRouting)
}
+7 -12
View File
@@ -1,24 +1,19 @@
--- a/dae-core/trace/kern/trace.c
+++ b/dae-core/trace/kern/trace.c
@@ -228,7 +228,7 @@ KPROBE_SKB_AT(1)
@@ -452,7 +452,6 @@ KPROBE_SKB_AT(1)
KPROBE_SKB_AT(2)
KPROBE_SKB_AT(3)
KPROBE_SKB_AT(4)
-KPROBE_SKB_AT(5)
+//KPROBE_SKB_AT(5)
SEC("kprobe/skb_lifetime_termination")
int kprobe_skb_lifetime_termination(struct pt_regs *ctx)
--- a/dae-core/trace/trace.go
+++ b/dae-core/trace/trace.go
@@ -206,8 +206,8 @@ func attachBpfToTargets(objs *bpfObjects
kp, err = link.Kprobe(fn, objs.KprobeSkb3, nil)
case 4:
kp, err = link.Kprobe(fn, objs.KprobeSkb4, nil)
- case 5:
- kp, err = link.Kprobe(fn, objs.KprobeSkb5, nil)
+ //case 5:
+ // kp, err = link.Kprobe(fn, objs.KprobeSkb5, nil)
@@ -191,7 +191,6 @@ func attachBpfToTargets(objs *bpfObjects, targets map[string]int) (links []link.Link, err error) {
1: objs.KprobeSkb1,
2: objs.KprobeSkb2,
3: objs.KprobeSkb3,
4: objs.KprobeSkb4,
- 5: objs.KprobeSkb5,
}
if err != nil {
logrus.Debugf("failed to attach kprobe to %s: %+v\n", fn, err)
+2 -2
View File
@@ -6,8 +6,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=gowebdav
PKG_VERSION:=5.15.1
PKG_RELEASE:=21
PKG_VERSION:=5.16.0
PKG_RELEASE:=22
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/hacdias/webdav/tar.gz/v$(PKG_VERSION)?
+7 -5
View File
@@ -12,7 +12,7 @@ LUCI_DEPENDS:=+luci-base +logd +jsonfilter
LUCI_PKGARCH:=all
PKG_VERSION:=0.1.44
PKG_RELEASE:=26
PKG_RELEASE:=27
# Reproducible build: honor SOURCE_DATE_EPOCH from the build environment (set by docker-sdk.sh / CI).
ifdef SOURCE_DATE_EPOCH
@@ -22,12 +22,12 @@ endif
PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=Lucas Albers <lucas.b.albers@gmail.com>
include $(TOPDIR)/feeds/luci/luci.mk
define Package/luci-app-fwlive/prerm
#!/bin/sh
[ -n "$${IPKG_INSTROOT}" ] && exit 0
[ "$$1" = "remove" ] || exit 0
case "$${IPKG_INSTROOT}" in
''|/) ;;
*) exit 0 ;;
esac
. /usr/libexec/fwlive-logging.sh
# Best-effort restore: always exit 0 so a transient lock/pending-staging
# failure cannot strand the package half-removed. Baseline is kept on
@@ -37,4 +37,6 @@ restore_wan_log_baseline || logger -t fwlive "WAN log baseline restore failed du
exit 0
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
+1 -1
View File
@@ -38,7 +38,7 @@ No `luasrc/` — modern JS-only app.
- Optional reverse DNS uses BusyBox `nslookup` (stock image; not a package depend)
- No hard `firewall4` dependency
- Menu depends on ACL only (no `fs` AND of `nft`+`iptables` — that hid the entry on stock fw3 and fw4)
- Runtime backend detection selects **fw4/nft** (22.03+) or **iptables LOG** (21.02 fw3); best-effort iptables when nft absent
- Runtime backend detection selects **fw4/nft** on supported **23.05+** images; log lines tagged **iptables** still classify. OpenWrt **21.02** / **22.03** are unsupported
## Maintenance
@@ -16,7 +16,9 @@
* Modules must not mutate state. host is cleared then rebuilt (idempotent replace).
*/
function chipValueNodes(field, val) {
function chipValueNodes(spec, val) {
const key = spec.key;
const label = spec.label || key;
const p = log.parseFilterValue(val);
if (!p.value) return [''];
@@ -26,19 +28,19 @@ function chipValueNodes(field, val) {
return [
E('span', { 'class': 'fwlive-chip-polarity' }, [_('is')]),
' ',
log.formatFilterChipLabel(field, val)
log.formatFilterChipLabel(label, val)
];
}
if (field === 'q' || field === 'src' || field === 'dst')
if (key === 'q' || key === 'src' || key === 'dst')
return [
field + ': ',
label + ': ',
E('strong', { 'class': 'fwlive-chip-not' }, [_('not')]),
' contains ',
valueNode
];
return [field + ': ', E('strong', { 'class': 'fwlive-chip-not' }, [_('not')]), ' ', valueNode];
return [label + ': ', E('strong', { 'class': 'fwlive-chip-not' }, [_('not')]), ' ', valueNode];
}
function chipLeadingSym(negated) {
@@ -69,7 +71,7 @@ function renderFilterChips(host, state, callbacks) {
const lead = chipLeadingSym(negated);
if (lead) kids.push(lead);
kids.push(E('span', { 'class': 'fwlive-chip-label' }, chipValueNodes(spec.label, val)));
kids.push(E('span', { 'class': 'fwlive-chip-label' }, chipValueNodes(spec, val)));
kids.push(
E(
'span',
@@ -93,28 +93,19 @@ function addrFilterLink(field, ip, showHostnames, hostnameCache, onFilterClick)
);
}
/**
* @param {string} hint - rule hint token
* @param {string} firewallBackend - 'nft' or 'iptables'
*/
function ruleAdminPath(hint, firewallBackend) {
if (hint === 'fw4') return 'admin/network/firewall/rules';
if (firewallBackend === 'iptables') return 'admin/status/iptables';
return 'admin/status/nftables';
function ruleAdminPath() {
return 'admin/network/firewall/rules';
}
/**
* @param {string} hint - rule hint token
* @param {string} label - display label
* @param {string} firewallBackend - 'nft' or 'iptables'
* @param {function} onFilterClick - callback(field, value, ev)
*/
function ruleAdminLink(hint, label, firewallBackend, onFilterClick) {
function ruleAdminLink(hint, label, onFilterClick) {
if (!hint) return log.formatCell(hint);
const path = ruleAdminPath(hint, firewallBackend);
const path = ruleAdminPath();
const url = '%s#%s'.format(luciUrl(path), encodeURIComponent(hint));
const text = label || hint;
@@ -17,7 +17,7 @@
*
* renderManualTestNodes(host, state, callbacks) → void
* host - <ul> element inside #fwlive-help (cleared and rebuilt)
* state - { firewallBackend }
* state - unused; nft-only instruction
*
* Empty-state helpers:
* buildEmptyStateNodes(state, callbacks) → Node[]
@@ -330,21 +330,12 @@ function renderEmptyState(host, state, callbacks) {
}
/**
* renderManualTestNodes — fills a <li> host element with the backend-specific
* manual test instruction. Call from addFooter() after render() has inserted
* renderManualTestNodes — fills a <li> host element with the nft manual test
* instruction. Call from addFooter() after render() has inserted
* the placeholder <li id="fwlive-manual-test">.
*/
function renderManualTestNodes(host, state, _callbacks) {
function renderManualTestNodes(host, _state, _callbacks) {
host.innerHTML = '';
if (state.firewallBackend === 'iptables') {
host.appendChild(document.createTextNode(_('Manual test (System → Terminal): ')));
host.appendChild(
E('code', {}, [
'iptables -I INPUT -p icmp --icmp-type echo-request -j LOG --log-prefix "fwlive-ping: "'
])
);
host.appendChild(document.createTextNode(_(' then ping the router.')));
} else {
host.appendChild(document.createTextNode(_('Manual test (System → Terminal): ')));
host.appendChild(
E('code', {}, [
@@ -353,7 +344,6 @@ function renderManualTestNodes(host, state, _callbacks) {
);
host.appendChild(document.createTextNode(_(' then ping the router.')));
}
}
return baseclass.extend({
CONSENT_STORAGE_KEY: CONSENT_STORAGE_KEY,
@@ -17,7 +17,7 @@
* host - <tbody> element (kept; normal polls reuse keyed rows)
* state - shallow copy: { rows, columns, viewMode, messageLayout,
* expandedRowId, rowTint, showHostnames,
* hostnameCache, firewallBackend }
* hostnameCache }
* callbacks - { onRowClick(rowId, ev), onFilterClick(field, value, ev),
* actionRowTintClass(action) }
*
@@ -147,12 +147,7 @@ function buildColumnCell(col, row, state, callbacks) {
return E('td', { 'class': log.actionRowClass(row.action) }, [actionCell]);
case 'rule':
return E('td', { 'class': columnCellClass(col) }, [
links.ruleAdminLink(
row.rule_hint,
row.rule_label,
state.firewallBackend,
onFilterClick
)
links.ruleAdminLink(row.rule_hint, row.rule_label, onFilterClick)
]);
case 'iface':
return E('td', { 'class': columnCellClass(col) }, [
@@ -279,8 +274,7 @@ function rowRenderKey(row, state, columns) {
state.messageLayout,
state.expandedRowId === row.id,
!!state.rowTint,
!!state.showHostnames,
state.firewallBackend
!!state.showHostnames
]);
}
@@ -635,7 +635,6 @@ return view.extend({
},
backendDisplayLabel() {
if (this.firewallBackend === 'iptables') return _('using iptables');
if (this.firewallBackend === 'nft') return _('using fw4');
return '';
},
@@ -664,6 +663,13 @@ return view.extend({
text = text ? text + ' \u00b7 ' + warn : warn;
degraded = true;
}
if (warnings.indexOf('legacy_iptables_detected') >= 0) {
const warn = _(
'Live view may be incomplete — a legacy iptables table is registered'
);
text = text ? text + ' \u00b7 ' + warn : warn;
degraded = true;
}
label.textContent = text;
label.classList.toggle('fwlive-backend-warn', degraded);
}
@@ -1847,8 +1853,7 @@ return view.extend({
expandedRowId: this.expandedRowId,
rowTint: this.rowTintEnabled(),
showHostnames: !!this.showHostnames,
hostnameCache: this.hostnameCache,
firewallBackend: this.firewallBackend
hostnameCache: this.hostnameCache
},
{
onRowClick: (rowId, ev) => this.onRowClick(rowId, ev),
@@ -2471,8 +2476,7 @@ return view.extend({
this.updateTintWarnUi();
this.renderRows(true);
const testLi = document.getElementById('fwlive-manual-test');
if (testLi)
logging.renderManualTestNodes(testLi, { firewallBackend: this.firewallBackend }, {});
if (testLi) logging.renderManualTestNodes(testLi, {}, {});
if (this.showHostnames) this.resolveHostnamesForEntries(this.filteredRows());
}
});
+3 -3
View File
@@ -753,9 +753,9 @@ msgstr "turn it back off with the WAN logging on control on the watch strip."
msgid "using fw4"
msgstr "mit fw4"
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:510
msgid "using iptables"
msgstr "mit iptables"
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js
msgid "Live view may be incomplete — a legacy iptables table is registered"
msgstr "Live view may be incomplete — a legacy iptables table is registered"
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:89
msgid "· %s"
+3 -3
View File
@@ -758,9 +758,9 @@ msgstr "turn it back off with the WAN logging on control on the watch strip."
msgid "using fw4"
msgstr "используется fw4"
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:510
msgid "using iptables"
msgstr "используется iptables"
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js
msgid "Live view may be incomplete — a legacy iptables table is registered"
msgstr "Live view may be incomplete — a legacy iptables table is registered"
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:89
msgid "· %s"
@@ -687,8 +687,8 @@ msgstr ""
msgid "using fw4"
msgstr ""
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:510
msgid "using iptables"
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js
msgid "Live view may be incomplete — a legacy iptables table is registered"
msgstr ""
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:89
@@ -722,9 +722,9 @@ msgstr "turn it back off with the WAN logging on control on the watch strip."
msgid "using fw4"
msgstr "使用 fw4"
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js:510
msgid "using iptables"
msgstr "使用 iptables"
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/view/status/fwlive.js
msgid "Live view may be incomplete — a legacy iptables table is registered"
msgstr "Live view may be incomplete — a legacy iptables table is registered"
#: openwrt-feed/luci-app-fwlive/htdocs/luci-static/resources/fwlive/logging.js:89
msgid "· %s"
+8 -116
View File
@@ -39,8 +39,6 @@ RESOLVE_BUDGET=5
POLL_LINES_MAX=2000
# Bound nft ruleset dumps (corrupted/hung nft must not block LuCI rules load).
NFT_TIMEOUT=5
# CLI-only iptables fixture path for __rulesmap_iptables (no arbitrary paths).
RULESMAP_IPTABLES_FILE='/tmp/rulesmap'
# json_escape lives in fwlive-logging.sh so prerm can source that file alone.
# shellcheck disable=SC1090,SC1091 # LOGGING_SH resolves to sibling ../fwlive-logging.sh
@@ -55,9 +53,6 @@ slug_key() {
# Cap rules JSON size so a huge ruleset cannot unbound an rpcd reply.
RULES_MAP_MAX_KEYS=512
RULES_MAP_MAX_BYTES=65536
# Same wall-clock bound as nft for iptables-save / ip6tables-save.
# Overridable for host tests (e.g. FWLIVE_IPTABLES_TIMEOUT=1).
IPTABLES_TIMEOUT="${FWLIVE_IPTABLES_TIMEOUT:-5}"
map_add() {
key="$1"
@@ -147,25 +142,6 @@ run_with_timeout() {
fi
}
map_log_prefix_entry() {
prefix="$1"
comment="$2"
pass="${3:-all}"
label=''
if [ -n "$comment" ] && is_uci_style_name "$comment"; then
label="$comment"
fi
[ -n "$prefix" ] || return 0
case "$pass" in
labeled) [ -n "$label" ] || return 0 ;;
unlabeled) [ -z "$label" ] || return 0 ;;
esac
map_prefix_with_label "$prefix" "$label"
}
map_uci_rule_names() {
# Line-wise (here-doc, not an unquoted for-loop) so a name with
# spaces stays one token. Names that fail is_uci_style_name are
@@ -221,20 +197,6 @@ map_from_nft_stream() {
done
}
map_from_iptables_save_stream() {
pass="${1:-all}"
while IFS= read -r line; do
case "$line" in
-*) ;;
*) continue ;;
esac
prefix=$(echo "$line" | sed -n 's/.*--log-prefix "\([^"\\]*\(\\.[^"\\]*\)*\)".*/\1/p')
comment=$(echo "$line" | sed -n 's/.*--comment "\([^"\\]*\(\\.[^"\\]*\)*\)".*/\1/p')
[ -n "$prefix" ] || continue
map_log_prefix_entry "$prefix" "$comment" "$pass"
done
}
nft_list_ruleset() {
run_with_timeout "$NFT_TIMEOUT" nft list ruleset
}
@@ -244,10 +206,6 @@ detect_firewall_backend() {
printf '%s' nft
return 0
fi
if command -v iptables-save >/dev/null 2>&1; then
printf '%s' iptables
return 0
fi
printf '%s' unknown
}
@@ -282,57 +240,16 @@ _fwlive_mktemp() {
enrich_rules_dump() {
_dump_prefix="$1"
_dump_kind="$2"
case "$_dump_kind" in
nft)
_dump_error='nft_failed'
;;
iptables)
_dump_error='iptables_failed'
;;
ip6tables)
_dump_error='ip6tables_failed'
;;
*)
return 1
;;
esac
_rules_dump=$(_fwlive_mktemp "$_dump_prefix") || _rules_dump=''
if [ -n "$_rules_dump" ]; then
# Keep the dump command and stream parser paired by backend. The
# ip6tables caller is availability-gated below; its empty-error
# precedence is intentionally preserved in the mktemp branch.
if case "$_dump_kind" in
nft)
nft_list_ruleset >"$_rules_dump" 2>/dev/null
;;
iptables|ip6tables)
run_with_timeout "$IPTABLES_TIMEOUT" "${_dump_kind}-save" >"$_rules_dump" 2>/dev/null
;;
esac
then
case "$_dump_kind" in
nft)
if nft_list_ruleset >"$_rules_dump" 2>/dev/null; then
map_from_nft_stream labeled <"$_rules_dump"
map_from_nft_stream unlabeled <"$_rules_dump"
;;
iptables|ip6tables)
map_from_iptables_save_stream labeled <"$_rules_dump"
map_from_iptables_save_stream unlabeled <"$_rules_dump"
;;
esac
elif [ -z "$_enrich_error" ]; then
_enrich_error="$_dump_error"
_enrich_error='nft_failed'
fi
rm -f "$_rules_dump"
elif [ "$_dump_kind" = ip6tables ]; then
# IPv6 enrichment is optional and must not replace an earlier
# IPv4/backend error when its temp file cannot be allocated.
if [ -z "$_enrich_error" ]; then
_enrich_error='mktemp_failed'
fi
else
_enrich_error='mktemp_failed'
fi
@@ -349,17 +266,9 @@ build_rules_map() {
# First-wins keeps UCI over nft and !fw4: over cosmetic.
map_uci_rule_names
case "$backend" in
nft)
enrich_rules_dump fwlive-nft nft
;;
iptables)
enrich_rules_dump fwlive-ipt iptables
if command -v ip6tables-save >/dev/null 2>&1; then
enrich_rules_dump fwlive-ip6t ip6tables
if [ "$backend" = nft ]; then
enrich_rules_dump fwlive-nft
fi
;;
esac
if [ "$backend" = unknown ] && [ -z "$_enrich_error" ]; then
_enrich_error='no_backend'
@@ -373,22 +282,6 @@ build_rules_map() {
printf '%s}' "$_rules_json"
}
rulesmap_from_iptables_file() {
file="$1"
OUT=''
_map_keys=0
_map_overflow=''
[ "$file" = "$RULESMAP_IPTABLES_FILE" ] || return 1
[ -f "$file" ] || return 1
map_from_iptables_save_stream labeled < "$file"
map_from_iptables_save_stream unlabeled < "$file"
_rules_json="{\"backend\":\"iptables\",\"rules\":{$OUT}"
if [ -n "$_map_overflow" ]; then
_rules_json="${_rules_json},\"error\":\"rules_truncated\""
fi
printf '%s}' "$_rules_json"
}
poll_clamp_lines() {
# Contract: non-digit input returns default 50; digit strings are
# clamped to 1..POLL_LINES_MAX with leading zeros stripped. Validates
@@ -927,11 +820,6 @@ case "${1:-}" in
run_selftest
exit $?
;;
__rulesmap_iptables)
# CLI selftest only — never a ubus method. Fixed path only (no argv file read).
rulesmap_from_iptables_file "$RULESMAP_IPTABLES_FILE"
exit $?
;;
__poll_clamp)
poll_clamp_lines "${2:-}"
exit $?
@@ -988,4 +876,8 @@ case "${1:-}" in
;;
esac
;;
*)
# Removed CLI hooks (including __rulesmap_iptables) must not succeed.
exit 1
;;
esac
+1 -1
View File
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.9.16
PKG_RELEASE:=284
PKG_RELEASE:=285
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
@@ -96,6 +96,7 @@ if load_urltest_options then -- [[ URLTest Start ]]
o = s:option(MultiValue, "urltest_node", translate("URLTest node list"), translate("List of nodes to test, <a target='_blank' href='https://sing-box.sagernet.org/configuration/outbound/urltest'>document</a>"))
o:depends({ node_add_mode = "manual" })
o.widget = "checkbox"
o.cast = "table"
o.template = m:template_path("/cbi/nodes_multivalue")
o.group = {}
for k1, v1 in pairs(node_list) do
@@ -106,30 +107,6 @@ if load_urltest_options then -- [[ URLTest Start ]]
end
end
end
-- 读取旧 DynamicList
function o.cfgvalue(self, section)
return table.concat(m:get(section, "urltest_node") or {}, " ")
end
-- 写入保持 DynamicList
function o.write(self, section, value)
local old = m:get(section, "urltest_node") or {}
local new, set = {}, {}
for v in value:gmatch("%S+") do
new[#new + 1] = v
set[v] = 1
end
for _, v in ipairs(old) do
if not set[v] then
m:set(section, "urltest_node", new)
return
end
set[v] = nil
end
for _ in pairs(set) do
m:set(section, "urltest_node", new)
return
end
end
o = s:option(MultiValue, "node_group", translate("Select Group"))
o:depends({ node_add_mode = "batch" })
@@ -90,6 +90,7 @@ if load_balancing_options then -- [[ Load balancing Start ]]
o = s:option(MultiValue, "balancing_node", translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://xtls.github.io/config/routing.html#balancerobject'>document</a>"))
o:depends({ node_add_mode = "manual" })
o.widget = "checkbox"
o.cast = "table"
o.template = m:template_path("/cbi/nodes_multivalue")
o.group = {}
for k1, v1 in pairs(node_list) do
@@ -100,30 +101,6 @@ if load_balancing_options then -- [[ Load balancing Start ]]
end
end
end
-- 读取旧 DynamicList
function o.cfgvalue(self, section)
return table.concat(m:get(section, "balancing_node") or {}, " ")
end
-- 写入保持 DynamicList
function o.write(self, section, value)
local old = m:get(section, "balancing_node") or {}
local new, set = {}, {}
for v in value:gmatch("%S+") do
new[#new + 1] = v
set[v] = 1
end
for _, v in ipairs(old) do
if not set[v] then
m:set(section, "balancing_node", new)
return
end
set[v] = nil
end
for _ in pairs(set) do
m:set(section, "balancing_node", new)
return
end
end
o = s:option(MultiValue, "node_group", translate("Select Group"))
o:depends({ node_add_mode = "batch" })
@@ -36,7 +36,7 @@ o = s:option(Flag, "firewall_allow", translate("Firewall Allow"))
o.default = "0"
o = s:option(Value, "firewall_allow_src", translate("Source zone"))
o.rmempty = false
o.rmempty = not m.is_js_luci
o.nocreate = true
o.allowany = true
o.default = "wan"
@@ -50,7 +50,7 @@ o.validate = function(self, value)
if v then return v end
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end
o.cfgvalue = function(self, section, value)
o.cfgvalue = function(self, section)
local config_str = m:get(section, "config_str")
if config_str then
return api.base64Decode(config_str)
@@ -79,7 +79,8 @@ o = s:option(Value, "port", translate("Listen Port"))
o.datatype = "port"
o:depends({ custom = false })
o = s:option(DynamicList, "users", translate("User"))
o = s:option(MultiValue, "users", translate("User"))
o.cast = "table"
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
@@ -535,7 +536,7 @@ o.default = "0"
o:depends({ custom = false })
o = s:option(Value, "firewall_allow_src", translate("Source zone"))
o.rmempty = false
o.rmempty = not m.is_js_luci
o.nocreate = true
o.allowany = true
o.default = "wan"
@@ -46,7 +46,7 @@ o.validate = function(self, value)
if v then return v end
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end
o.cfgvalue = function(self, section, value)
o.cfgvalue = function(self, section)
local config_str = m:get(section, "config_str")
if config_str then
return api.base64Decode(config_str)
@@ -72,7 +72,8 @@ o = s:option(Value, "port", translate("Listen Port"))
o.datatype = "port"
o:depends({ custom = false })
o = s:option(DynamicList, "users", translate("User"))
o = s:option(MultiValue, "users", translate("User"))
o.cast = "table"
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
@@ -521,7 +522,7 @@ o.default = "0"
o:depends({ custom = false })
o = s:option(Value, "firewall_allow_src", translate("Source zone"))
o.rmempty = false
o.rmempty = not m.is_js_luci
o.nocreate = true
o.allowany = true
o.default = "wan"
@@ -41,7 +41,7 @@ o.validate = function(self, value)
if v then return v end
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end
o.cfgvalue = function(self, section, value)
o.cfgvalue = function(self, section)
local config_str = m:get(section, "config_str")
if config_str then
return api.base64Decode(config_str)
@@ -79,7 +79,7 @@ o.default = "0"
o:depends({ custom = false })
o = s:option(Value, "firewall_allow_src", translate("Source zone"))
o.rmempty = false
o.rmempty = not m.is_js_luci
o.nocreate = true
o.allowany = true
o.default = "wan"
@@ -54,7 +54,7 @@ o.validate = function(self, value)
if v then return v end
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end
o.cfgvalue = function(self, section, value)
o.cfgvalue = function(self, section)
local config_str = m:get(section, "config_str")
if config_str then
return api.base64Decode(config_str)
@@ -110,7 +110,7 @@ o.default = "0"
o:depends({ custom = false })
o = s:option(Value, "firewall_allow_src", translate("Source zone"))
o.rmempty = false
o.rmempty = not m.is_js_luci
o.nocreate = true
o.allowany = true
o.default = "wan"
@@ -31,7 +31,7 @@ o.validate = function(self, value)
if v then return v end
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end
o.cfgvalue = function(self, section, value)
o.cfgvalue = function(self, section)
local config_str = m:get(section, "config_str")
if config_str then
return api.base64Decode(config_str)
@@ -45,7 +45,8 @@ o = s:option(Value, "port", translate("Listen Port"))
o.datatype = "port"
o:depends({ custom = false })
o = s:option(DynamicList, "users", translate("User"))
o = s:option(MultiValue, "users", translate("User"))
o.cast = "table"
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
@@ -168,7 +169,7 @@ o.default = "0"
o:depends({ custom = false })
o = s:option(Value, "firewall_allow_src", translate("Source zone"))
o.rmempty = false
o.rmempty = not m.is_js_luci
o.nocreate = true
o.allowany = true
o.default = "wan"
+26 -3
View File
@@ -19,7 +19,7 @@ OPENWRT_ARCH = nil
DISTRIB_ARCH = nil
OPENWRT_BOARD = nil
LOCK_PREFIX = "/tmp/lock/" .. c_config
LOCK_PREFIX = "/var/lock/" .. c_config
LOG_FILE = "/tmp/log/" .. c_config .. ".log"
TMP_PATH = "/tmp/etc/" .. c_config
CACHE_PATH = TMP_PATH .. "_tmp"
@@ -1542,11 +1542,18 @@ function set_type_cbi(s)
obj.option_prefix = s_self.option_prefix
obj.option = s_self.option_prefix .. option
obj.cfgvalue = function(self, section)
local v
if self.rewrite_option then
return self.map:get(section, self.rewrite_option)
v = self.map:get(section, self.rewrite_option)
else
return self.map:get(section, self.config_option)
v = self.map:get(section, self.config_option)
end
if util.instanceof(self, cbi.MultiValue) then
if v and self.cast == "table" then
return table.concat(v, " ")
end
end
return v
end
obj.write = function(self, section, value)
if s1.fields["type"]:formvalue(s_self.section) == s_self.type_name then
@@ -1564,6 +1571,21 @@ function set_type_cbi(s)
new_val = new_t
end
end
if util.instanceof(self, cbi.MultiValue) then
local new_t = {}
if type(value) == "table" then
new_t = table_remove_duplicates(value)
else
string.gsub(value, '[^' .. " " .. ']+', function(v)
new_t[#new_t + 1] = v
end)
end
if self.cast == "string" then
new_val = table.concat(new_t, " ")
else
new_val = new_t
end
end
if self.rewrite_option then
self.map:set(section, self.rewrite_option, new_val)
else
@@ -1619,6 +1641,7 @@ end
function type_cbi_section(s, s2)
for i, v in ipairs(s2.children) do
local o = s2.children[i]
o.section = s
s:append(o)
s.fields[o.option] = o
end
@@ -118,8 +118,16 @@ function copy_instance(var)
local TMP_DNSMASQ_PATH = var["-TMP_DNSMASQ_PATH"]
local conf_lines = {}
local DEFAULT_DNSMASQ_CFGID = sys.exec("echo -n $(uci -q show dhcp.@dnsmasq[0] | awk 'NR==1 {split($0, conf, /[.=]/); print conf[2]}')")
if fs.access("/var/etc/dnsmasq.conf." .. DEFAULT_DNSMASQ_CFGID) then
for line in io.lines("/var/etc/dnsmasq.conf." .. DEFAULT_DNSMASQ_CFGID) do
local conf_file = "/var/etc/dnsmasq.conf." .. DEFAULT_DNSMASQ_CFGID
local retry = 5
while not fs.access(conf_file) and retry > 0 do
api.nixio.nanosleep(1, 0)
retry = retry - 1
end
if fs.access(conf_file) then
for line in io.lines(conf_file) do
local filter
if line:find("passwall") then filter = true end
if line:find("ubus") then filter = true end
@@ -139,7 +147,10 @@ function copy_instance(var)
tinsert(conf_lines, line)
end
end
else
sys.call("logger -t passwall 'ERROR: dnsmasq config " .. conf_file .. " not found after 5s wait! DNS hijacking will fail.'")
end
tinsert(conf_lines, "port=" .. LISTEN_PORT)
if TMP_DNSMASQ_PATH then
sys.call("rm -rf " .. TMP_DNSMASQ_PATH .. "/*passwall*")
@@ -6,7 +6,7 @@ CONFIG=passwall
APP_PATH=/usr/share/${CONFIG}
TMP_PATH=/tmp/etc/${CONFIG}
TMP_PATH2=${TMP_PATH}_tmp
LOCK_PATH=/tmp/lock
LOCK_PATH=/var/lock
LOG_FILE=/tmp/log/${CONFIG}.log
TMP_ACL_PATH=${TMP_PATH}/acl
TMP_BIN_PATH=${TMP_PATH}/bin
+1 -1
View File
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2
PKG_VERSION:=26.9.16
PKG_RELEASE:=130
PKG_RELEASE:=131
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
@@ -99,6 +99,7 @@ if load_urltest_options then -- [[ URLTest Start ]]
o = s:option(MultiValue, "urltest_node", translate("URLTest node list"), translate("List of nodes to test, <a target='_blank' href='https://sing-box.sagernet.org/configuration/outbound/urltest'>document</a>"))
o:depends({ node_add_mode = "manual" })
o.widget = "checkbox"
o.cast = "table"
o.template = m:template_path("/cbi/nodes_multivalue")
o.group = {}
for k1, v1 in pairs(node_list) do
@@ -109,28 +110,6 @@ if load_urltest_options then -- [[ URLTest Start ]]
end
end
end
function o.cfgvalue(self, section)
return table.concat(m:get(section, "urltest_node") or {}, " ")
end
function o.write(self, section, value)
local old = m:get(section, "urltest_node") or {}
local new, set = {}, {}
for v in value:gmatch("%S+") do
new[#new + 1] = v
set[v] = 1
end
for _, v in ipairs(old) do
if not set[v] then
m:set(section, "urltest_node", new)
return
end
set[v] = nil
end
for _ in pairs(set) do
m:set(section, "urltest_node", new)
return
end
end
o = s:option(MultiValue, "node_group", translate("Select Group"))
o:depends({ node_add_mode = "batch" })
@@ -91,6 +91,7 @@ if load_balancing_options then -- [[ Load balancing Start ]]
o = s:option(MultiValue, "balancing_node", translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://xtls.github.io/config/routing.html#balancerobject'>document</a>"))
o:depends({ node_add_mode = "manual" })
o.widget = "checkbox"
o.cast = "table"
o.template = m:template_path("/cbi/nodes_multivalue")
o.group = {}
for k1, v1 in pairs(node_list) do
@@ -101,28 +102,6 @@ if load_balancing_options then -- [[ Load balancing Start ]]
end
end
end
function o.cfgvalue(self, section)
return table.concat(m:get(section, "balancing_node") or {}, " ")
end
function o.write(self, section, value)
local old = m:get(section, "balancing_node") or {}
local new, set = {}, {}
for v in value:gmatch("%S+") do
new[#new + 1] = v
set[v] = 1
end
for _, v in ipairs(old) do
if not set[v] then
m:set(section, "balancing_node", new)
return
end
set[v] = nil
end
for _ in pairs(set) do
m:set(section, "balancing_node", new)
return
end
end
o = s:option(MultiValue, "node_group", translate("Select Group"))
o:depends({ node_add_mode = "batch" })
+1 -1
View File
@@ -20,7 +20,7 @@ command_timeout = 300
OPENWRT_ARCH = nil
DISTRIB_ARCH = nil
LOCK_PREFIX = "/tmp/lock/" .. c_config
LOCK_PREFIX = "/var/lock/" .. c_config
LOG_FILE = "/tmp/log/" .. c_config .. ".log"
TMP_PATH = "/tmp/etc/" .. c_config
CACHE_PATH = TMP_PATH .. "_tmp"
@@ -120,8 +120,17 @@ function copy_instance(var)
local TMP_DNSMASQ_PATH = var["TMP_DNSMASQ_PATH"]
local conf_lines = {}
local DEFAULT_DNSMASQ_CFGID = sys.exec("echo -n $(uci -q show dhcp.@dnsmasq[0] | awk 'NR==1 {split($0, conf, /[.=]/); print conf[2]}')")
if fs.access("/var/etc/dnsmasq.conf." .. DEFAULT_DNSMASQ_CFGID) then
for line in io.lines("/var/etc/dnsmasq.conf." .. DEFAULT_DNSMASQ_CFGID) do
local conf_file = "/var/etc/dnsmasq.conf." .. DEFAULT_DNSMASQ_CFGID
local retry = 5
while not fs.access(conf_file) and retry > 0 do
api.nixio.nanosleep(1, 0)
retry = retry - 1
end
if fs.access(conf_file) then
for line in io.lines(conf_file) do
local filter
if line:find("passwall2") then filter = true end
if line:find("ubus") then filter = true end
@@ -140,7 +149,10 @@ function copy_instance(var)
tinsert(conf_lines, line)
end
end
else
sys.call("logger -t passwall2 'ERROR: dnsmasq config " .. conf_file .. " not found after 5s wait! DNS hijacking will fail.'")
end
tinsert(conf_lines, "port=" .. LISTEN_PORT)
--dhcp.leases to hostsMore actions
local hosts = api.CACHE_PATH .. "/dhcp-hosts"
@@ -6,7 +6,7 @@ CONFIG=passwall2
APP_PATH=/usr/share/${CONFIG}
TMP_PATH=/tmp/etc/${CONFIG}
TMP_PATH2=${TMP_PATH}_tmp
LOCK_PATH=/tmp/lock
LOCK_PATH=/var/lock
LOG_FILE=/tmp/log/${CONFIG}.log
TMP_ACL_PATH=${TMP_PATH}/acl
TMP_BIN_PATH=${TMP_PATH}/bin
@@ -1,150 +1,508 @@
'use strict';
'require view';
'require dom';
'require poll';
'require uci';
'require rpc';
'require form';
'require uci';
'require fs';
'require ui';
// 辅助工具:统一纯净数组转换
function toArray(val) {
if (Array.isArray(val)) return val.filter(Boolean);
return typeof val === 'string' ? val.trim().split(/\s+/).filter(Boolean) : [];
}
// 辅助工具:提取首个 IP 并去除可能的 CIDR 掩码(容错支持 String、Array 及 null/undefined)
function getFirstIp(val, defaultVal) {
if (Array.isArray(val)) val = val[0];
if (typeof val === 'string') return val.split('/')[0].trim();
return defaultVal || '';
}
// 辅助工具:通用值深度比对(支持标量与数组)
function isEqual(a, b) {
if (Array.isArray(a) || Array.isArray(b)) {
var arrA = toArray(a).sort(), arrB = toArray(b).sort();
return arrA.length === arrB.length && arrA.every(function(v, i) { return v === arrB[i]; });
}
return String(a == null ? '' : a) === String(b == null ? '' : b);
}
// 辅助工具:检查数组是否包含元素(兼容低版本环境)
function has(arr, item) {
return Array.isArray(arr) && arr.indexOf(item) !== -1;
}
// 辅助工具:安全删除 UCI 选项(只有当该选项在 UCI 内存中真实存在时才调用 unset,防止 ubus 报错)
function safeUnset(conf, sid, opt) {
try {
if (uci.get(conf, sid, opt) != null) {
uci.unset(conf, sid, opt);
return true;
}
} catch(e) {}
return false;
}
// 辅助工具:智能判断无线物理网卡的频段 (2.4G / 5G / 6G)
function getRadioBand(devName) {
if (!devName) return '2.4G';
var b = uci.get('wireless', devName, 'band');
if (b === '6g') return '6G';
if (b === '5g') return '5G';
if (b === '2g') return '2.4G';
var ch = parseInt(uci.get('wireless', devName, 'channel'), 10);
if (!isNaN(ch)) {
if (ch >= 1 && ch <= 14) return '2.4G';
if (ch >= 32 && ch <= 196) return '5G';
if (ch > 196) return '6G';
}
var hw = (uci.get('wireless', devName, 'hwmode') || '').toLowerCase();
if (hw.indexOf('a') !== -1) return '5G';
if (hw.indexOf('b') !== -1 || hw.indexOf('g') !== -1) return '2.4G';
var dl = devName.toLowerCase();
if (dl.indexOf('6g') !== -1) return '6G';
if (dl.indexOf('5g') !== -1) return '5G';
return '2.4G';
}
return view.extend({
load: async function () {
const promises = await Promise.all([
fs.exec('/etc/init.d/wizard', ['reconfig']),
uci.changes(),
L.resolveDefault(uci.load('wireless')),
uci.load('wizard'),
L.resolveDefault(fs.stat('/www/luci-static/istorex/style.css'), null),
L.resolveDefault(fs.stat('/www/luci-static/routerdog/style.css'), null),
L.resolveDefault(fs.stat('/usr/sbin/nginx'), null)
]);
const data = {
istorex: promises[4],
routerdog: promises[5],
nginx: promises[6]
initialValues: {},
initialShortcuts: [],
hasWireless: false,
hasNginx: false,
map: null,
fields: null,
optMap: null,
// 1. 直连加载系统真实配置包,无需任何中间文件
load: function() {
var pkgs = ['network', 'wireless', 'dhcp', 'uhttpd', 'firewall', 'luci', 'wizard'];
var tasks = pkgs.map(function(p) {
return uci.load(p).catch(function() { return null; });
});
tasks.push(fs.stat('/etc/nginx/uci.conf').then(function() { return true; }).catch(function() { return false; }));
return Promise.all(tasks).then(function(res) {
var wifiSecs = (res && res) ? (uci.sections('wireless', 'wifi-device') || []) : [];
return {
hasWireless: wifiSecs.length > 0,
hasNginx: !!res[7]
};
});
},
// 2. 统一聚合读取系统当前各子模块的实时配置
getSystemConfig: function() {
var isSideRouter = !!uci.get('network', 'lan', 'gateway') && uci.get('network', 'wan', 'auto') === '0';
var ap = null;
if (this.hasWireless) {
var ifaces = uci.sections('wireless', 'wifi-iface') || [];
for (var i = 0; i < ifaces.length; i++) {
if (ifaces[i].mode === 'ap' || !ifaces[i].mode) {
ap = ifaces[i];
break;
}
}
}
// 剥离现有频段后缀,确保输入框展示纯净的基准名称
var rawSsid = (ap && ap.ssid) || (this.hasWireless ? 'Kwrt' : '');
var baseSsid = rawSsid.replace(/_(2\.4G|5G|6G)$/i, '').trim();
return {
wan_proto: isSideRouter ? 'siderouter' : (uci.get('network', 'wan', 'proto') || 'dhcp'),
wan_pppoe_user: uci.get('network', 'wan', 'username') || '',
wan_pppoe_pass: uci.get('network', 'wan', 'password') || '',
lan_ipaddr: getFirstIp(uci.get('network', 'lan', 'ipaddr'), '10.0.0.1'),
lan_gateway: getFirstIp(uci.get('network', 'lan', 'gateway'), ''),
lan_dns: toArray(uci.get('network', 'lan', 'dns')),
dhcp: uci.get('dhcp', 'lan', 'ignore') === '1' ? '0' : '1',
ipv6: uci.get('network', 'wan6', 'auto') === '0' ? '0' : '1',
https: uci.get('wizard', 'default', 'https') || '0',
cookie_p: uci.get('wizard', 'default', 'persistent_cookies') || '1',
landing_page: uci.get('wizard', 'default', 'landing_page') || 'default',
autoupgrade_fm: uci.get('wizard', 'default', 'autoupgrade_fm') || '1',
coremark: uci.get('wizard', 'default', 'coremark') || '0',
wifi_ssid: baseSsid || (this.hasWireless ? 'Kwrt' : ''),
wifi_key: (ap && ap.key) || ''
};
return data;
},
render: function(data) {
var m, s, o;
var has_wifi = false;
this.hasWireless = !!data.hasWireless;
this.hasNginx = !!data.hasNginx;
if (uci.sections('wireless', 'wifi-device').length > 0) {
has_wifi = true;
}
var sys = this.getSystemConfig();
this.initialValues = Object.assign({}, sys);
m = new form.Map('wizard', [_('Initial Router Setup')],
_('If you are using this router for the first time, please configure it here.'));
// 缓存初始 shortcuts 配置快照,用于保存时比对差异
var shortcuts = uci.sections('wizard', 'shortcuts') || [];
this.initialShortcuts = shortcuts.map(function(sec) {
return {
shortcut: sec.shortcut || '',
to_url: sec.to_url || '',
comments: sec.comments || ''
};
});
s = m.section(form.NamedSection, 'default', 'wizard');
s.addremove = false;
s.tab('netsetup', _('Net Settings'), _('Three different ways to access the Internet, please choose according to your own situation.'));
var m = new form.Map('wizard', _('Setup Wizard'),
_('Quickly configure common network, wireless, and system settings.'));
o = s.taboption('netsetup', form.ListValue, 'wan_proto', _('Protocol'));
o.rmempty = false;
o.value('dhcp', _('DHCP client'));
o.value('pppoe', _('PPPoE'));
o.value('siderouter', _('Siderouter'));
o = s.taboption('netsetup', form.Value, 'wan_pppoe_user', _('PAP/CHAP username'));
o.depends('wan_proto', 'pppoe');
o = s.taboption('netsetup', form.Value, 'wan_pppoe_pass', _('PAP/CHAP password'));
o.depends('wan_proto', 'pppoe');
o.password = true;
o = s.taboption('netsetup', form.Value, 'lan_ipaddr', _('IPv4 address'));
o.datatype = 'ip4addr';
o = s.taboption('netsetup', form.DynamicList, 'lan_dns', _('Use custom DNS servers'), _('Leave empty to use ISP DNS'));
o.datatype = 'ip4addr';
o.cast = 'string';
o = s.taboption('netsetup', form.Value, 'lan_gateway', _('IPv4 gateway'));
o.depends('wan_proto', 'siderouter');
o.datatype = 'ip4addr';
o.placeholder = _('Enter the main router IP');
o.rmempty = false;
o = s.taboption('netsetup', form.Flag, 'dhcp', _('DHCP Server'),
_('To turn on this DHCP, you need to turn off the DHCP of the main router, and to turn off this DHCP, you need to manually change the gateway and DNS of all Internet devices to the IP of this bypass router'));
o.depends('wan_proto', 'siderouter');
o.default = o.enabled;
o = s.taboption('netsetup', form.Flag, 'ipv6', _('Enable IPv6'), _('Enable/Disable IPv6'));
o.default = o.enabled;
s.tab('firmware', _('Firmware Settings'));
// o = s.taboption('firmware', form.Flag, 'autoupgrade_pkg', _('Packages Auto Upgrade'),_('谨慎开启'));
// o.rmempty = false;
o = s.taboption('firmware', form.Flag, 'autoupgrade_fm', _('Firmware Upgrade Notice'));
o.rmempty = false;
o = s.taboption('firmware', form.Flag, 'coremark', _('CoreMark'), _('第一次开机后是否运行CPU跑分测试'));
o.rmempty = false;
o = s.taboption('firmware', form.Flag, 'cookie_p', _('Persistent cookies'),
_('Keep the background login state to avoid the need to log in again every time the browser is closed'));
o.default = o.enabled;
o = s.taboption('firmware', form.Flag, 'https', _('Force the use of HTTPS in the backend.'));
if (data.istorex || data.routerdog){
o = s.taboption('firmware', form.ListValue, 'landing_page', _('主题模式'));
o.value('default', _('默认'));
if (data.routerdog){
o.value('routerdog', _('路由狗(专业NAS模式)'));
}
if (data.istorex){
o.value('nas', _('NAS模式'));
o.value('next-nas', _('NEXT-NAS模式'));
o.value('router', _('路由模式'));
}
o.default = 'default';
}
if (has_wifi) {
s.tab('wifisetup', _('Wireless Settings'), _('Set the router\'s wireless name and password. For more advanced settings, please go to the Network-Wireless page.'));
o = s.taboption('wifisetup', form.Value, 'wifi_ssid', _('<abbr title=\"Extended Service Set Identifier\">ESSID</abbr>'));
o.datatype = 'maxlength(32)';
o = s.taboption("wifisetup", form.Value, "wifi_key", _("Key"));
o.datatype = 'wpakey';
o.password = true;
}
if (data.nginx) {
s.tab('shortcuts', _('Shortcuts'), _('比如设置google.com的快捷方式为字母g,则在此路由器网络的任何浏览器中输入g/即可访问google.com'));
o = s.taboption('shortcuts', form.SectionValue, 'shortcuts', form.GridSection, 'shortcuts', null,
_('Shortcuts'));
s = o.subsection;
s.addremove = true;
var s = m.section(form.NamedSection, 'default', 'wizard');
s.anonymous = true;
s.addremove = false;
o = s.option(form.Value, 'shortcut', _('Shortcut'));
o.rmempty = false;
s.tab('netsetup', _('Net Settings'));
s.tab('firmware', _('Firmware Settings'));
if (this.hasWireless) s.tab('wifisetup', _('Wireless Settings'));
o = s.option(form.Value, 'to_url', _('Target URL'));
o.rmempty = false;
o.placeholder = 'https://example.com';
o.validate = function(section_id, value) {
if (value.match(/^https?:\/\/.+/i)) {
if (this.hasNginx) {
s.tab('shortcuts', _('Shortcuts'), _('比如设置google.com的快捷方式为字母g,则在此路由器网络的任何浏览器中输入g/即可访问google.com'));
}
// 3. 声明式配置项驱动表
var opt, fields = [
// 网络设置
{ tab: 'netsetup', type: form.ListValue, id: 'wan_proto', title: _('WAN Protocol / Mode'),
choices: { dhcp: _('DHCP Client (Default Router)'), pppoe: _('PPPoE Dial-up'), siderouter: _('Side-Router Mode (Bypass Gateway)') } },
{ tab: 'netsetup', type: form.Value, id: 'wan_pppoe_user', title: _('PPPoE Username'), depends: { wan_proto: 'pppoe' } },
{ tab: 'netsetup', type: form.Value, id: 'wan_pppoe_pass', title: _('PPPoE Password'), depends: { wan_proto: 'pppoe' }, password: true },
{ tab: 'netsetup', type: form.Value, id: 'lan_ipaddr', title: _('LAN IPv4 Address'), datatype: 'ip4addr', placeholder: '10.0.0.1' },
{ tab: 'netsetup', type: form.DynamicList, id: 'lan_dns', title: _('Custom DNS Server(s)'), datatype: 'ipaddr', placeholder: '223.5.5.5' },
{ tab: 'netsetup', type: form.Value, id: 'lan_gateway', title: _('Gateway Address'), datatype: 'ip4addr', placeholder: '', depends: { wan_proto: 'siderouter' }, desc: _('Primary router IP address used when operating in side-router mode.') },
{ tab: 'netsetup', type: form.Flag, id: 'dhcp', title: _('Enable DHCP Server'), desc: _('Enable or disable LAN DHCP server (usually disabled in side-router mode to avoid IP address conflicts).') },
{ tab: 'netsetup', type: form.Flag, id: 'ipv6', title: _('IPv6 Support'), desc: _('Enable or disable IPv6 router advertisements and DHCPv6.') },
// 固件与系统设置
{ tab: 'firmware', type: form.Flag, id: 'autoupgrade_fm', title: _('Firmware Upgrade Notice'), desc: _('Check and display notices for newer firmware versions.') },
{ tab: 'firmware', type: form.Flag, id: 'coremark', title: _('Run CoreMark on Boot'), desc: _('Run CPU benchmark asynchronously upon router initialization.') },
{ tab: 'firmware', type: form.Flag, id: 'cookie_p', title: _('Persistent Cookie Session'), desc: _('Maintain persistent login sessions in the web browser.') },
{ tab: 'firmware', type: form.Flag, id: 'https', title: _('Enforce HTTPS Access'), desc: _('Automatically redirect HTTP requests to secure HTTPS.') },
{ tab: 'firmware', type: form.ListValue, id: 'landing_page', title: _('Landing Dashboard Mode'),
choices: { 'default': _('Default'), routerdog: _('RouterDog'), nas: _('NAS'), 'next-nas': _('Next-NAS'), router: _('Router') } }
];
// 无线配置根据设备硬件动态追加
if (this.hasWireless) {
fields.push(
{ tab: 'wifisetup', type: form.Value, id: 'wifi_ssid', title: _('Wireless Network Name (SSID)'), placeholder: 'Kwrt' },
{ tab: 'wifisetup', type: form.Value, id: 'wifi_key', title: _('Wireless Password (Key)'), password: true, placeholder: _('Leave empty for open network or 8+ characters') }
);
}
var optMap = {};
fields.forEach(function(f) {
opt = s.taboption(f.tab, f.type, f.id, f.title, f.desc);
opt.cfgvalue = function() { return sys[f.id]; };
opt.default = sys[f.id];
opt.write = function() {};
opt.remove = function() {};
if (f.password) opt.password = true;
if (f.datatype) opt.datatype = f.datatype;
if (f.placeholder) opt.placeholder = f.placeholder;
if (f.choices) Object.keys(f.choices).forEach(function(k) { opt.value(k, f.choices[k]); });
if (f.depends) Object.keys(f.depends).forEach(function(k) { opt.depends(k, f.depends[k]); });
// 为无线密码增加 8~64 位安全校验,防止 hostapd 崩溃
if (f.id === 'wifi_key') {
opt.validate = function(section_id, value) {
if (!value) return true;
if (value.length < 8 || value.length > 64) {
return _('Wireless key must be between 8 and 64 characters long');
}
return true;
};
}
optMap[f.id] = opt;
});
// Shortcuts GridSection 渲染
if (this.hasNginx) {
var so = s.taboption('shortcuts', form.SectionValue, '_shortcuts', form.GridSection, 'shortcuts', null, _('Shortcuts'));
var ss = so.subsection;
ss.addremove = true;
ss.anonymous = true;
ss.sortable = true;
var o_sc = ss.option(form.Value, 'shortcut', _('Shortcut'));
o_sc.rmempty = false;
o_sc.placeholder = 'g';
o_sc.validate = function(section_id, value) {
if (!value)
return _('Shortcut phrase cannot be empty');
if (!value.match(/^[a-zA-Z0-9_-]+$/))
return _('Only alphanumeric characters, dashes and underscores are allowed');
return true;
};
var o_url = ss.option(form.Value, 'to_url', _('Target URL'));
o_url.rmempty = false;
o_url.placeholder = 'https://example.com';
o_url.validate = function(section_id, value) {
if (value && value.match(/^https?:\/\/.+/i)) {
return true;
}
return _('Please enter a valid URL starting with http:// or https://');
};
o = s.option(form.Value, 'comments', _('Comments'));
o.optional = true;
var o_comm = ss.option(form.Value, 'comments', _('Comments'));
o_comm.optional = true;
o_comm.placeholder = _('Optional');
}
setTimeout("document.getElementsByClassName('cbi-button-apply')[0].children[3].children[0].value='1'", 1000)
this.map = m;
this.fields = fields;
this.optMap = optMap;
return m.render();
},
// 4. 重写 View 级别的 handleSave:精准写入发生变化的配置到 UCI 暂存区
handleSave: function(ev) {
var self = this;
if (!this.map) return Promise.resolve(false);
return this.map.parse().then(function() {
var cur = {};
self.fields.forEach(function(f) {
var v = self.optMap[f.id].formvalue('default');
// 对 Flag 进行布尔归一化,彻底消除 null 与 '0' 比对产生的假变动
if (f.type === form.Flag) {
cur[f.id] = (v == '1') ? '1' : '0';
} else {
cur[f.id] = (v != null) ? v : '';
}
});
cur.lan_dns = toArray(cur.lan_dns);
// 精准筛选出发生变化的常规字段
var changed = self.fields.map(function(f) { return f.id; }).filter(function(id) {
return !isEqual(cur[id], self.initialValues[id]);
});
// 检测 shortcuts 是否有增删改
var curShortcuts = (uci.sections('wizard', 'shortcuts') || []).map(function(sec) {
return {
shortcut: sec.shortcut || '',
to_url: sec.to_url || '',
comments: sec.comments || ''
};
});
var shortcutsChanged = JSON.stringify(curShortcuts) !== JSON.stringify(self.initialShortcuts);
// 未做任何改动直接提示并退出
if (changed.length === 0 && !shortcutsChanged) {
ui.addNotification(null, E('p', _('There are no changes to apply')), 'info');
return false;
}
// A. 无线配置直接写回 wireless(全频段适配:2.4G加_2.4G,5G加_5G,6G加_6G)
if (self.hasWireless && (has(changed, 'wifi_ssid') || has(changed, 'wifi_key'))) {
var rawInputSsid = (cur.wifi_ssid || '').trim();
var baseSsid = rawInputSsid.replace(/_(2\.4G|5G|6G)$/i, '').trim();
if (!baseSsid) baseSsid = rawInputSsid;
var ifaces = uci.sections('wireless', 'wifi-iface') || [];
ifaces.forEach(function(ifc) {
if (ifc.mode === 'ap' || !ifc.mode) {
var band = getRadioBand(ifc.device);
var autoSsid = baseSsid ? (baseSsid + '_' + band) : '';
if (has(changed, 'wifi_ssid') && autoSsid) {
uci.set('wireless', ifc['.name'], 'ssid', autoSsid);
}
if (has(changed, 'wifi_key')) {
if (cur.wifi_key) {
uci.set('wireless', ifc['.name'], 'key', cur.wifi_key);
var enc = ifc.encryption || uci.get('wireless', ifc['.name'], 'encryption') || '';
if (enc.indexOf('psk') === -1 && enc.indexOf('sae') === -1) {
uci.set('wireless', ifc['.name'], 'encryption', 'psk2');
}
} else {
safeUnset('wireless', ifc['.name'], 'key');
uci.set('wireless', ifc['.name'], 'encryption', 'none');
}
}
// 解除物理无线与接口禁用状态
if (ifc.device) {
safeUnset('wireless', ifc.device, 'disabled');
}
safeUnset('wireless', ifc['.name'], 'disabled');
}
});
}
// B. WAN 模式与 PPPoE(单网卡判空保护 + LAN 防火墙动态伪装清理)
if (has(changed, 'wan_proto') || has(changed, 'wan_pppoe_user') || has(changed, 'wan_pppoe_pass')) {
var zones = uci.sections('firewall', 'zone') || [];
var lanZone = null;
for (var zi = 0; zi < zones.length; zi++) {
if (zones[zi].name === 'lan') {
lanZone = zones[zi];
break;
}
}
var hasWanSec = !!uci.get('network', 'wan');
if (cur.wan_proto === 'siderouter') {
if (hasWanSec) uci.set('network', 'wan', 'auto', '0');
// 旁路由模式:开启 LAN 区域动态伪装 (masq)
if (lanZone) uci.set('firewall', lanZone['.name'], 'masq', '1');
} else {
if (hasWanSec) {
uci.set('network', 'wan', 'auto', '1');
uci.set('network', 'wan', 'proto', cur.wan_proto);
if (cur.wan_proto === 'pppoe') {
uci.set('network', 'wan', 'username', cur.wan_pppoe_user);
uci.set('network', 'wan', 'password', cur.wan_pppoe_pass);
} else {
safeUnset('network', 'wan', 'username');
safeUnset('network', 'wan', 'password');
}
}
// 切换回主路由模式:恢复清除 LAN 区域的动态伪装 (masq)
if (lanZone) safeUnset('firewall', lanZone['.name'], 'masq');
}
}
// C. LAN IP(兼容 list ipaddr / option ipaddr / CIDR 掩码)
if (has(changed, 'lan_ipaddr') && cur.lan_ipaddr) {
var origIpRaw = uci.get('network', 'lan', 'ipaddr');
var firstIp = Array.isArray(origIpRaw) ? origIpRaw[0] : origIpRaw;
var mask = '';
if (typeof firstIp === 'string') {
var slashIdx = firstIp.indexOf('/');
if (slashIdx !== -1) {
mask = '/' + firstIp.substring(slashIdx + 1).trim();
}
}
var cleanIp = (typeof cur.lan_ipaddr === 'string') ? cur.lan_ipaddr.split('/')[0].trim() : String(cur.lan_ipaddr);
var targetVal = cleanIp + mask;
if (Array.isArray(origIpRaw)) {
var newArr = origIpRaw.slice();
newArr[0] = targetVal;
uci.set('network', 'lan', 'ipaddr', newArr);
} else {
uci.set('network', 'lan', 'ipaddr', targetVal);
if (!mask && !uci.get('network', 'lan', 'netmask')) {
uci.set('network', 'lan', 'netmask', '255.255.255.0');
}
}
}
// D. 自定义 LAN DNS
if (has(changed, 'lan_dns')) {
if (cur.lan_dns && cur.lan_dns.length > 0) {
uci.set('network', 'lan', 'dns', cur.lan_dns);
} else {
safeUnset('network', 'lan', 'dns');
}
}
// E. LAN 网关(独立变更或切换至旁路由模式时更新)
if (has(changed, 'lan_gateway') || has(changed, 'wan_proto')) {
if (cur.wan_proto === 'siderouter') {
if (cur.lan_gateway) {
uci.set('network', 'lan', 'gateway', cur.lan_gateway);
} else {
safeUnset('network', 'lan', 'gateway');
}
} else {
safeUnset('network', 'lan', 'gateway');
}
}
// F. DHCP 开关(根据勾选状态精准配置,解除模式死锁冲突)
if (has(changed, 'dhcp') || has(changed, 'wan_proto')) {
if (cur.dhcp === '0') {
uci.set('dhcp', 'lan', 'ignore', '1');
} else {
safeUnset('dhcp', 'lan', 'ignore');
}
}
// G. IPv6
if (has(changed, 'ipv6')) {
var enabled = (cur.ipv6 === '1');
if (uci.get('network', 'wan6')) {
uci.set('network', 'wan6', 'auto', enabled ? '1' : '0');
}
uci.set('dhcp', 'lan', 'ra', enabled ? 'server' : 'disabled');
uci.set('dhcp', 'lan', 'dhcpv6', enabled ? 'server' : 'disabled');
uci.set('dhcp', 'lan', 'ndp', enabled ? 'server' : 'disabled');
}
// H. HTTPS 访问重定向(由 wizard.init 调度底层生效)
if (has(changed, 'https')) {
uci.set('wizard', 'default', 'https', cur.https);
}
// I. 页面与会话设置(持久Cookie双向同步至luci系统底层与向导配置包)
if (has(changed, 'cookie_p')) {
uci.set('wizard', 'default', 'persistent_cookies', cur.cookie_p);
}
if (has(changed, 'landing_page')) {
uci.set('wizard', 'default', 'landing_page', cur.landing_page);
}
// J. 向导专属项(完整保留 autoupgrade_fm 与 coremark)
if (has(changed, 'autoupgrade_fm')) {
uci.set('wizard', 'default', 'autoupgrade_fm', cur.autoupgrade_fm);
}
if (has(changed, 'coremark')) {
uci.set('wizard', 'default', 'coremark', cur.coremark);
}
self.initialValues = Object.assign({}, cur);
self.initialShortcuts = curShortcuts;
return uci.save().then(function() {
return true;
});
});
},
// 5. 重写 View 级别的 handleSaveApply:应用生效并实现 LAN IP 变更平滑迁移与自动跳转
handleSaveApply: function(ev, mode) {
var self = this;
return this.handleSave(ev).then(function(hasChanges) {
if (!hasChanges) return false;
var oldIp = self.initialValues.lan_ipaddr;
var newIp = (self.optMap['lan_ipaddr'].formvalue('default') || '').split('/')[0].trim();
var ipChanged = newIp && (newIp !== oldIp);
return ui.changes.apply(mode == '0').then(function() {
if (ipChanged) {
var sec = 15;
ui.showModal(_('LAN IP Address Changed'), [
E('p', _('LAN IP changed to %s. Redirecting in %d seconds...').format(newIp, sec)),
E('div', { 'class': 'spinning', 'style': 'margin: 1em auto;' })
]);
var timer = window.setInterval(function() {
sec--;
if (sec <= 0) {
window.clearInterval(timer);
window.location.href = window.location.protocol + '//' + newIp + window.location.pathname;
}
}, 1000);
}
});
});
},
handleReset: null
});
+1 -1
View File
@@ -22,7 +22,7 @@ function landing_page()
http.redirect(luci.dispatcher.build_url("admin","quickstart"));
end
else
http.redirect(luci.dispatcher.build_url("admin","status"))
http.redirect(luci.dispatcher.build_url("admin","status", "overview"))
end
end
+122 -57
View File
@@ -1,75 +1,140 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "Wizard"
msgid "Setup Wizard"
msgstr "设置向导"
msgid "Initial Router Setup"
msgstr "路由器设置向导"
msgid "Quickly configure common network, wireless, and system settings."
msgstr "快速配置常用网络、无线与系统设置。"
msgid "Net Settings"
msgstr "网络设置"
msgid "Firmware Settings"
msgstr "固件与系统"
msgid "Wireless Settings"
msgstr "无线设置"
msgid "If you are using this router for the first time, please configure it here."
msgstr "如果你首次使用这个路由器,请在这里简单设置。"
msgid "Three different ways to access the Internet, please choose according to your own situation."
msgstr "二种不同的上网方式,请您根据自身情况进行选择。"
msgid "Set the router's wireless name and password. For more advanced settings, please go to the Network-Wireless page."
msgstr "设置路由器的无线名字和密码,更多高级设置请进入 网络-无线 页面。"
msgid "Leave empty to use ISP DNS"
msgstr "留空则使用运营商DNS"
msgid "Enter the main router IP"
msgstr "请输入主路由IP"
msgid "Siderouter"
msgstr "旁路由模式"
msgid "To turn on this DHCP, you need to turn off the DHCP of the main router, and to turn off this DHCP, you need to manually change the gateway and DNS of all Internet devices to the IP of this bypass router"
msgstr "开启此DHCP则需要关闭主路由的DHCP, 关闭此DHCP则需要手动将所有上网设备的网关和DNS改为此旁路由的IP"
msgid "Enable IPv6"
msgstr "启用IPv6"
msgid "Enable/Disable IPv6"
msgstr "一键开启/关闭 IPv6"
msgid "Firmware Settings"
msgstr "固件设置"
msgid "Packages Auto Upgrade"
msgstr "软件包自动更新"
msgid "Firmware Upgrade Notice"
msgstr "固件更新提醒"
msgid "Persistent cookies"
msgstr "持久化登录"
msgid "Keep the background login state to avoid the need to log in again every time the browser is closed"
msgstr "保持后台登录状态,避免每次关闭浏览器后都需要重新登录"
msgid "Home"
msgstr "首页"
msgid "Shortcuts"
msgstr "快捷访问"
msgid "Shortcut"
msgstr "快捷方式"
msgid "Target URL"
msgstr "目标URL"
msgid "WAN Protocol / Mode"
msgstr "WAN 协议 / 工作模式"
msgid "Please enter a valid URL starting with http:// or https://"
msgstr "URL需以http:// 或 https://开头"
msgid "DHCP Client (Default Router)"
msgstr "DHCP 客户端 (标准主路由模式)"
msgid "Force the use of HTTPS in the backend."
msgstr "后台强制使用https"
msgid "PPPoE Dial-up"
msgstr "PPPoE 拨号上网"
msgid "Side-Router Mode (Bypass Gateway)"
msgstr "旁路由模式 (网关旁路)"
msgid "PPPoE Username"
msgstr "宽带拨号账号"
msgid "PPPoE Password"
msgstr "宽带拨号密码"
msgid "LAN IPv4 Address"
msgstr "LAN 口 IPv4 地址"
msgid "Custom DNS Server(s)"
msgstr "自定义 DNS 服务器,留空默认运营商DNS"
msgid "Gateway Address"
msgstr "网关地址"
msgid "Primary router IP address used when operating in side-router mode."
msgstr "处于旁路由模式时的主路由 IP 地址。"
msgid "Enable DHCP Server"
msgstr "启用 DHCP 服务"
msgid "Usually disabled in side-router mode to avoid IP address assignment conflicts with the main router."
msgstr "在旁路由模式下通常关闭此项,避免与主路由的 IP 分配冲突。"
msgid "IPv6 Support"
msgstr "IPv6 支持"
msgid "Enable or disable IPv6 router advertisements and DHCPv6."
msgstr "启用或关闭 IPv6 路由通告与 DHCPv6 服务。"
msgid "Firmware Upgrade Notice"
msgstr "固件升级提示"
msgid "Check and display notices for newer firmware versions."
msgstr "检查并显示新版本固件更新提示。"
msgid "Run CoreMark on Boot"
msgstr "开机 CoreMark 跑分"
msgid "Run CPU benchmark upon initial router initialization."
msgstr "在路由器初始化启动时运行 CPU 基准测试。"
msgid "Persistent Cookie Session"
msgstr "持久 Cookie 登录"
msgid "Maintain persistent login sessions in the web browser."
msgstr "在浏览器中保持登录会话状态,避免频繁重复登录。"
msgid "Enforce HTTPS Access"
msgstr "强制 HTTPS 访问"
msgid "Automatically redirect HTTP requests to secure HTTPS."
msgstr "将所有 HTTP 请求自动重定向至安全加密的 HTTPS。"
msgid "Landing Dashboard Mode"
msgstr "主页展示模式"
msgid "Default"
msgstr "默认"
msgid "RouterDog"
msgstr "路由狗"
msgid "NAS"
msgstr "NAS 模式"
msgid "Next-NAS"
msgstr "Next-NAS 模式"
msgid "Router"
msgstr "路由器模式"
msgid "Wireless Network Name (SSID)"
msgstr "无线网络名称 (SSID)"
msgid "Wireless Password (Key)"
msgstr "无线网络密码"
msgid "Leave empty for open network or 8+ characters"
msgstr "留空为开放无密码网络,或输入 8 位以上密码"
msgid "URL Shortcuts"
msgstr "快捷方式列表"
msgid "Applying Changes"
msgstr "正在应用修改"
msgid "Applying modified settings: %s..."
msgstr "正在应用已修改的设置项: %s..."
msgid "No settings were modified. Services remain uninterrupted."
msgstr "未检测到任何修改,无需保存或重启服务。"
msgid "Settings successfully applied: %s"
msgstr "已成功应用并重载修改项: %s"
msgid "Failed to apply settings: %s"
msgstr "应用设置失败: %s"
msgid "LAN IP Address Changed"
msgstr "LAN 口 IP 地址已变更"
msgid "LAN IP changed to %s. Redirecting to the new address in %d seconds..."
msgstr "LAN 口 IP 已变更为 %s。将在 %d 秒后自动跳转至新地址..."
msgid "Redirect Now"
msgstr "立即跳转"
+102 -213
View File
@@ -1,182 +1,70 @@
#!/bin/sh /etc/rc.common
START=19
START=99
USE_PROCD=1
extra_command "reconfig" ""
boot() {
exit 0;
}
add_wizard() {
local cfg="$1"
local wan_proto wan_pppoe_user wan_pppoe_pass
local lan_ipaddr lan_dns lan_gateway dhcp
local device ipv6 old_ipv6 wifi_ssid wifi_key old_wifi_ssid old_wifi_key
config_get wan_proto "$cfg" wan_proto
config_get lan_gateway "$cfg" lan_gateway
config_get dhcp "$cfg" dhcp
config_get lan_dns "$cfg" lan_dns
config_get ipv6 "$cfg" ipv6
config_get old_ipv6 "$cfg" old_ipv6
config_get lan_ipaddr "$cfg" lan_ipaddr
config_get autoupgrade_pkg "$cfg" autoupgrade_pkg
test -n "${lan_ipaddr}" && {
uci set network.lan.ipaddr="${lan_ipaddr}"
}
device=$(uci -q get network.wan.device || uci -q get wizard.default.old_network_device)
case "${wan_proto}" in
dhcp)
uci -q del_list network.@device[0].ports="$device"
uci -q del network.lan.gateway
uci -q del dhcp.lan.ignore
uci -q del network.lan.dns
uci -q del firewall.@zone[0].masq
uci delete network.wan
uci set network.wan=interface
uci set network.wan.device="$device"
uci set network.wan.proto='dhcp'
;;
pppoe)
config_get wan_pppoe_user "$cfg" wan_pppoe_user
config_get wan_pppoe_pass "$cfg" wan_pppoe_pass
[[ "${wan_pppoe_user} != $(uci -q get network.wan.username)" || "${wan_pppoe_pass} != $(uci -q get network.wan.password)" ]] && {
uci -q del_list network.@device[0].ports="$device"
uci -q del network.lan.gateway
uci -q del dhcp.lan.ignore
uci -q del network.lan.dns
uci -q del firewall.@zone[0].masq
uci delete network.wan
uci set network.wan=interface
uci set network.wan.proto='pppoe'
uci set network.wan.device="$device"
uci set network.wan.username="${wan_pppoe_user}"
uci set network.wan.password="${wan_pppoe_pass}"
[ -n "${ipv6}" ] && {
uci -q set network.wan.ipv6='0'
uci -q set network.wan.delegate='0'
}
}
;;
siderouter)
[ "$dhcp" == "0" ] && dhcp="1"
if [[ "${lan_gateway}" != "$(uci -q get network.lan.gateway)" || "${dhcp}" != "$(uci -q get dhcp.lan.ignore)" ]]; then
uci -q set network.lan.gateway="${lan_gateway}"
[ -n "$lan_dns" ] || lan_dns='223.5.5.5'
[ -n "$dhcp" ] && uci -q set dhcp.lan.ignore="1" || uci -q del dhcp.lan.ignore
uci -q set firewall.@zone[0].masq='1'
WAN=$(uci -q get network.wan.device)
uci -q add_list network.@device[0].ports="$WAN"
uci -q add_list network.@device[0].ports="$WAN"
uci -q set wizard.default.old_network_device="$WAN"
;;
esac
[ -n "$lan_dns" ] && {
[ "$(uci -q get dhcp.@dnsmasq[0].noresolv)" == 1 ] && {
uci -q del dhcp.@dnsmasq[0].noresolv
}
uci -q set network.wan.peerdns='0'
uci -q set network.lan.dns="${lan_dns}"
} || {
uci -q del network.wan.peerdns
uci -q del network.lan.dns
}
if [ "${ipv6}" != "${old_ipv6}" ]; then
if [ -n "${ipv6}" ]; then
uci -q delete dhcp.lan.ra
uci -q delete dhcp.lan.dhcpv6
uci -q set network.lan.delegate='0'
uci -q set network.wan.ipv6='0'
uci -q set network.wan.delegate='0'
uci -q delete network.globals.ula_prefix
else
uci -q set dhcp.lan.ra='hybrid'
uci -q set dhcp.lan.dhcpv6='hybrid'
uci -q del network.lan.delegate
uci -q set network.wan.ipv6='auto'
uci -q del network.wan.delegate
fi
uci -q set wizard.default.old_ipv6="${ipv6}"
fi
wifi_setup_radio(){
local radio=$1
uci -q get wireless.${radio} >/dev/null 2>&1 && {
if [ "$(uci -q get wireless.${radio}.band)" = "5g" ]; then
uci -q set wireless.default_${radio}.ssid="${wifi_ssid}_5G"
elif [ "$(uci -q get wireless.${radio}.band)" = "2g" ]; then
uci -q set wireless.default_${radio}.ssid="${wifi_ssid}_2.4G"
fi
uci -q set wireless.default_${radio}.device="${radio}"
if [ "${wifi_key}" ]; then
uci -q set wireless.default_${radio}.encryption='psk2'
uci -q set wireless.default_${radio}.key="${wifi_key}"
else
uci -q set wireless.default_${radio}.encryption='none'
fi
}
}
config_get wifi_ssid "$cfg" wifi_ssid
config_get wifi_key "$cfg" wifi_key
config_get old_wifi_ssid "$cfg" old_wifi_ssid
config_get old_wifi_key "$cfg" old_wifi_key
if [[ "${wifi_ssid}" != "${old_wifi_ssid}" || "${wifi_key}" != "${old_wifi_key}" ]]; then
test -n "${wifi_ssid}" && {
for radio in radio0 radio1 radio2 radio3; do
wifi_setup_radio ${radio}
# 清理历史已生成的快捷方式(防止改名或删除后遗留失效条目)
clean_shortcuts() {
local sec
# 清理 nginx 中由 wizard 生成的 _sc_* server 节
for sec in $(uci -q show nginx 2>/dev/null | sed -n 's/^nginx\.\(_sc_[^=.]*\)=server$/\1/p'); do
uci -q delete nginx."$sec"
done
# 清理 dhcp (dnsmasq) 中由 wizard 生成的 _sc_* domain 节
for sec in $(uci -q show dhcp 2>/dev/null | sed -n 's/^dhcp\.\(_sc_[^=.]*\)=domain$/\1/p'); do
uci -q delete dhcp."$sec"
done
uci commit wireless
uci -q set wizard.default.old_wifi_ssid="${wifi_ssid}"
uci -q set wizard.default.old_wifi_key="${wifi_key}"
}
# 处理单条 shortcut 节
process_shortcut() {
local shortcut to_url comments lanaddr
local cfg="$1"
# 获取当前 LAN 口 IP,支持 DSA 列表与 CIDR 掩码剔除
lanaddr="$(uci -q get network.lan.ipaddr | tr ' ' '\n' | head -n1)"
lanaddr="${lanaddr%%/*}"
[ -z "$lanaddr" ] && lanaddr="10.0.0.1"
config_get shortcut "$cfg" shortcut
config_get to_url "$cfg" to_url
config_get comments "$cfg" comments
# 校验必要参数
if [ -z "$shortcut" ] || [ -z "$to_url" ]; then
return 0
fi
sethttps
# 1. 配置 Nginx 302 重定向 (仅在存在 /etc/nginx/uci.conf 时配置,仅监听80,禁止未配证书监听443)
if [ -f /etc/nginx/uci.conf ]; then
uci -q set nginx._sc_$shortcut=server
uci -q set nginx._sc_$shortcut.server_name="$shortcut"
uci -q add_list nginx._sc_$shortcut.listen="80"
uci -q set nginx._sc_$shortcut.return="302 $to_url"
uci commit wizard
uci commit dhcp
uci commit firewall
uci commit network
if [ "${autoupgrade_pkg}" != "1" ]; then
sed -i '/opkg-upgrade/d' /etc/crontabs/root
else
grep -q "opkg-upgrade" /etc/crontabs/root || {
hour="$(grep -m1 -ao '[4-6]' /dev/urandom | head -n1)"
min="$(grep -m1 -ao '[0-5][0-9]' /dev/urandom | head -n1)"
echo "$min $hour * * * . /etc/hotplug.d/online/51-opkg-upgrade >/dev/null 2>&1" >> /etc/crontabs/root
}
# 2. 配置 Dnsmasq (DHCP) 本地域名劫持
uci -q set dhcp._sc_$shortcut=domain
uci -q set dhcp._sc_$shortcut.name="$shortcut"
uci -q set dhcp._sc_$shortcut.ip="$lanaddr"
[ -n "$comments" ] && uci -q set dhcp._sc_$shortcut.comments="$comments"
fi
}
reconfig() {
uci -q set wizard.default.wan_proto="$(uci -q get network.wan.proto)"
uci -q set wizard.default.wan_pppoe_user="$(uci -q get network.wan.username)"
uci -q set wizard.default.wan_pppoe_pass="$(uci -q get network.wan.password)"
uci -q set wizard.default.lan_ipaddr="$(uci -q get network.lan.ipaddr)"
uci -q set wizard.default.lan_gateway="$(uci -q get network.lan.gateway)"
uci -q set wizard.default.lan_dns="$(uci -q get network.lan.dns)"
[ "$(uci -q get dhcp.lan.ignore)" ] && uci -q set wizard.default.dhcp="0" || uci -q del wizard.default.dhcp
uci commit wizard
}
# 配置 HTTPS 访问重定向 (适配 Nginx / Lighttpd / uHTTPd)
sethttps() {
config_get https "$cfg" https
if [ -n "$(command -v nginx)" ]; then
if [ -z "`uci -q get nginx._redirect2ssl`" ]; then
local cfg="${1:-default}"
local https
config_get https "$cfg" https 0
if [ -x /etc/init.d/nginx ] && [ -f /etc/nginx/uci.conf ]; then
if [ "x${https}" = "x1" ]; then
if [ -z "$(uci -q get nginx._redirect2ssl)" ]; then
uci -q set nginx._redirect2ssl=server
uci -q set nginx._redirect2ssl.server_name='_redirect2ssl'
uci -q set nginx._redirect2ssl.return='302 https://$host$request_uri'
uci -q set nginx._redirect2ssl.access_log='off; # logd openwrt'
fi
if [ "x${https}" = "x1" ] ; then
uci -q del nginx.default_server.listen
uci -q add_list nginx.default_server.listen='80'
uci -q add_list nginx.default_server.listen='[::]:80'
@@ -184,78 +72,79 @@ sethttps() {
uci -q add_list nginx._redirect2ssl.listen='80 default_server'
uci -q add_list nginx._redirect2ssl.listen='[::]:80 default_server'
else
uci -q del nginx._redirect2ssl.listen
uci -q add_list nginx._redirect2ssl.listen='80'
uci -q add_list nginx._redirect2ssl.listen='[::]:80'
uci -q delete nginx._redirect2ssl
uci -q del nginx.default_server.listen
uci -q add_list nginx.default_server.listen='80 default_server'
uci -q add_list nginx.default_server.listen='[::]:80 default_server'
fi
uci commit nginx
/etc/init.d/nginx reload
elif [ -n "$(command -v lighttpd)" ]; then
uci -q commit nginx
/etc/init.d/nginx reload >/dev/null 2>&1
elif [ -x /etc/init.d/lighttpd ] && [ -f /etc/lighttpd/conf.d/82-https.conf ]; then
if [ "x${https}" = "x1" ]; then
sed -i "s/ # url.redirect =/ url.redirect =/" /etc/lighttpd/conf.d/82-https.conf
sed -i "s/^[ \t]*#*[ \t]*url\.redirect[ \t]*=.*/\turl.redirect =/" /etc/lighttpd/conf.d/82-https.conf 2>/dev/null
else
sed -i "s/ url.redirect =/ # url.redirect =/" /etc/lighttpd/conf.d/82-https.conf
sed -i "s/^[ \t]*url\.redirect[ \t]*=.*/\t# url.redirect =/" /etc/lighttpd/conf.d/82-https.conf 2>/dev/null
fi
/etc/init.d/lighttpd reload
else
/etc/init.d/lighttpd reload >/dev/null 2>&1
elif [ -x /etc/init.d/uhttpd ]; then
if [ "x${https}" = "x1" ]; then
uci -q set uhttpd.main.redirect_https='1'
else
uci -q set uhttpd.main.redirect_https='0'
fi
uci commit uhttpd
/etc/init.d/uhttpd reload
uci -q commit uhttpd
/etc/init.d/uhttpd reload >/dev/null 2>&1
fi
}
process_shortcut() {
local shortcut to_url comments lanaddr
local cfg="$1"
lanaddr="$(uci get network.lan.ipaddr)"
[ -n $lanaddr ] && lanaddr="$(echo "$lanaddr"| cut -d/ -f1)"
# 提交配置并平滑重载底层服务
reload_services() {
uci -q commit dhcp
if [ -x /etc/init.d/dnsmasq ]; then
/etc/init.d/dnsmasq reload >/dev/null 2>&1
fi
config_get shortcut "$cfg" shortcut
config_get to_url "$cfg" to_url
config_get comments "$cfg" comments
uci -q set nginx._sc_$shortcut=server
uci -q set nginx._sc_$shortcut.server_name="$shortcut"
uci -q add_list nginx._sc_$shortcut.listen="80"
uci -q add_list nginx._sc_$shortcut.listen="443"
uci -q set nginx._sc_$shortcut.return="302 $to_url"
uci -q set dhcp._sc_$shortcut=domain
uci -q set dhcp._sc_$shortcut.name="$shortcut"
uci -q set dhcp._sc_$shortcut.ip="$lanaddr"
uci -q set dhcp._sc_$shortcut.comments="$comments"
if [ -f /etc/nginx/uci.conf ] && [ -x /etc/init.d/nginx ]; then
uci -q commit nginx
/etc/init.d/nginx reload >/dev/null 2>&1
fi
}
start() {
start_service() {
config_load wizard
config_foreach add_wizard wizard
if [ -n "$(command -v nginx)" ]; then
for server in $(uci show nginx | grep '_sc_' | grep '=server' | cut -d. -f2 | cut -d= -f1); do
uci delete nginx.$server
done
for server in $(uci show dhcp | grep '_sc_' | grep '=domain' | cut -d. -f2 | cut -d= -f1); do
uci delete dhcp.$server
done
config_foreach process_shortcut shortcuts
uci commit nginx
uci commit dhcp
/etc/init.d/nginx reload
# 1. 开机 CoreMark 跑分处理 (后台异步执行,杜绝阻塞系统启动与前端保存卡死)
local coremark
config_get coremark default coremark 0
if [ "$coremark" = "1" ] && [ -x /bin/coremark ] && && [ -x /sbin/coremark ][ ! -f /etc/bench.log ] && [ ! -f /var/lock/opkg.lock ]; then
(sleep 5;/sbin/coremark >/dev/null 2>&1) &
fi
(
/etc/init.d/network restart
/etc/init.d/firewall reload
/etc/init.d/dnsmasq reload
)&
# 2. 持久 Cookie 会话配置同步到 LuCI 底层系统
local cookie_p
config_get cookie_p default persistent_cookies 1
if [ -n "$cookie_p" ]; then
[ -z "$(uci -q get luci.sgi)" ] && uci -q set luci.sgi=sgi
uci -q set luci.sgi.persistent_cookies="$cookie_p"
uci -q commit luci
fi
# 3. 清理并重新配置所有 Shortcuts
clean_shortcuts
config_foreach process_shortcut shortcuts
# 4. 设置 HTTPS 重定向
sethttps default
# 5. 提交并重载生效服务
reload_services
}
restart() {
start
stop_service() {
clean_shortcuts
reload_services
}
service_triggers() {
procd_add_reload_trigger "wizard"
}
@@ -1,23 +0,0 @@
#!/bin/sh
[ ! -f "/usr/share/ucitrack/luci-app-wizard.json" ] && {
cat > /usr/share/ucitrack/luci-app-wizard.json << EEOF
{
"config": "wizard",
"init": "wizard"
}
EEOF
}
touch /etc/config/wizard
uci -q batch <<-EOF >/dev/null
delete ucitrack.@wizard[-1]
add ucitrack wizard
set ucitrack.@wizard[-1].init=wizard
commit ucitrack
EOF
chmod +x /etc/init.d/wizard 2>/dev/null
rm -f /tmp/luci-indexcache
exit 0
@@ -1,7 +1,7 @@
{
"admin/system/initsetup": {
"title": "Wizard",
"order": 1,
"admin/system/wizard": {
"title": "Setup Wizard",
"order": 10,
"action": {
"type": "view",
"path": "wizard/initsetup"
@@ -1,14 +1,30 @@
{
"luci-app-wizard": {
"description": "Grant UCI access for luci-app-wizard",
"description": "Grant UCI and filesystem access for LuCI Setup Wizard",
"read": {
"uci": [
"network",
"wireless",
"dhcp",
"uhttpd",
"firewall",
"luci",
"wizard"
],
"file": {
"/etc/init.d/wizard reconfig": [ "exec" ]
},
"uci": [ "wizard", "wireless" ]
"/etc/nginx/uci.conf": [ "stat" ]
}
},
"write": {
"uci": [ "wizard" ]
"uci": [
"network",
"wireless",
"dhcp",
"uhttpd",
"firewall",
"luci",
"wizard"
]
}
}
}
@@ -1,4 +0,0 @@
{
"config": "wizard",
"init": "wizard"
}
+3 -3
View File
@@ -17,12 +17,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-wwand
PKG_VERSION:=1.6.7_p5
PKG_RELEASE:=23
PKG_VERSION:=1.6.7_p11
PKG_RELEASE:=24
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/ddimension/luci-app-wwand.git
PKG_SOURCE_VERSION:=28b49c1034a2944b6b0b3a76746938eb9813cdd9
PKG_SOURCE_VERSION:=2ca9a94506f972d89a398d560d5667a809e89d9f
PKG_MIRROR_HASH:=skip
PKG_LICENSE:=GPL-2.0-only
@@ -8,7 +8,7 @@
.Dashboard {
--dashboard-surface-alt: var(--background-color-medium, #f2f4f7);
--dashboard-text: var(--text-color-high, #1f2933);
--dashboard-text-muted: var(--text-color-medium, #6b7280);
--dashboard-text-muted: var(--text-color-high, #4b5563);
--dashboard-border: var(--border-color-medium, rgba(0, 0, 0, 0.12));
--dashboard-primary: var(--primary-color-high, #1976d2);
--dashboard-success: var(--success-color-high, #00ac59);
@@ -33,7 +33,7 @@
[data-darkmode="true"] .Dashboard {
--dashboard-surface-alt: var(--background-color-medium, #262b34);
--dashboard-text: var(--text-color-high, #e6e8eb);
--dashboard-text-muted: var(--text-color-medium, #9aa3ad);
--dashboard-text-muted: var(--text-color-high, #9aa3ad);
--dashboard-border: var(--border-color-medium, rgba(255, 255, 255, 0.12));
--dashboard-series-4: #a98cf0;
--dashboard-series-5: #e5652c;
@@ -46,7 +46,7 @@
:root:not([data-darkmode="false"]) .Dashboard {
--dashboard-surface-alt: var(--background-color-medium, #262b34);
--dashboard-text: var(--text-color-high, #e6e8eb);
--dashboard-text-muted: var(--text-color-medium, #9aa3ad);
--dashboard-text-muted: var(--text-color-high, #9aa3ad);
--dashboard-border: var(--border-color-medium, rgba(255, 255, 255, 0.12));
--dashboard-series-4: #a98cf0;
--dashboard-series-5: #e5652c;
@@ -337,7 +337,7 @@
.dashboard-bars-stack i.dashboard-bar-mid { background: var(--dashboard-warn); }
.dashboard-bars-stack i.dashboard-bar-up { background: var(--dashboard-series-1); }
.dashboard-bars-stack i.dashboard-bar-down { background: var(--dashboard-series-2); }
.dashboard-bars-stack i.dashboard-bar-none { background: var(--dashboard-text-muted); }
.dashboard-bars-stack i.dashboard-bar-none { background: var(--dashboard-border); }
.dashboard-bars-label {
font-size: 10.5px;
+3 -3
View File
@@ -20,12 +20,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-proto-wwand
PKG_VERSION:=1.6.7_p2
PKG_RELEASE:=12
PKG_VERSION:=1.6.7_p3
PKG_RELEASE:=13
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/ddimension/luci-proto-wwand.git
PKG_SOURCE_VERSION:=ebacded705a3bd5886c42c54ba66a1e26b7e4f42
PKG_SOURCE_VERSION:=0e8dad065d301320163f65a34469d8f57b44b2da
PKG_MIRROR_HASH:=skip
PKG_LICENSE:=GPL-2.0-only
+3 -1
View File
@@ -10,7 +10,7 @@ Write narrow `[data-page]`/class-scoped overrides with native declarations, stil
The `@reference` + `@apply` route still _compiles_ for theme-repo patches and has real upsides — build-time validation (a typo'd utility fails the build; a typo'd `var()` fails silently at runtime) and the shared `dark:`/`md:`/`hover:` vocabulary — native is the default for the size numbers above, not a hard gate. App-shipped patches bypass the build entirely and have always been plain-CSS-only.
Globally-applicable chrome tweaks (e.g. icon opacity) belong in `_shared.css`, not here.
Before writing a patch, ask whether the symptom is really page-specific. Something that breaks the same way on any app's page is a theme bug: fix it in the core CSS so every page gets the fix, and keep globally-applicable chrome tweaks (e.g. icon opacity) in `_shared.css`, not here. A patch is for what is genuinely local to one app — or, for a preference the theme should not adopt for everyone, the global `admin.css` case below.
## Discovery and matching
@@ -20,6 +20,8 @@ Globally-applicable chrome tweaks (e.g. icon opacity) belong in `_shared.css`, n
Because discovery is at render time, **any package — not just the theme — may drop a `<page-prefix>.css` into `luci-static/shadcn/patches/`** and it takes effect immediately, no theme rebuild required.
**`patches/admin.css` is therefore the global patch.** Every admin page's path starts with `admin`, so the shortest prefix matches all of them; sorting loads it first and the page-specific patches cascade on top (the login page loads no patches at all). That is how a user carries a preference the theme should not ship for everyone — a CJK-friendly monospace UI font, say — without forking it: write the file by hand on the device at `/www/luci-static/shadcn/patches/admin.css`. The theme neither builds nor installs that path, so a theme upgrade leaves it alone.
`PATCH_ALIASES` in `vite.config.ts` can duplicate one built payload (CSS and JS) under several page names when unrelated pages share it. It is currently empty — the log viewer ships only `patches/admin-status-logs.js`, whose prefix covers both log tabs on every supported release; its CSS is core-page styling and lives in `components/_syslog.css` inside `main.css`, where the `.syslog-view` markup contract is deliberately global so other packages can reuse the viewer. `_`-prefixed files in `patches/` are `@import`-only fragments, never entries.
## JS payloads
+1 -1
View File
@@ -9,7 +9,7 @@ LUCI_TITLE:=A modern sidebar LuCI theme for OpenWrt, built with shadcn/ui design
LUCI_DEPENDS:=+luci-base
PKG_VERSION:=0.6.0
PKG_RELEASE:=6
PKG_RELEASE:=7
PKG_LICENSE:=Apache-2.0
LUCI_MINIFY_CSS:=
+2 -2
View File
@@ -14,8 +14,8 @@ MISE_HASH_x86_64:=3832f39c325e343f81fe3d92b2447c5d1a5eea1bc85092bb7b6c2580622264
MISE_HASH_aarch64:=06186cfbfe947049b21d58575fb0ea800cc26ed1375f20f4b678cb3a9d679437
PKG_NAME:=mise
PKG_VERSION:=2026.9.11
PKG_RELEASE:=17
PKG_VERSION:=2026.9.12
PKG_RELEASE:=18
PKG_SOURCE:=$(PKG_NAME)-v$(PKG_VERSION)-linux-$(MISE_ARCH)-musl.tar.gz
PKG_SOURCE_URL:=https://github.com/jdx/mise/releases/download/v$(PKG_VERSION)/
PKG_HASH:=skip
+2 -2
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=opera-proxy
PKG_VERSION:=1.29.0
PKG_RELEASE:=10
PKG_VERSION:=1.30.0
PKG_RELEASE:=11
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
PKG_LICENSE:=MIT
+1 -1
View File
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=shadowsocksr-libev
PKG_VERSION:=2.5.6
PKG_RELEASE:=4
PKG_RELEASE:=5
PKG_LICENSE:=GPL-3.0
PKG_LICENSE_FILES:=LICENSE
@@ -1,6 +1,6 @@
--- a/src/obfs/obfs.c
+++ b/src/obfs/obfs.c
@@ -88,7 +88,7 @@ obfs_class *new_obfs_class(const char *p
@@ -88,10 +88,11 @@ obfs_class *new_obfs_class(const char *p
plugin->client_decode = tls12_ticket_auth_client_decode;
return plugin;
@@ -9,7 +9,19 @@
obfs_class * plugin = (obfs_class*)malloc(sizeof(obfs_class));
plugin->init_data = init_data;
plugin->new_obfs = verify_simple_new_obfs;
@@ -115,7 +115,7 @@ obfs_class *new_obfs_class(const char *p
+ plugin->get_overhead = get_overhead;
plugin->get_server_info = get_server_info;
plugin->set_server_info = set_server_info;
plugin->dispose = verify_simple_dispose;
@@ -106,6 +107,7 @@ obfs_class *new_obfs_class(const char *p
obfs_class * plugin = (obfs_class*)malloc(sizeof(obfs_class));
plugin->init_data = auth_simple_init_data;
plugin->new_obfs = auth_simple_new_obfs;
+ plugin->get_overhead = get_overhead;
plugin->get_server_info = get_server_info;
plugin->set_server_info = set_server_info;
plugin->dispose = auth_simple_dispose;
@@ -115,7 +117,7 @@ obfs_class *new_obfs_class(const char *p
plugin->client_udp_pre_encrypt = NULL;
plugin->client_udp_post_decrypt = NULL;
@@ -0,0 +1,610 @@
From a7291ea725b74dc64c5806385100c535a15e889e Mon Sep 17 00:00:00 2001
From: Max Lv <max.c.lv@gmail.com>
Date: Tue, 30 Apr 2019 09:22:54 +0800
Subject: [PATCH] Enlarge the socket buffer size to 16KB.
After five years, memory footprint is not a problem for most of
low-end boxes and high-end routers now.
It should also improve the speed on loopback and high-speed LAN
networks.
---
src/local.c | 26 +++++++++++---------------
src/netutils.h | 2 ++
src/redir.c | 24 ++++++++++++------------
src/server.c | 24 ++++++++++--------------
src/tunnel.c | 18 +++++++-----------
5 files changed, 42 insertions(+), 52 deletions(-)
--- a/server/netutils.h
+++ b/server/netutils.h
@@ -60,6 +60,8 @@
/** byte size of ip6 address */
#define INET6_SIZE 16
+#define SOCKET_BUF_SIZE (16 * 1024) // 16KB
+
size_t get_sockaddr_len(struct sockaddr *addr);
ssize_t get_sockaddr(char *host, char *port,
struct sockaddr_storage *storage, int block,
--- a/server/server.c
+++ b/server/server.c
@@ -73,10 +73,6 @@
#define EWOULDBLOCK EAGAIN
#endif
-#ifndef BUF_SIZE
-#define BUF_SIZE 2048
-#endif
-
#ifndef SSMAXCONN
#define SSMAXCONN 1024
#endif
@@ -142,13 +138,13 @@ stat_update_cb(EV_P_ ev_timer *watcher,
struct sockaddr_un svaddr, claddr;
int sfd = -1;
size_t msgLen;
- char resp[BUF_SIZE];
+ char resp[SOCKET_BUF_SIZE];
if (verbose) {
LOGI("update traffic stat: tx: %" PRIu64 " rx: %" PRIu64 "", tx, rx);
}
- snprintf(resp, BUF_SIZE, "stat: {\"%s\":%" PRIu64 "}", server_port, tx + rx);
+ snprintf(resp, SOCKET_BUF_SIZE, "stat: {\"%s\":%" PRIu64 "}", server_port, tx + rx);
msgLen = strlen(resp) + 1;
ss_addr_t ip_addr = { .host = NULL, .port = NULL };
@@ -625,14 +621,14 @@ server_recv_cb(EV_P_ ev_io *w, int reven
ev_timer_again(EV_A_ & server->recv_ctx->watcher);
}
- if (len > BUF_SIZE) {
+ if (len > SOCKET_BUF_SIZE) {
ERROR("out of recv buffer");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
return;
}
- ssize_t r = recv(server->fd, buf->array + len, BUF_SIZE - len, 0);
+ ssize_t r = recv(server->fd, buf->array + len, SOCKET_BUF_SIZE - len, 0);
if (r == 0) {
// connection closed
@@ -712,7 +708,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
}
if (needsendback) {
- size_t capacity = BUF_SIZE;
+ size_t capacity = SOCKET_BUF_SIZE;
char *sendback_buf = (char*)malloc(capacity);
obfs_class *obfs_plugin = server->obfs_plugin;
if (obfs_plugin->server_encode) {
@@ -725,7 +721,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
}
}
- int err = ss_decrypt(buf, server->d_ctx, BUF_SIZE);
+ int err = ss_decrypt(buf, server->d_ctx, SOCKET_BUF_SIZE);
if (err) {
report_addr(server->fd, MALICIOUS);
@@ -797,7 +793,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
if (server->stage == STAGE_HANDSHAKE) {
size_t header_len = server->header_buf->len;
- if (brealloc(server->header_buf, server->buf->len + header_len, BUF_SIZE) != 0) {
+ if (brealloc(server->header_buf, server->buf->len + header_len, SOCKET_BUF_SIZE) != 0) {
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
return;
@@ -809,7 +805,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
int ret = is_header_complete(server->buf);
if (ret == 1) {
- brealloc(server->buf, server->header_buf->len, BUF_SIZE);
+ brealloc(server->buf, server->header_buf->len, SOCKET_BUF_SIZE);
memcpy(server->buf->array, server->header_buf->array, server->header_buf->len);
server->buf->len = server->header_buf->len;
bfree(server->header_buf);
@@ -826,7 +822,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
// handshake and transmit data
if (server->stage == STAGE_STREAM) {
- if (server->auth && !ss_check_hash(remote->buf, server->chunk, server->d_ctx, BUF_SIZE)) {
+ if (server->auth && !ss_check_hash(remote->buf, server->chunk, server->d_ctx, SOCKET_BUF_SIZE)) {
LOGE("hash error");
report_addr(server->fd, BAD);
close_and_free_server(EV_A_ server);
@@ -1040,7 +1036,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
LOGI("connect to %s:%d", host, ntohs(port));
}
- if (server->auth && !ss_check_hash(server->buf, server->chunk, server->d_ctx, BUF_SIZE)) {
+ if (server->auth && !ss_check_hash(server->buf, server->chunk, server->d_ctx, SOCKET_BUF_SIZE)) {
LOGE("hash error");
report_addr(server->fd, BAD);
close_and_free_server(EV_A_ server);
@@ -1258,7 +1254,7 @@ remote_recv_cb(EV_P_ ev_io *w, int reven
ev_timer_again(EV_A_ & server->recv_ctx->watcher);
- ssize_t r = recv(remote->fd, server->buf->array, BUF_SIZE, 0);
+ ssize_t r = recv(remote->fd, server->buf->array, SOCKET_BUF_SIZE, 0);
if (r == 0) {
// connection closed
@@ -1300,7 +1296,7 @@ remote_recv_cb(EV_P_ ev_io *w, int reven
}
}
- int err = ss_encrypt(server->buf, server->e_ctx, BUF_SIZE);
+ int err = ss_encrypt(server->buf, server->e_ctx, SOCKET_BUF_SIZE);
if (err) {
LOGE("invalid password or cipher");
@@ -1459,7 +1455,7 @@ new_remote(int fd)
ev_io_init(&remote->recv_ctx->io, remote_recv_cb, fd, EV_READ);
ev_io_init(&remote->send_ctx->io, remote_send_cb, fd, EV_WRITE);
- balloc(remote->buf, BUF_SIZE);
+ balloc(remote->buf, SOCKET_BUF_SIZE);
return remote;
}
@@ -1538,8 +1534,8 @@ new_server(int fd, listen_ctx_t *listene
ev_timer_init(&server->recv_ctx->watcher, server_timeout_cb,
request_timeout, listener->timeout);
- balloc(server->buf, BUF_SIZE);
- balloc(server->header_buf, BUF_SIZE);
+ balloc(server->buf, SOCKET_BUF_SIZE);
+ balloc(server->header_buf, SOCKET_BUF_SIZE);
server->chunk = (chunk_t *)malloc(sizeof(chunk_t));
memset(server->chunk, 0, sizeof(chunk_t));
--- a/src/local.c
+++ b/src/local.c
@@ -92,10 +92,6 @@
#define EWOULDBLOCK EAGAIN
#endif
-#ifndef BUF_SIZE
-#define BUF_SIZE 2048
-#endif
-
int verbose = 0;
int keep_resolving = 1;
@@ -305,7 +301,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
buf = remote->buf;
}
- r = recv(server->fd, buf->array + buf->len, BUF_SIZE - buf->len, 0);
+ r = recv(server->fd, buf->array + buf->len, SOCKET_BUF_SIZE - buf->len, 0);
if (r == 0) {
// connection closed
@@ -372,7 +368,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
);
}
}
- int err = ss_encrypt(&server_env->cipher, remote->buf, server->e_ctx, BUF_SIZE);
+ int err = ss_encrypt(&server_env->cipher, remote->buf, server->e_ctx, SOCKET_BUF_SIZE);
if (err) {
LOGE("server invalid password or cipher");
@@ -592,7 +588,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
buffer_t resp_to_send;
buffer_t *resp_buf = &resp_to_send;
- balloc(resp_buf, BUF_SIZE);
+ balloc(resp_buf, SOCKET_BUF_SIZE);
memcpy(resp_buf->array, &response, sizeof(struct socks5_response));
memcpy(resp_buf->array + sizeof(struct socks5_response),
@@ -624,7 +620,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
buffer_t ss_addr_to_send;
buffer_t *abuf = &ss_addr_to_send;
- balloc(abuf, BUF_SIZE);
+ balloc(abuf, SOCKET_BUF_SIZE);
abuf->array[abuf->len++] = request->atyp;
int atyp = request->atyp;
@@ -694,7 +690,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
else if (p == tls_protocol->default_port)
ret = tls_protocol->parse_packet(buf->array + 3 + abuf->len,
buf->len - 3 - abuf->len, &hostname);
- if (ret == -1 && buf->len < BUF_SIZE) {
+ if (ret == -1 && buf->len < SOCKET_BUF_SIZE) {
server->stage = STAGE_PARSE;
bfree(abuf);
return;
@@ -880,7 +876,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
_server_info.key = enc_get_key(&server_env->cipher);
_server_info.key_len = enc_get_key_len(&server_env->cipher);
_server_info.tcp_mss = 1452;
- _server_info.buffer_size = BUF_SIZE;
+ _server_info.buffer_size = SOCKET_BUF_SIZE;
_server_info.cipher_env = &server_env->cipher;
_server_info.param = server_env->obfs_param;
@@ -909,7 +905,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
}
// SSR end
- brealloc(remote->buf, buf->len + abuf->len, BUF_SIZE);
+ brealloc(remote->buf, buf->len + abuf->len, SOCKET_BUF_SIZE);
memcpy(remote->buf->array, abuf->array, abuf->len);
remote->buf->len = buf->len + abuf->len;
@@ -1012,7 +1008,7 @@ remote_recv_cb(EV_P_ ev_io *w, int reven
stat_update_cb();
#endif
- ssize_t r = recv(remote->fd, server->buf->array, BUF_SIZE, 0);
+ ssize_t r = recv(remote->fd, server->buf->array, SOCKET_BUF_SIZE, 0);
if (r == 0) {
// connection closed
@@ -1094,7 +1090,7 @@ remote_recv_cb(EV_P_ ev_io *w, int reven
}
}
if (server->buf->len > 0) {
- int err = ss_decrypt(&server_env->cipher, server->buf, server->d_ctx, BUF_SIZE);
+ int err = ss_decrypt(&server_env->cipher, server->buf, server->d_ctx, SOCKET_BUF_SIZE);
if (err) {
LOGE("remote invalid password or cipher");
close_and_free_remote(EV_A_ remote);
@@ -1220,7 +1216,7 @@ new_remote(int fd, int timeout) {
remote->buf = ss_malloc(sizeof(buffer_t));
remote->recv_ctx = ss_malloc(sizeof(remote_ctx_t));
remote->send_ctx = ss_malloc(sizeof(remote_ctx_t));
- balloc(remote->buf, BUF_SIZE);
+ balloc(remote->buf, SOCKET_BUF_SIZE);
memset(remote->recv_ctx, 0, sizeof(remote_ctx_t));
memset(remote->send_ctx, 0, sizeof(remote_ctx_t));
remote->recv_ctx->connected = 0;
@@ -1276,7 +1272,7 @@ new_server(int fd, listen_ctx_t *profile
server->recv_ctx = ss_malloc(sizeof(server_ctx_t));
server->send_ctx = ss_malloc(sizeof(server_ctx_t));
server->buf = ss_malloc(sizeof(buffer_t));
- balloc(server->buf, BUF_SIZE);
+ balloc(server->buf, SOCKET_BUF_SIZE);
memset(server->recv_ctx, 0, sizeof(server_ctx_t));
memset(server->send_ctx, 0, sizeof(server_ctx_t));
server->stage = STAGE_INIT;
--- a/src/netutils.h
+++ b/src/netutils.h
@@ -60,6 +60,8 @@
/** byte size of ip6 address */
#define INET6_SIZE 16
+#define SOCKET_BUF_SIZE (16 * 1024) // 16KB
+
size_t get_sockaddr_len(struct sockaddr *addr);
ssize_t get_sockaddr(char *host, char *port,
struct sockaddr_storage *storage, int block,
--- a/src/redir.c
+++ b/src/redir.c
@@ -63,10 +63,6 @@
#define EWOULDBLOCK EAGAIN
#endif
-#ifndef BUF_SIZE
-#define BUF_SIZE 2048
-#endif
-
#ifndef IP6T_SO_ORIGINAL_DST
#define IP6T_SO_ORIGINAL_DST 80
#endif
@@ -226,7 +222,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
server_def_t *server_env = server->server_env;
ssize_t r = recv(server->fd, remote->buf->array + remote->buf->len,
- BUF_SIZE - remote->buf->len, 0);
+ SOCKET_BUF_SIZE - remote->buf->len, 0);
if (r == 0) {
// connection closed
@@ -299,7 +295,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
remote->buf->len = protocol_plugin->client_pre_encrypt(server->protocol, &remote->buf->array, remote->buf->len, &remote->buf->capacity);
}
}
- int err = ss_encrypt(&server_env->cipher, remote->buf, server->e_ctx, BUF_SIZE);
+ int err = ss_encrypt(&server_env->cipher, remote->buf, server->e_ctx, SOCKET_BUF_SIZE);
if (err) {
LOGE("invalid password or cipher");
@@ -416,7 +412,7 @@ remote_recv_cb(EV_P_ ev_io *w, int reven
ev_timer_again(EV_A_ & remote->recv_ctx->watcher);
- ssize_t r = recv(remote->fd, server->buf->array, BUF_SIZE, 0);
+ ssize_t r = recv(remote->fd, server->buf->array, SOCKET_BUF_SIZE, 0);
if (r == 0) {
// connection closed
@@ -482,7 +478,7 @@ remote_recv_cb(EV_P_ ev_io *w, int reven
if ( server->buf->len == 0 )
return;
- int err = ss_decrypt(&server_env->cipher, server->buf, server->d_ctx, BUF_SIZE);
+ int err = ss_decrypt(&server_env->cipher, server->buf, server->d_ctx, SOCKET_BUF_SIZE);
if (err) {
LOGE("invalid password or cipher");
close_and_free_remote(EV_A_ remote);
@@ -550,7 +546,7 @@ remote_send_cb(EV_P_ ev_io *w, int reven
// send destaddr
buffer_t ss_addr_to_send;
buffer_t *abuf = &ss_addr_to_send;
- balloc(abuf, BUF_SIZE);
+ balloc(abuf, SOCKET_BUF_SIZE);
if (server->hostname_len > 0
&& validate_hostname(server->hostname, server->hostname_len)) { // HTTP/SNI
@@ -591,12 +587,12 @@ remote_send_cb(EV_P_ ev_io *w, int reven
abuf->len += 2;
if (remote->buf->len > 0) {
- brealloc(remote->buf, remote->buf->len + abuf->len, BUF_SIZE);
+ brealloc(remote->buf, remote->buf->len + abuf->len, SOCKET_BUF_SIZE);
memmove(remote->buf->array + abuf->len, remote->buf->array, remote->buf->len);
memcpy(remote->buf->array, abuf->array, abuf->len);
remote->buf->len += abuf->len;
} else {
- brealloc(remote->buf, abuf->len, BUF_SIZE);
+ brealloc(remote->buf, abuf->len, SOCKET_BUF_SIZE);
memcpy(remote->buf->array, abuf->array, abuf->len);
remote->buf->len = abuf->len;
}
@@ -616,7 +612,7 @@ remote_send_cb(EV_P_ ev_io *w, int reven
}
}
- int err = ss_encrypt(&server_env->cipher, remote->buf, server->e_ctx, BUF_SIZE);
+ int err = ss_encrypt(&server_env->cipher, remote->buf, server->e_ctx, SOCKET_BUF_SIZE);
if (err) {
LOGE("invalid password or cipher");
close_and_free_remote(EV_A_ remote);
@@ -683,7 +679,7 @@ new_remote(int fd, int timeout)
remote->buf = ss_malloc(sizeof(buffer_t));
remote->recv_ctx = ss_malloc(sizeof(remote_ctx_t));
remote->send_ctx = ss_malloc(sizeof(remote_ctx_t));
- balloc(remote->buf, BUF_SIZE);
+ balloc(remote->buf, SOCKET_BUF_SIZE);
memset(remote->recv_ctx, 0, sizeof(remote_ctx_t));
memset(remote->send_ctx, 0, sizeof(remote_ctx_t));
remote->recv_ctx->connected = 0;
@@ -741,7 +737,7 @@ new_server(int fd, listen_ctx_t* profile
server->recv_ctx = ss_malloc(sizeof(server_ctx_t));
server->send_ctx = ss_malloc(sizeof(server_ctx_t));
server->buf = ss_malloc(sizeof(buffer_t));
- balloc(server->buf, BUF_SIZE);
+ balloc(server->buf, SOCKET_BUF_SIZE);
memset(server->recv_ctx, 0, sizeof(server_ctx_t));
memset(server->send_ctx, 0, sizeof(server_ctx_t));
server->recv_ctx->connected = 0;
@@ -1028,7 +1024,7 @@ accept_cb(EV_P_ ev_io *w, int revents)
_server_info.key = enc_get_key(&server_env->cipher);
_server_info.key_len = enc_get_key_len(&server_env->cipher);
_server_info.tcp_mss = 1452;
- _server_info.buffer_size = BUF_SIZE;
+ _server_info.buffer_size = SOCKET_BUF_SIZE;
_server_info.cipher_env = &server_env->cipher;
if (server_env->obfs_plugin) {
--- a/src/server.c
+++ b/src/server.c
@@ -71,10 +71,6 @@
#define EWOULDBLOCK EAGAIN
#endif
-#ifndef BUF_SIZE
-#define BUF_SIZE 2048
-#endif
-
#ifndef SSMAXCONN
#define SSMAXCONN 1024
#endif
@@ -138,13 +134,13 @@ stat_update_cb(EV_P_ ev_timer *watcher,
int sfd = -1;
size_t msgLen;
- char resp[BUF_SIZE];
+ char resp[SOCKET_BUF_SIZE];
if (verbose) {
LOGI("update traffic stat: tx: %" PRIu64 " rx: %" PRIu64 "", tx, rx);
}
- snprintf(resp, BUF_SIZE, "stat: {\"%s\":%" PRIu64 "}", server_port, tx + rx);
+ snprintf(resp, SOCKET_BUF_SIZE, "stat: {\"%s\":%" PRIu64 "}", server_port, tx + rx);
msgLen = strlen(resp) + 1;
ss_addr_t ip_addr = { .host = NULL, .port = NULL };
@@ -600,14 +596,14 @@ server_recv_cb(EV_P_ ev_io *w, int reven
ev_timer_again(EV_A_ & server->recv_ctx->watcher);
}
- if (len > BUF_SIZE) {
+ if (len > SOCKET_BUF_SIZE) {
ERROR("out of recv buffer");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
return;
}
- ssize_t r = recv(server->fd, buf->array + len, BUF_SIZE - len, 0);
+ ssize_t r = recv(server->fd, buf->array + len, SOCKET_BUF_SIZE - len, 0);
if (r == 0) {
// connection closed
@@ -649,7 +645,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
buf->len = r;
}
- int err = ss_decrypt(&cipher_env, buf, server->d_ctx, BUF_SIZE);
+ int err = ss_decrypt(&cipher_env, buf, server->d_ctx, SOCKET_BUF_SIZE);
if (err) {
report_addr(server->fd, MALICIOUS);
@@ -678,7 +674,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
if (server->stage == STAGE_HANDSHAKE) {
size_t header_len = server->header_buf->len;
- brealloc(server->header_buf, server->buf->len + header_len, BUF_SIZE);
+ brealloc(server->header_buf, server->buf->len + header_len, SOCKET_BUF_SIZE);
memcpy(server->header_buf->array + header_len,
server->buf->array, server->buf->len);
server->header_buf->len = server->buf->len + header_len;
@@ -686,7 +682,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
int ret = is_header_complete(server->buf);
if (ret == 1) {
- brealloc(server->buf, server->header_buf->len, BUF_SIZE);
+ brealloc(server->buf, server->header_buf->len, SOCKET_BUF_SIZE);
memcpy(server->buf->array, server->header_buf->array, server->header_buf->len);
server->buf->len = server->header_buf->len;
bfree(server->header_buf);
@@ -1086,7 +1082,7 @@ remote_recv_cb(EV_P_ ev_io *w, int reven
ev_timer_again(EV_A_ & server->recv_ctx->watcher);
- ssize_t r = recv(remote->fd, server->buf->array, BUF_SIZE, 0);
+ ssize_t r = recv(remote->fd, server->buf->array, SOCKET_BUF_SIZE, 0);
if (r == 0) {
// connection closed
@@ -1112,7 +1108,7 @@ remote_recv_cb(EV_P_ ev_io *w, int reven
rx += r;
server->buf->len = r;
- int err = ss_encrypt(&cipher_env, server->buf, server->e_ctx, BUF_SIZE);
+ int err = ss_encrypt(&cipher_env, server->buf, server->e_ctx, SOCKET_BUF_SIZE);
if (err) {
LOGE("invalid password or cipher");
@@ -1249,7 +1245,7 @@ new_remote(int fd)
remote->recv_ctx = ss_malloc(sizeof(remote_ctx_t));
remote->send_ctx = ss_malloc(sizeof(remote_ctx_t));
remote->buf = ss_malloc(sizeof(buffer_t));
- balloc(remote->buf, BUF_SIZE);
+ balloc(remote->buf, SOCKET_BUF_SIZE);
memset(remote->recv_ctx, 0, sizeof(remote_ctx_t));
memset(remote->send_ctx, 0, sizeof(remote_ctx_t));
remote->fd = fd;
@@ -1313,8 +1309,8 @@ new_server(int fd, listen_ctx_t *listene
server->header_buf = ss_malloc(sizeof(buffer_t));
memset(server->recv_ctx, 0, sizeof(server_ctx_t));
memset(server->send_ctx, 0, sizeof(server_ctx_t));
- balloc(server->buf, BUF_SIZE);
- balloc(server->header_buf, BUF_SIZE);
+ balloc(server->buf, SOCKET_BUF_SIZE);
+ balloc(server->header_buf, SOCKET_BUF_SIZE);
server->fd = fd;
server->recv_ctx->server = server;
server->recv_ctx->connected = 0;
--- a/src/tunnel.c
+++ b/src/tunnel.c
@@ -67,10 +67,6 @@
#define EWOULDBLOCK EAGAIN
#endif
-#ifndef BUF_SIZE
-#define BUF_SIZE 2048
-#endif
-
#include "includeobfs.h" // I don't want to modify makefile
static void accept_cb(EV_P_ ev_io *w, int revents);
@@ -180,7 +176,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
return;
}
- ssize_t r = recv(server->fd, remote->buf->array, BUF_SIZE, 0);
+ ssize_t r = recv(server->fd, remote->buf->array, SOCKET_BUF_SIZE, 0);
if (r == 0) {
// connection closed
@@ -209,7 +205,7 @@ server_recv_cb(EV_P_ ev_io *w, int reven
remote->buf->len = protocol_plugin->client_pre_encrypt(server->protocol, &remote->buf->array, remote->buf->len, &remote->buf->capacity);
}
}
- int err = ss_encrypt(&cipher_env, remote->buf, server->e_ctx, BUF_SIZE);
+ int err = ss_encrypt(&cipher_env, remote->buf, server->e_ctx, SOCKET_BUF_SIZE);
if (err) {
LOGE("server invalid password or cipher");
@@ -325,7 +321,7 @@ remote_recv_cb(EV_P_ ev_io *w, int reven
remote_t *remote = remote_recv_ctx->remote;
server_t *server = remote->server;
- ssize_t r = recv(remote->fd, server->buf->array, BUF_SIZE, 0);
+ ssize_t r = recv(remote->fd, server->buf->array, SOCKET_BUF_SIZE, 0);
if (r == 0) {
// connection closed
@@ -391,7 +387,7 @@ remote_recv_cb(EV_P_ ev_io *w, int reven
if ( server->buf->len == 0 )
return;
- int err = ss_decrypt(&cipher_env, server->buf, server->d_ctx, BUF_SIZE);
+ int err = ss_decrypt(&cipher_env, server->buf, server->d_ctx, SOCKET_BUF_SIZE);
if (err) {
LOGE("invalid password or cipher");
@@ -456,7 +452,7 @@ remote_send_cb(EV_P_ ev_io *w, int reven
buffer_t ss_addr_to_send;
buffer_t *abuf = &ss_addr_to_send;
- balloc(abuf, BUF_SIZE);
+ balloc(abuf, SOCKET_BUF_SIZE);
ss_addr_t *sa = &server->destaddr;
struct cork_ip ip;
@@ -503,12 +499,12 @@ remote_send_cb(EV_P_ ev_io *w, int reven
abuf->len += 2;
if (remote->buf->len > 0) {
- brealloc(remote->buf, remote->buf->len + abuf->len, BUF_SIZE);
+ brealloc(remote->buf, remote->buf->len + abuf->len, SOCKET_BUF_SIZE);
memmove(remote->buf->array + abuf->len, remote->buf->array, remote->buf->len);
memcpy(remote->buf->array, abuf->array, abuf->len);
remote->buf->len += abuf->len;
} else {
- brealloc(remote->buf, abuf->len, BUF_SIZE);
+ brealloc(remote->buf, abuf->len, SOCKET_BUF_SIZE);
memcpy(remote->buf->array, abuf->array, abuf->len);
remote->buf->len = abuf->len;
}
@@ -528,7 +524,7 @@ remote_send_cb(EV_P_ ev_io *w, int reven
}
}
- int err = ss_encrypt(&cipher_env, remote->buf, server->e_ctx, BUF_SIZE);
+ int err = ss_encrypt(&cipher_env, remote->buf, server->e_ctx, SOCKET_BUF_SIZE);
if (err) {
LOGE("invalid password or cipher");
close_and_free_remote(EV_A_ remote);
@@ -610,7 +606,7 @@ new_remote(int fd, int timeout)
remote->buf = ss_malloc(sizeof(buffer_t));
remote->recv_ctx = ss_malloc(sizeof(remote_ctx_t));
remote->send_ctx = ss_malloc(sizeof(remote_ctx_t));
- balloc(remote->buf, BUF_SIZE);
+ balloc(remote->buf, SOCKET_BUF_SIZE);
memset(remote->recv_ctx, 0, sizeof(remote_ctx_t));
memset(remote->send_ctx, 0, sizeof(remote_ctx_t));
remote->fd = fd;
@@ -665,7 +661,7 @@ new_server(int fd, int method)
server->buf = ss_malloc(sizeof(buffer_t));
server->recv_ctx = ss_malloc(sizeof(server_ctx_t));
server->send_ctx = ss_malloc(sizeof(server_ctx_t));
- balloc(server->buf, BUF_SIZE);
+ balloc(server->buf, SOCKET_BUF_SIZE);
memset(server->recv_ctx, 0, sizeof(server_ctx_t));
memset(server->send_ctx, 0, sizeof(server_ctx_t));
server->fd = fd;
@@ -0,0 +1,31 @@
From 38e83d6b5d4648fb87ae6680c8fa159df54c73ce Mon Sep 17 00:00:00 2001
From: Max Lv <max.c.lv@gmail.com>
Date: Tue, 30 Apr 2019 09:34:31 +0800
Subject: [PATCH] Make sure socket buffer size no larger than max chunk size
---
src/netutils.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/server/netutils.h
+++ b/server/netutils.h
@@ -60,7 +60,7 @@
/** byte size of ip6 address */
#define INET6_SIZE 16
-#define SOCKET_BUF_SIZE (16 * 1024) // 16KB
+#define SOCKET_BUF_SIZE (16 * 1024 - 1) // 16383 Byte, equals to the max chunk size
size_t get_sockaddr_len(struct sockaddr *addr);
ssize_t get_sockaddr(char *host, char *port,
--- a/src/netutils.h
+++ b/src/netutils.h
@@ -60,7 +60,7 @@
/** byte size of ip6 address */
#define INET6_SIZE 16
-#define SOCKET_BUF_SIZE (16 * 1024) // 16KB
+#define SOCKET_BUF_SIZE (16 * 1024 - 1) // 16383 Byte, equals to the max chunk size
size_t get_sockaddr_len(struct sockaddr *addr);
ssize_t get_sockaddr(char *host, char *port,
@@ -0,0 +1,205 @@
--- a/server/auth.c
+++ b/server/auth.c
@@ -27,7 +27,7 @@ typedef struct auth_simple_local_data {
void auth_simple_local_data_init(auth_simple_local_data* local) {
local->has_sent_header = 0;
- local->recv_buffer = (char*)malloc(16384);
+ local->recv_buffer = (char*)malloc(SSR_RECV_BUF_SIZE);
local->recv_buffer_size = 0;
local->recv_id = 1;
local->pack_id = 1;
@@ -173,7 +173,7 @@ int auth_simple_client_post_decrypt(obfs
char *plaindata = *pplaindata;
auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
- if (local->recv_buffer_size + datalength > 16384)
+ if (local->recv_buffer_size + datalength > SSR_RECV_BUF_SIZE)
return -1;
memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
local->recv_buffer_size += datalength;
@@ -290,7 +290,7 @@ int auth_sha1_client_post_decrypt(obfs *
char *plaindata = *pplaindata;
auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
- if (local->recv_buffer_size + datalength > 16384)
+ if (local->recv_buffer_size + datalength > SSR_RECV_BUF_SIZE)
return -1;
memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
local->recv_buffer_size += datalength;
@@ -428,7 +428,7 @@ int auth_sha1_v2_client_post_decrypt(obf
char *plaindata = *pplaindata;
auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
- if (local->recv_buffer_size + datalength > 16384)
+ if (local->recv_buffer_size + datalength > SSR_RECV_BUF_SIZE)
return -1;
memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
local->recv_buffer_size += datalength;
@@ -586,7 +586,7 @@ int auth_sha1_v4_client_post_decrypt(obf
char *plaindata = *pplaindata;
auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
- if (local->recv_buffer_size + datalength > 16384)
+ if (local->recv_buffer_size + datalength > SSR_RECV_BUF_SIZE)
return -1;
memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
local->recv_buffer_size += datalength;
@@ -843,7 +843,7 @@ int auth_aes128_sha1_client_post_decrypt
auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
//server_info *server = (server_info*)&self->server;
uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
- if (local->recv_buffer_size + datalength > 16384)
+ if (local->recv_buffer_size + datalength > SSR_RECV_BUF_SIZE)
return -1;
memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
local->recv_buffer_size += datalength;
--- a/server/obfs.h
+++ b/server/obfs.h
@@ -10,6 +10,9 @@
#include <stdint.h>
#include <unistd.h>
+/* Keep room for a TCP read, a pending obfs record and a protocol frame. */
+#define SSR_RECV_BUF_SIZE (32 * 1024)
+
typedef struct server_info {
char host[64];
uint16_t port;
--- a/server/verify.c
+++ b/server/verify.c
@@ -9,7 +9,7 @@ typedef struct verify_simple_local_data
}verify_simple_local_data;
void verify_simple_local_data_init(verify_simple_local_data* local) {
- local->recv_buffer = (char*)malloc(16384);
+ local->recv_buffer = (char*)malloc(SSR_RECV_BUF_SIZE);
local->recv_buffer_size = 0;
}
@@ -74,7 +74,7 @@ int verify_simple_client_post_decrypt(ob
char *plaindata = *pplaindata;
verify_simple_local_data *local = (verify_simple_local_data*)self->l_data;
uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
- if (local->recv_buffer_size + datalength > 16384)
+ if (local->recv_buffer_size + datalength > SSR_RECV_BUF_SIZE)
return -1;
memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
local->recv_buffer_size += datalength;
@@ -144,7 +144,7 @@ int verify_simple_server_post_decrypt(ob
char *plaindata = *pplaindata;
verify_simple_local_data *local = (verify_simple_local_data*)self->l_data;
uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
- if (local->recv_buffer_size + datalength > 16384)
+ if (local->recv_buffer_size + datalength > SSR_RECV_BUF_SIZE)
{
LOGE("verify_simple: wrong buf length %d", local->recv_buffer_size + datalength);
return -1;
--- a/src/obfs/auth.c
+++ b/src/obfs/auth.c
@@ -33,7 +33,7 @@ typedef struct auth_simple_local_data {
void auth_simple_local_data_init(auth_simple_local_data* local) {
local->has_sent_header = 0;
- local->recv_buffer = (char*)malloc(16384);
+ local->recv_buffer = (char*)malloc(SSR_RECV_BUF_SIZE);
local->recv_buffer_size = 0;
local->recv_id = 1;
local->pack_id = 1;
@@ -176,7 +176,7 @@ int auth_simple_client_post_decrypt(obfs
char *plaindata = *pplaindata;
auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
- if (local->recv_buffer_size + datalength > 16384)
+ if (local->recv_buffer_size + datalength > SSR_RECV_BUF_SIZE)
return -1;
memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
local->recv_buffer_size += datalength;
@@ -293,7 +293,7 @@ int auth_sha1_client_post_decrypt(obfs *
char *plaindata = *pplaindata;
auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
- if (local->recv_buffer_size + datalength > 16384)
+ if (local->recv_buffer_size + datalength > SSR_RECV_BUF_SIZE)
return -1;
memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
local->recv_buffer_size += datalength;
@@ -431,7 +431,7 @@ int auth_sha1_v2_client_post_decrypt(obf
char *plaindata = *pplaindata;
auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
- if (local->recv_buffer_size + datalength > 16384)
+ if (local->recv_buffer_size + datalength > SSR_RECV_BUF_SIZE)
return -1;
memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
local->recv_buffer_size += datalength;
@@ -589,7 +589,7 @@ int auth_sha1_v4_client_post_decrypt(obf
char *plaindata = *pplaindata;
auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
- if (local->recv_buffer_size + datalength > 16384)
+ if (local->recv_buffer_size + datalength > SSR_RECV_BUF_SIZE)
return -1;
memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
local->recv_buffer_size += datalength;
@@ -859,7 +859,7 @@ int auth_aes128_sha1_client_post_decrypt
auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
//server_info *server = (server_info*)&self->server;
uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
- if (local->recv_buffer_size + datalength > 16384)
+ if (local->recv_buffer_size + datalength > SSR_RECV_BUF_SIZE)
return -1;
memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
local->recv_buffer_size += datalength;
--- a/src/obfs/auth_chain.c
+++ b/src/obfs/auth_chain.c
@@ -130,7 +130,7 @@ typedef struct auth_chain_local_data {
void auth_chain_local_data_init(auth_chain_local_data *local) {
local->has_sent_header = 0;
- local->recv_buffer = (char *) malloc(16384);
+ local->recv_buffer = (char *) malloc(SSR_RECV_BUF_SIZE);
local->recv_buffer_size = 0;
local->recv_id = 1;
local->pack_id = 1;
@@ -904,7 +904,7 @@ int auth_chain_a_client_post_decrypt(obf
auth_chain_local_data *local = (auth_chain_local_data *) self->l_data;
server_info *server = (server_info *) &self->server;
uint8_t *recv_buffer = (uint8_t *) local->recv_buffer;
- if (local->recv_buffer_size + datalength > 16384)
+ if (local->recv_buffer_size + datalength > SSR_RECV_BUF_SIZE)
return -1;
memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
local->recv_buffer_size += datalength;
--- a/src/obfs/obfs.h
+++ b/src/obfs/obfs.h
@@ -13,6 +13,9 @@
#define OBFS_HMAC_SHA1_LEN 10
+/* Keep room for a TCP read, a pending obfs record and a protocol frame. */
+#define SSR_RECV_BUF_SIZE (32 * 1024)
+
typedef struct {
char host[256];
uint16_t port;
--- a/src/obfs/verify.c
+++ b/src/obfs/verify.c
@@ -12,7 +12,7 @@ typedef struct verify_simple_local_data
}verify_simple_local_data;
void verify_simple_local_data_init(verify_simple_local_data* local) {
- local->recv_buffer = (char*)malloc(16384);
+ local->recv_buffer = (char*)malloc(SSR_RECV_BUF_SIZE);
local->recv_buffer_size = 0;
}
@@ -77,7 +77,7 @@ int verify_simple_client_post_decrypt(ob
char *plaindata = *pplaindata;
verify_simple_local_data *local = (verify_simple_local_data*)self->l_data;
uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
- if (local->recv_buffer_size + datalength > 16384)
+ if (local->recv_buffer_size + datalength > SSR_RECV_BUF_SIZE)
return -1;
memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
local->recv_buffer_size += datalength;
+44 -3
View File
@@ -11,6 +11,7 @@
# wwand-ncm - NCM/ECM backend (DEPENDS wwand)
# wwand-mhi - PCIe/MHI transport + drivers (DEPENDS wwand; add a backend)
# wwand-esim - eSIM management (DEPENDS wwand + lpac; no backend)
# wwand-gps - GNSS/NMEA glue for ugps (DEPENDS wwand + ugps; no backend)
# A typical QMI router installs `wwand-qmi` (which pulls in `wwand`); add
# `wwand-mbim` / `wwand-ncm` for those modems, `wwand-mhi` for a PCIe/MHI modem.
# The ucode tree ships PRECOMPILED to bytecode (repo-root CMakeLists.txt,
@@ -49,12 +50,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=wwand
PKG_VERSION:=1.6.7_p46
PKG_RELEASE:=47
PKG_VERSION:=1.6.7_p55
PKG_RELEASE:=48
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
PKG_SOURCE_VERSION:=6ae4cab239ecd02e570d7cd51584d60101704732
PKG_SOURCE_VERSION:=aaf2ba064bbf751eaa0237e00f94c1619f05b648
PKG_MIRROR_HASH:=skip
PKG_LICENSE:=GPL-2.0-only
@@ -437,6 +438,45 @@ define Package/wwand-esim/install
$(INSTALL_DATA) $(WWAND_UCODE)/esim_bridge.uc $(1)$(UCDIR)/
endef
# ---------------------------------------------------------------------------
# wwand-gps: point ugps at the modem's NMEA port. Backend-neutral — finding the
# port and starting the receiver are both in the base package, and neither
# depends on the control protocol.
# ---------------------------------------------------------------------------
define Package/wwand-gps
SECTION:=net
CATEGORY:=Network
SUBMENU:=WWAN
TITLE:=GNSS/NMEA glue for wwand (ugps)
# ugps is OpenWrt base: it parses NMEA, can set the clock, and publishes a
# `gps` ubus object. It knows nothing about modems, and it reads its tty from
# a STATIC /etc/config/gps — which is exactly the gap this package fills,
# because wwand's NMEA port is discovered and can move between boots or when
# a modem is replaced.
DEPENDS:=+wwand +ugps
endef
define Package/wwand-gps/description
Joins three pieces that already existed with nothing between them: wwand finds
the modem's NMEA port during enumeration (reported as `gps_port`), `option
gnss` starts the receiver with the vendor AT command — QMI's LOC service is
broken on Quectel and AT is what works, and only wwand has the port — and ugps
reads the NMEA and publishes a position.
What this adds is the config write that connects them, plus a `modem_gps` ubus
method that answers with both halves at once: the port and receiver state wwand
knows, merged with whatever ugps reports.
Good citizen: it manages exactly one `config gps` section and only one it
created itself (marked `option wwand '1'`). An operator's own section — a hat
GPS on a serial port, a second receiver — is never touched or repointed.
endef
define Package/wwand-gps/install
$(INSTALL_DIR) $(1)$(UCDIR)
$(INSTALL_DATA) $(WWAND_UCODE)/gps.uc $(1)$(UCDIR)/
endef
# ---------------------------------------------------------------------------
# wwand-datapath-rmnet_nss: Qualcomm NSS offload behind the VENDOR qmi_wwan_q.
# A datapath add-on, not a control backend — it plugs into wwand's datapath
@@ -527,6 +567,7 @@ $(eval $(call BuildPackage,wwand-mbim))
$(eval $(call BuildPackage,wwand-ncm))
$(eval $(call BuildPackage,wwand-mhi))
$(eval $(call BuildPackage,wwand-esim))
$(eval $(call BuildPackage,wwand-gps))
$(eval $(call BuildPackage,wwand-apntest))
define Package/wwand-datapath-rmnet_nss_mhi
SECTION:=net