| // 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) |
| } |