Skip to content

Commit 1a78871

Browse files
author
James Cunningham
committed
Initial Commit
0 parents  commit 1a78871

File tree

10 files changed

+307
-0
lines changed

10 files changed

+307
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
switcher

LICENSE

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Copyright (c) 2014, James Cunningham
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
* Redistributions of source code must retain the above copyright
7+
notice, this list of conditions and the following disclaimer.
8+
* Redistributions in binary form must reproduce the above copyright
9+
notice, this list of conditions and the following disclaimer in the
10+
documentation and/or other materials provided with the distribution.
11+
* Neither the name of the switcher nor the
12+
names of its contributors may be used to endorse or promote products
13+
derived from this software without specific prior written permission.
14+
15+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18+
DISCLAIMED. IN NO EVENT SHALL JAMES CUNNINGHAM BE LIABLE FOR ANY
19+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.PHONY: build
2+
3+
build:
4+
go build -o switcher *.go

README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
![Switcher Logo](https://i.imgur.com/YL46yH8.png)
2+
3+
Switcher
4+
========
5+
6+
Switcher is a proxy server which accepts connections and proxies based on which protocol is detected.
7+
8+
Currently implemented is:
9+
10+
- SSH
11+
12+
The use case is running HTTP(S) and SSH on the same port.
13+
14+
15+
Usage
16+
-----
17+
18+
To Build:
19+
20+
make
21+
22+
To get help:
23+
24+
$ ./switcher --help
25+
Switcher 1.0.0
26+
usage: switcher [options]
27+
28+
Options:
29+
--listen <:80> Server Listen Address
30+
--ssh <127.0.0.1:22> SSH Server Address
31+
--default <127.0.0.1:8080> Default Server Address
32+
33+
Examples:
34+
To serve SSH(127.0.0.1:22) and HTTP(127.0.0.1:8080) on port 80
35+
$ switcher
36+
37+
To serve SSH(127.0.0.1:2222) and HTTPS(127.0.0.1:443) on port 443
38+
$ switcher --listen :443 --ssh 127.0.0.1:2222 --default 127.0.0.1:443
39+
40+
41+
Example
42+
-------
43+
44+
Run switcher on HTTP port 80, proxy to SSH on 127.0.0.1:22 and Nginx on 127.0.0.1:8080
45+
46+
$ switcher --listen :80 --ssh 127.0.0.1:22 --default 127.0.0.1:8080
47+
48+
To test HTTP:
49+
50+
$ curl -I http://my-server.local
51+
HTTP/1.1 200 OK
52+
53+
To test SSH
54+
55+
$ ssh [email protected] -p 80
56+
Password:
57+
58+
59+
Why not sslh
60+
------------
61+
62+
Switcher is heavily influenced by (sslh)[https://github.com/yrutschle/sslh]. It started out as a learning exercise to discover how sslh worked and attempt an implementation in Go.
63+
64+
The result is useful in its own right through use of Go's interfaces for protocol matching (making adding new protocols trivial), and lightweight goroutines (instead of forking, which is more CPU intensive under load).
65+
66+
67+
License
68+
-------
69+
70+
3-Clause "Modified" BSD Licence.
71+
72+
(License)[LICENSE]

extra/logo.psd

121 KB
Binary file not shown.

main.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"log"
7+
)
8+
9+
var (
10+
listenAddress = flag.String("listen", ":80", "Server Listen Address")
11+
sshAddress = flag.String("ssh", "127.0.0.1:22", "SSH Server Address")
12+
defaultAddress = flag.String("default", "127.0.0.1:8080", "Default Server Address")
13+
)
14+
15+
func usage() {
16+
fmt.Println("Switcher 1.0.0")
17+
fmt.Println("usage: switcher [options]\n")
18+
19+
fmt.Println("Options:")
20+
fmt.Println(" --listen <:80> Server Listen Address")
21+
fmt.Println(" --ssh <127.0.0.1:22> SSH Server Address")
22+
fmt.Println(" --default <127.0.0.1:8080> Default Server Address\n")
23+
24+
fmt.Println("Examples:")
25+
fmt.Println(" To serve SSH(127.0.0.1:22) and HTTP(127.0.0.1:8080) on port 80")
26+
fmt.Println(" $ switcher\n")
27+
28+
fmt.Println(" To serve SSH(127.0.0.1:2222) and HTTPS(127.0.0.1:443) on port 443")
29+
fmt.Println(" $ switcher --listen :443 --ssh 127.0.0.1:2222 --default 127.0.0.1:443")
30+
}
31+
32+
func main() {
33+
flag.Usage = usage
34+
flag.Parse()
35+
36+
mux := NewMux()
37+
38+
mux.Handle(SSH(*sshAddress))
39+
mux.Handle(TCP(*defaultAddress))
40+
41+
log.Printf("[INFO] listen: %s\n", *listenAddress)
42+
err := mux.ListenAndServe(*listenAddress)
43+
if err != nil {
44+
log.Fatalf("[FATAL] listen: %s\n", err)
45+
}
46+
}

mux.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package main
2+
3+
import (
4+
"io"
5+
"log"
6+
"net"
7+
)
8+
9+
type Protocol interface {
10+
// address to proxy to
11+
Address() string
12+
13+
// identify protocol from header
14+
Identify(header []byte) bool
15+
}
16+
17+
type Mux struct {
18+
Handlers []Protocol
19+
}
20+
21+
// create a new Mux assignment
22+
func NewMux() *Mux {
23+
return &Mux{}
24+
}
25+
26+
// add a protocol to mux handler set
27+
func (m *Mux) Handle(p Protocol) {
28+
m.Handlers = append(m.Handlers, p)
29+
}
30+
31+
// match protocol to handler
32+
// returns address to proxy to
33+
func (m *Mux) Identify(header []byte) (address string) {
34+
if len(m.Handlers) < 1 {
35+
return ""
36+
}
37+
38+
for _, handler := range m.Handlers {
39+
if handler.Identify(header) {
40+
return handler.Address()
41+
}
42+
}
43+
44+
// return address of last handler, default
45+
return m.Handlers[len(m.Handlers)-1].Address()
46+
}
47+
48+
// create a server on given address and handle incoming connections
49+
func (m *Mux) ListenAndServe(addr string) error {
50+
server, err := net.Listen("tcp", addr)
51+
if err != nil {
52+
return err
53+
}
54+
55+
for {
56+
conn, err := server.Accept()
57+
if err != nil {
58+
return err
59+
}
60+
61+
go m.Serve(conn)
62+
}
63+
64+
return nil
65+
}
66+
67+
// serve takes an incomming connection, applies configured protocol
68+
// handlers and proxies the connection based on result
69+
func (m *Mux) Serve(conn net.Conn) error {
70+
defer conn.Close()
71+
72+
// get first 3 bytes of connection as header
73+
header := make([]byte, 3)
74+
if _, err := io.ReadAtLeast(conn, header, 3); err != nil {
75+
return err
76+
}
77+
78+
// identify protocol from header
79+
address := m.Identify(header)
80+
81+
log.Printf("[INFO] proxy: from=%s to=%s\n", conn.RemoteAddr(), address)
82+
83+
// connect to remote
84+
remote, err := net.Dial("tcp", address)
85+
if err != nil {
86+
log.Printf("[ERROR] remote: %s\n", err)
87+
return err
88+
}
89+
defer remote.Close()
90+
91+
// write header we chopped back to remote
92+
remote.Write(header)
93+
94+
// proxy between us and remote server
95+
err = Shovel(conn, remote)
96+
if err != nil {
97+
return err
98+
}
99+
100+
return nil
101+
}

shovel.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package main
2+
3+
import (
4+
"io"
5+
)
6+
7+
// proxy between two sockets
8+
func Shovel(local, remote io.ReadWriteCloser) error {
9+
err := make(chan error)
10+
11+
go chanCopy(err, local, remote)
12+
go chanCopy(err, remote, local)
13+
14+
return <-err
15+
}
16+
17+
// copy between pipes, sending errors to channel
18+
func chanCopy(e chan error, dst, src io.ReadWriter) {
19+
_, err := io.Copy(dst, src)
20+
if err != nil {
21+
e <- err
22+
}
23+
}

ssh.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
)
6+
7+
type SSH string
8+
9+
// address to proxy to
10+
func (s SSH) Address() string {
11+
return string(s)
12+
}
13+
14+
// identify header as one of SSH
15+
func (s SSH) Identify(header []byte) bool {
16+
// first 3 bytes of 1.0/2.0 is literal `SSH`
17+
if bytes.Compare(header, []byte("SSH")) == 0 {
18+
return true
19+
}
20+
21+
return false
22+
}

tcp.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package main
2+
3+
type TCP string
4+
5+
// address to proxy to
6+
func (t TCP) Address() string {
7+
return string(t)
8+
}
9+
10+
// identify header as one of TCP
11+
func (t TCP) Identify(header []byte) bool {
12+
// this is a dummy protocol handler used for the default
13+
return false
14+
}

0 commit comments

Comments
 (0)