/
holeysocks.go
156 lines (134 loc) · 3.75 KB
/
holeysocks.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package holeysocks
import (
"fmt"
"io"
"log"
"net"
"time"
"github.com/armon/go-socks5"
"golang.org/x/crypto/ssh"
)
// MainConfig contains SSH and Socks configuration variables
type MainConfig struct {
SSH sshConfig `json:"ssh"`
Socks socksConfig `json:"socks"`
}
type sshConfig struct {
Username string `json:"username"`
Host string `json:"host"`
Port int `json:"port"`
PrivKey []ssh.AuthMethod
}
func (s *sshConfig) SetKey(keyBytes []byte) error {
privateKey, err := ssh.ParsePrivateKey(keyBytes)
if err != nil {
return err
}
auth := ssh.PublicKeys(privateKey)
s.PrivKey = []ssh.AuthMethod{auth}
return err
}
func (s *sshConfig) connectionString() string {
return fmt.Sprintf("%s:%v", s.Host, s.Port)
}
type socksConfig struct {
Local string `json:"local"`
Remote string `json:"remote"`
}
// Handle local client connections and tunnel data to the remote server
// will use io.Copy - http://golang.org/pkg/io/#Copy
// https://sosedoff.com/2015/05/25/ssh-port-forwarding-with-go.html
func handleClient(client net.Conn, remote net.Conn) {
defer client.Close()
chDone := make(chan bool)
// Start remote -> local data transfer
go func() {
_, err := io.Copy(client, remote)
if err != nil {
log.Fatalln(fmt.Sprintf("error while copy remote->local: %s", err))
}
chDone <- true
}()
// Start local -> remote data transfer
go func() {
_, err := io.Copy(remote, client)
if err != nil {
log.Fatalln(fmt.Sprintf("error while copy local->remote: %s", err))
}
chDone <- true
}()
<-chDone
}
// ForwardService implements reverse port forwarding similar to the -R flag
// in openssh-client. Configuration is done in the /configs/config.json file.
// NOTE The generated keys and config.json data are embedded in the binary so
// take the appropriate precautions when setting up the ssh server user.
func ForwardService(config MainConfig) error {
sshClientConf := &ssh.ClientConfig{
User: config.SSH.Username,
Auth: config.SSH.PrivKey,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// Connect to SSH server
serverConn, err := ssh.Dial("tcp", config.SSH.connectionString(), sshClientConf)
if err != nil {
return fmt.Errorf("Dial INTO remote server error: %s", err)
}
// Publish the designated local port to the same port on the remote SSH server
remoteListener, err := serverConn.Listen("tcp", config.Socks.Remote)
if err != nil {
return fmt.Errorf(fmt.Sprintf("INFO: %s", err))
}
defer remoteListener.Close()
// Handle incoming requests from the remote tunnel
for {
// Grab a handle to the pre-configured local port that will be sent to the remote
// SSH server
local, err := net.Dial("tcp", config.Socks.Local)
if err != nil {
return fmt.Errorf("Unable to start local listen: %s", err)
}
// Grab a handle on the remote port
remote, err := remoteListener.Accept()
if err != nil {
return fmt.Errorf("Unable to accept remote traffic locally: %s", err)
}
// Swap IO from the local and remote hanles
handleClient(remote, local)
}
}
// DarnSocks creates a new SOCKS5 server at the provided ports and
// remote-forwards the port to another machine over SSH
func DarnSocks(config MainConfig) error {
conf := &socks5.Config{}
chErr := make(chan error)
server, err := socks5.New(conf)
if err != nil {
return err
}
go func() {
// Create a SOCKS5 server
err = server.ListenAndServe("tcp", config.Socks.Local)
if err != nil {
chErr <- fmt.Errorf("SOCKS: %v", err)
return
}
}()
go func() {
// Publish SOCKS to remote server
err = ForwardService(config)
if err != nil {
chErr <- fmt.Errorf("SSH: %v", err)
return
}
}()
timeout := time.After(1000 * time.Millisecond)
for {
select {
case err := <-chErr:
return err
case <-timeout:
return nil
}
}
}