- windows 透明代理
This commit is contained in:
0x1024
2017-07-02 19:21:59 +08:00
parent 1f1626a4c7
commit fcf771e160
20 changed files with 1073 additions and 547 deletions

2
.gitignore vendored
View File

@@ -29,3 +29,5 @@ flora-kit
*.log
release/
*.tar.*
.idea
dist/

9
CHANGELOG.md Normal file
View File

@@ -0,0 +1,9 @@
0.2.3
-----
- 支持```[General]skip-Proxy```
- 重构版,方便其他代理模块加入
0.2.2
-----
- 修正打包确缺失默认配置文件的问题。

View File

@@ -1,30 +1,39 @@
RELEASE_PATH = release/flora/
RELEASE_PATH = release
PACKAGE_PATH = release/flora
install:
@go get
build:
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o release/flora-kit-darwin-amd64
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o release/flora-kit-linux-amd64
GOOS=linux GOARCH=386 go build -ldflags "-s -w" -o release/flora-kit-linux-386
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o $(RELEASE_PATH)/flora-darwin-amd64
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o $(RELEASE_PATH)/flora-amd64
GOOS=linux GOARCH=386 go build -ldflags "-s -w" -o $(RELEASE_PATH)/flora-386
GOOS=windows GOARCH=386 go build -ldflags "-s -w" -o $(RELEASE_PATH)/flora-386.exe
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o $(RELEASE_PATH)/flora-amd64.exe
package:
rm -Rf $(RELEASE_PATH)/*
mkdir -p $(RELEASE_PATH)
cp ./flora.default.conf $(RELEASE_PATH)
cp ./geoip.mmdb $(RELEASE_PATH)
rm -Rf $(PACKAGE_PATH)/*
mkdir -p $(PACKAGE_PATH)
cp ./flora.default.conf $(PACKAGE_PATH)
cp ./geoip.mmdb $(PACKAGE_PATH)
cp ./LICENSE $(RELEASE_PATH)
cp ./README.md $(RELEASE_PATH)
cp ./README.md $(PACKAGE_PATH)
# macOS
cp ./release/flora-kit-darwin-amd64 $(RELEASE_PATH)flora
cp ./release/flora-darwin-amd64 $(PACKAGE_PATH)
cd ./release && zip flora-darwin-amd64.zip flora
# Linux amd64
cp ./release/flora-kit-linux-amd64 $(RELEASE_PATH)flora
cp ./release/flora-amd64 $(PACKAGE_PATH)flora
cd ./release && tar zcf flora-linux-amd64.tar.gz flora
# Linux 386
cp ./release/flora-kit-linux-386 $(RELEASE_PATH)flora
cp ./release/flora-386 $(PACKAGE_PATH)
cd ./release && tar zcf flora-linux-386.tar.gz flora
# Windows 386
#cp ./release/flora-386.exe $(PACKAGE_PATH)
#cd ./release && tar zcf flora-win-386.tar.gz flora
# Windows amd64
#cp ./release/flora-amd64.exe $(PACKAGE_PATH)
#cd ./release && tar zcf flora-win-amd64.tar.gz flora
# remove history
rm $(RELEASE_PATH)flora
rm $(PACKAGE_PATH)flora
run:
@go run main.go
test:
@go test ./flora
@go test ./flora

View File

@@ -14,7 +14,8 @@ Flora
- 支持域名关键词、前缀、后缀匹配,制定 Direct 访问(白名单)或用 Proxy 访问(黑名单);
- 支持 IP 白名单,黑名单;
- 支持 GeoIP 判断目标网站服务器所在区域,自动选择线路;
- 启动的时候自动改变 macOS 网路代理配置,无需手工调整;
- 启动的时候自动改变 macOS,windows 网路代理配置,无需手工调整;
## TODO
@@ -30,9 +31,21 @@ https://github.com/huacnlee/flora-kit/releases
> NOTE: 由于启动的时候,需要修改系统的网络配置,所以你需要用 sudo 来执行:
```bash
cd flora
sudo ./flora
#### macOS
```
$ cd flora
$ sudo ./flora
```
#### Linux
```
$ cd flora
$ ./flora
```
#### Windows
```
flora.exe
```
## License

View File

@@ -1,34 +1,37 @@
[General]
loglevel = notify
skip-proxy = 127.0.0.1, 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 100.64.0.0/10, localhost, *.local, ::ffff:0:0:0:0/1, ::ffff:128:0:0:0/1
bypass-tun = 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12
replica = false
skip-Proxy = 127.0.0.1, 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 100.64.0.0/10, localhost, *.local, e.crashlytics.com
# dns-server = 119.29.29.29,223.5.5.5,114.114.115.115
# external-controller-access = PASSWORD@0.0.0.0:6155
# ipv6 = true
# 以下参数仅供 iOS 版本使用
bypass-system = true
bypass-tun = 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 0.0.0.0/31
ipv6 = true
# 以下参数仅供 macOS 版本使用
interface = 127.0.0.1
socks-port = 1080
allow-wifi-access = true
enhanced-mode-by-rule = true
exclude-simple-hostnames = true
[Proxy]
DIRECT = direct
# Proxy = shadowsocks, server-ip, port, method, password
# [SSID Setting]
# WiFi-SSID-NAME suspend=true
[URL Rewrite]
# 302 REDIRECT
[Proxy Group]
# 🚀 Proxy = select, 🌞 Line
[Rule]
// BLOCK ADS
# DOMAIN-SUFFIX,mgid.com,REJECT
// DIRECT RULES
# DOMAIN-SUFFIX,cn,DIRECT
// NORMAL RULES
# DOMAIN-KEYWORD,instagram,Proxy
# DOMAIN-SUFFIX,youtube.com,Proxy
# China IP use DIRECT
// China IP use DIRECT
GEOIP,CN,DIRECT
# Other
FINAL,Proxy
FINAL,Proxy

View File

@@ -9,70 +9,73 @@ import (
"os"
"strconv"
"strings"
"math/rand"
)
const (
VERSION = "0.1.1"
SOCKS_PORT = 7657
RULE_REJECT = 0
RULE_DIRECT = 1
RULE_PROXY = 2
DEFAULT_SOCKS_PORT = 1080
)
type DomainRule struct {
S string
T int
type ProxyConfig struct {
SurgeConfig *ini.File
GeoDbPath string
LocalSocksPort int
LocalHost string
proxyServer map[string]ProxyServer
proxyGroup map[string]*proxyGroup
bypassDomains []interface{}
systemBypass []string
ruleSuffixDomains []*Rule
rulePrefixDomains []*Rule
ruleKeywordDomains []*Rule
ruleUserAgent []*Rule
ruleGeoIP []*Rule
ruleFinal *Rule
}
type proxyGroup struct {
mode string
proxyServers []ProxyServer
}
var ruleSuffixDomains = []*DomainRule{}
var rulePrefixDomains = []*DomainRule{}
var ruleKeywordDomains = []*DomainRule{}
var ruleGeoIP = &DomainRule{}
var ssConfig ss.Config
var iniConfig *ini.File
var debug ss.DebugLog
type ProxyServerCipher struct {
Server string
Cipher *ss.Cipher
}
var ProxyServers struct {
SrvCipher []*ProxyServerCipher
FailCnt []int // failed connection count
}
func init() {
var configFilename = "./flora.default.conf"
var userConfigFilename = "./flora.user.conf"
func LoadConfig(cfgFile string, geoFile string) (*ProxyConfig) {
proxyConfig := ProxyConfig{}
var iniOpts = ini.LoadOptions{
AllowBooleanKeys: true,
Loose: true,
Insensitive: true,
}
cfg, err := ini.LoadSources(iniOpts, configFilename, userConfigFilename)
sep := string(os.PathSeparator)
pwd, _ := os.Getwd()
var geoFilename = geoFile
var err error
var defaultCfgName = strings.Join([]string{pwd, "flora.default.conf"}, sep)
var userConfigFilename = strings.Join([]string{pwd, "flora.user.conf"}, sep)
var speCfgName string
if _, err := os.Stat(cfgFile); nil != err && os.IsNotExist(err) {
speCfgName = strings.Join([]string{pwd, cfgFile}, sep)
}
if _, err := os.Stat(geoFilename); nil != err && os.IsNotExist(err) {
geoFilename = strings.Join([]string{pwd, "geoip.mmdb"}, sep)
}
proxyConfig.SurgeConfig, err = ini.LoadSources(iniOpts, speCfgName, defaultCfgName, userConfigFilename)
if err != nil {
panic(fmt.Sprintf("Config file %v not found, or have error: \n\t%v", configFilename, err))
panic(fmt.Sprintf("Config file %v not found, or have error: \n\t%v", cfgFile, err))
}
iniConfig = cfg
loadGeneral(&proxyConfig)
loadProxy(&proxyConfig)
loadProxyGroup(&proxyConfig)
loadGeoIP(geoFilename)
loadRules(&proxyConfig)
loadGeoIP()
loadGeneral()
loadProxy()
loadRules()
SetSocksFirewallProxy()
debug.Println("104.244.42.129", GeoIPString("104.244.42.129"))
debug.Println(RuleOfHost("www.twitter.com"))
return &proxyConfig
}
// [General] section
func loadGeneral() {
section := iniConfig.Section("General")
func loadGeneral(config *ProxyConfig) {
section := config.SurgeConfig.Section("General")
bypassDomains := []string{}
if section.HasKey("skip-proxy") {
bypassDomains = append(bypassDomains, readArrayLine(section.Key("skip-proxy").String())...)
@@ -80,74 +83,142 @@ func loadGeneral() {
if section.HasKey("bypass-tun") {
bypassDomains = append(bypassDomains, readArrayLine(section.Key("bypass-tun").String())...)
}
SetProxyBypassDomains(bypassDomains)
config.LocalSocksPort = DEFAULT_SOCKS_PORT
if section.HasKey("socks-port") {
port, err := strconv.Atoi(section.Key("socks-port").String())
if nil == err {
config.LocalSocksPort = port
}
}
config.LocalHost = "127.0.0.1"
if section.Haskey("interface") {
ipStr := section.Key("interface").String()
addr := net.ParseIP(ipStr)
if nil != addr {
config.LocalHost = ipStr
}
}
config.systemBypass = bypassDomains
//load bypass
config.bypassDomains = make([]interface{}, len(bypassDomains))
for i, v := range bypassDomains {
ip := net.ParseIP(v)
if nil != ip {
config.bypassDomains[i] = ip
} else if _, n, err := net.ParseCIDR(v); err == nil {
config.bypassDomains[i] = n
} else {
config.bypassDomains[i] = v
}
}
}
// [Proxy] Section
func loadProxy() {
ssConfig.LocalPort = SOCKS_PORT
for _, key := range iniConfig.Section("Proxy").Keys() {
var proxys = readArrayLine(key.String())
// ShadowSocks Proxys
if proxys[0] == "custom" || proxys[0] == "shadowsocks" {
var server = strings.Join(proxys[1:3], ":")
var serverInfo = []string{server, proxys[4], proxys[3]}
ssConfig.ServerPassword = append(ssConfig.ServerPassword, serverInfo)
}
}
if ssConfig.Method == "" {
ssConfig.Method = "aes-256-cfb"
}
if len(ssConfig.ServerPassword) == 0 {
if !enoughSSOptions(&ssConfig) {
fmt.Fprintln(os.Stderr, "must specify server address, password and both server/local port")
os.Exit(1)
}
} else {
if ssConfig.LocalPort == 0 {
fmt.Fprintln(os.Stderr, "must specify local port")
os.Exit(1)
}
}
parseServerConfig()
}
// 载入 [Rule]
func loadRules() {
for _, key := range iniConfig.Section("Rule").KeyStrings() {
var items = readArrayLine(key)
var ruleType = RULE_DIRECT
if len(items) >= 3 {
switch items[2] {
case "proxy":
ruleType = RULE_PROXY
case "direct":
ruleType = RULE_DIRECT
case "reject":
ruleType = RULE_REJECT
default:
ruleType = RULE_DIRECT
func loadProxy(config *ProxyConfig) {
config.proxyServer = make(map[string]ProxyServer)
section := config.SurgeConfig.Section("Proxy")
for _, name := range section.KeyStrings() {
proxyName := strings.ToLower(name)
v, _ := section.GetKey(proxyName)
var proxyStrCfg = readArrayLine(v.String())
serverType := strings.ToLower(proxyStrCfg[0])
var proxy ProxyServer
if serverType == ServerTypeShadowSocks || serverType == ServerTypeCustom {
//[ip:port,password,method]
if len(proxyStrCfg) > 1 {
c, err := ss.NewCipher(proxyStrCfg[3], proxyStrCfg[4])
if nil != err {
log.Printf("Loading shadowsocks proxy server %s has error ", proxyName)
continue
}
proxy = NewShadowSocks(strings.Join(proxyStrCfg[1:3], ":"), c)
}
}
switch items[0] {
case "domain-suffix":
ruleSuffixDomains = append(ruleSuffixDomains, &DomainRule{S: items[1], T: ruleType})
case "domain-prefix":
rulePrefixDomains = append(rulePrefixDomains, &DomainRule{S: items[1], T: ruleType})
case "domain-keyword":
ruleKeywordDomains = append(ruleKeywordDomains, &DomainRule{S: items[1], T: ruleType})
case "geoip":
ruleGeoIP = &DomainRule{S: items[1], T: ruleType}
} else if serverType == ServerTypeDirect {
proxy = NewDirect()
} else if serverType == ServerTypeReject {
proxy = NewReject()
}
if nil != proxy {
log.Printf("Loading proxy server %s done. ", proxyName)
config.proxyServer[proxyName] = proxy
}
}
}
func enoughSSOptions(config *ss.Config) bool {
return config.Server != nil && config.ServerPort != 0 &&
config.LocalPort != 0 && config.Password != ""
func (c *ProxyConfig) GetProxyServer(action string) (ProxyServer) {
const maxFailCnt = 30
var server ProxyServer
var ok bool
server, ok = c.proxyServer[action]
if !ok {
group, ok := c.proxyGroup[action]
if ok {
for _, s := range group.proxyServers {
eff := []ProxyServer{}
if s.FailCount() < maxFailCnt {
eff = append(eff, s)
}
l := len(eff)
if l > 0 {
return eff[rand.Intn(l)]
}
}
} else {
server = NewDirect()
}
}
return server
}
//[Proxy Group] Section
func loadProxyGroup(config *ProxyConfig) {
section := config.SurgeConfig.Section("Proxy Group")
config.proxyGroup = make(map[string]*proxyGroup)
for _, name := range section.KeyStrings() {
groupName := strings.ToLower(name)
v, _ := section.GetKey(groupName)
proxyArr := readArrayLine(v.String())
//🚀 Proxy = select, 🌞 Line
if len(proxyArr) > 1 {
groupItems := proxyGroup{mode: proxyArr[0]}
servers := make([]ProxyServer, len(proxyArr)-1)
for i, p := range proxyArr[1:] {
proxyName := strings.ToLower(p)
servers[i] = config.proxyServer[proxyName]
}
groupItems.proxyServers = servers
config.proxyGroup[groupName] = &groupItems
}
}
}
//[Rule] Section
func loadRules(config *ProxyConfig) {
for _, key := range config.SurgeConfig.Section("Rule").KeyStrings() {
if strings.HasPrefix(key, "//") {
continue
}
items := readArrayLine(key)
ruleName := strings.ToLower(items[0])
switch ruleName {
case "user-agent":
config.ruleUserAgent = append(config.ruleUserAgent, &Rule{Match: items[1], Action: strings.ToLower(items[2])})
case "domain-suffix":
config.ruleSuffixDomains = append(config.ruleSuffixDomains, &Rule{Match: items[1], Action: strings.ToLower(items[2])})
case "domain-prefix":
config.rulePrefixDomains = append(config.rulePrefixDomains, &Rule{Match: items[1], Action: strings.ToLower(items[2])})
case "domain-keyword":
config.ruleKeywordDomains = append(config.ruleKeywordDomains, &Rule{Match: items[1], Action: strings.ToLower(items[2])})
case "geoip":
config.ruleGeoIP = append(config.ruleGeoIP, &Rule{Match: items[1], Action: strings.ToLower(items[2])})
case "final":
config.ruleFinal = &Rule{Match: "final", Action: strings.ToUpper(items[1])}
}
}
}
func readArrayLine(source string) []string {
@@ -157,120 +228,3 @@ func readArrayLine(source string) []string {
}
return out
}
func RuleOfHost(host string) (result *DomainRule) {
result = &DomainRule{S: "", T: RULE_DIRECT}
hostParts := strings.Split(host, ":")
domain := hostParts[0]
for _, rule := range ruleSuffixDomains {
if strings.HasSuffix(domain, rule.S) {
result = rule
return
}
}
for _, rule := range rulePrefixDomains {
if strings.HasPrefix(domain, rule.S) {
result = rule
return
}
}
for _, rule := range ruleKeywordDomains {
if strings.Contains(domain, rule.S) {
result = rule
return
}
}
ips, err := net.LookupIP(host)
if err != nil || len(ips) == 0 {
return
}
country := GeoIPs(ips)
log.Println("Found ip geo", country)
if len(country) != 0 && ruleGeoIP.S == country {
result = ruleGeoIP
return
}
return
}
func parseServerConfig() {
config := ssConfig
hasPort := func(s string) bool {
_, port, err := net.SplitHostPort(s)
if err != nil {
return false
}
return port != ""
}
if len(config.ServerPassword) == 0 {
method := config.Method
if config.Auth {
method += "-auth"
}
// only one encryption table
cipher, err := ss.NewCipher(method, config.Password)
if err != nil {
log.Fatal("Failed generating ciphers:", err)
}
srvPort := strconv.Itoa(config.ServerPort)
srvArr := config.GetServerArray()
n := len(srvArr)
ProxyServers.SrvCipher = make([]*ProxyServerCipher, n)
for i, s := range srvArr {
if hasPort(s) {
log.Println("ignore server_port option for server", s)
ProxyServers.SrvCipher[i] = &ProxyServerCipher{s, cipher}
} else {
ProxyServers.SrvCipher[i] = &ProxyServerCipher{net.JoinHostPort(s, srvPort), cipher}
}
}
} else {
// multiple servers
n := len(config.ServerPassword)
ProxyServers.SrvCipher = make([]*ProxyServerCipher, n)
cipherCache := make(map[string]*ss.Cipher)
i := 0
for _, serverInfo := range config.ServerPassword {
if len(serverInfo) < 2 || len(serverInfo) > 3 {
log.Fatalf("server %v syntax error\n", serverInfo)
}
server := serverInfo[0]
passwd := serverInfo[1]
encmethod := ""
if len(serverInfo) == 3 {
encmethod = serverInfo[2]
}
if !hasPort(server) {
log.Fatalf("no port for server %s\n", server)
}
// Using "|" as delimiter is safe here, since no encryption
// method contains it in the name.
cacheKey := encmethod + "|" + passwd
cipher, ok := cipherCache[cacheKey]
if !ok {
var err error
cipher, err = ss.NewCipher(encmethod, passwd)
if err != nil {
log.Fatal("Failed generating ciphers:", err)
}
cipherCache[cacheKey] = cipher
}
ProxyServers.SrvCipher[i] = &ProxyServerCipher{server, cipher}
i++
}
}
ProxyServers.FailCnt = make([]int, len(ProxyServers.SrvCipher))
for _, se := range ProxyServers.SrvCipher {
debug.Println("available remote server", se.Server)
}
return
}

227
flora/flora.go Normal file
View File

@@ -0,0 +1,227 @@
package flora
import (
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
"net"
"errors"
"log"
"fmt"
"strings"
"regexp"
"io"
)
const (
ServerTypeShadowSocks = "shadowsocks"
ServerTypeCustom = "custom"
ServerTypeHttp = "http"
ServerTypeHttps = "https"
ServerTypeDirect = "direct"
ServerTypeReject = "direct"
LocalServerSocksV5 = "localSocksv5"
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 {
//proxy type
ProxyType() string
//dial
DialWithRawAddr(raw []byte, host string) (remote net.Conn, err error)
//
FailCount() int
AddFail()
//
ResetFailCount()
}
type Rule struct {
Match string
Action string
}
var (
errAddrType = errors.New("socks addr type not supported")
errVer = errors.New("socks version not supported")
errAuthExtraData = errors.New("socks authentication get extra data")
errReqExtraData = errors.New("socks request get extra data")
errCmd = errors.New("socks command not supported")
errReject = errors.New("socks reject this request")
errSupported = errors.New("proxy type not supported")
)
var proxyConfig *ProxyConfig
func Run(surgeCfg, geoipCfg string) {
proxyConfig = LoadConfig(surgeCfg, geoipCfg)
listenAddr := fmt.Sprintf("%s:%d", proxyConfig.LocalHost, proxyConfig.LocalSocksPort)
initProxySettings(proxyConfig.systemBypass,listenAddr)
ln, err := net.Listen("tcp", listenAddr)
if err != nil {
log.Fatal(err)
}
log.Println("Listen socket", listenAddr)
for {
conn, err := ln.Accept()
if err != nil {
log.Println("accept:", err)
continue
}
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
isClose := false
defer func() {
if !isClose {
conn.Close()
}
}()
var (
host string
hostType int
err error
rawData []byte
)
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 {
return
}
remote, err := matchRuleAndCreateConn(conn, host, hostType, rawData)
if nil != err {
return
}
//create remote connect
defer func() {
if !isClose {
remote.Close()
}
}()
go ss.PipeThenClose(conn, remote)
ss.PipeThenClose(remote, conn)
isClose = true
}
func matchRuleAndCreateConn(conn net.Conn, addr string, hostType int, raw []byte) (net.Conn, error) {
if nil == conn {
return nil, errors.New("local connect is nil")
}
host, _, _ := net.SplitHostPort(addr)
var rule *Rule
rule = matchBypass(host)
if nil == rule {
switch hostType {
case typeIPv4, typeIPv6:
rule = matchIpRule(host)
case typeDm:
rule = matchDomainRule(host)
}
}
if nil == rule {
if nil != proxyConfig.ruleFinal {
rule = proxyConfig.ruleFinal
} else {
rule = &Rule{Match: "default", Action: ServerTypeDirect}
}
}
return createRemoteConn(raw, rule, addr)
}
func matchDomainRule(domain string) (*Rule) {
for _, rule := range proxyConfig.ruleSuffixDomains {
if strings.HasSuffix(domain, rule.Match) {
return rule
}
}
for _, rule := range proxyConfig.rulePrefixDomains {
if strings.HasPrefix(domain, rule.Match) {
return rule
}
}
for _, rule := range proxyConfig.ruleKeywordDomains {
if strings.Contains(domain, rule.Match) {
return rule
}
}
return nil
}
func matchIpRule(addr string) (*Rule) {
ips := resolveRequestIPAddr(addr)
if nil != ips {
country := strings.ToLower(GeoIPs(ips))
for _, rule := range proxyConfig.ruleGeoIP {
if len(country) != 0 && strings.ToLower(rule.Match) == country {
return rule
}
}
}
return nil
}
func matchBypass(addr string) (*Rule) {
ip := net.ParseIP(addr)
for _, h := range proxyConfig.bypassDomains {
var bypass bool = false
var isIp = nil != ip
switch h.(type) {
case net.IP:
if isIp {
bypass = ip.Equal(h.(net.IP))
}
case *net.IPNet:
if isIp {
bypass = h.(*net.IPNet).Contains(ip)
}
case string:
dm := h.(string)
r, err := regexp.Compile(dm)
if err != nil {
continue
}
bypass = r.MatchString(addr)
}
if bypass {
return &Rule{Match: "bypass", Action: ServerTypeDirect}
}
}
return nil
}
func createRemoteConn(raw []byte, rule *Rule, host string) (net.Conn, error) {
server := proxyConfig.GetProxyServer(rule.Action)
conn, err := server.DialWithRawAddr(raw, host)
if nil != err {
log.Printf("[%s]->[%s] 💊 [%s]", rule.Match, rule.Action, host)
server.AddFail()
} else {
log.Printf("[%s]->[%s] ✅ [%s]", rule.Match, rule.Action, host)
server.ResetFailCount()
}
return conn, err
}

View File

@@ -9,9 +9,8 @@ import (
var geoDB *geoip2.Reader
func loadGeoIP() {
file := "./geoip.mmdb"
db, err := geoip2.Open(file)
func loadGeoIP(geoFile string) {
db, err := geoip2.Open(geoFile)
// defer db.Close()
if err != nil {
log.Printf("Could not open GeoIP database\n")
@@ -41,3 +40,20 @@ func GeoIP(ip net.IP) string {
}
return strings.ToLower(country.Country.IsoCode)
}
func resolveRequestIPAddr(host string) []net.IP {
var (
ips []net.IP
err error
)
ip := net.ParseIP(host)
if nil == ip {
ips, err = net.LookupIP(host)
if err != nil || len(ips) == 0 {
return nil
}
} else {
ips = []net.IP{ip}
}
return ips
}

79
flora/http.go Normal file
View File

@@ -0,0 +1,79 @@
package flora
import (
"net"
"io"
"net/http"
"bufio"
"bytes"
"net/http/httputil"
)
// 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 {
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
hostType = getRequestType(addr)
switch method {
case http.MethodConnect:
_, err = conn.Write(HTTP_200)
default:
removeHeaders(req)
raw, err = httputil.DumpRequest(req, true)
}
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")
}

43
flora/network_setup.go Normal file
View File

@@ -0,0 +1,43 @@
package flora
import (
"os/signal"
"syscall"
"os"
"log"
"runtime"
"time"
)
type SystemProxySettings interface {
TurnOnGlobProxy()
TurnOffGlobProxy()
}
var sigs = make(chan os.Signal, 1)
func resetProxySettings(proxySettings SystemProxySettings) {
for {
select {
case <-sigs:
log.Print("Flora-kit is shutdown now ...")
proxySettings.TurnOffGlobProxy()
time.Sleep(time.Duration(2000))
os.Exit(0)
}
}
}
func initProxySettings(bypass []string, addr string) {
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
var proxySettings SystemProxySettings
if runtime.GOOS == "windows" {
w := &windows{addr}
proxySettings = w
} else if runtime.GOOS == "darwin" {
d := &darwin{bypass,addr}
proxySettings = d
}
proxySettings.TurnOnGlobProxy()
go resetProxySettings(proxySettings)
}

View File

@@ -2,22 +2,22 @@ package flora
import (
"bytes"
"fmt"
"log"
"os/exec"
"runtime"
"strings"
"net"
)
type darwin struct {
bypassDomains []string
address string
}
type execNetworkFunc func(name string)
var allow_services = "Wi-Fi|Thunderbolt Bridge|Thunderbolt Ethernet"
func ResetAllProxys() {
if runtime.GOOS != "darwin" {
log.Println("WARN: Your not in macOS, Networksetup skiped. Please change Network proxy setting by manually.")
}
func (d *darwin) TurnOffGlobProxy() {
execNetworks(func(name string) {
runNetworksetup("-setftpproxystate", name, "off")
runNetworksetup("-setwebproxystate", name, "off")
@@ -29,24 +29,21 @@ func ResetAllProxys() {
})
}
func SetSocksFirewallProxy() {
execNetworks(func(name string) {
runNetworksetup("-setsocksfirewallproxy", name, "127.0.0.1", fmt.Sprintf("%d", SOCKS_PORT))
})
}
func (d *darwin) TurnOnGlobProxy() {
host, port, _ := net.SplitHostPort(d.address)
execNetworks(func(name string) {
runNetworksetup("-setsocksfirewallproxy", name, host, port)
})
func SetProxyBypassDomains(domains []string) {
execNetworks(func(name string) {
args := []string{"-setproxybypassdomains", name}
args = append(args, domains...)
args = append(args, d.bypassDomains...)
runNetworksetup(args...)
})
}
func runNetworksetup(args ...string) string {
if runtime.GOOS != "darwin" {
return ""
}
// log.Println("networksetup", args)
cmd := exec.Command("networksetup", args...)

View File

@@ -0,0 +1,47 @@
package flora
import (
"os/exec"
"log"
)
type windows struct {
address string
}
const (
cmdRegistry = `reg`
cmdRegistryAdd = `add`
internetSettingsKey = `HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings`
keyProxyEnable = `ProxyEnable`
keyProxyServer = `ProxyServer`
dataTypeDWord = `REG_DWORD`
dataTypeRegSZ = `REG_SZ`
)
func (w *windows) TurnOnGlobProxy() {
c := exec.Command(cmdRegistry, cmdRegistryAdd, internetSettingsKey, `/v`, keyProxyEnable, `/t`, dataTypeDWord, `/d`, `1`, `/f`)
var err error
if _, err = c.CombinedOutput(); err != nil {
log.Printf("enable windows proxy has error %s", err)
}
c = exec.Command(cmdRegistry, cmdRegistryAdd, internetSettingsKey, `/v`, keyProxyServer, `/t`, dataTypeRegSZ, `/d`, w.address, `/f`)
if _, err = c.CombinedOutput(); err != nil {
log.Printf("Windows global proxy settings has error %s , Try to set it manually ", err)
}
if nil == err {
log.Print("Windows global proxy settings are successful Please use after 2 minutes ...")
}
}
// TurnOffSystemProxy
func (w *windows) TurnOffGlobProxy() {
var err error
c := exec.Command(cmdRegistry, cmdRegistryAdd, internetSettingsKey, `/v`, keyProxyEnable, `/t`, dataTypeDWord, `/d`, `0`, `/f`)
if _, err = c.CombinedOutput(); err != nil {
log.Printf("disable windows proxy has error %s", err)
}
if nil == err{
log.Print("disable windows proxy settings are successful ...")
}
}

38
flora/proxy_direct.go Normal file
View File

@@ -0,0 +1,38 @@
package flora
import "net"
type DirectServer struct {
proxyType string
}
func NewDirect() (*DirectServer) {
return &DirectServer{proxyType: ServerTypeDirect}
}
func (s *DirectServer) FailCount() int {
return 0
}
func (s *DirectServer) ResetFailCount() {
}
func (s *DirectServer) AddFail() {
}
func (s *DirectServer) ProxyType() string {
return s.proxyType
}
func (s *DirectServer) DialWithRawAddr(raw []byte, host string) (remote net.Conn, err error) {
conn, err := net.Dial("tcp", host)
if nil != err{
return nil,err
}
if nil != raw && len(raw) > 0 {
conn.Write(raw)
}
return conn, err
}

31
flora/proxy_reject.go Normal file
View File

@@ -0,0 +1,31 @@
package flora
import "net"
type Reject struct {
proxyType string
}
func NewReject() (*Reject) {
return &Reject{proxyType: ServerTypeReject}
}
func (s *Reject) FailCount() int {
return 0
}
func (s *Reject) ResetFailCount() {
}
func (s *Reject) AddFail() {
}
func (s *Reject) ProxyType() string {
return s.proxyType
}
func (s *Reject) DialWithRawAddr(raw []byte, host string) (remote net.Conn, err error) {
return nil, errReject
}

View File

@@ -0,0 +1,51 @@
package flora
import (
"net"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
"sync"
)
type ShadowSocksServer struct {
proxyType string
server string
cipher *ss.Cipher
failCount int
lock sync.RWMutex
}
func NewShadowSocks(server string, cipher *ss.Cipher) (*ShadowSocksServer) {
return &ShadowSocksServer{
proxyType: ServerTypeShadowSocks,
server: server,
cipher: cipher,
}
}
func (s *ShadowSocksServer) ResetFailCount() {
s.lock.Lock()
defer s.lock.Unlock()
s.failCount = 0
}
func (s *ShadowSocksServer) AddFail() {
s.failCount ++
}
func (s *ShadowSocksServer) FailCount() int {
s.lock.RLock()
defer s.lock.RUnlock()
return s.failCount
}
func (s *ShadowSocksServer) ProxyType() string {
return s.proxyType
}
func (s *ShadowSocksServer) DialWithRawAddr(raw []byte, host string) (net.Conn, error) {
if nil != raw && len(raw) > 0 {
return ss.DialWithRawAddr(raw, s.server, s.cipher.Copy())
} else {
return ss.Dial(host, s.server, s.cipher.Copy())
}
}

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
}

158
flora/socks5.go Normal file
View File

@@ -0,0 +1,158 @@
package flora
import (
"io"
"net"
"strconv"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
"encoding/binary"
"log"
)
/*
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 handshake(conn net.Conn,first byte ) (err error) {
const (
idVer = 0
idNmethod = 1
)
// version identification and method selection message in theory can have
// at most 256 methods, plus version and nmethod field in total 258 bytes
// the current rfc defines only 3 authentication methods (plus 2 reserved),
// 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[1:], idNmethod+1); err != nil {
return
}
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
return errAuthExtraData
}
// send confirmation: version 5, no authentication required
_, err = conn.Write([]byte{socksVer5, 0})
return
}
// local socks server connect
func socks5Connect(conn net.Conn) (host string, hostType int, err error) {
const (
idVer = 0
idCmd = 1
idType = 3 // address type index
idIP0 = 4 // ip addres start index
idDmLen = 4 // domain address length index
idDm0 = 5 // domain address start index
lenIPv4 = 3 + 1 + net.IPv4len + 2 // 3(ver+cmd+rsv) + 1addrType + ipv4 + 2port
lenIPv6 = 3 + 1 + net.IPv6len + 2 // 3(ver+cmd+rsv) + 1addrType + ipv6 + 2port
lenDmBase = 3 + 1 + 1 + 2 // 3 + 1addrType + 1addrLen + 2port, plus addrLen
)
// refer to getRequest in flora.go for why set buffer size to 263
buf := make([]byte, 263)
var n int
ss.SetReadTimeout(conn)
// read till we get possible domain length field
if n, err = io.ReadAtLeast(conn, buf, idDmLen+1); err != nil {
return
}
// check version and cmd
//if buf[idVer] != socksVer5 {
// err = errVer
// return
//}
if buf[idCmd] != socksCmdConnect {
err = errCmd
return
}
reqLen := -1
hostType = int(buf[idType])
switch hostType {
case typeIPv4:
reqLen = lenIPv4
case typeIPv6:
reqLen = lenIPv6
case typeDm:
reqLen = int(buf[idDmLen]) + lenDmBase
default:
err = errAddrType
return
}
if n == reqLen {
// common case, do nothing
} else if n < reqLen { // rare case
if _, err = io.ReadFull(conn, buf[n:reqLen]); err != nil {
return
}
} else {
err = errReqExtraData
return
}
//raw := buf[idType:reqLen]
switch hostType {
case typeIPv4:
host = net.IP(buf[idIP0: idIP0+net.IPv4len]).String()
case typeIPv6:
host = net.IP(buf[idIP0: idIP0+net.IPv6len]).String()
case typeDm:
host = string(buf[idDm0: idDm0+buf[idDmLen]])
}
port := binary.BigEndian.Uint16(buf[reqLen-2: reqLen])
host = net.JoinHostPort(host, strconv.Itoa(int(port)))
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x08, 0x43})
return
}

View File

@@ -1,11 +1,15 @@
package main_test
import (
"github.com/huacnlee/flora-kit/flora"
"flora-kit/flora"
"testing"
"os"
)
func TestGeoIP(t *testing.T) {
p, _ := os.Getwd()
flora.LoadConfig("", p+"/geoip.mmdb")
if flora.GeoIPString("121.0.29.91") != "cn" {
t.Errorf("121.0.29.91 should be cn")
}
@@ -17,4 +21,8 @@ func TestGeoIP(t *testing.T) {
if flora.GeoIPString("218.176.242.11") != "jp" {
t.Errorf("218.176.242.11 should be jp")
}
if flora.GeoIPString("8.8.8.8") != "us" {
t.Errorf("218.176.242.11 should be jp")
}
}

28
goreleaser.yml Normal file
View File

@@ -0,0 +1,28 @@
# goreleaser.yml
# Build customization
build:
main: main.go
binary: flora-kit
goos:
- windows
- darwin
- linux
goarch:
- amd64
ldflags: -s -w
release:
github:
owner: huacnlee
name: flora-kit
# Archive customization
archive:
format: tar.gz
replacements:
amd64: 64-bit
darwin: macOS
files:
- README.md
- LICENSE
- CHANGELOG.md
- flora.default.conf
- geoip.mmdb

279
main.go
View File

@@ -1,281 +1,16 @@
package main
import (
"encoding/binary"
"errors"
"fmt"
"github.com/huacnlee/flora-kit/flora"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
"io"
"log"
"math/rand"
"net"
"strconv"
"time"
"flora-kit/flora"
"flag"
)
var debug ss.DebugLog
var (
errAddrType = errors.New("socks addr type not supported")
errVer = errors.New("socks version not supported")
errMethod = errors.New("socks only support 1 method now")
errAuthExtraData = errors.New("socks authentication get extra data")
errReqExtraData = errors.New("socks request get extra data")
errCmd = errors.New("socks command not supported")
)
const (
socksVer5 = 5
socksCmdConnect = 1
)
func init() {
rand.Seed(time.Now().Unix())
}
func handShake(conn net.Conn) (err error) {
const (
idVer = 0
idNmethod = 1
)
// version identification and method selection message in theory can have
// at most 256 methods, plus version and nmethod field in total 258 bytes
// the current rfc defines only 3 authentication methods (plus 2 reserved),
// so it won't be such long in practice
buf := make([]byte, 258)
var n int
ss.SetReadTimeout(conn)
// make sure we get the nmethod field
if n, err = io.ReadAtLeast(conn, buf, idNmethod+1); err != nil {
return
}
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 {
return
}
} else { // error, should not get extra data
return errAuthExtraData
}
// send confirmation: version 5, no authentication required
_, err = conn.Write([]byte{socksVer5, 0})
return
}
func getRequest(conn net.Conn) (rawaddr []byte, host string, err error) {
const (
idVer = 0
idCmd = 1
idType = 3 // address type index
idIP0 = 4 // ip addres start index
idDmLen = 4 // domain address length index
idDm0 = 5 // domain address start index
typeIPv4 = 1 // type is ipv4 address
typeDm = 3 // type is domain address
typeIPv6 = 4 // type is ipv6 address
lenIPv4 = 3 + 1 + net.IPv4len + 2 // 3(ver+cmd+rsv) + 1addrType + ipv4 + 2port
lenIPv6 = 3 + 1 + net.IPv6len + 2 // 3(ver+cmd+rsv) + 1addrType + ipv6 + 2port
lenDmBase = 3 + 1 + 1 + 2 // 3 + 1addrType + 1addrLen + 2port, plus addrLen
)
// refer to getRequest in server.go for why set buffer size to 263
buf := make([]byte, 263)
var n int
ss.SetReadTimeout(conn)
// read till we get possible domain length field
if n, err = io.ReadAtLeast(conn, buf, idDmLen+1); err != nil {
return
}
// check version and cmd
if buf[idVer] != socksVer5 {
err = errVer
return
}
if buf[idCmd] != socksCmdConnect {
err = errCmd
return
}
reqLen := -1
switch buf[idType] {
case typeIPv4:
reqLen = lenIPv4
case typeIPv6:
reqLen = lenIPv6
case typeDm:
reqLen = int(buf[idDmLen]) + lenDmBase
default:
err = errAddrType
return
}
if n == reqLen {
// common case, do nothing
} else if n < reqLen { // rare case
if _, err = io.ReadFull(conn, buf[n:reqLen]); err != nil {
return
}
} else {
err = errReqExtraData
return
}
rawaddr = buf[idType:reqLen]
switch buf[idType] {
case typeIPv4:
host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String()
case typeIPv6:
host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String()
case typeDm:
host = string(buf[idDm0 : idDm0+buf[idDmLen]])
}
port := binary.BigEndian.Uint16(buf[reqLen-2 : reqLen])
host = net.JoinHostPort(host, strconv.Itoa(int(port)))
return
}
func connectToServer(serverId int, rawaddr []byte, addr string) (remote *ss.Conn, err error) {
se := flora.ProxyServers.SrvCipher[serverId]
remote, err = ss.DialWithRawAddr(rawaddr, se.Server, se.Cipher.Copy())
if err != nil {
log.Println("error connecting to shadowsocks server:", err)
const maxFailCnt = 30
if flora.ProxyServers.FailCnt[serverId] < maxFailCnt {
flora.ProxyServers.FailCnt[serverId]++
}
return nil, err
}
debug.Printf("connected to %s via %s\n", addr, se.Server)
flora.ProxyServers.FailCnt[serverId] = 0
return
}
// Connection to the server in the order specified in the config. On
// connection failure, try the next server. A failed server will be tried with
// some probability according to its fail count, so we can discover recovered
// servers.
func createServerConn(rawaddr []byte, addr string) (remote *ss.Conn, err error) {
const baseFailCnt = 20
n := len(flora.ProxyServers.SrvCipher)
skipped := make([]int, 0)
for i := 0; i < n; i++ {
// skip failed server, but try it with some probability
if flora.ProxyServers.FailCnt[i] > 0 && rand.Intn(flora.ProxyServers.FailCnt[i]+baseFailCnt) != 0 {
skipped = append(skipped, i)
continue
}
remote, err = connectToServer(i, rawaddr, addr)
if err == nil {
return
}
}
// last resort, try skipped servers, not likely to succeed
for _, i := range skipped {
remote, err = connectToServer(i, rawaddr, addr)
if err == nil {
return
}
}
return nil, err
}
// 连接请求入口
func handleConnection(conn net.Conn) {
closed := false
defer func() {
if !closed {
conn.Close()
}
}()
var err error = nil
if err = handShake(conn); err != nil {
log.Println("socks handshake:", err)
return
}
rawaddr, host, err := getRequest(conn)
if err != nil {
log.Println("error getting request:", err)
return
}
// Sending connection established message immediately to client.
// This some round trip time for creating socks connection with the client.
// But if connection failed, the client will get connection reset error.
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x08, 0x43})
if err != nil {
debug.Println("send connection confirmation:", err)
return
}
rule := flora.RuleOfHost(host)
if rule.T == flora.RULE_REJECT {
log.Println("REJECT", host)
return
}
var remote net.Conn
if rule.T == flora.RULE_DIRECT {
log.Println("DIRECT", host)
remote, err = net.Dial("tcp", host)
if err != nil {
log.Println("Failed connect to all remote server via Direct")
return
}
} else {
log.Println("PROXY ", host)
remote, err = createServerConn(rawaddr, host)
if err != nil {
if len(flora.ProxyServers.SrvCipher) > 1 {
log.Println("Failed connect to all avaiable shadowsocks server")
}
return
}
}
defer func() {
if !closed {
remote.Close()
}
}()
go ss.PipeThenClose(conn, remote)
ss.PipeThenClose(remote, conn)
closed = true
}
func run(listenAddr string) {
flora.ResetAllProxys()
ln, err := net.Listen("tcp", listenAddr)
if err != nil {
log.Fatal(err)
}
log.Println("Listen socks", listenAddr)
for {
conn, err := ln.Accept()
if err != nil {
log.Println("accept:", err)
continue
}
go handleConnection(conn)
}
}
func main() {
ss.SetDebug(true)
log.Println("Floar", flora.VERSION)
run("0.0.0.0:" + fmt.Sprintf("%d", flora.SOCKS_PORT))
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)
}