mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-17 07:37:15 +00:00
d5ab84e8d5
* feat(amneziawg): add AmneziaWG as an outbound protocol - AmneziaWG outbound protocol end-to-end: config schema, socks bridge, netstack, panel UI - Route amneziawg outbounds to HTTP probe in TCP mode (backend + frontend classifiers) with pinning test - Add 2-minute idle read deadline to pumpUDPEgress to reap idle egress sessions - Require SOCKS5 username/password auth on the egress server (reject NO-AUTH with 0xFF) with test - Bound the egress TCP tunnel dial with portForwardDialTimeout (10s), matching portfwd.go - Resolve UDP domain targets off the association's reader loop via deliverUDPDatagram; race-safe getOrDial starts the reply pump at session creation; client passed by value into resolver goroutines (pinned by TestEgressUDPDatagramDomainInterleavedClients) - Reconcile early-returns on an empty desired set and closes the egress listener; EgressBasePort (64900) is reserved against local inbound port conflicts like the internal API port, with pinning tests for both the port reservation (TestCheckPortConflict_EgressPortBlockedLocal) and the Reconcile empty-desired Close/Listen lifecycle (TestOutboundManagerReconcileEmptyDesiredClosesEgress) - Eliminate acceptLoop shutdown race by validating listener != nil and registering to tracked under s.mu before wg.Add; bound pre-auth handshake with deadline (pinned by TestEgressServerCloseDuringConcurrentAccepts) - Support AAAA and dual-stack domain resolution in tunnel DNS resolver with v6 default fallback (DefaultTunnelDNSServerV6); add DNS field to frontend protocol form; avoid unneeded cache flushes on unchanged SetStack ticks * fix(amneziawg): resolve IPv6-only DNS default fallback and validate required keys - Default to IPv6 tunnel DNS on IPv6-only outbounds with blank dns - Require non-empty secretKey and peer publicKey in ValidateAmneziaWGOutbound - Add end-to-end IPv6 tunnel domain resolution test and test empty key rejection - Trim comment blocks exceeding 2 lines across modified files - Fix Storybook test execution on environments with POSIX locale Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: rqzbeh <rqzbeh@users.noreply.github.com> Co-authored-by: Claude Code <noreply@anthropic.com> Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
112 lines
3.6 KiB
Go
112 lines
3.6 KiB
Go
package job
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/amneziawgnet"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
|
)
|
|
|
|
// AmneziaWGJob converges embedded AmneziaWG interfaces (inbounds AND the
|
|
// template's "amneziawg" outbounds) every 10s; stats stay with Xray.
|
|
type AmneziaWGJob struct {
|
|
inboundService service.InboundService
|
|
settingService service.SettingService
|
|
}
|
|
|
|
// NewAmneziaWGJob creates a new AmneziaWG reconcile job instance.
|
|
func NewAmneziaWGJob() *AmneziaWGJob {
|
|
return new(AmneziaWGJob)
|
|
}
|
|
|
|
// Run reconciles desired AmneziaWG inbounds with running embedded interfaces.
|
|
func (j *AmneziaWGJob) Run() {
|
|
desired, err := j.inboundService.DesiredAmneziaWGInstances()
|
|
if err != nil {
|
|
logger.Warning("amneziawg job: get desired instances failed:", err)
|
|
return
|
|
}
|
|
|
|
wanted := make([]amneziawgnet.Desired, 0, len(desired))
|
|
for _, inst := range desired {
|
|
wanted = append(wanted, amneziawgnet.Desired{
|
|
Instance: inst,
|
|
Options: amneziawgnet.DeviceOptions{
|
|
HeaderProtectionKey: inst.Obfuscation.HeaderProtectionKey,
|
|
ContentPaddingAddition: inst.Obfuscation.ContentPaddingAddition,
|
|
RekeyAfterTime: inst.Obfuscation.RekeyAfterTime,
|
|
RekeyTimeout: inst.Obfuscation.RekeyTimeout,
|
|
RejectAfterTime: inst.Obfuscation.RejectAfterTime,
|
|
KeepaliveTimeout: inst.Obfuscation.KeepaliveTimeout,
|
|
MaxHandshakeAttempts: inst.Obfuscation.MaxHandshakeAttempts,
|
|
RandomTrailers: inst.Obfuscation.RandomTrailers,
|
|
DisableCookies: inst.Obfuscation.DisableCookies,
|
|
},
|
|
})
|
|
}
|
|
amneziawgnet.GetManager().Reconcile(wanted)
|
|
|
|
outboundDesired, err := j.desiredOutboundInstances()
|
|
if err != nil {
|
|
logger.Warning("amneziawg job: get desired outbound instances failed:", err)
|
|
return
|
|
}
|
|
amneziawgnet.GetOutboundManager().Reconcile(outboundDesired)
|
|
}
|
|
|
|
// desiredOutboundInstances derives client instances per template "amneziawg" outbound.
|
|
func (j *AmneziaWGJob) desiredOutboundInstances() ([]amneziawgnet.OutboundDesired, error) {
|
|
template, err := j.settingService.GetXrayConfigTemplate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if template == "" {
|
|
return nil, nil
|
|
}
|
|
cfg := &xray.Config{}
|
|
if err := json.Unmarshal([]byte(template), cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(cfg.OutboundConfigs) == 0 {
|
|
return nil, nil
|
|
}
|
|
var raws []json.RawMessage
|
|
if err := json.Unmarshal(cfg.OutboundConfigs, &raws); err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]amneziawgnet.OutboundDesired, 0, len(raws))
|
|
for _, raw := range raws {
|
|
if !amneziawg.IsAmneziaWGOutbound(raw) {
|
|
continue
|
|
}
|
|
var probe struct {
|
|
Tag string `json:"tag"`
|
|
}
|
|
if err := json.Unmarshal(raw, &probe); err != nil || probe.Tag == "" {
|
|
continue
|
|
}
|
|
inst, ok := amneziawg.InstanceFromOutbound(probe.Tag, raw)
|
|
if !ok {
|
|
continue
|
|
}
|
|
out = append(out, amneziawgnet.OutboundDesired{
|
|
Instance: inst,
|
|
Options: amneziawgnet.DeviceOptions{
|
|
HeaderProtectionKey: inst.Obfuscation.HeaderProtectionKey,
|
|
ContentPaddingAddition: inst.Obfuscation.ContentPaddingAddition,
|
|
RekeyAfterTime: inst.Obfuscation.RekeyAfterTime,
|
|
RekeyTimeout: inst.Obfuscation.RekeyTimeout,
|
|
RejectAfterTime: inst.Obfuscation.RejectAfterTime,
|
|
KeepaliveTimeout: inst.Obfuscation.KeepaliveTimeout,
|
|
MaxHandshakeAttempts: inst.Obfuscation.MaxHandshakeAttempts,
|
|
RandomTrailers: inst.Obfuscation.RandomTrailers,
|
|
DisableCookies: inst.Obfuscation.DisableCookies,
|
|
},
|
|
})
|
|
}
|
|
return out, nil
|
|
}
|