Merge pull request #105 from sheerun/sheerun/permissions
Implement whitelisting on socks, ssh, and http2
This commit is contained in:
commit
852b2ca937
@ -65,6 +65,8 @@ func main() {
|
|||||||
glog.Fatal(err)
|
glog.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glog.Info(serverNode)
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(node gost.ProxyNode) {
|
go func(node gost.ProxyNode) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
17
http.go
17
http.go
@ -5,14 +5,15 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/ginuerzh/pht"
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"golang.org/x/net/http2"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ginuerzh/pht"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HttpServer struct {
|
type HttpServer struct {
|
||||||
@ -75,6 +76,11 @@ func (s *HttpServer) HandleRequest(req *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !s.Base.Node.Can("tcp", req.Host) {
|
||||||
|
glog.Errorf("Unauthorized to tcp connect to %s", req.Host)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c, err := s.Base.Chain.Dial(req.Host)
|
c, err := s.Base.Chain.Dial(req.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(LWARNING).Infof("[http] %s -> %s : %s", s.conn.RemoteAddr(), req.Host, err)
|
glog.V(LWARNING).Infof("[http] %s -> %s : %s", s.conn.RemoteAddr(), req.Host, err)
|
||||||
@ -184,6 +190,11 @@ func (s *Http2Server) HandleRequest(w http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
w.Header().Set("Proxy-Agent", "gost/"+Version)
|
w.Header().Set("Proxy-Agent", "gost/"+Version)
|
||||||
|
|
||||||
|
if !s.Base.Node.Can("tcp", target) {
|
||||||
|
glog.Errorf("Unauthorized to tcp connect to %s", target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// HTTP2 as transport
|
// HTTP2 as transport
|
||||||
if req.Header.Get("Proxy-Switch") == "gost" {
|
if req.Header.Get("Proxy-Switch") == "gost" {
|
||||||
conn, err := s.Upgrade(w, req)
|
conn, err := s.Upgrade(w, req)
|
||||||
|
48
node.go
48
node.go
@ -19,6 +19,8 @@ type ProxyNode struct {
|
|||||||
Transport string // transport: ws/wss/tls/http2/tcp/udp/rtcp/rudp
|
Transport string // transport: ws/wss/tls/http2/tcp/udp/rtcp/rudp
|
||||||
Remote string // remote address, used by tcp/udp port forwarding
|
Remote string // remote address, used by tcp/udp port forwarding
|
||||||
Users []*url.Userinfo // authentication for proxy
|
Users []*url.Userinfo // authentication for proxy
|
||||||
|
Whitelist *Permissions
|
||||||
|
Blacklist *Permissions
|
||||||
values url.Values
|
values url.Values
|
||||||
serverName string
|
serverName string
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
@ -36,12 +38,36 @@ func ParseProxyNode(s string) (node ProxyNode, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query := u.Query()
|
||||||
|
|
||||||
node = ProxyNode{
|
node = ProxyNode{
|
||||||
Addr: u.Host,
|
Addr: u.Host,
|
||||||
values: u.Query(),
|
values: query,
|
||||||
serverName: u.Host,
|
serverName: u.Host,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Get("whitelist") != "" {
|
||||||
|
node.Whitelist, err = ParsePermissions(query.Get("whitelist"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatal(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// By default allow for everyting
|
||||||
|
node.Whitelist, _ = ParsePermissions("*:*:*")
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.Get("blacklist") != "" {
|
||||||
|
node.Blacklist, err = ParsePermissions(query.Get("blacklist"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatal(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// By default block nothing
|
||||||
|
node.Blacklist, _ = ParsePermissions("")
|
||||||
|
}
|
||||||
|
|
||||||
if u.User != nil {
|
if u.User != nil {
|
||||||
node.Users = append(node.Users, u.User)
|
node.Users = append(node.Users, u.User)
|
||||||
}
|
}
|
||||||
@ -126,6 +152,24 @@ func (node *ProxyNode) Get(key string) string {
|
|||||||
return node.values.Get(key)
|
return node.values.Get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (node *ProxyNode) Can(action string, addr string) bool {
|
||||||
|
host, strport, err := net.SplitHostPort(addr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := strconv.Atoi(strport)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.V(LDEBUG).Infof("Can action: %s, host: %s, port %d", action, host, port)
|
||||||
|
|
||||||
|
return node.Whitelist.Can(action, host, port) && !node.Blacklist.Can(action, host, port)
|
||||||
|
}
|
||||||
|
|
||||||
func (node *ProxyNode) getBool(key string) bool {
|
func (node *ProxyNode) getBool(key string) bool {
|
||||||
s := node.Get(key)
|
s := node.Get(key)
|
||||||
if b, _ := strconv.ParseBool(s); b {
|
if b, _ := strconv.ParseBool(s); b {
|
||||||
@ -162,5 +206,5 @@ func (node *ProxyNode) keyFile() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (node ProxyNode) String() string {
|
func (node ProxyNode) String() string {
|
||||||
return fmt.Sprintf("transport: %s, protocol: %s, addr: %s", node.Transport, node.Protocol, node.Addr)
|
return fmt.Sprintf("transport: %s, protocol: %s, addr: %s, whitelist: %v, blacklist: %v", node.Transport, node.Protocol, node.Addr, node.Whitelist, node.Blacklist)
|
||||||
}
|
}
|
||||||
|
43
node_test.go
Normal file
43
node_test.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package gost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNodeDefaultWhitelist(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
node, _ := ParseProxyNode("http2://localhost:8000")
|
||||||
|
|
||||||
|
assert.True(node.Can("connect", "google.pl:80"))
|
||||||
|
assert.True(node.Can("connect", "google.pl:443"))
|
||||||
|
assert.True(node.Can("connect", "google.pl:22"))
|
||||||
|
assert.True(node.Can("bind", "google.pl:80"))
|
||||||
|
assert.True(node.Can("bind", "google.com:80"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNodeWhitelist(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
node, _ := ParseProxyNode("http2://localhost:8000?whitelist=connect:google.pl:80,443")
|
||||||
|
|
||||||
|
assert.True(node.Can("connect", "google.pl:80"))
|
||||||
|
assert.True(node.Can("connect", "google.pl:443"))
|
||||||
|
assert.False(node.Can("connect", "google.pl:22"))
|
||||||
|
assert.False(node.Can("bind", "google.pl:80"))
|
||||||
|
assert.False(node.Can("bind", "google.com:80"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNodeBlacklist(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
node, _ := ParseProxyNode("http2://localhost:8000?blacklist=connect:google.pl:80,443")
|
||||||
|
|
||||||
|
assert.False(node.Can("connect", "google.pl:80"))
|
||||||
|
assert.False(node.Can("connect", "google.pl:443"))
|
||||||
|
assert.True(node.Can("connect", "google.pl:22"))
|
||||||
|
assert.True(node.Can("bind", "google.pl:80"))
|
||||||
|
assert.True(node.Can("bind", "google.com:80"))
|
||||||
|
}
|
185
permissions.go
Normal file
185
permissions.go
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
package gost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
glob "github.com/ryanuber/go-glob"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PortRange struct {
|
||||||
|
Min, Max int
|
||||||
|
}
|
||||||
|
|
||||||
|
type PortSet []PortRange
|
||||||
|
|
||||||
|
type StringSet []string
|
||||||
|
|
||||||
|
type Permission struct {
|
||||||
|
Actions StringSet
|
||||||
|
Hosts StringSet
|
||||||
|
Ports PortSet
|
||||||
|
}
|
||||||
|
|
||||||
|
type Permissions []Permission
|
||||||
|
|
||||||
|
func minint(x, y int) int {
|
||||||
|
if x < y {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
|
||||||
|
func maxint(x, y int) int {
|
||||||
|
if x > y {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ir *PortRange) Contains(value int) bool {
|
||||||
|
return value >= ir.Min && value <= ir.Max
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePortRange(s string) (*PortRange, error) {
|
||||||
|
if s == "*" {
|
||||||
|
return &PortRange{Min: 0, Max: 65535}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
minmax := strings.Split(s, "-")
|
||||||
|
switch len(minmax) {
|
||||||
|
case 1:
|
||||||
|
port, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if port < 0 || port > 65535 {
|
||||||
|
return nil, fmt.Errorf("invalid port: %s", s)
|
||||||
|
}
|
||||||
|
return &PortRange{Min: port, Max: port}, nil
|
||||||
|
case 2:
|
||||||
|
min, err := strconv.Atoi(minmax[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
max, err := strconv.Atoi(minmax[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
realmin := maxint(0, minint(min, max))
|
||||||
|
realmax := minint(65535, maxint(min, max))
|
||||||
|
|
||||||
|
return &PortRange{Min: realmin, Max: realmax}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid range: %s", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *PortSet) Contains(value int) bool {
|
||||||
|
for _, portRange := range *ps {
|
||||||
|
if portRange.Contains(value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePortSet(s string) (*PortSet, error) {
|
||||||
|
ps := &PortSet{}
|
||||||
|
|
||||||
|
if s == "" {
|
||||||
|
return nil, errors.New("must specify at least one port")
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges := strings.Split(s, ",")
|
||||||
|
|
||||||
|
for _, r := range ranges {
|
||||||
|
portRange, err := ParsePortRange(r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
*ps = append(*ps, *portRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *StringSet) Contains(subj string) bool {
|
||||||
|
for _, s := range *ss {
|
||||||
|
if glob.Glob(s, subj) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseStringSet(s string) (*StringSet, error) {
|
||||||
|
ss := &StringSet{}
|
||||||
|
if s == "" {
|
||||||
|
return nil, errors.New("cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
*ss = strings.Split(s, ",")
|
||||||
|
|
||||||
|
return ss, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *Permissions) Can(action string, host string, port int) bool {
|
||||||
|
for _, p := range *ps {
|
||||||
|
if p.Actions.Contains(action) && p.Hosts.Contains(host) && p.Ports.Contains(port) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePermissions(s string) (*Permissions, error) {
|
||||||
|
ps := &Permissions{}
|
||||||
|
|
||||||
|
if s == "" {
|
||||||
|
return &Permissions{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
perms := strings.Split(s, " ")
|
||||||
|
|
||||||
|
for _, perm := range perms {
|
||||||
|
parts := strings.Split(perm, ":")
|
||||||
|
|
||||||
|
switch len(parts) {
|
||||||
|
case 3:
|
||||||
|
actions, err := ParseStringSet(parts[0])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("action list must look like connect,bind given: %s", parts[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts, err := ParseStringSet(parts[1])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("hosts list must look like google.pl,*.google.com given: %s", parts[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
ports, err := ParsePortSet(parts[2])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ports list must look like 80,8000-9000, given: %s", parts[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
permission := Permission{Actions: *actions, Hosts: *hosts, Ports: *ports}
|
||||||
|
|
||||||
|
*ps = append(*ps, permission)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("permission must have format [actions]:[hosts]:[ports] given: %s", perm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ps, nil
|
||||||
|
}
|
152
permissions_test.go
Normal file
152
permissions_test.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package gost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var portRangeTests = []struct {
|
||||||
|
in string
|
||||||
|
out *PortRange
|
||||||
|
}{
|
||||||
|
{"1", &PortRange{Min: 1, Max: 1}},
|
||||||
|
{"1-3", &PortRange{Min: 1, Max: 3}},
|
||||||
|
{"3-1", &PortRange{Min: 1, Max: 3}},
|
||||||
|
{"0-100000", &PortRange{Min: 0, Max: 65535}},
|
||||||
|
{"*", &PortRange{Min: 0, Max: 65535}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringSetTests = []struct {
|
||||||
|
in string
|
||||||
|
out *StringSet
|
||||||
|
}{
|
||||||
|
{"*", &StringSet{"*"}},
|
||||||
|
{"google.pl,google.com", &StringSet{"google.pl", "google.com"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var portSetTests = []struct {
|
||||||
|
in string
|
||||||
|
out *PortSet
|
||||||
|
}{
|
||||||
|
{"1,3", &PortSet{PortRange{Min: 1, Max: 1}, PortRange{Min: 3, Max: 3}}},
|
||||||
|
{"1-3,7-5", &PortSet{PortRange{Min: 1, Max: 3}, PortRange{Min: 5, Max: 7}}},
|
||||||
|
{"0-100000", &PortSet{PortRange{Min: 0, Max: 65535}}},
|
||||||
|
{"*", &PortSet{PortRange{Min: 0, Max: 65535}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var permissionsTests = []struct {
|
||||||
|
in string
|
||||||
|
out *Permissions
|
||||||
|
}{
|
||||||
|
{"", &Permissions{}},
|
||||||
|
{"*:*:*", &Permissions{
|
||||||
|
Permission{
|
||||||
|
Actions: StringSet{"*"},
|
||||||
|
Hosts: StringSet{"*"},
|
||||||
|
Ports: PortSet{PortRange{Min: 0, Max: 65535}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
{"bind:127.0.0.1,localhost:80,443,8000-8100 connect:*.google.pl:80,443", &Permissions{
|
||||||
|
Permission{
|
||||||
|
Actions: StringSet{"bind"},
|
||||||
|
Hosts: StringSet{"127.0.0.1", "localhost"},
|
||||||
|
Ports: PortSet{
|
||||||
|
PortRange{Min: 80, Max: 80},
|
||||||
|
PortRange{Min: 443, Max: 443},
|
||||||
|
PortRange{Min: 8000, Max: 8100},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Permission{
|
||||||
|
Actions: StringSet{"connect"},
|
||||||
|
Hosts: StringSet{"*.google.pl"},
|
||||||
|
Ports: PortSet{
|
||||||
|
PortRange{Min: 80, Max: 80},
|
||||||
|
PortRange{Min: 443, Max: 443},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPortRangeParse(t *testing.T) {
|
||||||
|
for _, test := range portRangeTests {
|
||||||
|
actual, err := ParsePortRange(test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ParsePortRange(%q) returned error: %v", test.in, err)
|
||||||
|
} else if *actual != *test.out {
|
||||||
|
t.Errorf("ParsePortRange(%q): got %v, want %v", test.in, actual, test.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPortRangeContains(t *testing.T) {
|
||||||
|
actual, _ := ParsePortRange("5-10")
|
||||||
|
|
||||||
|
if !actual.Contains(5) || !actual.Contains(7) || !actual.Contains(10) {
|
||||||
|
t.Errorf("5-10 should contain 5, 7 and 10")
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual.Contains(4) || actual.Contains(11) {
|
||||||
|
t.Errorf("5-10 should not contain 4, 11")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSetParse(t *testing.T) {
|
||||||
|
for _, test := range stringSetTests {
|
||||||
|
actual, err := ParseStringSet(test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ParseStringSet(%q) returned error: %v", test.in, err)
|
||||||
|
} else if fmt.Sprintln(actual) != fmt.Sprintln(test.out) {
|
||||||
|
t.Errorf("ParseStringSet(%q): got %v, want %v", test.in, actual, test.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSetContains(t *testing.T) {
|
||||||
|
ss, _ := ParseStringSet("google.pl,*.google.com")
|
||||||
|
|
||||||
|
if !ss.Contains("google.pl") || !ss.Contains("www.google.com") {
|
||||||
|
t.Errorf("google.pl,*.google.com should contain google.pl and www.google.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ss.Contains("www.google.pl") || ss.Contains("foobar.com") {
|
||||||
|
t.Errorf("google.pl,*.google.com shound not contain www.google.pl and foobar.com")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPortSetParse(t *testing.T) {
|
||||||
|
for _, test := range portSetTests {
|
||||||
|
actual, err := ParsePortSet(test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ParsePortRange(%q) returned error: %v", test.in, err)
|
||||||
|
} else if fmt.Sprintln(actual) != fmt.Sprintln(test.out) {
|
||||||
|
t.Errorf("ParsePortRange(%q): got %v, want %v", test.in, actual, test.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPortSetContains(t *testing.T) {
|
||||||
|
actual, _ := ParsePortSet("5-10,20-30")
|
||||||
|
|
||||||
|
if !actual.Contains(5) || !actual.Contains(7) || !actual.Contains(10) {
|
||||||
|
t.Errorf("5-10,20-30 should contain 5, 7 and 10")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !actual.Contains(20) || !actual.Contains(27) || !actual.Contains(30) {
|
||||||
|
t.Errorf("5-10,20-30 should contain 20, 27 and 30")
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual.Contains(4) || actual.Contains(11) || actual.Contains(31) {
|
||||||
|
t.Errorf("5-10,20-30 should not contain 4, 11, 31")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPermissionsParse(t *testing.T) {
|
||||||
|
for _, test := range permissionsTests {
|
||||||
|
actual, err := ParsePermissions(test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ParsePermissions(%q) returned error: %v", test.in, err)
|
||||||
|
} else if fmt.Sprintln(actual) != fmt.Sprintln(test.out) {
|
||||||
|
t.Errorf("ParsePermissions(%q): got %v, want %v", test.in, actual, test.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
socks.go
91
socks.go
@ -3,13 +3,14 @@ package gost
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"github.com/ginuerzh/gosocks4"
|
|
||||||
"github.com/ginuerzh/gosocks5"
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ginuerzh/gosocks4"
|
||||||
|
"github.com/ginuerzh/gosocks5"
|
||||||
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -191,7 +192,7 @@ func (s *Socks5Server) HandleRequest(req *gosocks5.Request) {
|
|||||||
s.handleUDPRelay(req)
|
s.handleUDPRelay(req)
|
||||||
|
|
||||||
case CmdUdpTun:
|
case CmdUdpTun:
|
||||||
glog.V(LINFO).Infof("[socks5-udp] %s - %s", s.conn.RemoteAddr(), req.Addr)
|
glog.V(LINFO).Infof("[socks5-rudp] %s - %s", s.conn.RemoteAddr(), req.Addr)
|
||||||
s.handleUDPTunnel(req)
|
s.handleUDPTunnel(req)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -200,7 +201,16 @@ func (s *Socks5Server) HandleRequest(req *gosocks5.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Socks5Server) handleConnect(req *gosocks5.Request) {
|
func (s *Socks5Server) handleConnect(req *gosocks5.Request) {
|
||||||
cc, err := s.Base.Chain.Dial(req.Addr.String())
|
addr := req.Addr.String()
|
||||||
|
|
||||||
|
if !s.Base.Node.Can("tcp", addr) {
|
||||||
|
glog.Errorf("Unauthorized to tcp connect to %s", addr)
|
||||||
|
rep := gosocks5.NewReply(gosocks5.NotAllowed, nil)
|
||||||
|
rep.Write(s.conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cc, err := s.Base.Chain.Dial(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(LWARNING).Infof("[socks5-connect] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err)
|
glog.V(LWARNING).Infof("[socks5-connect] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err)
|
||||||
rep := gosocks5.NewReply(gosocks5.HostUnreachable, nil)
|
rep := gosocks5.NewReply(gosocks5.HostUnreachable, nil)
|
||||||
@ -226,7 +236,7 @@ func (s *Socks5Server) handleConnect(req *gosocks5.Request) {
|
|||||||
func (s *Socks5Server) handleBind(req *gosocks5.Request) {
|
func (s *Socks5Server) handleBind(req *gosocks5.Request) {
|
||||||
cc, err := s.Base.Chain.GetConn()
|
cc, err := s.Base.Chain.GetConn()
|
||||||
|
|
||||||
// connection error
|
// connection error when forwarding bind
|
||||||
if err != nil && err != ErrEmptyChain {
|
if err != nil && err != ErrEmptyChain {
|
||||||
glog.V(LWARNING).Infof("[socks5-bind] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err)
|
glog.V(LWARNING).Infof("[socks5-bind] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err)
|
||||||
reply := gosocks5.NewReply(gosocks5.Failure, nil)
|
reply := gosocks5.NewReply(gosocks5.Failure, nil)
|
||||||
@ -234,23 +244,42 @@ func (s *Socks5Server) handleBind(req *gosocks5.Request) {
|
|||||||
glog.V(LDEBUG).Infof("[socks5-bind] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, reply)
|
glog.V(LDEBUG).Infof("[socks5-bind] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, reply)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// serve socks5 bind
|
// serve socks5 bind
|
||||||
if err == ErrEmptyChain {
|
if err == ErrEmptyChain {
|
||||||
s.bindOn(req.Addr.String())
|
addr := req.Addr.String()
|
||||||
|
|
||||||
|
if !s.Base.Node.Can("rtcp", addr) {
|
||||||
|
glog.Errorf("Unauthorized to tcp bind to %s", addr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer cc.Close()
|
s.bindOn(addr)
|
||||||
// forward request
|
|
||||||
req.Write(cc)
|
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// forward request
|
||||||
|
// note: this type of request forwarding is defined when starting server
|
||||||
|
// so we don't need to authenticate it, as it's as explicit as whitelisting
|
||||||
|
defer cc.Close()
|
||||||
|
req.Write(cc)
|
||||||
glog.V(LINFO).Infof("[socks5-bind] %s <-> %s", s.conn.RemoteAddr(), cc.RemoteAddr())
|
glog.V(LINFO).Infof("[socks5-bind] %s <-> %s", s.conn.RemoteAddr(), cc.RemoteAddr())
|
||||||
s.Base.transport(s.conn, cc)
|
s.Base.transport(s.conn, cc)
|
||||||
glog.V(LINFO).Infof("[socks5-bind] %s >-< %s", s.conn.RemoteAddr(), cc.RemoteAddr())
|
glog.V(LINFO).Infof("[socks5-bind] %s >-< %s", s.conn.RemoteAddr(), cc.RemoteAddr())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Socks5Server) handleUDPRelay(req *gosocks5.Request) {
|
func (s *Socks5Server) handleUDPRelay(req *gosocks5.Request) {
|
||||||
bindAddr, _ := net.ResolveUDPAddr("udp", req.Addr.String())
|
addr := req.Addr.String()
|
||||||
|
|
||||||
|
if !s.Base.Node.Can("udp", addr) {
|
||||||
|
glog.Errorf("Unauthorized to udp connect to %s", addr)
|
||||||
|
rep := gosocks5.NewReply(gosocks5.NotAllowed, nil)
|
||||||
|
rep.Write(s.conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bindAddr, _ := net.ResolveUDPAddr("udp", addr)
|
||||||
relay, err := net.ListenUDP("udp", bindAddr) // udp associate, strict mode: if the port already in use, it will return error
|
relay, err := net.ListenUDP("udp", bindAddr) // udp associate, strict mode: if the port already in use, it will return error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err)
|
glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err)
|
||||||
@ -338,19 +367,26 @@ func (s *Socks5Server) handleUDPTunnel(req *gosocks5.Request) {
|
|||||||
|
|
||||||
// connection error
|
// connection error
|
||||||
if err != nil && err != ErrEmptyChain {
|
if err != nil && err != ErrEmptyChain {
|
||||||
glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err)
|
glog.V(LWARNING).Infof("[socks5-rudp] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err)
|
||||||
reply := gosocks5.NewReply(gosocks5.Failure, nil)
|
reply := gosocks5.NewReply(gosocks5.Failure, nil)
|
||||||
reply.Write(s.conn)
|
reply.Write(s.conn)
|
||||||
glog.V(LDEBUG).Infof("[socks5-udp] %s -> %s\n%s", s.conn.RemoteAddr(), req.Addr, reply)
|
glog.V(LDEBUG).Infof("[socks5-rudp] %s -> %s\n%s", s.conn.RemoteAddr(), req.Addr, reply)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// serve tunnel udp, tunnel <-> remote, handle tunnel udp request
|
// serve tunnel udp, tunnel <-> remote, handle tunnel udp request
|
||||||
if err == ErrEmptyChain {
|
if err == ErrEmptyChain {
|
||||||
bindAddr, _ := net.ResolveUDPAddr("udp", req.Addr.String())
|
addr := req.Addr.String()
|
||||||
|
|
||||||
|
if !s.Base.Node.Can("rudp", addr) {
|
||||||
|
glog.Errorf("Unauthorized to udp bind to %s", addr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bindAddr, _ := net.ResolveUDPAddr("udp", addr)
|
||||||
uc, err := net.ListenUDP("udp", bindAddr)
|
uc, err := net.ListenUDP("udp", bindAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err)
|
glog.V(LWARNING).Infof("[socks5-rudp] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer uc.Close()
|
defer uc.Close()
|
||||||
@ -359,25 +395,27 @@ func (s *Socks5Server) handleUDPTunnel(req *gosocks5.Request) {
|
|||||||
socksAddr.Host, _, _ = net.SplitHostPort(s.conn.LocalAddr().String())
|
socksAddr.Host, _, _ = net.SplitHostPort(s.conn.LocalAddr().String())
|
||||||
reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr)
|
reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr)
|
||||||
if err := reply.Write(s.conn); err != nil {
|
if err := reply.Write(s.conn); err != nil {
|
||||||
glog.V(LWARNING).Infof("[socks5-udp] %s <- %s : %s", s.conn.RemoteAddr(), socksAddr, err)
|
glog.V(LWARNING).Infof("[socks5-rudp] %s <- %s : %s", s.conn.RemoteAddr(), socksAddr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", s.conn.RemoteAddr(), socksAddr, reply)
|
glog.V(LDEBUG).Infof("[socks5-rudp] %s <- %s\n%s", s.conn.RemoteAddr(), socksAddr, reply)
|
||||||
|
|
||||||
glog.V(LINFO).Infof("[socks5-udp] %s <-> %s", s.conn.RemoteAddr(), socksAddr)
|
glog.V(LINFO).Infof("[socks5-rudp] %s <-> %s", s.conn.RemoteAddr(), socksAddr)
|
||||||
s.tunnelServerUDP(s.conn, uc)
|
s.tunnelServerUDP(s.conn, uc)
|
||||||
glog.V(LINFO).Infof("[socks5-udp] %s >-< %s", s.conn.RemoteAddr(), socksAddr)
|
glog.V(LINFO).Infof("[socks5-rudp] %s >-< %s", s.conn.RemoteAddr(), socksAddr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer cc.Close()
|
defer cc.Close()
|
||||||
|
|
||||||
// tunnel <-> tunnel, direct forwarding
|
// tunnel <-> tunnel, direct forwarding
|
||||||
|
// note: this type of request forwarding is defined when starting server
|
||||||
|
// so we don't need to authenticate it, as it's as explicit as whitelisting
|
||||||
req.Write(cc)
|
req.Write(cc)
|
||||||
|
|
||||||
glog.V(LINFO).Infof("[socks5-udp] %s <-> %s [tun]", s.conn.RemoteAddr(), cc.RemoteAddr())
|
glog.V(LINFO).Infof("[socks5-rudp] %s <-> %s [tun]", s.conn.RemoteAddr(), cc.RemoteAddr())
|
||||||
s.Base.transport(s.conn, cc)
|
s.Base.transport(s.conn, cc)
|
||||||
glog.V(LINFO).Infof("[socks5-udp] %s >-< %s [tun]", s.conn.RemoteAddr(), cc.RemoteAddr())
|
glog.V(LINFO).Infof("[socks5-rudp] %s >-< %s [tun]", s.conn.RemoteAddr(), cc.RemoteAddr())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Socks5Server) bindOn(addr string) {
|
func (s *Socks5Server) bindOn(addr string) {
|
||||||
@ -697,7 +735,16 @@ func (s *Socks4Server) HandleRequest(req *gosocks4.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Socks4Server) handleConnect(req *gosocks4.Request) {
|
func (s *Socks4Server) handleConnect(req *gosocks4.Request) {
|
||||||
cc, err := s.Base.Chain.Dial(req.Addr.String())
|
addr := req.Addr.String()
|
||||||
|
|
||||||
|
if !s.Base.Node.Can("tcp", addr) {
|
||||||
|
glog.Errorf("Unauthorized to tcp connect to %s", addr)
|
||||||
|
rep := gosocks5.NewReply(gosocks4.Rejected, nil)
|
||||||
|
rep.Write(s.conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cc, err := s.Base.Chain.Dial(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(LWARNING).Infof("[socks4-connect] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err)
|
glog.V(LWARNING).Infof("[socks4-connect] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err)
|
||||||
rep := gosocks4.NewReply(gosocks4.Failed, nil)
|
rep := gosocks4.NewReply(gosocks4.Failed, nil)
|
||||||
|
17
ssh.go
17
ssh.go
@ -5,11 +5,12 @@ package gost
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/golang/glog"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Applicaple SSH Request types for Port Forwarding - RFC 4254 7.X
|
// Applicaple SSH Request types for Port Forwarding - RFC 4254 7.X
|
||||||
@ -121,6 +122,11 @@ func (s *SSHServer) directPortForwardChannel(channel ssh.Channel, raddr string)
|
|||||||
|
|
||||||
glog.V(LINFO).Infof("[ssh-tcp] %s - %s", s.Addr, raddr)
|
glog.V(LINFO).Infof("[ssh-tcp] %s - %s", s.Addr, raddr)
|
||||||
|
|
||||||
|
if !s.Base.Node.Can("tcp", raddr) {
|
||||||
|
glog.Errorf("Unauthorized to tcp connect to %s", raddr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
conn, err := s.Base.Chain.Dial(raddr)
|
conn, err := s.Base.Chain.Dial(raddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(LINFO).Infof("[ssh-tcp] %s - %s : %s", s.Addr, raddr, err)
|
glog.V(LINFO).Infof("[ssh-tcp] %s - %s : %s", s.Addr, raddr, err)
|
||||||
@ -143,6 +149,13 @@ func (s *SSHServer) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Request, quit
|
|||||||
t := tcpipForward{}
|
t := tcpipForward{}
|
||||||
ssh.Unmarshal(req.Payload, &t)
|
ssh.Unmarshal(req.Payload, &t)
|
||||||
addr := fmt.Sprintf("%s:%d", t.Host, t.Port)
|
addr := fmt.Sprintf("%s:%d", t.Host, t.Port)
|
||||||
|
|
||||||
|
if !s.Base.Node.Can("rtcp", addr) {
|
||||||
|
glog.Errorf("Unauthorized to tcp bind to %s", addr)
|
||||||
|
req.Reply(false, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
glog.V(LINFO).Infoln("[ssh-rtcp] listening tcp", addr)
|
glog.V(LINFO).Infoln("[ssh-rtcp] listening tcp", addr)
|
||||||
ln, err := net.Listen("tcp", addr) //tie to the client connection
|
ln, err := net.Listen("tcp", addr) //tie to the client connection
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user