mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-07-19 21:08:40 +08:00
Fix SSO support for anyconnect
This commit is contained in:
parent
37b4386bdd
commit
fea6299fa7
@ -16,15 +16,15 @@ type OpenConnectEndpoint interface {
|
||||
Endpoint
|
||||
OpenConnectStatus() OpenConnectStatus
|
||||
StatusUpdated() <-chan struct{}
|
||||
CompleteAuthForm(formID string, values map[string]string) error
|
||||
CancelAuthForm(formID string) error
|
||||
CompleteAuthChallenge(challengeID string, response OpenConnectAuthResponse) error
|
||||
CancelAuthChallenge(challengeID string) error
|
||||
}
|
||||
|
||||
type OpenConnectStatus struct {
|
||||
State string
|
||||
AuthForm *OpenConnectAuthForm
|
||||
Error string
|
||||
TunnelInfo *OpenConnectTunnelInfo
|
||||
State string
|
||||
AuthChallenge *OpenConnectAuthChallenge
|
||||
Error string
|
||||
TunnelInfo *OpenConnectTunnelInfo
|
||||
}
|
||||
|
||||
type OpenConnectTunnelInfo struct {
|
||||
@ -38,13 +38,49 @@ type OpenConnectTunnelInfo struct {
|
||||
ConnectedSince time.Time
|
||||
}
|
||||
|
||||
type OpenConnectAuthForm struct {
|
||||
type OpenConnectAuthChallenge struct {
|
||||
ID string
|
||||
Banner string
|
||||
Message string
|
||||
Error string
|
||||
URL string
|
||||
Fields []OpenConnectAuthFormField
|
||||
Form *OpenConnectAuthForm
|
||||
Browser *OpenConnectBrowserRequest
|
||||
}
|
||||
|
||||
type OpenConnectAuthForm struct {
|
||||
Fields []OpenConnectAuthFormField
|
||||
}
|
||||
|
||||
type OpenConnectBrowserRequest struct {
|
||||
URL string
|
||||
FinalURL string
|
||||
CookieNames []string
|
||||
HeaderNames []string
|
||||
}
|
||||
|
||||
type OpenConnectBrowserCookie struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
type OpenConnectBrowserHeader struct {
|
||||
Name string
|
||||
Values []string
|
||||
}
|
||||
|
||||
type OpenConnectAuthResponse struct {
|
||||
Form *OpenConnectAuthFormResponse
|
||||
Browser *OpenConnectBrowserResult
|
||||
}
|
||||
|
||||
type OpenConnectAuthFormResponse struct {
|
||||
Values map[string]string
|
||||
}
|
||||
|
||||
type OpenConnectBrowserResult struct {
|
||||
FinalURL string
|
||||
Cookies []OpenConnectBrowserCookie
|
||||
Headers []OpenConnectBrowserHeader
|
||||
}
|
||||
|
||||
type OpenConnectAuthFormField struct {
|
||||
|
||||
@ -1620,35 +1620,46 @@ func openConnectEndpointStatusToProto(tag string, endpointStatus adapter.OpenCon
|
||||
Error: endpointStatus.Error,
|
||||
TunnelInfo: openConnectTunnelInfoToProto(endpointStatus.TunnelInfo),
|
||||
}
|
||||
if endpointStatus.AuthForm != nil {
|
||||
fields := common.Map(endpointStatus.AuthForm.Fields, func(field adapter.OpenConnectAuthFormField) *OpenConnectAuthFormField {
|
||||
return &OpenConnectAuthFormField{
|
||||
SubmissionKey: field.SubmissionKey,
|
||||
Name: field.Name,
|
||||
Label: field.Label,
|
||||
Kind: field.Kind,
|
||||
Value: field.Value,
|
||||
Options: common.Map(field.Options, func(option adapter.OpenConnectAuthFormChoice) *OpenConnectAuthFormChoice {
|
||||
return &OpenConnectAuthFormChoice{
|
||||
Value: option.Value,
|
||||
Label: option.Label,
|
||||
if endpointStatus.AuthChallenge != nil {
|
||||
challenge := &OpenConnectAuthChallenge{
|
||||
Id: endpointStatus.AuthChallenge.ID,
|
||||
Banner: endpointStatus.AuthChallenge.Banner,
|
||||
Message: endpointStatus.AuthChallenge.Message,
|
||||
Error: endpointStatus.AuthChallenge.Error,
|
||||
}
|
||||
if endpointStatus.AuthChallenge.Form != nil {
|
||||
challenge.Challenge = &OpenConnectAuthChallenge_Form{Form: &OpenConnectAuthForm{
|
||||
Fields: common.Map(endpointStatus.AuthChallenge.Form.Fields, func(field adapter.OpenConnectAuthFormField) *OpenConnectAuthFormField {
|
||||
return &OpenConnectAuthFormField{
|
||||
SubmissionKey: field.SubmissionKey,
|
||||
Name: field.Name,
|
||||
Label: field.Label,
|
||||
Kind: field.Kind,
|
||||
Value: field.Value,
|
||||
Options: common.Map(field.Options, func(option adapter.OpenConnectAuthFormChoice) *OpenConnectAuthFormChoice {
|
||||
return &OpenConnectAuthFormChoice{
|
||||
Value: option.Value,
|
||||
Label: option.Label,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}),
|
||||
}
|
||||
})
|
||||
result.AuthForm = &OpenConnectAuthForm{
|
||||
Id: endpointStatus.AuthForm.ID,
|
||||
Banner: endpointStatus.AuthForm.Banner,
|
||||
Message: endpointStatus.AuthForm.Message,
|
||||
Error: endpointStatus.AuthForm.Error,
|
||||
Url: endpointStatus.AuthForm.URL,
|
||||
Fields: fields,
|
||||
}}
|
||||
}
|
||||
if endpointStatus.AuthChallenge.Browser != nil {
|
||||
challenge.Challenge = &OpenConnectAuthChallenge_Browser{Browser: &OpenConnectBrowserRequest{
|
||||
Url: endpointStatus.AuthChallenge.Browser.URL,
|
||||
FinalURL: endpointStatus.AuthChallenge.Browser.FinalURL,
|
||||
CookieNames: endpointStatus.AuthChallenge.Browser.CookieNames,
|
||||
HeaderNames: endpointStatus.AuthChallenge.Browser.HeaderNames,
|
||||
}}
|
||||
}
|
||||
result.AuthChallenge = challenge
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *StartedService) SubmitOpenConnectAuthForm(ctx context.Context, request *OpenConnectAuthFormSubmission) (*emptypb.Empty, error) {
|
||||
func (s *StartedService) SubmitOpenConnectAuthResponse(ctx context.Context, request *OpenConnectAuthResponseSubmission) (*emptypb.Empty, error) {
|
||||
err := s.waitForStarted(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -1661,14 +1672,31 @@ func (s *StartedService) SubmitOpenConnectAuthForm(ctx context.Context, request
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = endpoint.CompleteAuthForm(request.FormID, request.Values)
|
||||
var authResponse adapter.OpenConnectAuthResponse
|
||||
form := request.GetForm()
|
||||
if form != nil {
|
||||
authResponse.Form = &adapter.OpenConnectAuthFormResponse{Values: form.Values}
|
||||
}
|
||||
browser := request.GetBrowser()
|
||||
if browser != nil {
|
||||
authResponse.Browser = &adapter.OpenConnectBrowserResult{
|
||||
FinalURL: browser.FinalURL,
|
||||
Cookies: common.Map(browser.Cookies, func(cookie *OpenConnectBrowserCookie) adapter.OpenConnectBrowserCookie {
|
||||
return adapter.OpenConnectBrowserCookie{Name: cookie.Name, Value: cookie.Value}
|
||||
}),
|
||||
Headers: common.Map(browser.Headers, func(header *OpenConnectBrowserHeader) adapter.OpenConnectBrowserHeader {
|
||||
return adapter.OpenConnectBrowserHeader{Name: header.Name, Values: header.Values}
|
||||
}),
|
||||
}
|
||||
}
|
||||
err = endpoint.CompleteAuthChallenge(request.ChallengeID, authResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) CancelOpenConnectAuthForm(ctx context.Context, request *OpenConnectAuthFormCancel) (*emptypb.Empty, error) {
|
||||
func (s *StartedService) CancelOpenConnectAuthChallenge(ctx context.Context, request *OpenConnectAuthChallengeCancel) (*emptypb.Empty, error) {
|
||||
err := s.waitForStarted(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -1681,7 +1709,7 @@ func (s *StartedService) CancelOpenConnectAuthForm(ctx context.Context, request
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = endpoint.CancelAuthForm(request.FormID)
|
||||
err = endpoint.CancelAuthChallenge(request.ChallengeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -39,8 +39,8 @@ service StartedService {
|
||||
rpc ProvideUSBDevices(stream USBProviderMessage) returns (stream USBServerMessage) {}
|
||||
rpc SubscribeUSBIPServerStatus(google.protobuf.Empty) returns (stream USBIPServerStatusUpdate) {}
|
||||
rpc SubscribeOpenConnectStatus(google.protobuf.Empty) returns (stream OpenConnectStatusUpdate) {}
|
||||
rpc SubmitOpenConnectAuthForm(OpenConnectAuthFormSubmission) returns (google.protobuf.Empty) {}
|
||||
rpc CancelOpenConnectAuthForm(OpenConnectAuthFormCancel) returns (google.protobuf.Empty) {}
|
||||
rpc SubmitOpenConnectAuthResponse(OpenConnectAuthResponseSubmission) returns (google.protobuf.Empty) {}
|
||||
rpc CancelOpenConnectAuthChallenge(OpenConnectAuthChallengeCancel) returns (google.protobuf.Empty) {}
|
||||
rpc SubscribeOpenVPNStatus(google.protobuf.Empty) returns (stream OpenVPNStatusUpdate) {}
|
||||
rpc SubmitOpenVPNChallengeResponse(OpenVPNChallengeSubmission) returns (google.protobuf.Empty) {}
|
||||
rpc CancelOpenVPNChallenge(OpenVPNChallengeCancel) returns (google.protobuf.Empty) {}
|
||||
@ -532,7 +532,7 @@ message OpenConnectEndpointStatus {
|
||||
string endpointTag = 1;
|
||||
string state = 2;
|
||||
string stateText = 3;
|
||||
OpenConnectAuthForm authForm = 4;
|
||||
OpenConnectAuthChallenge authChallenge = 4;
|
||||
string error = 5;
|
||||
OpenConnectTunnelInfo tunnelInfo = 6;
|
||||
}
|
||||
@ -548,13 +548,19 @@ message OpenConnectTunnelInfo {
|
||||
int64 connectedSince = 8;
|
||||
}
|
||||
|
||||
message OpenConnectAuthForm {
|
||||
message OpenConnectAuthChallenge {
|
||||
string id = 1;
|
||||
string banner = 2;
|
||||
string message = 3;
|
||||
string error = 4;
|
||||
string url = 5;
|
||||
repeated OpenConnectAuthFormField fields = 6;
|
||||
oneof challenge {
|
||||
OpenConnectAuthForm form = 5;
|
||||
OpenConnectBrowserRequest browser = 6;
|
||||
}
|
||||
}
|
||||
|
||||
message OpenConnectAuthForm {
|
||||
repeated OpenConnectAuthFormField fields = 1;
|
||||
}
|
||||
|
||||
message OpenConnectAuthFormField {
|
||||
@ -571,15 +577,45 @@ message OpenConnectAuthFormChoice {
|
||||
string label = 2;
|
||||
}
|
||||
|
||||
message OpenConnectAuthFormSubmission {
|
||||
string endpointTag = 1;
|
||||
string formID = 2;
|
||||
map<string, string> values = 3;
|
||||
message OpenConnectBrowserRequest {
|
||||
string url = 1;
|
||||
string finalURL = 2;
|
||||
repeated string cookieNames = 3;
|
||||
repeated string headerNames = 4;
|
||||
}
|
||||
|
||||
message OpenConnectAuthFormCancel {
|
||||
message OpenConnectBrowserCookie {
|
||||
string name = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message OpenConnectBrowserHeader {
|
||||
string name = 1;
|
||||
repeated string values = 2;
|
||||
}
|
||||
|
||||
message OpenConnectAuthFormResponse {
|
||||
map<string, string> values = 1;
|
||||
}
|
||||
|
||||
message OpenConnectBrowserResult {
|
||||
string finalURL = 1;
|
||||
repeated OpenConnectBrowserCookie cookies = 2;
|
||||
repeated OpenConnectBrowserHeader headers = 3;
|
||||
}
|
||||
|
||||
message OpenConnectAuthResponseSubmission {
|
||||
string endpointTag = 1;
|
||||
string formID = 2;
|
||||
string challengeID = 2;
|
||||
oneof response {
|
||||
OpenConnectAuthFormResponse form = 3;
|
||||
OpenConnectBrowserResult browser = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message OpenConnectAuthChallengeCancel {
|
||||
string endpointTag = 1;
|
||||
string challengeID = 2;
|
||||
}
|
||||
|
||||
message OpenVPNStatusUpdate {
|
||||
|
||||
@ -44,8 +44,8 @@ const (
|
||||
StartedService_ProvideUSBDevices_FullMethodName = "/daemon.StartedService/ProvideUSBDevices"
|
||||
StartedService_SubscribeUSBIPServerStatus_FullMethodName = "/daemon.StartedService/SubscribeUSBIPServerStatus"
|
||||
StartedService_SubscribeOpenConnectStatus_FullMethodName = "/daemon.StartedService/SubscribeOpenConnectStatus"
|
||||
StartedService_SubmitOpenConnectAuthForm_FullMethodName = "/daemon.StartedService/SubmitOpenConnectAuthForm"
|
||||
StartedService_CancelOpenConnectAuthForm_FullMethodName = "/daemon.StartedService/CancelOpenConnectAuthForm"
|
||||
StartedService_SubmitOpenConnectAuthResponse_FullMethodName = "/daemon.StartedService/SubmitOpenConnectAuthResponse"
|
||||
StartedService_CancelOpenConnectAuthChallenge_FullMethodName = "/daemon.StartedService/CancelOpenConnectAuthChallenge"
|
||||
StartedService_SubscribeOpenVPNStatus_FullMethodName = "/daemon.StartedService/SubscribeOpenVPNStatus"
|
||||
StartedService_SubmitOpenVPNChallengeResponse_FullMethodName = "/daemon.StartedService/SubmitOpenVPNChallengeResponse"
|
||||
StartedService_CancelOpenVPNChallenge_FullMethodName = "/daemon.StartedService/CancelOpenVPNChallenge"
|
||||
@ -84,8 +84,8 @@ type StartedServiceClient interface {
|
||||
ProvideUSBDevices(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[USBProviderMessage, USBServerMessage], error)
|
||||
SubscribeUSBIPServerStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[USBIPServerStatusUpdate], error)
|
||||
SubscribeOpenConnectStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OpenConnectStatusUpdate], error)
|
||||
SubmitOpenConnectAuthForm(ctx context.Context, in *OpenConnectAuthFormSubmission, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
CancelOpenConnectAuthForm(ctx context.Context, in *OpenConnectAuthFormCancel, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
SubmitOpenConnectAuthResponse(ctx context.Context, in *OpenConnectAuthResponseSubmission, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
CancelOpenConnectAuthChallenge(ctx context.Context, in *OpenConnectAuthChallengeCancel, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
SubscribeOpenVPNStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OpenVPNStatusUpdate], error)
|
||||
SubmitOpenVPNChallengeResponse(ctx context.Context, in *OpenVPNChallengeSubmission, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
CancelOpenVPNChallenge(ctx context.Context, in *OpenVPNChallengeCancel, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
@ -512,20 +512,20 @@ func (c *startedServiceClient) SubscribeOpenConnectStatus(ctx context.Context, i
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeOpenConnectStatusClient = grpc.ServerStreamingClient[OpenConnectStatusUpdate]
|
||||
|
||||
func (c *startedServiceClient) SubmitOpenConnectAuthForm(ctx context.Context, in *OpenConnectAuthFormSubmission, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
func (c *startedServiceClient) SubmitOpenConnectAuthResponse(ctx context.Context, in *OpenConnectAuthResponseSubmission, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, StartedService_SubmitOpenConnectAuthForm_FullMethodName, in, out, cOpts...)
|
||||
err := c.cc.Invoke(ctx, StartedService_SubmitOpenConnectAuthResponse_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) CancelOpenConnectAuthForm(ctx context.Context, in *OpenConnectAuthFormCancel, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
func (c *startedServiceClient) CancelOpenConnectAuthChallenge(ctx context.Context, in *OpenConnectAuthChallengeCancel, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, StartedService_CancelOpenConnectAuthForm_FullMethodName, in, out, cOpts...)
|
||||
err := c.cc.Invoke(ctx, StartedService_CancelOpenConnectAuthChallenge_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -604,8 +604,8 @@ type StartedServiceServer interface {
|
||||
ProvideUSBDevices(grpc.BidiStreamingServer[USBProviderMessage, USBServerMessage]) error
|
||||
SubscribeUSBIPServerStatus(*emptypb.Empty, grpc.ServerStreamingServer[USBIPServerStatusUpdate]) error
|
||||
SubscribeOpenConnectStatus(*emptypb.Empty, grpc.ServerStreamingServer[OpenConnectStatusUpdate]) error
|
||||
SubmitOpenConnectAuthForm(context.Context, *OpenConnectAuthFormSubmission) (*emptypb.Empty, error)
|
||||
CancelOpenConnectAuthForm(context.Context, *OpenConnectAuthFormCancel) (*emptypb.Empty, error)
|
||||
SubmitOpenConnectAuthResponse(context.Context, *OpenConnectAuthResponseSubmission) (*emptypb.Empty, error)
|
||||
CancelOpenConnectAuthChallenge(context.Context, *OpenConnectAuthChallengeCancel) (*emptypb.Empty, error)
|
||||
SubscribeOpenVPNStatus(*emptypb.Empty, grpc.ServerStreamingServer[OpenVPNStatusUpdate]) error
|
||||
SubmitOpenVPNChallengeResponse(context.Context, *OpenVPNChallengeSubmission) (*emptypb.Empty, error)
|
||||
CancelOpenVPNChallenge(context.Context, *OpenVPNChallengeCancel) (*emptypb.Empty, error)
|
||||
@ -735,12 +735,12 @@ func (UnimplementedStartedServiceServer) SubscribeOpenConnectStatus(*emptypb.Emp
|
||||
return status.Error(codes.Unimplemented, "method SubscribeOpenConnectStatus not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SubmitOpenConnectAuthForm(context.Context, *OpenConnectAuthFormSubmission) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method SubmitOpenConnectAuthForm not implemented")
|
||||
func (UnimplementedStartedServiceServer) SubmitOpenConnectAuthResponse(context.Context, *OpenConnectAuthResponseSubmission) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method SubmitOpenConnectAuthResponse not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) CancelOpenConnectAuthForm(context.Context, *OpenConnectAuthFormCancel) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method CancelOpenConnectAuthForm not implemented")
|
||||
func (UnimplementedStartedServiceServer) CancelOpenConnectAuthChallenge(context.Context, *OpenConnectAuthChallengeCancel) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method CancelOpenConnectAuthChallenge not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SubscribeOpenVPNStatus(*emptypb.Empty, grpc.ServerStreamingServer[OpenVPNStatusUpdate]) error {
|
||||
@ -1184,38 +1184,38 @@ func _StartedService_SubscribeOpenConnectStatus_Handler(srv interface{}, stream
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeOpenConnectStatusServer = grpc.ServerStreamingServer[OpenConnectStatusUpdate]
|
||||
|
||||
func _StartedService_SubmitOpenConnectAuthForm_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(OpenConnectAuthFormSubmission)
|
||||
func _StartedService_SubmitOpenConnectAuthResponse_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(OpenConnectAuthResponseSubmission)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).SubmitOpenConnectAuthForm(ctx, in)
|
||||
return srv.(StartedServiceServer).SubmitOpenConnectAuthResponse(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_SubmitOpenConnectAuthForm_FullMethodName,
|
||||
FullMethod: StartedService_SubmitOpenConnectAuthResponse_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).SubmitOpenConnectAuthForm(ctx, req.(*OpenConnectAuthFormSubmission))
|
||||
return srv.(StartedServiceServer).SubmitOpenConnectAuthResponse(ctx, req.(*OpenConnectAuthResponseSubmission))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_CancelOpenConnectAuthForm_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(OpenConnectAuthFormCancel)
|
||||
func _StartedService_CancelOpenConnectAuthChallenge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(OpenConnectAuthChallengeCancel)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).CancelOpenConnectAuthForm(ctx, in)
|
||||
return srv.(StartedServiceServer).CancelOpenConnectAuthChallenge(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_CancelOpenConnectAuthForm_FullMethodName,
|
||||
FullMethod: StartedService_CancelOpenConnectAuthChallenge_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).CancelOpenConnectAuthForm(ctx, req.(*OpenConnectAuthFormCancel))
|
||||
return srv.(StartedServiceServer).CancelOpenConnectAuthChallenge(ctx, req.(*OpenConnectAuthChallengeCancel))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
@ -1331,12 +1331,12 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
|
||||
Handler: _StartedService_TailscaleLogout_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SubmitOpenConnectAuthForm",
|
||||
Handler: _StartedService_SubmitOpenConnectAuthForm_Handler,
|
||||
MethodName: "SubmitOpenConnectAuthResponse",
|
||||
Handler: _StartedService_SubmitOpenConnectAuthResponse_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CancelOpenConnectAuthForm",
|
||||
Handler: _StartedService_CancelOpenConnectAuthForm_Handler,
|
||||
MethodName: "CancelOpenConnectAuthChallenge",
|
||||
Handler: _StartedService_CancelOpenConnectAuthChallenge_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SubmitOpenVPNChallengeResponse",
|
||||
|
||||
@ -966,29 +966,45 @@ func (c *CommandClient) SubscribeOpenConnectStatus(handler OpenConnectStatusHand
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (c *CommandClient) SubmitOpenConnectAuthForm(endpointTag string, formID string, values *OpenConnectFormValues) error {
|
||||
func (c *CommandClient) SubmitOpenConnectAuthResponse(endpointTag string, challengeID string, response *OpenConnectAuthResponse) error {
|
||||
submission := &daemon.OpenConnectAuthResponseSubmission{
|
||||
EndpointTag: endpointTag,
|
||||
ChallengeID: challengeID,
|
||||
}
|
||||
if response.formValues != nil {
|
||||
submission.Response = &daemon.OpenConnectAuthResponseSubmission_Form{Form: &daemon.OpenConnectAuthFormResponse{
|
||||
Values: response.formValues.values,
|
||||
}}
|
||||
}
|
||||
if response.browserResult != nil {
|
||||
submission.Response = &daemon.OpenConnectAuthResponseSubmission_Browser{Browser: &daemon.OpenConnectBrowserResult{
|
||||
FinalURL: response.browserResult.FinalURL,
|
||||
Cookies: common.Map(response.browserResult.cookies, func(cookie openConnectBrowserCookie) *daemon.OpenConnectBrowserCookie {
|
||||
return &daemon.OpenConnectBrowserCookie{Name: cookie.Name, Value: cookie.Value}
|
||||
}),
|
||||
Headers: common.Map(response.browserResult.headers, func(header openConnectBrowserHeader) *daemon.OpenConnectBrowserHeader {
|
||||
return &daemon.OpenConnectBrowserHeader{Name: header.Name, Values: header.Values}
|
||||
}),
|
||||
}}
|
||||
}
|
||||
_, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) {
|
||||
return client.SubmitOpenConnectAuthForm(ctx, &daemon.OpenConnectAuthFormSubmission{
|
||||
EndpointTag: endpointTag,
|
||||
FormID: formID,
|
||||
Values: values.values,
|
||||
})
|
||||
return client.SubmitOpenConnectAuthResponse(ctx, submission)
|
||||
})
|
||||
if err != nil {
|
||||
return E.Cause(err, "submit openconnect authentication form")
|
||||
return E.Cause(err, "submit openconnect authentication response")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CommandClient) CancelOpenConnectAuthForm(endpointTag string, formID string) error {
|
||||
func (c *CommandClient) CancelOpenConnectAuthChallenge(endpointTag string, challengeID string) error {
|
||||
_, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) {
|
||||
return client.CancelOpenConnectAuthForm(ctx, &daemon.OpenConnectAuthFormCancel{
|
||||
return client.CancelOpenConnectAuthChallenge(ctx, &daemon.OpenConnectAuthChallengeCancel{
|
||||
EndpointTag: endpointTag,
|
||||
FormID: formID,
|
||||
ChallengeID: challengeID,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return E.Cause(err, "cancel openconnect authentication form")
|
||||
return E.Cause(err, "cancel openconnect authentication challenge")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -19,12 +19,12 @@ type OpenConnectEndpointStatusIterator interface {
|
||||
}
|
||||
|
||||
type OpenConnectEndpointStatus struct {
|
||||
EndpointTag string
|
||||
State string
|
||||
StateText string
|
||||
AuthForm *OpenConnectAuthForm
|
||||
Error string
|
||||
TunnelInfo *OpenConnectTunnelInfo
|
||||
EndpointTag string
|
||||
State string
|
||||
StateText string
|
||||
AuthChallenge *OpenConnectAuthChallenge
|
||||
Error string
|
||||
TunnelInfo *OpenConnectTunnelInfo
|
||||
}
|
||||
|
||||
type OpenConnectTunnelInfo struct {
|
||||
@ -50,19 +50,38 @@ func (i *OpenConnectTunnelInfo) DNS() StringIterator {
|
||||
return newIterator(i.dns)
|
||||
}
|
||||
|
||||
type OpenConnectAuthForm struct {
|
||||
type OpenConnectAuthChallenge struct {
|
||||
ID string
|
||||
Banner string
|
||||
Message string
|
||||
Error string
|
||||
URL string
|
||||
fields []*OpenConnectAuthFormField
|
||||
Form *OpenConnectAuthForm
|
||||
Browser *OpenConnectBrowserRequest
|
||||
}
|
||||
|
||||
type OpenConnectAuthForm struct {
|
||||
fields []*OpenConnectAuthFormField
|
||||
}
|
||||
|
||||
func (f *OpenConnectAuthForm) Fields() OpenConnectAuthFormFieldIterator {
|
||||
return newIterator(f.fields)
|
||||
}
|
||||
|
||||
type OpenConnectBrowserRequest struct {
|
||||
URL string
|
||||
FinalURL string
|
||||
cookieNames []string
|
||||
headerNames []string
|
||||
}
|
||||
|
||||
func (r *OpenConnectBrowserRequest) CookieNames() StringIterator {
|
||||
return newIterator(r.cookieNames)
|
||||
}
|
||||
|
||||
func (r *OpenConnectBrowserRequest) HeaderNames() StringIterator {
|
||||
return newIterator(r.headerNames)
|
||||
}
|
||||
|
||||
type OpenConnectAuthFormFieldIterator interface {
|
||||
Next() *OpenConnectAuthFormField
|
||||
HasNext() bool
|
||||
@ -103,6 +122,53 @@ func (v *OpenConnectFormValues) Add(key string, value string) {
|
||||
v.values[key] = value
|
||||
}
|
||||
|
||||
type OpenConnectBrowserResult struct {
|
||||
FinalURL string
|
||||
cookies []openConnectBrowserCookie
|
||||
headers []openConnectBrowserHeader
|
||||
}
|
||||
|
||||
func NewOpenConnectBrowserResult(finalURL string) *OpenConnectBrowserResult {
|
||||
return &OpenConnectBrowserResult{FinalURL: finalURL}
|
||||
}
|
||||
|
||||
func (r *OpenConnectBrowserResult) AddCookie(name string, value string) {
|
||||
r.cookies = append(r.cookies, openConnectBrowserCookie{Name: name, Value: value})
|
||||
}
|
||||
|
||||
func (r *OpenConnectBrowserResult) AddHeader(name string, value string) {
|
||||
for _, header := range r.headers {
|
||||
if header.Name == name {
|
||||
header.Values = append(header.Values, value)
|
||||
return
|
||||
}
|
||||
}
|
||||
r.headers = append(r.headers, openConnectBrowserHeader{Name: name, Values: []string{value}})
|
||||
}
|
||||
|
||||
type openConnectBrowserCookie struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
type openConnectBrowserHeader struct {
|
||||
Name string
|
||||
Values []string
|
||||
}
|
||||
|
||||
type OpenConnectAuthResponse struct {
|
||||
formValues *OpenConnectFormValues
|
||||
browserResult *OpenConnectBrowserResult
|
||||
}
|
||||
|
||||
func NewOpenConnectAuthFormResponse(values *OpenConnectFormValues) *OpenConnectAuthResponse {
|
||||
return &OpenConnectAuthResponse{formValues: values}
|
||||
}
|
||||
|
||||
func NewOpenConnectBrowserAuthResponse(result *OpenConnectBrowserResult) *OpenConnectAuthResponse {
|
||||
return &OpenConnectAuthResponse{browserResult: result}
|
||||
}
|
||||
|
||||
type OpenConnectStatusHandler interface {
|
||||
OnStatusUpdate(status *OpenConnectStatusUpdate)
|
||||
OnError(message string)
|
||||
@ -125,30 +191,43 @@ func openConnectEndpointStatusFromGRPC(status *daemon.OpenConnectEndpointStatus)
|
||||
StateText: status.StateText,
|
||||
Error: status.Error,
|
||||
}
|
||||
if status.AuthForm != nil {
|
||||
fields := common.Map(status.AuthForm.Fields, func(field *daemon.OpenConnectAuthFormField) *OpenConnectAuthFormField {
|
||||
return &OpenConnectAuthFormField{
|
||||
SubmissionKey: field.SubmissionKey,
|
||||
Name: field.Name,
|
||||
Label: field.Label,
|
||||
Kind: field.Kind,
|
||||
Value: field.Value,
|
||||
options: common.Map(field.Options, func(option *daemon.OpenConnectAuthFormChoice) *OpenConnectAuthFormChoice {
|
||||
return &OpenConnectAuthFormChoice{
|
||||
Value: option.Value,
|
||||
Label: option.Label,
|
||||
if status.AuthChallenge != nil {
|
||||
challenge := &OpenConnectAuthChallenge{
|
||||
ID: status.AuthChallenge.Id,
|
||||
Banner: status.AuthChallenge.Banner,
|
||||
Message: status.AuthChallenge.Message,
|
||||
Error: status.AuthChallenge.Error,
|
||||
}
|
||||
form := status.AuthChallenge.GetForm()
|
||||
if form != nil {
|
||||
challenge.Form = &OpenConnectAuthForm{
|
||||
fields: common.Map(form.Fields, func(field *daemon.OpenConnectAuthFormField) *OpenConnectAuthFormField {
|
||||
return &OpenConnectAuthFormField{
|
||||
SubmissionKey: field.SubmissionKey,
|
||||
Name: field.Name,
|
||||
Label: field.Label,
|
||||
Kind: field.Kind,
|
||||
Value: field.Value,
|
||||
options: common.Map(field.Options, func(option *daemon.OpenConnectAuthFormChoice) *OpenConnectAuthFormChoice {
|
||||
return &OpenConnectAuthFormChoice{
|
||||
Value: option.Value,
|
||||
Label: option.Label,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}),
|
||||
}
|
||||
})
|
||||
result.AuthForm = &OpenConnectAuthForm{
|
||||
ID: status.AuthForm.Id,
|
||||
Banner: status.AuthForm.Banner,
|
||||
Message: status.AuthForm.Message,
|
||||
Error: status.AuthForm.Error,
|
||||
URL: status.AuthForm.Url,
|
||||
fields: fields,
|
||||
}
|
||||
browser := status.AuthChallenge.GetBrowser()
|
||||
if browser != nil {
|
||||
challenge.Browser = &OpenConnectBrowserRequest{
|
||||
URL: browser.Url,
|
||||
FinalURL: browser.FinalURL,
|
||||
cookieNames: browser.CookieNames,
|
||||
headerNames: browser.HeaderNames,
|
||||
}
|
||||
}
|
||||
result.AuthChallenge = challenge
|
||||
}
|
||||
if status.TunnelInfo != nil {
|
||||
result.TunnelInfo = &OpenConnectTunnelInfo{
|
||||
|
||||
4
go.mod
4
go.mod
@ -46,8 +46,8 @@ require (
|
||||
github.com/sagernet/sing v0.8.12-0.20260717153536-4f1ed45a99a5
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3
|
||||
github.com/sagernet/sing-mux v0.3.5
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260717081856-cf2c71a71aba
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260718013246-3cd8a7b83247
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260718163953-a1c7815e4f04
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260718163953-26ecbeb6352c
|
||||
github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||
|
||||
8
go.sum
8
go.sum
@ -285,10 +285,10 @@ github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 h1:3y6
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3/go.mod h1:XEqEDYRCAYLaoPjZ1ifVWJg5iWAJHL2gOAXe/PM28Cg=
|
||||
github.com/sagernet/sing-mux v0.3.5 h1:RHnhVEc+SFqkrK4xMygYjDwwLhzp2Bj3lztSukONfhI=
|
||||
github.com/sagernet/sing-mux v0.3.5/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260717081856-cf2c71a71aba h1:S87Ej/jFssn0qhPF1ExF0YIV0USfGZF2If6kSjGLPt8=
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260717081856-cf2c71a71aba/go.mod h1:EIzh5HtImfQJxPKXFwS9lyMnmMy4aCQCx7ntQ4u41Gs=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260718013246-3cd8a7b83247 h1:IfZqHohaWz13eqc6SAUHkmP9xhMpMBDIShl7OtgRd5Y=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260718013246-3cd8a7b83247/go.mod h1:CmTGnS5ijVSqFQV1dTq4WvFLUoz7bk9xasBPsX8NcYo=
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260718163953-a1c7815e4f04 h1:HIb3Tu19qqH5fD5xnyyHb6zJaETIsnXamb/hWTtxil8=
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260718163953-a1c7815e4f04/go.mod h1:EIzh5HtImfQJxPKXFwS9lyMnmMy4aCQCx7ntQ4u41Gs=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260718163953-26ecbeb6352c h1:EhwLZF3IUyDj4uZ7vkUZAI7GymXCeOCwiseuOTsFjp8=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260718163953-26ecbeb6352c/go.mod h1:CmTGnS5ijVSqFQV1dTq4WvFLUoz7bk9xasBPsX8NcYo=
|
||||
github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc h1:zdc0fj4JdAdgAmQIoh7ZF+B/wPTEF2X75lYDqTmvlaw=
|
||||
github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc/go.mod h1:9k+dzGsWMttUGldBzq3dU792YHXzW6NgfbOGltnXq+0=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||
|
||||
@ -364,7 +364,10 @@ func (e *Endpoint) readLoop() {
|
||||
err = e.device.WriteInboundBuffers(packetBuffers)
|
||||
buf.ReleaseMulti(packetBuffers)
|
||||
if err != nil {
|
||||
e.logger.Error(E.Cause(err, "write OpenConnect packet to device"))
|
||||
err = E.Cause(err, "write packet to device")
|
||||
e.logger.Error(err)
|
||||
e.setTerminalError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package openconnect
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@ -13,37 +14,48 @@ var _ adapter.OpenConnectEndpoint = (*Endpoint)(nil)
|
||||
func (e *Endpoint) OpenConnectStatus() adapter.OpenConnectStatus {
|
||||
var status adapter.OpenConnectStatus
|
||||
clientState := e.state.Load()
|
||||
authForm := e.client.PendingAuthForm()
|
||||
authChallenge := e.client.PendingAuthChallenge()
|
||||
e.statusAccess.Lock()
|
||||
status.Error = e.terminalError
|
||||
e.statusAccess.Unlock()
|
||||
if authForm != nil {
|
||||
fields := common.Map(authForm.Fields, func(field openconnect.AuthFormField) adapter.OpenConnectAuthFormField {
|
||||
return adapter.OpenConnectAuthFormField{
|
||||
SubmissionKey: field.SubmissionKey,
|
||||
Name: field.Name,
|
||||
Label: field.Label,
|
||||
Kind: field.Kind,
|
||||
Value: field.Value,
|
||||
Options: common.Map(field.Options, func(choice openconnect.AuthFormChoice) adapter.OpenConnectAuthFormChoice {
|
||||
return adapter.OpenConnectAuthFormChoice{
|
||||
Value: choice.Value,
|
||||
Label: choice.Label,
|
||||
if authChallenge != nil {
|
||||
challenge := &adapter.OpenConnectAuthChallenge{
|
||||
ID: authChallenge.ID,
|
||||
Banner: authChallenge.Banner,
|
||||
Message: authChallenge.Message,
|
||||
Error: authChallenge.Error,
|
||||
}
|
||||
if authChallenge.Form != nil {
|
||||
challenge.Form = &adapter.OpenConnectAuthForm{
|
||||
Fields: common.Map(authChallenge.Form.Fields, func(field openconnect.AuthFormField) adapter.OpenConnectAuthFormField {
|
||||
return adapter.OpenConnectAuthFormField{
|
||||
SubmissionKey: field.SubmissionKey,
|
||||
Name: field.Name,
|
||||
Label: field.Label,
|
||||
Kind: field.Kind,
|
||||
Value: field.Value,
|
||||
Options: common.Map(field.Options, func(choice openconnect.AuthFormChoice) adapter.OpenConnectAuthFormChoice {
|
||||
return adapter.OpenConnectAuthFormChoice{
|
||||
Value: choice.Value,
|
||||
Label: choice.Label,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}),
|
||||
}
|
||||
})
|
||||
status.AuthForm = &adapter.OpenConnectAuthForm{
|
||||
ID: authForm.ID,
|
||||
Banner: authForm.Banner,
|
||||
Message: authForm.Message,
|
||||
Error: authForm.Error,
|
||||
URL: authForm.URL,
|
||||
Fields: fields,
|
||||
}
|
||||
if authChallenge.Browser != nil {
|
||||
challenge.Browser = &adapter.OpenConnectBrowserRequest{
|
||||
URL: authChallenge.Browser.URL,
|
||||
FinalURL: authChallenge.Browser.FinalURL,
|
||||
CookieNames: slices.Clone(authChallenge.Browser.CookieNames),
|
||||
HeaderNames: slices.Clone(authChallenge.Browser.HeaderNames),
|
||||
}
|
||||
}
|
||||
status.AuthChallenge = challenge
|
||||
}
|
||||
switch {
|
||||
case status.AuthForm != nil:
|
||||
case status.AuthChallenge != nil:
|
||||
status.State = adapter.OpenConnectStateAuthPending
|
||||
case status.Error != "":
|
||||
status.State = adapter.OpenConnectStateError
|
||||
@ -66,12 +78,31 @@ func (e *Endpoint) StatusUpdated() <-chan struct{} {
|
||||
return e.statusUpdated
|
||||
}
|
||||
|
||||
func (e *Endpoint) CompleteAuthForm(formID string, values map[string]string) error {
|
||||
return e.client.CompleteAuthForm(formID, values)
|
||||
func (e *Endpoint) CompleteAuthChallenge(challengeID string, response adapter.OpenConnectAuthResponse) error {
|
||||
var authResponse openconnect.AuthResponse
|
||||
if response.Form != nil {
|
||||
authResponse.Form = &openconnect.AuthFormResponse{Values: response.Form.Values}
|
||||
}
|
||||
if response.Browser != nil {
|
||||
browserResult := &openconnect.BrowserResult{
|
||||
FinalURL: response.Browser.FinalURL,
|
||||
Cookies: common.Map(response.Browser.Cookies, func(cookie adapter.OpenConnectBrowserCookie) openconnect.BrowserCookie {
|
||||
return openconnect.BrowserCookie{Name: cookie.Name, Value: cookie.Value}
|
||||
}),
|
||||
Header: make(http.Header),
|
||||
}
|
||||
for _, header := range response.Browser.Headers {
|
||||
for _, value := range header.Values {
|
||||
browserResult.Header.Add(header.Name, value)
|
||||
}
|
||||
}
|
||||
authResponse.Browser = browserResult
|
||||
}
|
||||
return e.client.CompleteAuthChallenge(challengeID, authResponse)
|
||||
}
|
||||
|
||||
func (e *Endpoint) CancelAuthForm(formID string) error {
|
||||
return e.client.CancelAuthForm(formID)
|
||||
func (e *Endpoint) CancelAuthChallenge(challengeID string) error {
|
||||
return e.client.CancelAuthChallenge(challengeID)
|
||||
}
|
||||
|
||||
func (e *Endpoint) notifyStatusUpdated() {
|
||||
@ -94,14 +125,14 @@ func (e *Endpoint) setTerminalError(err error) {
|
||||
|
||||
func (e *Endpoint) watchAuthForms() {
|
||||
defer close(e.authFormLoopDone)
|
||||
var loggedAuthFormID string
|
||||
var loggedAuthChallengeID string
|
||||
for {
|
||||
authFormUpdated := e.client.AuthFormUpdated()
|
||||
authForm := e.client.PendingAuthForm()
|
||||
if authForm != nil && authForm.ID != loggedAuthFormID {
|
||||
loggedAuthFormID = authForm.ID
|
||||
if authForm.URL != "" {
|
||||
e.logger.Info("waiting for authentication: ", authForm.URL)
|
||||
authChallengeUpdated := e.client.AuthChallengeUpdated()
|
||||
authChallenge := e.client.PendingAuthChallenge()
|
||||
if authChallenge != nil && authChallenge.ID != loggedAuthChallengeID {
|
||||
loggedAuthChallengeID = authChallenge.ID
|
||||
if authChallenge.Browser != nil {
|
||||
e.logger.Info("waiting for browser authentication")
|
||||
} else {
|
||||
e.logger.Info("waiting for authentication")
|
||||
}
|
||||
@ -110,7 +141,7 @@ func (e *Endpoint) watchAuthForms() {
|
||||
select {
|
||||
case <-e.loopContext.Done():
|
||||
return
|
||||
case <-authFormUpdated:
|
||||
case <-authChallengeUpdated:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -425,7 +425,10 @@ func (c *ClientEndpoint) readLoop() {
|
||||
err = c.device.WriteInboundBuffers(packetBuffers)
|
||||
buf.ReleaseMulti(packetBuffers)
|
||||
if err != nil {
|
||||
c.logger.Error(E.Cause(err, "write OpenVPN packet to device"))
|
||||
err = E.Cause(err, "write packet to device")
|
||||
c.logger.Error(err)
|
||||
c.setTerminalError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,7 +433,8 @@ func (s *ServerEndpoint) readLoop() {
|
||||
err = s.device.WriteInboundBuffers(packetBuffers)
|
||||
buf.ReleaseMulti(packetBuffers)
|
||||
if err != nil {
|
||||
s.logger.Error(E.Cause(err, "write OpenVPN packet to device"))
|
||||
s.logger.Error(E.Cause(err, "write packet to device"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,8 +153,8 @@ require (
|
||||
github.com/sagernet/nftables v0.3.0-mod.4 // indirect
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 // indirect
|
||||
github.com/sagernet/sing-mux v0.3.5 // indirect
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260717081856-cf2c71a71aba // indirect
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260718013246-3cd8a7b83247 // indirect
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260718163953-a1c7815e4f04 // indirect
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260718163953-26ecbeb6352c // indirect
|
||||
github.com/sagernet/sing-shadowtls v0.2.1 // indirect
|
||||
github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb // indirect
|
||||
github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb // indirect
|
||||
|
||||
@ -304,10 +304,10 @@ github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 h1:3y6
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3/go.mod h1:XEqEDYRCAYLaoPjZ1ifVWJg5iWAJHL2gOAXe/PM28Cg=
|
||||
github.com/sagernet/sing-mux v0.3.5 h1:RHnhVEc+SFqkrK4xMygYjDwwLhzp2Bj3lztSukONfhI=
|
||||
github.com/sagernet/sing-mux v0.3.5/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260717081856-cf2c71a71aba h1:S87Ej/jFssn0qhPF1ExF0YIV0USfGZF2If6kSjGLPt8=
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260717081856-cf2c71a71aba/go.mod h1:EIzh5HtImfQJxPKXFwS9lyMnmMy4aCQCx7ntQ4u41Gs=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260718013246-3cd8a7b83247 h1:IfZqHohaWz13eqc6SAUHkmP9xhMpMBDIShl7OtgRd5Y=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260718013246-3cd8a7b83247/go.mod h1:CmTGnS5ijVSqFQV1dTq4WvFLUoz7bk9xasBPsX8NcYo=
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260718163953-a1c7815e4f04 h1:HIb3Tu19qqH5fD5xnyyHb6zJaETIsnXamb/hWTtxil8=
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260718163953-a1c7815e4f04/go.mod h1:EIzh5HtImfQJxPKXFwS9lyMnmMy4aCQCx7ntQ4u41Gs=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260718163953-26ecbeb6352c h1:EhwLZF3IUyDj4uZ7vkUZAI7GymXCeOCwiseuOTsFjp8=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260718163953-26ecbeb6352c/go.mod h1:CmTGnS5ijVSqFQV1dTq4WvFLUoz7bk9xasBPsX8NcYo=
|
||||
github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc h1:zdc0fj4JdAdgAmQIoh7ZF+B/wPTEF2X75lYDqTmvlaw=
|
||||
github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc/go.mod h1:9k+dzGsWMttUGldBzq3dU792YHXzW6NgfbOGltnXq+0=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||
|
||||
@ -123,7 +123,7 @@ func TestOpenConnectDockerInterop(t *testing.T) {
|
||||
))
|
||||
endpoint := requireOpenConnectEndpoint(subtest, instance)
|
||||
status := waitForOpenConnectState(subtest, endpoint, adapter.OpenConnectStateConnected, 45*time.Second)
|
||||
require.Nil(subtest, status.AuthForm)
|
||||
require.Nil(subtest, status.AuthChallenge)
|
||||
err := exchangeOpenConnectTCPEcho(endpoint, 256*1024, 30*time.Second)
|
||||
require.NoError(subtest, err)
|
||||
err = exchangeOpenConnectUDPEcho(endpoint, 1400, 30*time.Second)
|
||||
@ -167,7 +167,7 @@ func TestOpenConnectDockerInterop(t *testing.T) {
|
||||
|
||||
status := endpoint.OpenConnectStatus()
|
||||
require.Equal(subtest, adapter.OpenConnectStateConnected, status.State, status.Error)
|
||||
require.Nil(subtest, status.AuthForm)
|
||||
require.Nil(subtest, status.AuthChallenge)
|
||||
logs, err := openConnectDockerOutput(ctx, "logs", container.name)
|
||||
require.NoError(subtest, err)
|
||||
require.GreaterOrEqual(subtest, strings.Count(logs, "HTTP CONNECT /CSCOSSLC/tunnel"), 2, logs)
|
||||
@ -254,13 +254,15 @@ func driveOpenConnectInteractiveAuthentication(t *testing.T, endpoint adapter.Op
|
||||
case adapter.OpenConnectStateError:
|
||||
t.Fatal(status.Error)
|
||||
case adapter.OpenConnectStateAuthPending:
|
||||
form := status.AuthForm
|
||||
require.NotNil(t, form)
|
||||
require.NotEmpty(t, form.ID)
|
||||
_, completed := completedForms[form.ID]
|
||||
challenge := status.AuthChallenge
|
||||
require.NotNil(t, challenge)
|
||||
require.NotNil(t, challenge.Form)
|
||||
require.Nil(t, challenge.Browser)
|
||||
require.NotEmpty(t, challenge.ID)
|
||||
_, completed := completedForms[challenge.ID]
|
||||
if !completed {
|
||||
values := make(map[string]string, len(form.Fields))
|
||||
for _, field := range form.Fields {
|
||||
values := make(map[string]string, len(challenge.Form.Fields))
|
||||
for _, field := range challenge.Form.Fields {
|
||||
require.NotEmpty(t, field.SubmissionKey)
|
||||
switch field.Name {
|
||||
case "username":
|
||||
@ -274,9 +276,11 @@ func driveOpenConnectInteractiveAuthentication(t *testing.T, endpoint adapter.Op
|
||||
}
|
||||
}
|
||||
require.NotEmpty(t, values)
|
||||
err := endpoint.CompleteAuthForm(form.ID, values)
|
||||
err := endpoint.CompleteAuthChallenge(challenge.ID, adapter.OpenConnectAuthResponse{
|
||||
Form: &adapter.OpenConnectAuthFormResponse{Values: values},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
completedForms[form.ID] = struct{}{}
|
||||
completedForms[challenge.ID] = struct{}{}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,7 +150,7 @@ func (d *systemDevice) readLoop(tunInterface tun.Tun, mtu int) {
|
||||
return
|
||||
}
|
||||
d.options.Logger.Error(E.Cause(err, "read packet"))
|
||||
continue
|
||||
return
|
||||
}
|
||||
if readN <= tun.PacketOffset {
|
||||
continue
|
||||
@ -162,6 +162,7 @@ func (d *systemDevice) readLoop(tunInterface tun.Tun, mtu int) {
|
||||
packetBuffer.DecRef()
|
||||
if err != nil {
|
||||
d.options.Logger.Error(E.Cause(err, "write packet"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -192,6 +193,7 @@ func (d *systemDevice) readLoopLinux(tunInterface tun.LinuxTUN, batchSize int, m
|
||||
}
|
||||
if writeErr != nil {
|
||||
d.options.Logger.Error(E.Cause(writeErr, "write packet batch"))
|
||||
return
|
||||
}
|
||||
}
|
||||
if readErr != nil {
|
||||
@ -199,6 +201,7 @@ func (d *systemDevice) readLoopLinux(tunInterface tun.LinuxTUN, batchSize int, m
|
||||
return
|
||||
}
|
||||
d.options.Logger.Error(E.Cause(readErr, "batch read packet"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -218,6 +221,7 @@ func (d *systemDevice) readLoopDarwin(tunInterface tun.DarwinTUN) {
|
||||
writeErr := d.writeOutbound(outboundBuffers)
|
||||
if writeErr != nil {
|
||||
d.options.Logger.Error(E.Cause(writeErr, "write packet batch"))
|
||||
return
|
||||
}
|
||||
}
|
||||
if readErr != nil {
|
||||
@ -225,6 +229,7 @@ func (d *systemDevice) readLoopDarwin(tunInterface tun.DarwinTUN) {
|
||||
return
|
||||
}
|
||||
d.options.Logger.Error(E.Cause(readErr, "batch read packet"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,6 +103,7 @@ func (d *stackDevice) UpdateConfiguration(configuration Configuration) error {
|
||||
defer d.stateAccess.Unlock()
|
||||
if d.logRouteOptions && hasRouteOptions(configuration.Routes) {
|
||||
d.options.Logger.Debug("OpenVPN route gateway and metric options are not representable by the gVisor stack device; routes are installed by prefix")
|
||||
d.logRouteOptions = false
|
||||
}
|
||||
if configuration.MTU != 0 {
|
||||
d.options.MTU = configuration.MTU
|
||||
|
||||
@ -30,13 +30,14 @@ const (
|
||||
|
||||
type systemDevice struct {
|
||||
baseDevice
|
||||
stateAccess sync.RWMutex
|
||||
options DeviceOptions
|
||||
dialer N.Dialer
|
||||
device tun.Tun
|
||||
inet4Address netip.Addr
|
||||
inet6Address netip.Addr
|
||||
closed bool
|
||||
stateAccess sync.RWMutex
|
||||
options DeviceOptions
|
||||
dialer N.Dialer
|
||||
device tun.Tun
|
||||
inet4Address netip.Addr
|
||||
inet6Address netip.Addr
|
||||
logRouteOptions bool
|
||||
closed bool
|
||||
}
|
||||
|
||||
func newSystemDevice(options DeviceOptions) (*systemDevice, error) {
|
||||
@ -54,10 +55,11 @@ func newSystemDevice(options DeviceOptions) (*systemDevice, error) {
|
||||
}
|
||||
inet4Address, inet6Address := firstAddresses(options.Configuration.Address)
|
||||
return &systemDevice{
|
||||
options: options,
|
||||
dialer: interfaceDialer,
|
||||
inet4Address: inet4Address,
|
||||
inet6Address: inet6Address,
|
||||
options: options,
|
||||
dialer: interfaceDialer,
|
||||
inet4Address: inet4Address,
|
||||
inet6Address: inet6Address,
|
||||
logRouteOptions: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -191,7 +193,7 @@ func (d *systemDevice) readLoop(tunInterface tun.Tun, mtu int) {
|
||||
return
|
||||
}
|
||||
d.options.Logger.Error(E.Cause(err, "read packet"))
|
||||
continue
|
||||
return
|
||||
}
|
||||
if readN <= tun.PacketOffset {
|
||||
continue
|
||||
@ -206,6 +208,7 @@ func (d *systemDevice) readLoop(tunInterface tun.Tun, mtu int) {
|
||||
packetBuffer.DecRef()
|
||||
if err != nil {
|
||||
d.options.Logger.Error(E.Cause(err, "write packet"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -243,6 +246,7 @@ func (d *systemDevice) readLoopLinux(tunInterface tun.LinuxTUN, batchSize int, m
|
||||
}
|
||||
if writeErr != nil {
|
||||
d.options.Logger.Error(E.Cause(writeErr, "write packet batch"))
|
||||
return
|
||||
}
|
||||
}
|
||||
if readErr != nil {
|
||||
@ -250,6 +254,7 @@ func (d *systemDevice) readLoopLinux(tunInterface tun.LinuxTUN, batchSize int, m
|
||||
return
|
||||
}
|
||||
d.options.Logger.Error(E.Cause(readErr, "batch read packet"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -274,6 +279,7 @@ func (d *systemDevice) readLoopDarwin(tunInterface tun.DarwinTUN) {
|
||||
writeErr := d.writeOutbound(outboundBuffers)
|
||||
if writeErr != nil {
|
||||
d.options.Logger.Error(E.Cause(writeErr, "write packet batch"))
|
||||
return
|
||||
}
|
||||
}
|
||||
if readErr != nil {
|
||||
@ -281,6 +287,7 @@ func (d *systemDevice) readLoopDarwin(tunInterface tun.DarwinTUN) {
|
||||
return
|
||||
}
|
||||
d.options.Logger.Error(E.Cause(readErr, "batch read packet"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -291,8 +298,9 @@ func (d *systemDevice) UpdateConfiguration(configuration Configuration) error {
|
||||
routes := routesWithBlockIPv6(configuration)
|
||||
_, hasUnrepresentableInet4RouteOptions := systemRouteGateway(routes, true)
|
||||
_, hasUnrepresentableInet6RouteOptions := systemRouteGateway(routes, false)
|
||||
if hasUnrepresentableInet4RouteOptions || hasUnrepresentableInet6RouteOptions {
|
||||
if d.logRouteOptions && (hasUnrepresentableInet4RouteOptions || hasUnrepresentableInet6RouteOptions) {
|
||||
d.options.Logger.Debug("some OpenVPN route gateway or metric options are not representable by the system device; routes are installed by prefix")
|
||||
d.logRouteOptions = false
|
||||
}
|
||||
previousConfiguration := d.options.Configuration
|
||||
previousMTU := d.options.MTU
|
||||
|
||||
Loading…
Reference in New Issue
Block a user