-
Notifications
You must be signed in to change notification settings - Fork 881
feat: add port-forward subcommand #1350
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
f104e02
feat: add agent dial handler
deansheather 1972629
chore: Kyle PR review comments
deansheather e3eb839
feat: add port-forward subcommand
deansheather 46092cc
wip
deansheather f861ea8
fix: avoid dropped datachannels in quick succession
deansheather 6dfd2f6
chore: fix lint errors
deansheather fd7e32c
chore: block unix forwarding on windows
deansheather 8eac40a
merge master
deansheather 52142ba
fix problems
deansheather 6d9ed0c
chore: fix data race in port-forward test
deansheather f12ded0
chore: rename ExpandPath to ExpandRelativeHomePath
deansheather ee25623
chore: merge master
deansheather File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next
Next commit
feat: add agent dial handler
- Loading branch information
commit f104e02b7e4a2da4d3818b0b5f0aa92ad795015c
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package agent | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"io" | ||
"net" | ||
"net/url" | ||
"strings" | ||
|
||
"github.com/google/uuid" | ||
"golang.org/x/xerrors" | ||
|
||
"github.com/coder/coder/peer" | ||
) | ||
|
||
// DialResponse is written to datachannels with protocol "dial" by the agent as | ||
// the first packet to signify whether the dial succeeded or failed. | ||
type DialResponse struct { | ||
Error string `json:"error,omitempty"` | ||
} | ||
|
||
// Dial dials an arbitrary protocol+address from inside the workspace and | ||
// proxies it through the provided net.Conn. | ||
func (c *Conn) Dial(network string, addr string) (net.Conn, error) { | ||
deansheather marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Force unique URL by including a random UUID. | ||
id, err := uuid.NewRandom() | ||
if err != nil { | ||
return nil, xerrors.Errorf("generate random UUID: %w", err) | ||
} | ||
|
||
host := "" | ||
path := "" | ||
if strings.HasPrefix(network, "unix") { | ||
path = addr | ||
} else { | ||
host = addr | ||
} | ||
|
||
label := (&url.URL{ | ||
Scheme: network, | ||
Host: host, | ||
Path: path, | ||
RawQuery: (url.Values{ | ||
"id": []string{id.String()}, | ||
}).Encode(), | ||
}).String() | ||
|
||
channel, err := c.OpenChannel(context.Background(), label, &peer.ChannelOptions{ | ||
deansheather marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Protocol: "dial", | ||
}) | ||
if err != nil { | ||
return nil, xerrors.Errorf("pty: %w", err) | ||
} | ||
|
||
// The first message written from the other side is a JSON payload | ||
// containing the dial error. | ||
dec := json.NewDecoder(channel) | ||
var res DialResponse | ||
err = dec.Decode(&res) | ||
if err != nil { | ||
return nil, xerrors.Errorf("failed to decode initial packet: %w", err) | ||
} | ||
if res.Error != "" { | ||
_ = channel.Close() | ||
return nil, xerrors.Errorf("remote dial error: %v", res.Error) | ||
} | ||
|
||
return channel.NetConn(), nil | ||
} | ||
|
||
func (*agent) handleDial(ctx context.Context, label string, conn net.Conn) { | ||
defer conn.Close() | ||
|
||
writeError := func(responseError error) error { | ||
deansheather marked this conversation as resolved.
Show resolved
Hide resolved
|
||
msg := "" | ||
if responseError != nil { | ||
msg = responseError.Error() | ||
} | ||
b, err := json.Marshal(DialResponse{ | ||
Error: msg, | ||
}) | ||
if err != nil { | ||
return xerrors.Errorf("marshal agent webrtc dial response: %w", err) | ||
} | ||
|
||
_, err = conn.Write(b) | ||
return err | ||
} | ||
|
||
u, err := url.Parse(label) | ||
if err != nil { | ||
_ = writeError(xerrors.Errorf("parse URL %q: %w", label, err)) | ||
return | ||
} | ||
|
||
network := u.Scheme | ||
addr := u.Host + u.Path | ||
nconn, err := net.Dial(network, addr) | ||
if err != nil { | ||
_ = writeError(xerrors.Errorf("dial '%v://%v': %w", network, addr, err)) | ||
return | ||
} | ||
|
||
err = writeError(nil) | ||
if err != nil { | ||
return | ||
} | ||
|
||
bicopy(ctx, conn, nconn) | ||
} | ||
|
||
// bicopy copies all of the data between the two connections | ||
// and will close them after one or both of them are done writing. | ||
// If the context is canceled, both of the connections will be | ||
// closed. | ||
// | ||
// NOTE: This function will block until the copying is done or the | ||
deansheather marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// context is canceled. | ||
func bicopy(ctx context.Context, c1, c2 io.ReadWriteCloser) { | ||
defer c1.Close() | ||
defer c2.Close() | ||
|
||
ctx, cancel := context.WithCancel(ctx) | ||
|
||
copyFunc := func(dst io.WriteCloser, src io.Reader) { | ||
defer cancel() | ||
_, _ = io.Copy(dst, src) | ||
} | ||
|
||
go copyFunc(c1, c2) | ||
go copyFunc(c2, c1) | ||
|
||
<-ctx.Done() | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are done beautifully! 😍