| // Copyright 2024 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| //go:build windows | |
| package command | |
| import ( | |
| "context" | |
| "fmt" | |
| "net" | |
| "github.com/GoogleCloudPlatform/galog" | |
| "github.com/GoogleCloudPlatform/google-guest-agent/internal/cfg" | |
| winio "github.com/Microsoft/go-winio" | |
| ) | |
| const ( | |
| // securityDescriptor is a Windows security descriptor in SDDL format with | |
| // local system user as owner & group and default ACLs. | |
| // Note: Guest Agent process runs as a local system user. | |
| // Refer this for well known SIDs: | |
| // https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers#well-known-sids | |
| securityDescriptor = "O:S-1-5-18G:S-1-5-18" | |
| ) | |
| // PipeName generates and returns the full pipe name as [cfg.Unstable.CommandPipePath] | |
| // prefix and `_id` being the suffix. Unlike Linux, Windows does not allow "\" | |
| // separator in pipe names so we use `_` separator. | |
| // https://learn.microsoft.com/en-us/windows/win32/ipc/pipe-names | |
| func PipeName(listener KnownListeners) string { | |
| base := cfg.Retrieve().Unstable.CommandPipePath | |
| return fmt.Sprintf("%s_%s", base, listener) | |
| } | |
| func listen(ctx context.Context, path string, filemode int, group string) (net.Listener, error) { | |
| // Winio library does not provide any method to listen on context. Failing to | |
| // specify a pipeconfig (or using the zero value) results in flaky | |
| // ACCESS_DENIED errors when re-opening the same pipe (~1/10). | |
| // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#remarks | |
| // Even with a pipeconfig, this flakes ~1/200 runs, hence the retry until the | |
| // context is expired or listen is successful. | |
| var l net.Listener | |
| var lastError error | |
| for { | |
| if ctx.Err() != nil { | |
| return nil, fmt.Errorf("context expired: %v before successful listen (last error: %v)", ctx.Err(), lastError) | |
| } | |
| config := &winio.PipeConfig{ | |
| MessageMode: false, | |
| InputBufferSize: 1024, | |
| OutputBufferSize: 1024, | |
| SecurityDescriptor: securityDescriptor, | |
| } | |
| l, lastError = winio.ListenPipe(path, config) | |
| if lastError == nil { | |
| return l, lastError | |
| } | |
| galog.V(2).Errorf("Failed to listen on pipe %s: %v. Retrying...", path, lastError) | |
| } | |
| } | |
| func dialPipe(ctx context.Context, pipe string) (net.Conn, error) { | |
| return winio.DialPipeContext(ctx, pipe) | |
| } |