Skip to content

Commit 0319dad

Browse files
authored
Merge pull request #170 from SenseUnit/cmd_dialer
External command dialer
2 parents a9f4d2e + b9b2697 commit 0319dad

File tree

3 files changed

+87
-0
lines changed

3 files changed

+87
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,9 @@ Supported proxy schemes are:
432432
* `chain` - pseudo-proxy assembling a chain from other proxy specifications. Query string parameters are:
433433
* `next` - specification of the next proxy in chain. This query string parameter can be specified more than once. Example: `chain://?next=http%3A%2F%2F127.0.0.1%3A57800&next=force-resolve%3A%2F%2F`.
434434
* `set-dst` - pseudo-proxy replacing connection destination address with the one specified in hostname part of URI. It is mostly intended for programmatic usage from within routing JS script. Example: `set-dst://localhost:9999`.
435+
* `cmd` - wrapper which redirects connection into stdin/stdout of external program. Destination address and network are passed to subprocess via `DUMBPROXY_DST_ADDR` and `DUMBPROXY_DST_NET` environment variables. Example: `cmd://?cmd=openssl&arg=s_client&arg=-connect&arg=google.com%3A443`. Parameters:
436+
* `cmd` - program to execute.
437+
* `arg` - optional command line argument passed to a program. This parameter may be repeated more than once.
435438

436439
## Configuration files
437440

dialer/command.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package dialer
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"net"
8+
"net/url"
9+
"os"
10+
"os/exec"
11+
"time"
12+
13+
xproxy "golang.org/x/net/proxy"
14+
)
15+
16+
type CommandDialer struct {
17+
command []string
18+
waitDelay time.Duration
19+
}
20+
21+
func CommandDialerFromURL(u *url.URL, _ xproxy.Dialer) (xproxy.Dialer, error) {
22+
params, err := url.ParseQuery(u.RawQuery)
23+
if err != nil {
24+
return nil, err
25+
}
26+
cmd := params.Get("cmd")
27+
if cmd == "" {
28+
return nil, errors.New("command is not specified")
29+
}
30+
args := params["arg"]
31+
waitDelay := 5 * time.Second
32+
if wd := params.Get("wait_delay"); wd != "" {
33+
waitDelay, err = time.ParseDuration(wd)
34+
if err != nil {
35+
return nil, fmt.Errorf("unable to parse wait_delay parameter: %w", err)
36+
}
37+
}
38+
command := make([]string, 0, len(args)+1)
39+
command = append(command, cmd)
40+
command = append(command, args...)
41+
return &CommandDialer{
42+
command: command,
43+
waitDelay: waitDelay,
44+
}, nil
45+
}
46+
47+
func (d *CommandDialer) Dial(network, address string) (net.Conn, error) {
48+
return d.DialContext(context.Background(), network, address)
49+
}
50+
51+
func (d *CommandDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
52+
cmd := exec.CommandContext(ctx, d.command[0], d.command[1:]...)
53+
cmd.Env = append(os.Environ(),
54+
"DUMBPROXY_DST_ADDR="+address,
55+
"DUMBPROXY_DST_NET="+network,
56+
)
57+
cmdIn, err := cmd.StdinPipe()
58+
if err != nil {
59+
return nil, fmt.Errorf("can't create stdin pipe for subprocess: %w", err)
60+
}
61+
cmdOut, err := cmd.StdoutPipe()
62+
if err != nil {
63+
return nil, fmt.Errorf("can't create stdout pipe for subprocess: %w", err)
64+
}
65+
cmd.Stderr = os.Stderr
66+
cmd.WaitDelay = d.waitDelay
67+
if err := cmd.Start(); err != nil {
68+
return nil, fmt.Errorf("unable to start subprocess: %w", err)
69+
}
70+
go func() {
71+
cmd.Wait()
72+
cmdIn.Close()
73+
cmdOut.Close()
74+
}()
75+
return NewPipeConn(cmdOut.(ReadPipe), cmdIn.(WritePipe)), nil
76+
}
77+
78+
func (d *CommandDialer) WantsHostname(_ context.Context, _, _ string) bool {
79+
return true
80+
}
81+
82+
var _ Dialer = new(CommandDialer)
83+
var _ HostnameWanter = new(CommandDialer)

dialer/dialer.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func init() {
2929
return NeverRequireHostname(MaybeWrapWithContextDialer(d)), nil
3030
})
3131
xproxy.RegisterDialerType("chain", ChainFromURL)
32+
xproxy.RegisterDialerType("cmd", CommandDialerFromURL)
3233
}
3334

3435
type LegacyDialer interface {

0 commit comments

Comments
 (0)