|
@@ -22,6 +22,7 @@ import (
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/mtproto"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/mtproto"
|
|
|
|
|
+ "github.com/mhsanaei/3x-ui/v3/internal/tuic"
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/util/common"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/util/common"
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/util/netsafe"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/util/netsafe"
|
|
|
wgutil "github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
|
|
wgutil "github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
|
|
@@ -345,7 +346,8 @@ type InboundOption struct {
|
|
|
// AwgServer carries the full AmneziaWG server block (keys, subnet,
|
|
// AwgServer carries the full AmneziaWG server block (keys, subnet,
|
|
|
// obfuscation params) so the clients page can render a downloadable
|
|
// obfuscation params) so the clients page can render a downloadable
|
|
|
// per-client .conf without a second round trip.
|
|
// per-client .conf without a second round trip.
|
|
|
- AwgServer *amneziawg.ServerSettings `json:"awgServer,omitempty"`
|
|
|
|
|
|
|
+ AwgServer *amneziawg.ServerSettings `json:"awgServer,omitempty"`
|
|
|
|
|
+ TuicServer *tuic.TuicServerSettings `json:"tuicServer,omitempty"`
|
|
|
// Hosting node; nil for this panel's own inbounds. Lets the clients
|
|
// Hosting node; nil for this panel's own inbounds. Lets the clients
|
|
|
// page map a node filter onto inbound IDs (#4997).
|
|
// page map a node filter onto inbound IDs (#4997).
|
|
|
NodeId *int `json:"nodeId,omitempty"`
|
|
NodeId *int `json:"nodeId,omitempty"`
|
|
@@ -412,6 +414,7 @@ func (s *InboundService) GetInboundOptions(userId int) ([]InboundOption, error)
|
|
|
WgDns: wgDns,
|
|
WgDns: wgDns,
|
|
|
MtprotoDomain: inboundMtprotoDomain(r.Protocol, r.Settings),
|
|
MtprotoDomain: inboundMtprotoDomain(r.Protocol, r.Settings),
|
|
|
AwgServer: inboundAmneziaWGServer(r.Protocol, r.Settings),
|
|
AwgServer: inboundAmneziaWGServer(r.Protocol, r.Settings),
|
|
|
|
|
+ TuicServer: inboundTuicServer(r.Protocol, r.Settings),
|
|
|
NodeId: r.NodeId,
|
|
NodeId: r.NodeId,
|
|
|
NodeAddress: r.NodeAddress,
|
|
NodeAddress: r.NodeAddress,
|
|
|
Listen: r.Listen,
|
|
Listen: r.Listen,
|
|
@@ -506,6 +509,21 @@ func inboundAmneziaWGServer(protocol string, settings string) *amneziawg.ServerS
|
|
|
return &redacted
|
|
return &redacted
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func inboundTuicServer(protocol string, settings string) *tuic.TuicServerSettings {
|
|
|
|
|
+ if protocol != string(model.TUIC) || strings.TrimSpace(settings) == "" {
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
|
|
+ var parsed struct {
|
|
|
|
|
+ Server *tuic.TuicServerSettings `json:"server"`
|
|
|
|
|
+ }
|
|
|
|
|
+ if err := json.Unmarshal([]byte(settings), &parsed); err != nil || parsed.Server == nil {
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
|
|
+ redacted := *parsed.Server
|
|
|
|
|
+ redacted.PrivateKey = ""
|
|
|
|
|
+ return &redacted
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// inboundMtprotoDomain returns the inbound-level FakeTLS default domain, used by
|
|
// inboundMtprotoDomain returns the inbound-level FakeTLS default domain, used by
|
|
|
// the clients UI to seed a new mtproto client's secret with the right fronting
|
|
// the clients UI to seed a new mtproto client's secret with the right fronting
|
|
|
// hostname.
|
|
// hostname.
|
|
@@ -1181,6 +1199,16 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
|
|
if client.AdTag != "" && !model.ValidMtprotoAdTag(client.AdTag) {
|
|
if client.AdTag != "" && !model.ValidMtprotoAdTag(client.AdTag) {
|
|
|
return inbound, false, common.NewError("mtproto client ad tag must be 32 hex characters")
|
|
return inbound, false, common.NewError("mtproto client ad tag must be 32 hex characters")
|
|
|
}
|
|
}
|
|
|
|
|
+ case "tuic":
|
|
|
|
|
+ if client.ID == "" {
|
|
|
|
|
+ return inbound, false, common.NewError("empty client ID")
|
|
|
|
|
+ }
|
|
|
|
|
+ if client.Password == "" {
|
|
|
|
|
+ return inbound, false, common.NewError("tuic client requires a password")
|
|
|
|
|
+ }
|
|
|
|
|
+ if client.Email == "" {
|
|
|
|
|
+ return inbound, false, common.NewError("empty client email")
|
|
|
|
|
+ }
|
|
|
default:
|
|
default:
|
|
|
if client.ID == "" {
|
|
if client.ID == "" {
|
|
|
return inbound, false, common.NewError("empty client ID")
|
|
return inbound, false, common.NewError("empty client ID")
|
|
@@ -1271,7 +1299,7 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
|
|
if push {
|
|
if push {
|
|
|
payload := inbound
|
|
payload := inbound
|
|
|
pushable := true
|
|
pushable := true
|
|
|
- if inbound.Protocol == model.MTProto {
|
|
|
|
|
|
|
+ if inbound.Protocol == model.MTProto || inbound.Protocol == model.TUIC {
|
|
|
if built, bErr := s.buildInboundForLocalRuntime(tx, inbound); bErr == nil {
|
|
if built, bErr := s.buildInboundForLocalRuntime(tx, inbound); bErr == nil {
|
|
|
payload = built
|
|
payload = built
|
|
|
} else {
|
|
} else {
|
|
@@ -1285,7 +1313,9 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
|
|
logger.Debug("New inbound added on", rt.Name(), ":", inbound.Tag)
|
|
logger.Debug("New inbound added on", rt.Name(), ":", inbound.Tag)
|
|
|
} else {
|
|
} else {
|
|
|
logger.Debug("Unable to add inbound on", rt.Name(), ":", err1)
|
|
logger.Debug("Unable to add inbound on", rt.Name(), ":", err1)
|
|
|
- needRestart = true
|
|
|
|
|
|
|
+ if inbound.Protocol != model.MTProto && inbound.Protocol != model.TUIC {
|
|
|
|
|
+ needRestart = true
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -1629,6 +1659,20 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ if inbound.Protocol == model.TUIC {
|
|
|
|
|
+ for _, client := range clients {
|
|
|
|
|
+ if client.ID == "" {
|
|
|
|
|
+ return inbound, false, common.NewError("empty client ID")
|
|
|
|
|
+ }
|
|
|
|
|
+ if client.Password == "" {
|
|
|
|
|
+ return inbound, false, common.NewError("tuic client requires a password")
|
|
|
|
|
+ }
|
|
|
|
|
+ if client.Email == "" {
|
|
|
|
|
+ return inbound, false, common.NewError("empty client email")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Grandfather a row that was already stored incomplete so it stays editable;
|
|
// Grandfather a row that was already stored incomplete so it stays editable;
|
|
|
// only a save that breaks a previously valid TLS block is refused.
|
|
// only a save that breaks a previously valid TLS block is refused.
|
|
|
if !s.FromNodeSync {
|
|
if !s.FromNodeSync {
|
|
@@ -1801,7 +1845,7 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|
|
}
|
|
}
|
|
|
if !push {
|
|
if !push {
|
|
|
needRestart = true
|
|
needRestart = true
|
|
|
- } else if oldProtocol == model.MTProto || oldInbound.Protocol == model.MTProto {
|
|
|
|
|
|
|
+ } else if oldProtocol == model.MTProto || oldInbound.Protocol == model.MTProto || oldProtocol == model.TUIC || oldInbound.Protocol == model.TUIC {
|
|
|
oldSnapshot := *oldInbound
|
|
oldSnapshot := *oldInbound
|
|
|
oldSnapshot.Tag = tag
|
|
oldSnapshot.Tag = tag
|
|
|
oldSnapshot.Protocol = oldProtocol
|
|
oldSnapshot.Protocol = oldProtocol
|
|
@@ -1815,14 +1859,14 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|
|
pushable = false
|
|
pushable = false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- newProtocolIsMtproto := oldInbound.Protocol == model.MTProto
|
|
|
|
|
|
|
+ newProtocolIsSidecar := oldInbound.Protocol == model.MTProto || oldInbound.Protocol == model.TUIC
|
|
|
if pushable {
|
|
if pushable {
|
|
|
postCommitApply = func() {
|
|
postCommitApply = func() {
|
|
|
if err2 := rt.UpdateInbound(context.Background(), &oldSnapshot, payload); err2 == nil {
|
|
if err2 := rt.UpdateInbound(context.Background(), &oldSnapshot, payload); err2 == nil {
|
|
|
logger.Debug("Updated inbound applied on", rt.Name(), ":", oldInbound.Tag)
|
|
logger.Debug("Updated inbound applied on", rt.Name(), ":", oldInbound.Tag)
|
|
|
} else {
|
|
} else {
|
|
|
logger.Debug("Unable to update inbound on", rt.Name(), ":", err2)
|
|
logger.Debug("Unable to update inbound on", rt.Name(), ":", err2)
|
|
|
- if !newProtocolIsMtproto {
|
|
|
|
|
|
|
+ if !newProtocolIsSidecar {
|
|
|
needRestart = true
|
|
needRestart = true
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|