diff --git a/main.go b/main.go
index 1917b7b..d1ac126 100644
--- a/main.go
+++ b/main.go
@@ -1,13 +1,9 @@
package main
import (
- // _ "net/http/pprof"
- "bufio"
- "encoding/json"
"os"
"os/signal"
"regexp"
- "strings"
"sync"
"syscall"
"time"
@@ -141,50 +137,8 @@ func (fw *Firewall) runFilter() {
}
}
-type SocksJsonConfig struct {
- SocksListener string
- TorSocks string
-}
-
var commentRegexp = regexp.MustCompile("^[ \t]*#")
-func loadConfiguration(configFilePath string) (*SocksJsonConfig, error) {
- config := SocksJsonConfig{}
- file, err := os.Open(configFilePath)
- if err != nil {
- return nil, err
- }
- scanner := bufio.NewScanner(file)
- bs := ""
- for scanner.Scan() {
- line := scanner.Text()
- if !commentRegexp.MatchString(line) {
- bs += line + "\n"
- }
- }
- if err := json.Unmarshal([]byte(bs), &config); err != nil {
- return nil, err
- }
- return &config, nil
-}
-
-func getSocksChainConfig(config *SocksJsonConfig) *socksChainConfig {
- // XXX
- fields := strings.Split(config.TorSocks, "|")
- torSocksNet := fields[0]
- torSocksAddr := fields[1]
- fields = strings.Split(config.SocksListener, "|")
- socksListenNet := fields[0]
- socksListenAddr := fields[1]
- socksConfig := socksChainConfig{
- TargetSocksNet: torSocksNet,
- TargetSocksAddr: torSocksAddr,
- ListenSocksNet: socksListenNet,
- ListenSocksAddr: socksListenAddr,
- }
- return &socksConfig
-}
-
func main() {
readConfig()
logBackend := setupLoggerBackend(FirewallConfig.LoggingLevel)
@@ -217,24 +171,6 @@ func main() {
fw.loadRules()
- /*
- go func() {
- http.ListenAndServe("localhost:6060", nil)
- }()
- */
-
- wg := sync.WaitGroup{}
-
- config, err := loadConfiguration("/etc/fw-daemon-socks.json")
- if err != nil && !os.IsNotExist(err) {
- panic(err)
- }
- if config != nil {
- socksConfig := getSocksChainConfig(config)
- chain := NewSocksChain(socksConfig, &wg, fw)
- chain.start()
- }
-
fw.runFilter()
// observe process signals and either
@@ -249,7 +185,6 @@ func main() {
select {
case <-sigHupChan:
fw.reloadRules()
- // XXX perhaps restart SOCKS proxy chain service with new proxy config specification?
case <-sigKillChan:
fw.stop()
return
diff --git a/socks5/client.go b/socks5/client.go
deleted file mode 100644
index 474d2e1..0000000
--- a/socks5/client.go
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * client.go - SOCSK5 client implementation.
- *
- * To the extent possible under law, Yawning Angel has waived all copyright and
- * related or neighboring rights to or-ctl-filter, using the creative commons
- * "cc0" public domain dedication. See LICENSE or
- * for full details.
- */
-
-package socks5
-
-import (
- "fmt"
- "io"
- "net"
- "time"
-)
-
-// Redispatch dials the provided proxy and redispatches an existing request.
-func Redispatch(proxyNet, proxyAddr string, req *Request) (conn net.Conn, bndAddr *Address, err error) {
- defer func() {
- if err != nil && conn != nil {
- conn.Close()
- }
- }()
-
- conn, err = clientHandshake(proxyNet, proxyAddr, req)
- if err != nil {
- return nil, nil, err
- }
- bndAddr, err = clientCmd(conn, req)
- return
-}
-
-func clientHandshake(proxyNet, proxyAddr string, req *Request) (net.Conn, error) {
- conn, err := net.Dial(proxyNet, proxyAddr)
- if err != nil {
- return nil, err
- }
- if err := conn.SetDeadline(time.Now().Add(requestTimeout)); err != nil {
- return conn, err
- }
- authMethod, err := clientNegotiateAuth(conn, req)
- if err != nil {
- return conn, err
- }
- if err := clientAuthenticate(conn, req, authMethod); err != nil {
- return conn, err
- }
- if err := conn.SetDeadline(time.Time{}); err != nil {
- return conn, err
- }
-
- return conn, nil
-}
-
-func clientNegotiateAuth(conn net.Conn, req *Request) (byte, error) {
- useRFC1929 := req.Auth.Uname != nil && req.Auth.Passwd != nil
- // XXX: Validate uname/passwd lengths, though should always be valid.
-
- var buf [3]byte
- buf[0] = version
- buf[1] = 1
- if useRFC1929 {
- buf[2] = authUsernamePassword
- } else {
- buf[2] = authNoneRequired
- }
-
- if _, err := conn.Write(buf[:]); err != nil {
- return authNoAcceptableMethods, err
- }
-
- var resp [2]byte
- if _, err := io.ReadFull(conn, resp[:]); err != nil {
- return authNoAcceptableMethods, err
- }
- if err := validateByte("version", resp[0], version); err != nil {
- return authNoAcceptableMethods, err
- }
- if err := validateByte("method", resp[1], buf[2]); err != nil {
- return authNoAcceptableMethods, err
- }
-
- return resp[1], nil
-}
-
-func clientAuthenticate(conn net.Conn, req *Request, authMethod byte) error {
- switch authMethod {
- case authNoneRequired:
- case authUsernamePassword:
- var buf []byte
- buf = append(buf, authRFC1929Ver)
- buf = append(buf, byte(len(req.Auth.Uname)))
- buf = append(buf, req.Auth.Uname...)
- buf = append(buf, byte(len(req.Auth.Passwd)))
- buf = append(buf, req.Auth.Passwd...)
- if _, err := conn.Write(buf); err != nil {
- return err
- }
-
- var resp [2]byte
- if _, err := io.ReadFull(conn, resp[:]); err != nil {
- return err
- }
- if err := validateByte("version", resp[0], authRFC1929Ver); err != nil {
- return err
- }
- if err := validateByte("status", resp[1], authRFC1929Success); err != nil {
- return err
- }
- default:
- panic(fmt.Sprintf("unknown authentication method: 0x%02x", authMethod))
- }
- return nil
-}
-
-func clientCmd(conn net.Conn, req *Request) (*Address, error) {
- var buf []byte
- buf = append(buf, version)
- buf = append(buf, byte(req.Cmd))
- buf = append(buf, rsv)
- buf = append(buf, req.Addr.raw...)
- if _, err := conn.Write(buf); err != nil {
- return nil, err
- }
-
- var respHdr [3]byte
- if _, err := io.ReadFull(conn, respHdr[:]); err != nil {
- return nil, err
- }
-
- if err := validateByte("version", respHdr[0], version); err != nil {
- return nil, err
- }
- if err := validateByte("rep", respHdr[1], byte(ReplySucceeded)); err != nil {
- return nil, clientError(respHdr[1])
- }
- if err := validateByte("rsv", respHdr[2], rsv); err != nil {
- return nil, err
- }
-
- var bndAddr Address
- if err := bndAddr.read(conn); err != nil {
- return nil, err
- }
-
- if err := conn.SetDeadline(time.Time{}); err != nil {
- return nil, err
- }
-
- return &bndAddr, nil
-}
diff --git a/socks5/common.go b/socks5/common.go
deleted file mode 100644
index bcaae19..0000000
--- a/socks5/common.go
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * common.go - SOCSK5 common definitons/routines.
- *
- * To the extent possible under law, Yawning Angel has waived all copyright and
- * related or neighboring rights to or-ctl-filter, using the creative commons
- * "cc0" public domain dedication. See LICENSE or
- * for full details.
- */
-
-// Package socks5 implements a SOCKS5 client/server. For more information see
-// RFC 1928 and RFC 1929.
-//
-// Notes:
-// * GSSAPI authentication, is NOT supported.
-// * The authentication provided by the client is always accepted.
-// * A lot of the code is shamelessly stolen from obfs4proxy.
-package socks5
-
-import (
- "errors"
- "fmt"
- "io"
- "net"
- "strconv"
- "syscall"
- "time"
-)
-
-const (
- version = 0x05
- rsv = 0x00
-
- atypIPv4 = 0x01
- atypDomainName = 0x03
- atypIPv6 = 0x04
-
- authNoneRequired = 0x00
- authUsernamePassword = 0x02
- authNoAcceptableMethods = 0xff
-
- inboundTimeout = 5 * time.Second
- requestTimeout = 30 * time.Second
-)
-
-var errInvalidAtyp = errors.New("invalid address type")
-
-// ReplyCode is a SOCKS 5 reply code.
-type ReplyCode byte
-
-// The various SOCKS 5 reply codes from RFC 1928.
-const (
- ReplySucceeded ReplyCode = iota
- ReplyGeneralFailure
- ReplyConnectionNotAllowed
- ReplyNetworkUnreachable
- ReplyHostUnreachable
- ReplyConnectionRefused
- ReplyTTLExpired
- ReplyCommandNotSupported
- ReplyAddressNotSupported
-)
-
-// Command is a SOCKS 5 command.
-type Command byte
-
-// The various SOCKS 5 commands.
-const (
- CommandConnect Command = 0x01
- CommandTorResolve Command = 0xf0
- CommandTorResolvePTR Command = 0xf1
-)
-
-// Address is a SOCKS 5 address + port.
-type Address struct {
- atyp uint8
- raw []byte
- addrStr string
- portStr string
-}
-
-// FromString parses the provided "host:port" format address and populates the
-// Address fields.
-func (addr *Address) FromString(addrStr string) (err error) {
- addr.addrStr, addr.portStr, err = net.SplitHostPort(addrStr)
- if err != nil {
- return
- }
-
- var raw []byte
- if ip := net.ParseIP(addr.addrStr); ip != nil {
- if v4Addr := ip.To4(); v4Addr != nil {
- raw = append(raw, atypIPv4)
- raw = append(raw, v4Addr...)
- } else if v6Addr := ip.To16(); v6Addr != nil {
- raw = append(raw, atypIPv6)
- raw = append(raw, v6Addr...)
- } else {
- return errors.New("unsupported IP address type")
- }
- } else {
- // Must be a FQDN.
- if len(addr.addrStr) > 255 {
- return fmt.Errorf("invalid FQDN, len > 255 bytes (%d bytes)", len(addr.addrStr))
- }
- raw = append(raw, atypDomainName)
- raw = append(raw, addr.addrStr...)
- }
-
- var port uint64
- if port, err = strconv.ParseUint(addr.portStr, 10, 16); err != nil {
- return
- }
- raw = append(raw, byte(port>>8))
- raw = append(raw, byte(port&0xff))
-
- addr.raw = raw
- return
-}
-
-// String returns the string representation of the address, in "host:port"
-// format.
-func (addr *Address) String() string {
- return addr.addrStr + ":" + addr.portStr
-}
-
-// HostPort returns the string representation of the addess, split into the
-// host and port components.
-func (addr *Address) HostPort() (string, string) {
- return addr.addrStr, addr.portStr
-}
-
-// Type returns the address type from the connect command this address was
-// parsed from
-func (addr *Address) Type() uint8 {
- return addr.atyp
-}
-
-func (addr *Address) read(conn net.Conn) (err error) {
- // The address looks like:
- // uint8_t atyp
- // uint8_t addr[] (Length depends on atyp)
- // uint16_t port
-
- // Read the atype.
- var atyp byte
- if atyp, err = readByte(conn); err != nil {
- return
- }
- addr.raw = append(addr.raw, atyp)
-
- // Read the address.
- var rawAddr []byte
- switch atyp {
- case atypIPv4:
- rawAddr = make([]byte, net.IPv4len)
- if _, err = io.ReadFull(conn, rawAddr); err != nil {
- return
- }
- v4Addr := net.IPv4(rawAddr[0], rawAddr[1], rawAddr[2], rawAddr[3])
- addr.addrStr = v4Addr.String()
- case atypDomainName:
- var alen byte
- if alen, err = readByte(conn); err != nil {
- return
- }
- if alen == 0 {
- return fmt.Errorf("domain name with 0 length")
- }
- rawAddr = make([]byte, alen)
- addr.raw = append(addr.raw, alen)
- if _, err = io.ReadFull(conn, rawAddr); err != nil {
- return
- }
- addr.addrStr = string(rawAddr)
- case atypIPv6:
- rawAddr = make([]byte, net.IPv6len)
- if _, err = io.ReadFull(conn, rawAddr); err != nil {
- return
- }
- v6Addr := make(net.IP, net.IPv6len)
- copy(v6Addr[:], rawAddr)
- addr.addrStr = fmt.Sprintf("[%s]", v6Addr.String())
- default:
- return errInvalidAtyp
- }
- addr.atyp = atyp
- addr.raw = append(addr.raw, rawAddr...)
-
- // Read the port.
- var rawPort [2]byte
- if _, err = io.ReadFull(conn, rawPort[:]); err != nil {
- return
- }
- port := int(rawPort[0])<<8 | int(rawPort[1])
- addr.portStr = fmt.Sprintf("%d", port)
- addr.raw = append(addr.raw, rawPort[:]...)
-
- return
-}
-
-// ErrorToReplyCode converts an error to the "best" reply code.
-func ErrorToReplyCode(err error) ReplyCode {
- if cErr, ok := err.(clientError); ok {
- return ReplyCode(cErr)
- }
- opErr, ok := err.(*net.OpError)
- if !ok {
- return ReplyGeneralFailure
- }
-
- errno, ok := opErr.Err.(syscall.Errno)
- if !ok {
- return ReplyGeneralFailure
- }
- switch errno {
- case syscall.EADDRNOTAVAIL:
- return ReplyAddressNotSupported
- case syscall.ETIMEDOUT:
- return ReplyTTLExpired
- case syscall.ENETUNREACH:
- return ReplyNetworkUnreachable
- case syscall.EHOSTUNREACH:
- return ReplyHostUnreachable
- case syscall.ECONNREFUSED, syscall.ECONNRESET:
- return ReplyConnectionRefused
- default:
- return ReplyGeneralFailure
- }
-}
-
-// Request describes a SOCKS 5 request.
-type Request struct {
- Auth AuthInfo
- Cmd Command
- Addr Address
-
- conn net.Conn
-}
-
-type clientError ReplyCode
-
-func (e clientError) Error() string {
- switch ReplyCode(e) {
- case ReplySucceeded:
- return "socks5: succeeded"
- case ReplyGeneralFailure:
- return "socks5: general failure"
- case ReplyConnectionNotAllowed:
- return "socks5: connection not allowed"
- case ReplyNetworkUnreachable:
- return "socks5: network unreachable"
- case ReplyHostUnreachable:
- return "socks5: host unreachable"
- case ReplyConnectionRefused:
- return "socks5: connection refused"
- case ReplyTTLExpired:
- return "socks5: ttl expired"
- case ReplyCommandNotSupported:
- return "socks5: command not supported"
- case ReplyAddressNotSupported:
- return "socks5: address not supported"
- default:
- return fmt.Sprintf("socks5: reply code: 0x%02x", e)
- }
-}
-
-func readByte(conn net.Conn) (byte, error) {
- var tmp [1]byte
- if _, err := conn.Read(tmp[:]); err != nil {
- return 0, err
- }
- return tmp[0], nil
-}
-
-func validateByte(descr string, val, expected byte) error {
- if val != expected {
- return fmt.Errorf("message field '%s' was 0x%02x (expected 0x%02x)", descr, val, expected)
- }
- return nil
-}
diff --git a/socks5/server.go b/socks5/server.go
deleted file mode 100644
index 61c61e4..0000000
--- a/socks5/server.go
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * server.go - SOCSK5 server implementation.
- *
- * To the extent possible under law, Yawning Angel has waived all copyright and
- * related or neighboring rights to or-ctl-filter, using the creative commons
- * "cc0" public domain dedication. See LICENSE or
- * for full details.
- */
-
-package socks5
-
-import (
- "bytes"
- "fmt"
- "io"
- "net"
- "time"
-)
-
-// Handshake attempts to handle a incoming client handshake over the provided
-// connection and receive the SOCKS5 request. The routine handles sending
-// appropriate errors if applicable, but will not close the connection.
-func Handshake(conn net.Conn) (*Request, error) {
- // Arm the handshake timeout.
- var err error
- if err = conn.SetDeadline(time.Now().Add(inboundTimeout)); err != nil {
- return nil, err
- }
- defer func() {
- // Disarm the handshake timeout, only propagate the error if
- // the handshake was successful.
- nerr := conn.SetDeadline(time.Time{})
- if err == nil {
- err = nerr
- }
- }()
-
- req := new(Request)
- req.conn = conn
-
- // Negotiate the protocol version and authentication method.
- var method byte
- if method, err = req.negotiateAuth(); err != nil {
- return nil, err
- }
-
- // Authenticate if neccecary.
- if err = req.authenticate(method); err != nil {
- return nil, err
- }
-
- // Read the client command.
- if err = req.readCommand(); err != nil {
- return nil, err
- }
-
- return req, err
-}
-
-// Reply sends a SOCKS5 reply to the corresponding request. The BND.ADDR and
-// BND.PORT fields are always set to an address/port corresponding to
-// "0.0.0.0:0".
-func (req *Request) Reply(code ReplyCode) error {
- return req.ReplyAddr(code, nil)
-}
-
-// ReplyAddr sends a SOCKS5 reply to the corresponding request. The BND.ADDR
-// and BND.PORT fields are specified by addr, or "0.0.0.0:0" if not provided.
-func (req *Request) ReplyAddr(code ReplyCode, addr *Address) error {
- // The server sends a reply message.
- // uint8_t ver (0x05)
- // uint8_t rep
- // uint8_t rsv (0x00)
- // uint8_t atyp
- // uint8_t bnd_addr[]
- // uint16_t bnd_port
-
- resp := []byte{version, byte(code), rsv}
- if addr == nil {
- var nilAddr [net.IPv4len + 2]byte
- resp = append(resp, atypIPv4)
- resp = append(resp, nilAddr[:]...)
- } else {
- resp = append(resp, addr.raw...)
- }
-
- _, err := req.conn.Write(resp[:])
- return err
-
-}
-
-func (req *Request) negotiateAuth() (byte, error) {
- // The client sends a version identifier/selection message.
- // uint8_t ver (0x05)
- // uint8_t nmethods (>= 1).
- // uint8_t methods[nmethods]
-
- var err error
- if err = req.readByteVerify("version", version); err != nil {
- return 0, err
- }
-
- // Read the number of methods, and the methods.
- var nmethods byte
- method := byte(authNoAcceptableMethods)
- if nmethods, err = req.readByte(); err != nil {
- return method, err
- }
- methods := make([]byte, nmethods)
- if _, err := io.ReadFull(req.conn, methods); err != nil {
- return 0, err
- }
-
- // Pick the best authentication method, prioritizing authenticating
- // over not if both options are present.
- if bytes.IndexByte(methods, authUsernamePassword) != -1 {
- method = authUsernamePassword
- } else if bytes.IndexByte(methods, authNoneRequired) != -1 {
- method = authNoneRequired
- }
-
- // The server sends a method selection message.
- // uint8_t ver (0x05)
- // uint8_t method
- msg := []byte{version, method}
- if _, err = req.conn.Write(msg); err != nil {
- return 0, err
- }
-
- return method, nil
-}
-
-func (req *Request) authenticate(method byte) error {
- switch method {
- case authNoneRequired:
- return nil
- case authUsernamePassword:
- return req.authRFC1929()
- case authNoAcceptableMethods:
- return fmt.Errorf("no acceptable authentication methods")
- default:
- // This should never happen as only supported auth methods should be
- // negotiated.
- return fmt.Errorf("negotiated unsupported method 0x%02x", method)
- }
-}
-
-func (req *Request) readCommand() error {
- // The client sends the request details.
- // uint8_t ver (0x05)
- // uint8_t cmd
- // uint8_t rsv (0x00)
- // uint8_t atyp
- // uint8_t dst_addr[]
- // uint16_t dst_port
-
- var err error
- var cmd byte
- if err = req.readByteVerify("version", version); err != nil {
- req.Reply(ReplyGeneralFailure)
- return err
- }
- if cmd, err = req.readByte(); err != nil {
- req.Reply(ReplyGeneralFailure)
- return err
- }
- switch Command(cmd) {
- case CommandConnect, CommandTorResolve, CommandTorResolvePTR:
- req.Cmd = Command(cmd)
- default:
- req.Reply(ReplyCommandNotSupported)
- return fmt.Errorf("unsupported SOCKS command: 0x%02x", cmd)
- }
- if err = req.readByteVerify("reserved", rsv); err != nil {
- req.Reply(ReplyGeneralFailure)
- return err
- }
-
- // Read the destination address/port.
- err = req.Addr.read(req.conn)
- if err == errInvalidAtyp {
- req.Reply(ReplyAddressNotSupported)
- } else if err != nil {
- req.Reply(ReplyGeneralFailure)
- }
-
- return err
-}
-
-func (req *Request) readByte() (byte, error) {
- return readByte(req.conn)
-}
-
-func (req *Request) readByteVerify(descr string, expected byte) error {
- val, err := req.readByte()
- if err != nil {
- return err
- }
- return validateByte(descr, val, expected)
-}
diff --git a/socks5/server_rfc1929.go b/socks5/server_rfc1929.go
deleted file mode 100644
index 7d0566a..0000000
--- a/socks5/server_rfc1929.go
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * server_rfc1929.go - SOCSK 5 server authentication.
- *
- * To the extent possible under law, Yawning Angel has waived all copyright and
- * related or neighboring rights to or-ctl-filter, using the creative commons
- * "cc0" public domain dedication. See LICENSE or
- * for full details.
- */
-
-package socks5
-
-import (
- "fmt"
- "io"
-)
-
-const (
- authRFC1929Ver = 0x01
- authRFC1929Success = 0x00
- authRFC1929Fail = 0x01
-)
-
-// AuthInfo is the RFC 1929 Username/Password authentication data.
-type AuthInfo struct {
- Uname []byte
- Passwd []byte
-}
-
-func (req *Request) authRFC1929() (err error) {
- sendErrResp := func() {
- // Swallow write/flush errors, the auth failure is the relevant error.
- resp := []byte{authRFC1929Ver, authRFC1929Fail}
- req.conn.Write(resp[:])
- }
-
- // The client sends a Username/Password request.
- // uint8_t ver (0x01)
- // uint8_t ulen (>= 1)
- // uint8_t uname[ulen]
- // uint8_t plen (>= 1)
- // uint8_t passwd[plen]
-
- if err = req.readByteVerify("auth version", authRFC1929Ver); err != nil {
- sendErrResp()
- return
- }
-
- // Read the username.
- var ulen byte
- if ulen, err = req.readByte(); err != nil {
- sendErrResp()
- return
- } else if ulen < 1 {
- sendErrResp()
- return fmt.Errorf("username with 0 length")
- }
- uname := make([]byte, ulen)
- if _, err = io.ReadFull(req.conn, uname); err != nil {
- sendErrResp()
- return
- }
-
- // Read the password.
- var plen byte
- if plen, err = req.readByte(); err != nil {
- sendErrResp()
- return
- } else if plen < 1 {
- sendErrResp()
- return fmt.Errorf("password with 0 length")
- }
- passwd := make([]byte, plen)
- if _, err = io.ReadFull(req.conn, passwd); err != nil {
- sendErrResp()
- return
- }
-
- req.Auth.Uname = uname
- req.Auth.Passwd = passwd
-
- resp := []byte{authRFC1929Ver, authRFC1929Success}
- _, err = req.conn.Write(resp[:])
- return
-}
diff --git a/socks_server_chain.go b/socks_server_chain.go
deleted file mode 100644
index 9294e05..0000000
--- a/socks_server_chain.go
+++ /dev/null
@@ -1,262 +0,0 @@
-package main
-
-import (
- "io"
- "net"
- "os"
- "sync"
-
- "github.com/subgraph/fw-daemon/socks5"
- "github.com/subgraph/go-procsnitch"
- "strconv"
-)
-
-type socksChainConfig struct {
- TargetSocksNet string
- TargetSocksAddr string
- ListenSocksNet string
- ListenSocksAddr string
-}
-
-type socksChain struct {
- cfg *socksChainConfig
- fw *Firewall
- listener net.Listener
- wg *sync.WaitGroup
- procInfo procsnitch.ProcInfo
-}
-
-type socksChainSession struct {
- cfg *socksChainConfig
- clientConn net.Conn
- upstreamConn net.Conn
- req *socks5.Request
- bndAddr *socks5.Address
- optData []byte
- procInfo procsnitch.ProcInfo
- server *socksChain
-}
-
-const (
- socksVerdictDrop = 1
- socksVerdictAccept = 2
-)
-
-type pendingSocksConnection struct {
- pol *Policy
- hname string
- destIP net.IP
- destPort uint16
- pinfo *procsnitch.Info
- verdict chan int
-}
-
-func (sc *pendingSocksConnection) policy() *Policy {
- return sc.pol
-}
-
-func (sc *pendingSocksConnection) procInfo() *procsnitch.Info {
- return sc.pinfo
-}
-
-func (sc *pendingSocksConnection) hostname() string {
- return sc.hname
-}
-
-func (sc *pendingSocksConnection) dst() net.IP {
- return sc.destIP
-}
-func (sc *pendingSocksConnection) dstPort() uint16 {
- return sc.destPort
-}
-
-func (sc *pendingSocksConnection) deliverVerdict(v int) {
- sc.verdict <- v
- close(sc.verdict)
-}
-
-func (sc *pendingSocksConnection) accept() { sc.deliverVerdict(socksVerdictAccept) }
-
-func (sc *pendingSocksConnection) drop() { sc.deliverVerdict(socksVerdictDrop) }
-
-func (sc *pendingSocksConnection) print() string { return "socks connection" }
-
-func NewSocksChain(cfg *socksChainConfig, wg *sync.WaitGroup, fw *Firewall) *socksChain {
- chain := socksChain{
- cfg: cfg,
- fw: fw,
- wg: wg,
- procInfo: procsnitch.SystemProcInfo{},
- }
- return &chain
-}
-
-// Start initializes the SOCKS 5 server and starts
-// accepting connections.
-func (s *socksChain) start() {
- var err error
- s.listener, err = net.Listen(s.cfg.ListenSocksNet, s.cfg.ListenSocksAddr)
- if err != nil {
- log.Errorf("ERR/socks: Failed to listen on the socks address: %v", err)
- os.Exit(1)
- }
-
- s.wg.Add(1)
- go s.socksAcceptLoop()
-}
-
-func (s *socksChain) socksAcceptLoop() error {
- defer s.wg.Done()
- defer s.listener.Close()
-
- for {
- conn, err := s.listener.Accept()
- if err != nil {
- if e, ok := err.(net.Error); ok && !e.Temporary() {
- log.Infof("ERR/socks: Failed to Accept(): %v", err)
- return err
- }
- continue
- }
- session := &socksChainSession{cfg: s.cfg, clientConn: conn, procInfo: s.procInfo, server: s}
- go session.sessionWorker()
- }
-}
-
-func (c *socksChainSession) sessionWorker() {
- defer c.clientConn.Close()
-
- clientAddr := c.clientConn.RemoteAddr()
- log.Infof("INFO/socks: New connection from: %v", clientAddr)
-
- // Do the SOCKS handshake with the client, and read the command.
- var err error
- if c.req, err = socks5.Handshake(c.clientConn); err != nil {
- log.Infof("ERR/socks: Failed SOCKS5 handshake: %v", err)
- return
- }
-
- switch c.req.Cmd {
- case socks5.CommandTorResolve, socks5.CommandTorResolvePTR:
- err = c.dispatchTorSOCKS()
-
- // If we reach here, the request has been dispatched and completed.
- if err == nil {
- // Successfully even, send the response back with the addresc.
- c.req.ReplyAddr(socks5.ReplySucceeded, c.bndAddr)
- }
- case socks5.CommandConnect:
- if !c.filterConnect() {
- c.req.Reply(socks5.ReplyConnectionRefused)
- return
- }
- c.handleConnect()
- default:
- // Should *NEVER* happen, validated as part of handshake.
- log.Infof("BUG/socks: Unsupported SOCKS command: 0x%02x", c.req.Cmd)
- c.req.Reply(socks5.ReplyCommandNotSupported)
- }
-}
-
-func (c *socksChainSession) addressDetails() (string, net.IP, uint16) {
- addr := c.req.Addr
- host, pstr := addr.HostPort()
- port, err := strconv.ParseUint(pstr, 10, 16)
- if err != nil || port == 0 || port > 0xFFFF {
- log.Warningf("Illegal port value in socks address: %v", addr)
- return "", nil, 0
- }
- if addr.Type() == 3 {
- return host, nil, uint16(port)
- }
- ip := net.ParseIP(host)
- if ip == nil {
- log.Warningf("Failed to extract address information from socks address: %v", addr)
- }
- return "", ip, uint16(port)
-}
-
-func (c *socksChainSession) filterConnect() bool {
- pinfo := procsnitch.FindProcessForConnection(c.clientConn, c.procInfo)
- if pinfo == nil {
- log.Warningf("No proc found for connection from: %s", c.clientConn.RemoteAddr())
- return false
- }
-
- policy := c.server.fw.PolicyForPath(pinfo.ExePath)
-
- hostname, ip, port := c.addressDetails()
- if ip == nil && hostname == "" {
- return false
- }
- result := policy.rules.filter(nil, ip, port, hostname, pinfo)
- switch result {
- case FILTER_DENY:
- return false
- case FILTER_ALLOW:
- return true
- case FILTER_PROMPT:
- pending := &pendingSocksConnection{
- pol: policy,
- hname: hostname,
- destIP: ip,
- destPort: port,
- pinfo: pinfo,
- verdict: make(chan int),
- }
- policy.processPromptResult(pending)
- v := <-pending.verdict
- if v == socksVerdictAccept {
- return true
- }
- }
-
- return false
-
-}
-
-func (c *socksChainSession) handleConnect() {
- err := c.dispatchTorSOCKS()
- if err != nil {
- return
- }
- c.req.Reply(socks5.ReplySucceeded)
- defer c.upstreamConn.Close()
-
- if c.optData != nil {
- if _, err = c.upstreamConn.Write(c.optData); err != nil {
- log.Infof("ERR/socks: Failed writing OptData: %v", err)
- return
- }
- c.optData = nil
- }
-
- // A upstream connection has been established, push data back and forth
- // till the session is done.
- c.forwardTraffic()
- log.Infof("INFO/socks: Closed SOCKS connection from: %v", c.clientConn.RemoteAddr())
-}
-
-func (c *socksChainSession) forwardTraffic() {
- var wg sync.WaitGroup
- wg.Add(2)
-
- copyLoop := func(dst, src net.Conn) {
- defer wg.Done()
- defer dst.Close()
-
- io.Copy(dst, src)
- }
- go copyLoop(c.upstreamConn, c.clientConn)
- go copyLoop(c.clientConn, c.upstreamConn)
-
- wg.Wait()
-}
-
-func (c *socksChainSession) dispatchTorSOCKS() (err error) {
- c.upstreamConn, c.bndAddr, err = socks5.Redispatch(c.cfg.TargetSocksNet, c.cfg.TargetSocksAddr, c.req)
- if err != nil {
- c.req.Reply(socks5.ErrorToReplyCode(err))
- }
- return
-}
diff --git a/socks_server_chain_test.go b/socks_server_chain_test.go
deleted file mode 100644
index 05ae9b1..0000000
--- a/socks_server_chain_test.go
+++ /dev/null
@@ -1,305 +0,0 @@
-package main
-
-import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "net"
- "strings"
- "sync"
- "testing"
- "time"
-
- "github.com/subgraph/fw-daemon/socks5"
- "golang.org/x/net/proxy"
-)
-
-// MortalService can be killed at any time.
-type MortalService struct {
- network string
- address string
- connectionCallback func(net.Conn) error
-
- conns []net.Conn
- quit chan bool
- listener net.Listener
- waitGroup *sync.WaitGroup
-}
-
-// NewMortalService creates a new MortalService
-func NewMortalService(network, address string, connectionCallback func(net.Conn) error) *MortalService {
- l := MortalService{
- network: network,
- address: address,
- connectionCallback: connectionCallback,
-
- conns: make([]net.Conn, 0, 10),
- quit: make(chan bool),
- waitGroup: &sync.WaitGroup{},
- }
- return &l
-}
-
-// Stop will kill our listener and all it's connections
-func (l *MortalService) Stop() {
- log.Infof("stopping listener service %s:%s", l.network, l.address)
- close(l.quit)
- if l.listener != nil {
- l.listener.Close()
- }
- l.waitGroup.Wait()
-}
-
-func (l *MortalService) acceptLoop() {
- defer l.waitGroup.Done()
- defer func() {
- log.Infof("stoping listener service %s:%s", l.network, l.address)
- for i, conn := range l.conns {
- if conn != nil {
- log.Infof("Closing connection #%d", i)
- conn.Close()
- }
- }
- }()
- defer l.listener.Close()
-
- for {
- conn, err := l.listener.Accept()
- if nil != err {
- if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
- continue
- } else {
- log.Infof("MortalService connection accept failure: %s\n", err)
- select {
- case <-l.quit:
- return
- default:
- }
- continue
- }
- }
-
- l.conns = append(l.conns, conn)
- go l.handleConnection(conn, len(l.conns)-1)
- }
-}
-
-func (l *MortalService) createDeadlinedListener() error {
- if l.network == "tcp" {
- tcpAddr, err := net.ResolveTCPAddr("tcp", l.address)
- if err != nil {
- return fmt.Errorf("MortalService.createDeadlinedListener %s %s failure: %s", l.network, l.address, err)
- }
- tcpListener, err := net.ListenTCP("tcp", tcpAddr)
- if err != nil {
- return fmt.Errorf("MortalService.createDeadlinedListener %s %s failure: %s", l.network, l.address, err)
- }
- tcpListener.SetDeadline(time.Now().Add(1e9))
- l.listener = tcpListener
- return nil
- } else if l.network == "unix" {
- unixAddr, err := net.ResolveUnixAddr("unix", l.address)
- if err != nil {
- return fmt.Errorf("MortalService.createDeadlinedListener %s %s failure: %s", l.network, l.address, err)
- }
- unixListener, err := net.ListenUnix("unix", unixAddr)
- if err != nil {
- return fmt.Errorf("MortalService.createDeadlinedListener %s %s failure: %s", l.network, l.address, err)
- }
- unixListener.SetDeadline(time.Now().Add(1e9))
- l.listener = unixListener
- return nil
- } else {
- panic("")
- }
- return nil
-}
-
-// Start the MortalService
-func (l *MortalService) Start() error {
- var err error
- err = l.createDeadlinedListener()
- if err != nil {
- return err
- }
- l.waitGroup.Add(1)
- go l.acceptLoop()
- return nil
-}
-
-func (l *MortalService) handleConnection(conn net.Conn, id int) error {
- defer func() {
- log.Infof("Closing connection #%d", id)
- conn.Close()
- l.conns[id] = nil
- }()
-
- log.Infof("Starting connection #%d", id)
-
- for {
- if err := l.connectionCallback(conn); err != nil {
- log.Error(err.Error())
- return err
- }
- return nil
- }
-}
-
-type AccumulatingService struct {
- net, address string
- banner string
- buffer bytes.Buffer
- mortalService *MortalService
- hasProtocolInfo bool
- hasAuthenticate bool
- receivedChan chan bool
-}
-
-func NewAccumulatingService(net, address, banner string) *AccumulatingService {
- l := AccumulatingService{
- net: net,
- address: address,
- banner: banner,
- hasProtocolInfo: true,
- hasAuthenticate: true,
- receivedChan: make(chan bool, 0),
- }
- return &l
-}
-
-func (a *AccumulatingService) Start() {
- a.mortalService = NewMortalService(a.net, a.address, a.SessionWorker)
- a.mortalService.Start()
-}
-
-func (a *AccumulatingService) Stop() {
- fmt.Println("AccumulatingService STOP")
- a.mortalService.Stop()
-}
-
-func (a *AccumulatingService) WaitUntilReceived() {
- <-a.receivedChan
-}
-
-func (a *AccumulatingService) SessionWorker(conn net.Conn) error {
- connReader := bufio.NewReader(conn)
- conn.Write([]byte(a.banner))
- for {
- line, err := connReader.ReadBytes('\n')
- if err != nil {
- fmt.Printf("AccumulatingService read error: %s\n", err)
- }
- lineStr := strings.TrimSpace(string(line))
- a.buffer.WriteString(lineStr + "\n")
- a.receivedChan <- true
- }
- return nil
-}
-
-func fakeSocksSessionWorker(clientConn net.Conn, targetNet, targetAddr string) error {
- defer clientConn.Close()
-
- clientAddr := clientConn.RemoteAddr()
- fmt.Printf("INFO/socks: New connection from: %v\n", clientAddr)
-
- // Do the SOCKS handshake with the client, and read the command.
- req, err := socks5.Handshake(clientConn)
- if err != nil {
- panic(fmt.Sprintf("ERR/socks: Failed SOCKS5 handshake: %v", err))
- }
-
- var upstreamConn net.Conn
- upstreamConn, err = net.Dial(targetNet, targetAddr)
- if err != nil {
- panic(err)
- }
- defer upstreamConn.Close()
- req.Reply(socks5.ReplySucceeded)
-
- // A upstream connection has been established, push data back and forth
- // till the session is done.
- var wg sync.WaitGroup
- wg.Add(2)
- copyLoop := func(dst, src net.Conn) {
- defer wg.Done()
- defer dst.Close()
-
- io.Copy(dst, src)
- }
- go copyLoop(upstreamConn, clientConn)
- go copyLoop(clientConn, upstreamConn)
-
- wg.Wait()
- fmt.Printf("INFO/socks: Closed SOCKS connection from: %v\n", clientAddr)
- return nil
-}
-
-func TestSocksServerProxyChain(t *testing.T) {
- // socks client ---> socks chain ---> socks server ---> service
- socksChainNet := "tcp"
- socksChainAddr := "127.0.0.1:7750"
- socksServerNet := "tcp"
- socksServerAddr := "127.0.0.1:8850"
- serviceNet := "tcp"
- serviceAddr := "127.0.0.1:9950"
-
- banner := "meow 123\r\n"
- // setup the service listener
- service := NewAccumulatingService(serviceNet, serviceAddr, banner)
- service.Start()
- defer service.Stop()
-
- // setup the "socks server"
- session := func(clientConn net.Conn) error {
- return fakeSocksSessionWorker(clientConn, serviceNet, serviceAddr)
- }
- socksService := NewMortalService(socksServerNet, socksServerAddr, session)
- socksService.Start()
- defer socksService.Stop()
-
- // setup the SOCKS proxy chain
- socksConfig := socksChainConfig{
- TargetSocksNet: socksServerNet,
- TargetSocksAddr: socksServerAddr,
- ListenSocksNet: socksChainNet,
- ListenSocksAddr: socksChainAddr,
- }
- wg := sync.WaitGroup{}
- ds := dbusServer{}
- chain := NewSocksChain(&socksConfig, &wg, &ds)
- chain.start()
-
- // setup the SOCKS client
- auth := proxy.Auth{
- User: "",
- Password: "",
- }
- forward := proxy.NewPerHost(proxy.Direct, proxy.Direct)
- socksClient, err := proxy.SOCKS5(socksChainNet, socksChainAddr, &auth, forward)
- conn, err := socksClient.Dial(serviceNet, serviceAddr)
- if err != nil {
- panic(err)
- }
-
- // read a banner from the service
- rd := bufio.NewReader(conn)
- line := []byte{}
- line, err = rd.ReadBytes('\n')
- if err != nil {
- panic(err)
- }
- if string(line) != banner {
- t.Errorf("Did not receive expected banner. Got %s, wanted %s\n", string(line), banner)
- t.Fail()
- }
-
- // send the service some data and verify it was received
- clientData := "hello world\r\n"
- conn.Write([]byte(clientData))
- service.WaitUntilReceived()
- if service.buffer.String() != strings.TrimSpace(clientData)+"\n" {
- t.Errorf("Client sent %s but service only received %s\n", "hello world\n", service.buffer.String())
- t.Fail()
- }
-}