1.修正若干bug

2.增加本地监听socks4,socks4a,http
3.http 性能问题有待测试
4.socks4a 有待验证是否能正常工作
This commit is contained in:
0x1024
2017-07-02 01:11:44 +08:00
parent 4bef8a5d73
commit 96f175ddff
6 changed files with 251 additions and 38 deletions

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"strings"
"regexp"
"io"
)
const (
@@ -22,7 +23,13 @@ const (
LocalServerHttp = "localHttp"
socksVer5 = 5
socksVer4 = 4
httpProxy = 71
socksCmdConnect = 1
typeIPv4 = 1 // type is ipv4 address
typeDm = 3 // type is domain address
typeIPv6 = 4 // type is ipv6 address
)
type ProxyServer interface {
@@ -55,7 +62,7 @@ var (
var proxyConfig *ProxyConfig
func Run(surgeCfg, geoipCfg, localProxyType string) {
func Run(surgeCfg, geoipCfg string) {
proxyConfig = LoadConfig(surgeCfg, geoipCfg)
listenAddr := fmt.Sprintf("%s:%d", proxyConfig.LocalHost, proxyConfig.LocalSocksPort)
@@ -71,11 +78,11 @@ func Run(surgeCfg, geoipCfg, localProxyType string) {
log.Println("accept:", err)
continue
}
go handleConnection(conn, localProxyType)
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn, localProxyType string) {
func handleConnection(conn net.Conn) {
isClose := false
defer func() {
if !isClose {
@@ -86,18 +93,27 @@ func handleConnection(conn net.Conn, localProxyType string) {
host string
hostType int
err error
rawData []byte
)
if localProxyType == LocalServerSocksV5 {
err = socksAuth(conn)
host, hostType, err = socksConnect(conn)
} else if localProxyType == LocalServerHttp {
buf := make([]byte, 1)
io.ReadFull(conn, buf)
first := buf[0]
switch first {
case socksVer5:
err = handshake(conn, first)
host, hostType, err = socks5Connect(conn)
case socksVer4:
host, hostType, err = socks4Connect(conn, first)
default:
host, hostType, rawData, err = httpProxyConnect(conn, first)
}
if nil != err {
log.Fatal("local proxy server has error", err)
return
}
remote, err := matchRuleAndCreateConn(conn, host, hostType)
remote, err := matchRuleAndCreateConn(conn, host, hostType, rawData)
if nil != err {
return
}
@@ -112,18 +128,19 @@ func handleConnection(conn net.Conn, localProxyType string) {
isClose = true
}
func matchRuleAndCreateConn(conn net.Conn, host string, hostType int) (net.Conn, error) {
addArray := strings.Split(host, ":")
func matchRuleAndCreateConn(conn net.Conn, addr string, hostType int, raw []byte) (net.Conn, error) {
if nil == conn{
return nil,errors.New("...")
}
host, _, _ := net.SplitHostPort(addr)
var rule *Rule
var raw []byte
rule = matchBypass(addArray[0])
rule = matchBypass(host)
if nil == rule {
switch hostType {
case typeIPv4, typeIPv6:
rule = matchIpRule(addArray[0])
rule = matchIpRule(host)
case typeDm:
rule = matchDomainRule(addArray[0])
rule = matchDomainRule(host)
}
}
if nil == rule {
@@ -133,7 +150,7 @@ func matchRuleAndCreateConn(conn net.Conn, host string, hostType int) (net.Conn,
rule = &Rule{Match: "default", Action: ServerTypeDirect}
}
}
return createRemoteConn(raw, rule, host)
return createRemoteConn(raw, rule, addr)
}
func matchDomainRule(domain string) (*Rule) {
@@ -159,7 +176,6 @@ func matchIpRule(addr string) (*Rule) {
ips := resolveRequestIPAddr(addr)
if nil != ips {
country := strings.ToLower(GeoIPs(ips))
log.Println("Found ip geo", country)
for _, rule := range proxyConfig.ruleGeoIP {
if len(country) != 0 && strings.ToLower(rule.Match) == country {
return rule
@@ -169,7 +185,6 @@ func matchIpRule(addr string) (*Rule) {
return nil
}
func matchBypass(addr string) (*Rule) {
ip := net.ParseIP(addr)
for _, h := range proxyConfig.bypassDomains {

85
flora/http.go Normal file
View File

@@ -0,0 +1,85 @@
package flora
import (
"net"
"io"
"net/http"
"bufio"
"bytes"
"net/http/httputil"
"log"
)
// local socks server connect
func httpProxyConnect(conn net.Conn, first byte) (addr string, hostType int, raw []byte, err error) {
var (
HTTP_200 = []byte("HTTP/1.1 200 Connection Established\r\n\r\n")
host string
port string
)
buf := make([]byte, 4096)
buf[0] = first
io.ReadAtLeast(conn, buf[1:], 1)
req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(buf)))
if nil != err {
log.Print("cast []byte to request has error ", err)
return
}
host, port, err = net.SplitHostPort(req.Host)
if nil != err {
host = req.Host
port = req.URL.Port()
}
scheme := req.URL.Scheme
if "" == port {
if scheme == "http" {
port = "80"
} else {
port = "443"
}
}
addr = net.JoinHostPort(host, port)
method := req.Method
//path := req.URL.Path
switch method {
case http.MethodConnect:
hostType = getRequestType(addr)
_, err = conn.Write(HTTP_200)
default:
removeHeaders(req)
raw, err = httputil.DumpRequest(req, true)
}
//log.Printf(string(raw))
return
}
func getRequestType(addr string) int {
host,_,_ := net.SplitHostPort(addr)
ip := net.ParseIP(host)
if nil == ip {
return typeIPv4
}
return typeDm
}
func removeHeaders(req *http.Request) {
req.RequestURI = ""
req.Header.Del("Accept-Encoding")
// curl can add that, see
// https://jdebp.eu./FGA/web-proxy-connection-header.html
req.Header.Del("Proxy-Connection")
req.Header.Del("Proxy-Authenticate")
req.Header.Del("Proxy-Authorization")
//req.Header.Del("Referer")
// Connection, Authenticate and Authorization are single hop Header:
// http://www.w3.org/Protocols/rfc2616/rfc2616.txt
// 14.10 Connection
// The Connection general-header field allows the sender to specify
// options that are desired for that particular connection and MUST NOT
// be communicated by proxies over further connections.
req.Header.Del("Connection")
}

View File

@@ -28,7 +28,10 @@ func (s *DirectServer) ProxyType() string {
func (s *DirectServer) DialWithRawAddr(raw []byte, host string) (remote net.Conn, err error) {
conn, err := net.Dial("tcp", host)
if nil != raw && len(raw) > 0 {
if nil != err{
return nil,err
}
if nil != raw && len(raw) > 0 {
conn.Write(raw)
}
return conn, err

78
flora/socks4.go Normal file
View File

@@ -0,0 +1,78 @@
package flora
import (
"io"
"net"
"encoding/binary"
"fmt"
)
/*
socks4 protocol
request
byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ... |
|0x04|cmd| port | ip | user\0 |
reply
byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7|
|0x00|status| | |
socks4a protocol
request
byte | 0 | 1 | 2 | 3 |4 | 5 | 6 | 7 | 8 | ... |... |
|0x04|cmd| port | 0.0.0.x | user\0 |domain\0|
reply
byte | 0 | 1 | 2 | 3 | 4 | 5 | 6| 7 |
|0x00|staus| port | ip |
*/
// local socks server connect
func socks4Connect(conn net.Conn,first byte ) (addr string, hostType int, err error) {
const (
idVer = 0
idStatus = 1
idPort = 2 // address type index
idPortLen = 2
idIP = 4 // ip addres start index
idIPLen = 4 // domain address length index
idVariable = 8
id4aFixLen = 8
cmdConnect = 1
)
// refer to getRequest in flora.go for why set buffer size to 263
buf := make([]byte, 128)
buf[idVer] = first
var n int
// read till we get possible domain length field
if n, err = io.ReadAtLeast(conn, buf[1:], id4aFixLen); err != nil {
return
}
n ++
// command only support connect
if buf[idStatus] != cmdConnect {
return
}
// get port
port := binary.BigEndian.Uint16(buf[idPort:idPort+idPortLen])
// get ip
ip := net.IP(buf[idIP:idIP+idIPLen])
hostType = typeIPv4
var host = ip.String()
//socks4a
if ip[0] == 0x00 && ip[1] == 0x00 && ip[2] == 0x00 && ip[3] != 0x00 && n+1 >= id4aFixLen {
dm := buf[idVariable:n]
host = string(dm)
hostType = typeDm
}
addr = net.JoinHostPort(host, fmt.Sprintf("%d", port))
_, err = conn.Write([]byte{0x00, 0x5a, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00})
return
}

View File

@@ -6,17 +6,47 @@ import (
"strconv"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
"encoding/binary"
"log"
)
const(
typeIPv4 = 1 // type is ipv4 address
typeDm = 3 // type is domain address
typeIPv6 = 4 // type is ipv6 address
)
/*
socks5 protocol
initial
byte | 0 | 1 | 2 | ...... | n |
|0x05|num auth| auth methods |
reply
byte | 0 | 1 |
|0x05| auth|
username/password auth request
byte | 0 | 1 | | 1 byte | |
|0x01|username_len| username | password_len | password |
username/password auth reponse
byte | 0 | 1 |
|0x01|status|
request
byte | 0 | 1 | 2 | 3 | 4 | .. | n-2 | n-1| n |
|0x05|cmd|0x00|addrtype| addr | port |
response
byte |0 | 1 | 2 | 3 | 4 | .. | n-2 | n-1 | n |
|0x05|status|0x00|addrtype| addr | port |
*/
//local socks server auth
func socksAuth(conn net.Conn) (err error) {
func handshake(conn net.Conn,first byte ) (err error) {
const (
idVer = 0
idNmethod = 1
@@ -27,22 +57,24 @@ func socksAuth(conn net.Conn) (err error) {
// so it won't be such long in practice
buf := make([]byte, 258)
buf[idVer] = first
var n int
ss.SetReadTimeout(conn)
// make sure we get the nmethod field
if n, err = io.ReadAtLeast(conn, buf, idNmethod+1); err != nil {
if n, err = io.ReadAtLeast(conn, buf[1:], idNmethod+1); err != nil {
return
}
if buf[idVer] != socksVer5 {
return errVer
}
n ++
//if buf[idVer] != socksVer5 {
// return errVer
//}
nmethod := int(buf[idNmethod])
msgLen := nmethod + 2
if n == msgLen { // handshake done, common case
// do nothing, jump directly to send confirmation
} else if n < msgLen { // has more methods to read, rare case
if _, err = io.ReadFull(conn, buf[n:msgLen]); err != nil {
log.Print(err)
return
}
} else { // error, should not get extra data
@@ -54,7 +86,7 @@ func socksAuth(conn net.Conn) (err error) {
}
// local socks server connect
func socksConnect(conn net.Conn) (host string,hostType int, err error) {
func socks5Connect(conn net.Conn) (host string, hostType int, err error) {
const (
idVer = 0
idCmd = 1
@@ -76,10 +108,10 @@ func socksConnect(conn net.Conn) (host string,hostType int, err error) {
return
}
// check version and cmd
if buf[idVer] != socksVer5 {
err = errVer
return
}
//if buf[idVer] != socksVer5 {
// err = errVer
// return
//}
if buf[idCmd] != socksCmdConnect {
err = errCmd
return

View File

@@ -11,6 +11,6 @@ func main() {
var configFile, geoipdb string
flag.StringVar(&configFile, "s", "flora.default.conf", "specify surge config file")
flag.StringVar(&geoipdb, "d", "geoip.mmdb", "specify geoip db file")
flora.Run(configFile, geoipdb,flora.LocalServerSocksV5)
flora.Run(configFile, geoipdb)
}