mirror of
https://github.com/zhigang1992/flora-kit.git
synced 2026-04-28 20:05:28 +08:00
1.修正若干bug
2.增加本地监听socks4,socks4a,http 3.http 性能问题有待测试 4.socks4a 有待验证是否能正常工作
This commit is contained in:
@@ -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
85
flora/http.go
Normal 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")
|
||||
}
|
||||
@@ -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
78
flora/socks4.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
2
main.go
2
main.go
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user