Fix SSO support for anyconnect

This commit is contained in:
世界 2026-07-18 22:07:16 +08:00
parent 37b4386bdd
commit fea6299fa7
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
19 changed files with 1171 additions and 489 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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",

View File

@ -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
}

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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
}
}
}

View File

@ -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:
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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=

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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