diff --git a/.gitignore b/.gitignore index a59137d..5997e79 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ _testmain.go *.test *.prof flora-kit +.DS_Store diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..76a6d70 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +install: + @go get +build: + @go build main.go +run: + @go run main.go +test: + @go test ./flora \ No newline at end of file diff --git a/README.md b/README.md index ea11c3f..8158aae 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,36 @@ # flora-kit Flora Proxy server + +## 网络调整命令 + +列出所有网络链接方式 + +``` +$ networksetup -listallnetworkservices +``` + +并循环设置 + +获取某个连接的 Socks Proxy 配置 + +``` +$ networksetup -setsocksfirewallproxy +``` + +获取某个连接的 HTTP Procx 配置 + +``` +$ networksetup -setwebproxy +``` + +获取某个连接的 HTTPS Proxy 配置 + +``` +$ networksetup -setsecurewebproxy +``` + +设置 Proxy ByPass + +``` +$ networksetup -setproxybypassdomains [domain2] [...] +``` \ No newline at end of file diff --git a/flora/config.go b/flora/config.go index e71c6f6..63fccff 100644 --- a/flora/config.go +++ b/flora/config.go @@ -5,11 +5,14 @@ import ( "github.com/go-ini/ini" ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" "log" + "net" "os" + "strconv" "strings" ) const ( + SOCKS_PORT = 7657 RULE_REJECT = 0 RULE_DIRECT = 1 RULE_PROXY = 2 @@ -25,12 +28,20 @@ var rulePrefixDomains = []*DomainRule{} var ruleKeywordDomains = []*DomainRule{} var ruleGeoIP = &DomainRule{} -var Config ss.Config +var ssConfig ss.Config var iniConfig *ini.File -func init() { - Config.LocalPort = 7657 +type ProxyServerCipher struct { + Server string + Cipher *ss.Cipher +} +var ProxyServers struct { + SrvCipher []*ProxyServerCipher + FailCnt []int // failed connection count +} + +func init() { var configFilename = "/Users/jason/Dropbox/Surge/Default.conf" var userConfigFilename = "/Users/jason/Dropbox/Surge/User.conf" var iniOpts = ini.LoadOptions{ @@ -54,30 +65,33 @@ func init() { // [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]} - Config.ServerPassword = append(Config.ServerPassword, serverInfo) + ssConfig.ServerPassword = append(ssConfig.ServerPassword, serverInfo) } } - if Config.Method == "" { - Config.Method = "aes-256-cfb" + if ssConfig.Method == "" { + ssConfig.Method = "aes-256-cfb" } - if len(Config.ServerPassword) == 0 { - if !enoughSSOptions(&Config) { + 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 Config.LocalPort == 0 { + if ssConfig.LocalPort == 0 { fmt.Fprintln(os.Stderr, "must specify local port") os.Exit(1) } } + + parseServerConfig() } // 载入 [Rule] @@ -119,9 +133,7 @@ func enoughSSOptions(config *ss.Config) bool { func readArrayLine(source string) []string { out := strings.Split(source, ",") for i, str := range out { - out[i] = strings.Trim(str, " ") - out[i] = strings.Trim(str, "\t") - out[i] = strings.Trim(str, "\n") + out[i] = strings.TrimSpace(str) } return out } @@ -146,3 +158,79 @@ func RuleOfHost(host string) *DomainRule { } return &DomainRule{S: "", T: RULE_DIRECT} } + +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 { + log.Println("available remote server", se.Server) + } + return +} diff --git a/flora/geoip.go b/flora/geoip.go new file mode 100644 index 0000000..5ed5d6f --- /dev/null +++ b/flora/geoip.go @@ -0,0 +1,29 @@ +package flora + +import ( + "fmt" + "github.com/oschwald/geoip2-golang" + "net" + "strings" +) + +var geoDB *geoip2.Reader + +func init() { + file := "./geoip.mmdb" + db, err := geoip2.Open(file) + defer db.Close() + if err != nil { + fmt.Printf("Could not open GeoIP database\n") + } + geoDB = db +} + +func GeoIP(ipaddr string) string { + ip := net.ParseIP(ipaddr) + country, err := geoDB.Country(ip) + if err != nil { + return "" + } + return strings.ToLower(country.Country.IsoCode) +} diff --git a/geoip.mmdb b/geoip.mmdb new file mode 100644 index 0000000..0d16dd4 Binary files /dev/null and b/geoip.mmdb differ diff --git a/geoip_test.go b/geoip_test.go new file mode 100644 index 0000000..0560be8 --- /dev/null +++ b/geoip_test.go @@ -0,0 +1,20 @@ +package main_test + +import ( + "github.com/huacnlee/flora-kit/flora" + "testing" +) + +func TestGeoIP(t *testing.T) { + if flora.GeoIP("121.0.29.91") != "cn" { + t.Errorf("121.0.29.91 should be cn") + } + + if flora.GeoIP("218.253.0.89") != "hk" { + t.Errorf("218.253.0.89 should be hk") + } + + if flora.GeoIP("218.176.242.11") != "jp" { + t.Errorf("218.176.242.11 should be jp") + } +} diff --git a/main.go b/main.go index 416a512..843ae47 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,8 @@ package main import ( "encoding/binary" "errors" + "fmt" + "github.com/huacnlee/flora-kit/flora" "io" "log" "math/rand" @@ -10,8 +12,6 @@ import ( "strconv" "time" - "github.com/huacnlee/flora-kit/flora" - ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" ) @@ -149,104 +149,19 @@ func getRequest(conn net.Conn) (rawaddr []byte, host string, err error) { return } -type ServerCipher struct { - server string - cipher *ss.Cipher -} - -var servers struct { - srvCipher []*ServerCipher - failCnt []int // failed connection count -} - -func parseServerConfig(config *ss.Config) { - 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) - servers.srvCipher = make([]*ServerCipher, n) - - for i, s := range srvArr { - if hasPort(s) { - log.Println("ignore server_port option for server", s) - servers.srvCipher[i] = &ServerCipher{s, cipher} - } else { - servers.srvCipher[i] = &ServerCipher{net.JoinHostPort(s, srvPort), cipher} - } - } - } else { - // multiple servers - n := len(config.ServerPassword) - servers.srvCipher = make([]*ServerCipher, 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 - } - servers.srvCipher[i] = &ServerCipher{server, cipher} - i++ - } - } - servers.failCnt = make([]int, len(servers.srvCipher)) - for _, se := range servers.srvCipher { - log.Println("available remote server", se.server) - } - return -} - func connectToServer(serverId int, rawaddr []byte, addr string) (remote *ss.Conn, err error) { - se := servers.srvCipher[serverId] - remote, err = ss.DialWithRawAddr(rawaddr, se.server, se.cipher.Copy()) + 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 servers.failCnt[serverId] < maxFailCnt { - servers.failCnt[serverId]++ + if flora.ProxyServers.FailCnt[serverId] < maxFailCnt { + flora.ProxyServers.FailCnt[serverId]++ } return nil, err } - debug.Printf("connected to %s via %s\n", addr, se.server) - servers.failCnt[serverId] = 0 + debug.Printf("connected to %s via %s\n", addr, se.Server) + flora.ProxyServers.FailCnt[serverId] = 0 return } @@ -256,11 +171,11 @@ func connectToServer(serverId int, rawaddr []byte, addr string) (remote *ss.Conn // servers. func createServerConn(rawaddr []byte, addr string) (remote *ss.Conn, err error) { const baseFailCnt = 20 - n := len(servers.srvCipher) + 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 servers.failCnt[i] > 0 && rand.Intn(servers.failCnt[i]+baseFailCnt) != 0 { + if flora.ProxyServers.FailCnt[i] > 0 && rand.Intn(flora.ProxyServers.FailCnt[i]+baseFailCnt) != 0 { skipped = append(skipped, i) continue } @@ -311,7 +226,7 @@ func handleConnection(conn net.Conn) { remote, err := createServerConn(rawaddr, addr) if err != nil { - if len(servers.srvCipher) > 1 { + if len(flora.ProxyServers.SrvCipher) > 1 { log.Println("Failed connect to all avaiable shadowsocks server") } return @@ -346,8 +261,5 @@ func run(listenAddr string) { func main() { ss.SetDebug(true) - - parseServerConfig(&flora.Config) - - run("0.0.0.0:" + strconv.Itoa(flora.Config.LocalPort)) + run("0.0.0.0:" + fmt.Sprintf("%d", flora.SOCKS_PORT)) }