Skip to content

Commit 7c2b753

Browse files
committed
cmd/gomote: add gomote rdp subcommand to open an RDP proxy to a Windows buildlet
Fixes golang/go#26090 Change-Id: Ib58fb7767384778b67d0d62d34c1132d70d75c23 Reviewed-on: https://go-review.googlesource.com/c/build/+/207378 Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]>
1 parent 68b96de commit 7c2b753

File tree

3 files changed

+122
-1
lines changed

3 files changed

+122
-1
lines changed

buildlet/buildletclient.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ func (c *Client) String() string {
188188

189189
// RemoteName returns the name of this client's buildlet on the
190190
// coordinator. If this buildlet isn't a remote buildlet created via
191-
// a buildlet, this returns the empty string.
191+
// gomote, this returns the empty string.
192192
func (c *Client) RemoteName() string {
193193
return c.remoteBuildlet
194194
}
@@ -258,6 +258,37 @@ func (c *Client) do(req *http.Request) (*http.Response, error) {
258258
return c.httpClient.Do(req)
259259
}
260260

261+
// ProxyTCP connects to the given port on the remote buildlet.
262+
// The buildlet client must currently be a gomote client (RemoteName != "")
263+
// and the target type must be a VM type running on GCE. This was primarily
264+
// created for RDP to Windows machines, but it might get reused for other
265+
// purposes in the future.
266+
func (c *Client) ProxyTCP(port int) (io.ReadWriteCloser, error) {
267+
if c.RemoteName() == "" {
268+
return nil, errors.New("ProxyTCP currently only supports gomote-created buildlets")
269+
}
270+
req, err := http.NewRequest("POST", c.URL()+"/tcpproxy", nil)
271+
if err != nil {
272+
return nil, err
273+
}
274+
req.Header.Add("X-Target-Port", fmt.Sprint(port))
275+
res, err := c.do(req)
276+
if err != nil {
277+
return nil, err
278+
}
279+
if res.StatusCode != http.StatusSwitchingProtocols {
280+
slurp, _ := ioutil.ReadAll(io.LimitReader(res.Body, 4<<10))
281+
res.Body.Close()
282+
return nil, fmt.Errorf("wanted 101 Switching Protocols; unexpected response: %v, %q", res.Status, slurp)
283+
}
284+
rwc, ok := res.Body.(io.ReadWriteCloser)
285+
if !ok {
286+
res.Body.Close()
287+
return nil, fmt.Errorf("tcpproxy response was not a Writer")
288+
}
289+
return rwc, nil
290+
}
291+
261292
// ProxyRoundTripper returns a RoundTripper that sends HTTP requests directly
262293
// through to the underlying buildlet, adding auth and X-Buildlet-Proxy headers
263294
// as necessary. This is really only intended for use by the coordinator.

cmd/gomote/gomote.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ To list the subcommands, run "gomote" without arguments:
3434
put14 put Go 1.4 in place
3535
puttar extract a tar.gz to a buildlet
3636
rm delete files or directories
37+
rdp RDP (Remote Desktop Protocol) to a Windows buildlet
3738
run run a command on a buildlet
3839
ssh ssh to a buildlet
3940
@@ -149,6 +150,7 @@ func registerCommands() {
149150
registerCommand("put", "put files on a buildlet", put)
150151
registerCommand("put14", "put Go 1.4 in place", put14)
151152
registerCommand("puttar", "extract a tar.gz to a buildlet", putTar)
153+
registerCommand("rdp", "RDP (Remote Desktop Protocol) to a Windows buildlet", rdp)
152154
registerCommand("rm", "delete files or directories", rm)
153155
registerCommand("run", "run a command on a buildlet", run)
154156
registerCommand("ssh", "ssh to a buildlet", ssh)

cmd/gomote/rdp.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"context"
9+
"errors"
10+
"flag"
11+
"fmt"
12+
"io"
13+
"log"
14+
"net"
15+
"os"
16+
17+
"golang.org/x/build/buildlet"
18+
"golang.org/x/sync/errgroup"
19+
)
20+
21+
const rdpPort = 3389
22+
23+
func rdp(args []string) error {
24+
fs := flag.NewFlagSet("rdp", flag.ContinueOnError)
25+
fs.Usage = func() {
26+
fmt.Fprintln(os.Stderr, "rdp usage: gomote rdp [--listen=...] <instance>")
27+
fs.PrintDefaults()
28+
os.Exit(1)
29+
}
30+
var listen string
31+
fs.StringVar(&listen, "listen", "localhost:"+fmt.Sprint(rdpPort), "local address to listen on")
32+
fs.Parse(args)
33+
if fs.NArg() != 1 {
34+
fs.Usage()
35+
}
36+
name := fs.Arg(0)
37+
bc, err := remoteClient(name)
38+
if err != nil {
39+
return err
40+
}
41+
42+
ln, err := net.Listen("tcp", listen)
43+
if err != nil {
44+
return err
45+
}
46+
log.Printf("Listening on %v to proxy RDP.", ln.Addr())
47+
for {
48+
c, err := ln.Accept()
49+
if err != nil {
50+
return err
51+
}
52+
go handleRDPConn(bc, c)
53+
}
54+
}
55+
56+
func handleRDPConn(bc *buildlet.Client, c net.Conn) {
57+
const Lmsgprefix = 64 // new in Go 1.14, harmless before
58+
log := log.New(os.Stderr, c.RemoteAddr().String()+": ", log.LstdFlags|Lmsgprefix)
59+
log.Printf("accepted connection, dialing buildlet via coordinator proxy...")
60+
rwc, err := bc.ProxyTCP(rdpPort)
61+
if err != nil {
62+
c.Close()
63+
log.Printf("failed to connect to buildlet via coordinator: %v", err)
64+
return
65+
}
66+
67+
log.Printf("connected to buildlet; proxying data.")
68+
69+
grp, ctx := errgroup.WithContext(context.Background())
70+
grp.Go(func() error {
71+
_, err := io.Copy(rwc, c)
72+
if err == nil {
73+
return errors.New("local client closed")
74+
}
75+
return fmt.Errorf("error copying from local to remote: %v", err)
76+
})
77+
grp.Go(func() error {
78+
_, err := io.Copy(c, rwc)
79+
if err == nil {
80+
return errors.New("remote server closed")
81+
}
82+
return fmt.Errorf("error copying from remote to local: %v", err)
83+
})
84+
<-ctx.Done()
85+
rwc.Close()
86+
c.Close()
87+
log.Printf("closing RDP connection: %v", grp.Wait())
88+
}

0 commit comments

Comments
 (0)