diff --git a/adapter/rule.go b/adapter/rule.go index 2117ba45a6..fbe33448c5 100644 --- a/adapter/rule.go +++ b/adapter/rule.go @@ -27,6 +27,7 @@ type DNSRule interface { type RuleAction interface { Type() string + Tag() string String() string } diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index 9624c2e21e..17bfdc5138 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -1057,3 +1057,124 @@ func (c *CommandClient) StartTailscaleSSHSession(opts *TailscaleSSHOptions, hand return session, nil } + +func (c *CommandClient) StartTailscaleSSHSession(opts *TailscaleSSHOptions, handler TailscaleSSHHandler) (*TailscaleSSHSession, error) { + client, err := c.getClientForCall() + if err != nil { + return nil, E.Cause(err, "start tailscale ssh session") + } + + streamCtx, cancel := context.WithCancel(context.Background()) + failStart := func(cause error, message string) (*TailscaleSSHSession, error) { + cancel() + if c.standalone { + c.closeConnection() + } + return nil, E.Cause(cause, message) + } + + stream, err := client.StartTailscaleSSHSession(streamCtx) + if err != nil { + return failStart(err, "start tailscale ssh session") + } + + sendErr := stream.Send(&daemon.TailscaleSSHClientMessage{ + Message: &daemon.TailscaleSSHClientMessage_Start{Start: &daemon.TailscaleSSHStart{ + EndpointTag: opts.EndpointTag, + PeerAddress: opts.PeerAddress, + Username: opts.Username, + TerminalType: opts.TerminalType, + Columns: opts.Columns, + Rows: opts.Rows, + WidthPixels: opts.WidthPixels, + HeightPixels: opts.HeightPixels, + HostKeys: iteratorToArray[string](opts.HostKeys), + }}, + }) + if sendErr != nil { + return failStart(sendErr, "send tailscale ssh start") + } + + session := &TailscaleSSHSession{ + stream: stream, + inputCh: make(chan []byte, 8), + resizeCh: make(chan tailscaleSSHResize, 1), + ctx: streamCtx, + cancel: cancel, + closeDone: make(chan struct{}), + } + + session.wg.Add(1) + go func() { + defer session.wg.Done() + for { + select { + case <-streamCtx.Done(): + return + case data := <-session.inputCh: + sendErr := stream.Send(&daemon.TailscaleSSHClientMessage{ + Message: &daemon.TailscaleSSHClientMessage_Input{Input: &daemon.TailscaleSSHInput{Data: data}}, + }) + if sendErr != nil { + cancel() + return + } + case resize := <-session.resizeCh: + sendErr := stream.Send(&daemon.TailscaleSSHClientMessage{ + Message: &daemon.TailscaleSSHClientMessage_Resize{Resize: &daemon.TailscaleSSHResize{ + Columns: resize.columns, + Rows: resize.rows, + WidthPixels: resize.widthPixels, + HeightPixels: resize.heightPixels, + }}, + }) + if sendErr != nil { + cancel() + return + } + } + } + }() + + session.wg.Add(1) + go func() { + defer session.wg.Done() + for { + msg, recvErr := stream.Recv() + if recvErr == io.EOF { + cancel() + return + } + if recvErr != nil { + handler.OnError(E.Cause(recvErr, "tailscale ssh recv").Error()) + cancel() + return + } + switch payload := msg.GetMessage().(type) { + case *daemon.TailscaleSSHServerMessage_AuthBanner: + handler.OnAuthBanner(payload.AuthBanner.Message) + case *daemon.TailscaleSSHServerMessage_Ready: + handler.OnReady() + case *daemon.TailscaleSSHServerMessage_Output: + handler.OnOutput(payload.Output.Data) + case *daemon.TailscaleSSHServerMessage_Exit: + handler.OnExit(payload.Exit.ExitCode, payload.Exit.Signal, payload.Exit.ErrorMessage) + cancel() + return + case *daemon.TailscaleSSHServerMessage_Error: + handler.OnError(payload.Error.Message) + } + } + }() + + standalone := c.standalone + go func() { + session.wg.Wait() + close(session.closeDone) + if standalone { + c.closeConnection() + } + }() + + return session, nil +} diff --git a/option/rule_action.go b/option/rule_action.go index a6f181f2d7..5cd278b530 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -15,6 +15,7 @@ import ( type _RuleAction struct { Action string `json:"action,omitempty"` + Tag string `json:"tag,omitempty"` RouteOptions RouteActionOptions `json:"-"` RouteOptionsOptions RouteOptionsActionOptions `json:"-"` DirectOptions DirectActionOptions `json:"-"` @@ -98,6 +99,7 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error { type _DNSRuleAction struct { Action string `json:"action,omitempty"` + Tag string `json:"tag,omitempty"` RouteOptions DNSRouteActionOptions `json:"-"` RouteOptionsOptions DNSRouteOptionsActionOptions `json:"-"` RejectOptions RejectActionOptions `json:"-"` diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 207945c410..0f1e344f0e 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -55,8 +55,9 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti if err != nil { return nil, err } - return &RuleActionRoute{ - Outbound: action.RouteOptions.Outbound, + return &RuleActionRoute{ + tag: action.Tag, + Outbound: action.RouteOptions.Outbound, RuleActionRouteOptions: routeOptions, }, nil case C.RuleActionTypeRouteOptions: @@ -64,14 +65,16 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti if err != nil { return nil, err } + routeOptions.tag = action.Tag return &routeOptions, nil case C.RuleActionTypeBypass: routeOptions, err := newRuleActionRouteOptions(action.BypassOptions.RawRouteOptionsActionOptions) if err != nil { return nil, err } - return &RuleActionBypass{ - Outbound: action.BypassOptions.Outbound, + return &RuleActionBypass{ + tag: action.Tag, + Outbound: action.BypassOptions.Outbound, RuleActionRouteOptions: routeOptions, }, nil case C.RuleActionTypeDirect: @@ -90,27 +93,31 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti default: description = F.ToString("(", descriptions[0], ",", descriptions[1], ",...)") } - return &RuleActionDirect{ - Dialer: directDialer, + return &RuleActionDirect{ + tag: action.Tag, + Dialer: directDialer, description: description, }, nil case C.RuleActionTypeReject: - return &RuleActionReject{ - Method: action.RejectOptions.Method, - NoDrop: action.RejectOptions.NoDrop, - logger: logger, - }, nil + return &RuleActionReject{ + tag: action.Tag, + Method: action.RejectOptions.Method, + NoDrop: action.RejectOptions.NoDrop, + logger: logger, + }, nil case C.RuleActionTypeHijackDNS: - return &RuleActionHijackDNS{}, nil + return &RuleActionHijackDNS{tag: action.Tag}, nil case C.RuleActionTypeSniff: - sniffAction := &RuleActionSniff{ - SnifferNames: action.SniffOptions.Sniffer, + sniffAction := &RuleActionSniff{ + tag: action.Tag, + SnifferNames: action.SniffOptions.Sniffer, Timeout: time.Duration(action.SniffOptions.Timeout), } return sniffAction, sniffAction.build() case C.RuleActionTypeResolve: - return &RuleActionResolve{ - Server: action.ResolveOptions.Server, + return &RuleActionResolve{ + tag: action.Tag, + Server: action.ResolveOptions.Server, Timeout: time.Duration(action.ResolveOptions.Timeout), Strategy: C.DomainStrategy(action.ResolveOptions.Strategy), DisableCache: action.ResolveOptions.DisableCache, @@ -128,7 +135,8 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) case "": return nil case C.RuleActionTypeRoute: - return &RuleActionDNSRoute{ + return &RuleActionDNSRoute{ + tag: action.Tag, Server: action.RouteOptions.Server, RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{ Strategy: C.DomainStrategy(action.RouteOptions.Strategy), @@ -140,7 +148,8 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) }, } case C.RuleActionTypeEvaluate: - return &RuleActionEvaluate{ + return &RuleActionEvaluate{ + tag: action.Tag, Server: action.RouteOptions.Server, RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{ Strategy: C.DomainStrategy(action.RouteOptions.Strategy), @@ -152,9 +161,10 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) }, } case C.RuleActionTypeRespond: - return &RuleActionRespond{} + return &RuleActionRespond{tag: action.Tag} case C.RuleActionTypeRouteOptions: - return &RuleActionDNSRouteOptions{ + return &RuleActionDNSRouteOptions{ + tag: action.Tag, Strategy: C.DomainStrategy(action.RouteOptionsOptions.Strategy), Timeout: time.Duration(action.RouteOptionsOptions.Timeout), DisableCache: action.RouteOptionsOptions.DisableCache, @@ -163,13 +173,15 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)), } case C.RuleActionTypeReject: - return &RuleActionReject{ - Method: action.RejectOptions.Method, - NoDrop: action.RejectOptions.NoDrop, - logger: logger, - } + return &RuleActionReject{ + tag: action.Tag, + Method: action.RejectOptions.Method, + NoDrop: action.RejectOptions.NoDrop, + logger: logger, + } case C.RuleActionTypePredefined: - return &RuleActionPredefined{ + return &RuleActionPredefined{ + tag: action.Tag, Rcode: action.PredefinedOptions.Rcode.Build(), Answer: common.Map(action.PredefinedOptions.Answer, option.DNSRecordOptions.Build), Ns: common.Map(action.PredefinedOptions.Ns, option.DNSRecordOptions.Build), @@ -181,6 +193,7 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction) } type RuleActionRoute struct { + tag string Outbound string RuleActionRouteOptions } @@ -189,6 +202,10 @@ func (r *RuleActionRoute) Type() string { return C.RuleActionTypeRoute } +func (r *RuleActionRoute) Tag() string { + return r.tag +} + func (r *RuleActionRoute) String() string { var descriptions []string descriptions = append(descriptions, r.Outbound) @@ -197,6 +214,7 @@ func (r *RuleActionRoute) String() string { } type RuleActionBypass struct { + tag string Outbound string RuleActionRouteOptions } @@ -205,6 +223,10 @@ func (r *RuleActionBypass) Type() string { return C.RuleActionTypeBypass } +func (r *RuleActionBypass) Tag() string { + return r.tag +} + func (r *RuleActionBypass) String() string { if r.Outbound == "" { return "bypass()" @@ -216,6 +238,7 @@ func (r *RuleActionBypass) String() string { } type RuleActionRouteOptions struct { + tag string OverrideAddress M.Socksaddr OverridePort uint16 NetworkStrategy *C.NetworkStrategy @@ -236,6 +259,10 @@ func (r *RuleActionRouteOptions) Type() string { return C.RuleActionTypeRouteOptions } +func (r *RuleActionRouteOptions) Tag() string { + return r.tag +} + func (r *RuleActionRouteOptions) String() string { return F.ToString("route-options(", strings.Join(r.Descriptions(), ","), ")") } @@ -286,6 +313,7 @@ func (r *RuleActionRouteOptions) Descriptions() []string { } type RuleActionDNSRoute struct { + tag string Server string RuleActionDNSRouteOptions } @@ -294,11 +322,16 @@ func (r *RuleActionDNSRoute) Type() string { return C.RuleActionTypeRoute } +func (r *RuleActionDNSRoute) Tag() string { + return r.tag +} + func (r *RuleActionDNSRoute) String() string { return formatDNSRouteAction("route", r.Server, r.RuleActionDNSRouteOptions) } type RuleActionEvaluate struct { + tag string Server string RuleActionDNSRouteOptions } @@ -307,16 +340,26 @@ func (r *RuleActionEvaluate) Type() string { return C.RuleActionTypeEvaluate } +func (r *RuleActionEvaluate) Tag() string { + return r.tag +} + func (r *RuleActionEvaluate) String() string { return formatDNSRouteAction("evaluate", r.Server, r.RuleActionDNSRouteOptions) } -type RuleActionRespond struct{} +type RuleActionRespond struct{ + tag string +} func (r *RuleActionRespond) Type() string { return C.RuleActionTypeRespond } +func (r *RuleActionRespond) Tag() string { + return r.tag +} + func (r *RuleActionRespond) String() string { return "respond" } @@ -343,6 +386,7 @@ func formatDNSRouteAction(action string, server string, options RuleActionDNSRou } type RuleActionDNSRouteOptions struct { + tag string Strategy C.DomainStrategy Timeout time.Duration DisableCache bool @@ -355,6 +399,10 @@ func (r *RuleActionDNSRouteOptions) Type() string { return C.RuleActionTypeRouteOptions } +func (r *RuleActionDNSRouteOptions) Tag() string { + return r.tag +} + func (r *RuleActionDNSRouteOptions) String() string { var descriptions []string if r.DisableCache { @@ -376,6 +424,7 @@ func (r *RuleActionDNSRouteOptions) String() string { } type RuleActionDirect struct { + tag string Dialer N.Dialer description string } @@ -384,6 +433,10 @@ func (r *RuleActionDirect) Type() string { return C.RuleActionTypeDirect } +func (r *RuleActionDirect) Tag() string { + return r.tag +} + func (r *RuleActionDirect) String() string { return "direct" + r.description } @@ -423,6 +476,7 @@ func IsBypassed(err error) bool { } type RuleActionReject struct { + tag string Method string NoDrop bool logger logger.ContextLogger @@ -434,6 +488,10 @@ func (r *RuleActionReject) Type() string { return C.RuleActionTypeReject } +func (r *RuleActionReject) Tag() string { + return r.tag +} + func (r *RuleActionReject) String() string { if r.Method == C.RuleActionRejectMethodDefault { return "reject" @@ -472,17 +530,24 @@ func (r *RuleActionReject) Error(ctx context.Context) error { return returnErr } -type RuleActionHijackDNS struct{} +type RuleActionHijackDNS struct{ + tag string +} func (r *RuleActionHijackDNS) Type() string { return C.RuleActionTypeHijackDNS } +func (r *RuleActionHijackDNS) Tag() string { + return r.tag +} + func (r *RuleActionHijackDNS) String() string { return "hijack-dns" } type RuleActionSniff struct { + tag string SnifferNames []string StreamSniffers []sniff.StreamSniffer PacketSniffers []sniff.PacketSniffer @@ -495,6 +560,10 @@ func (r *RuleActionSniff) Type() string { return C.RuleActionTypeSniff } +func (r *RuleActionSniff) Tag() string { + return r.tag +} + func (r *RuleActionSniff) build() error { for _, name := range r.SnifferNames { switch name { @@ -541,6 +610,7 @@ func (r *RuleActionSniff) String() string { } type RuleActionResolve struct { + tag string Server string Timeout time.Duration Strategy C.DomainStrategy @@ -554,6 +624,10 @@ func (r *RuleActionResolve) Type() string { return C.RuleActionTypeResolve } +func (r *RuleActionResolve) Tag() string { + return r.tag +} + func (r *RuleActionResolve) String() string { var options []string if r.Server != "" { @@ -585,6 +659,7 @@ func (r *RuleActionResolve) String() string { } type RuleActionPredefined struct { + tag string Rcode int Answer []dns.RR Ns []dns.RR @@ -595,6 +670,10 @@ func (r *RuleActionPredefined) Type() string { return C.RuleActionTypePredefined } +func (r *RuleActionPredefined) Tag() string { + return r.tag +} + func (r *RuleActionPredefined) String() string { var options []string options = append(options, dns.RcodeToString[r.Rcode])