github.com/geph-official/geph2@v0.22.6-0.20210211030601-f527cb59b0df/cmd/geph-client/main.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"flag"
     6  	"fmt"
     7  	"math/rand"
     8  	mrand "math/rand"
     9  	"net"
    10  	"net/http"
    11  	"net/http/httputil"
    12  	"net/url"
    13  	"os"
    14  	"os/user"
    15  	"runtime/debug"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/vharitonsky/iniflags"
    20  
    21  	"github.com/acarl005/stripansi"
    22  	"github.com/ethereum/go-ethereum/rlp"
    23  	"github.com/geph-official/geph2/libs/bdclient"
    24  	"golang.org/x/net/proxy"
    25  
    26  	log "github.com/sirupsen/logrus"
    27  )
    28  
    29  var username string
    30  var password string
    31  var ticketFile string
    32  var binderFront string
    33  var binderHost string
    34  var exitName string
    35  var exitKey string
    36  var forceBridges bool
    37  
    38  var loginCheck bool
    39  var binderProxy string
    40  
    41  var direct bool
    42  
    43  var socksAddr string
    44  var httpAddr string
    45  var statsAddr string
    46  var dnsAddr string
    47  var fakeDNS bool
    48  var bypassChinese bool
    49  
    50  var singleHop string
    51  var upstreamProxy string
    52  var additionalBridges string
    53  var forceWarpfront bool
    54  
    55  var sWrap *multipool
    56  
    57  // GitVersion is the build version
    58  var GitVersion string
    59  
    60  var binders *bdclient.Multiclient
    61  
    62  func getBindInfo() (string, string) {
    63  	fronts := strings.Split(binderFront, ",")
    64  	hosts := strings.Split(binderHost, ",")
    65  	randIdx := rand.Int() % len(fronts)
    66  	return fronts[randIdx], hosts[randIdx]
    67  }
    68  
    69  // find the fastest binder and stick to it
    70  func binderRace() {
    71  	fronts := strings.Split(binderFront, ",")
    72  	hosts := strings.Split(binderHost, ",")
    73  	if len(fronts) != len(hosts) {
    74  		panic("binderFront and binderHost must be of identical length")
    75  	}
    76  	var bbb []*bdclient.Client
    77  	for i := 0; i < len(fronts); i++ {
    78  		i := i
    79  		bdc := bdclient.NewClient(fronts[i], hosts[i], fmt.Sprintf("geph_client/%v", GitVersion))
    80  		bbb = append(bbb, bdc)
    81  	}
    82  	binders = bdclient.NewMulticlient(bbb)
    83  }
    84  
    85  func memMiser() {
    86  	for {
    87  		debug.FreeOSMemory()
    88  		time.Sleep(time.Second * 5)
    89  	}
    90  }
    91  
    92  func main() {
    93  	// debug.SetGCPercent(5)
    94  	// go memMiser()
    95  	go mrand.Seed(time.Now().UnixNano())
    96  	log.SetFormatter(&log.TextFormatter{
    97  		FullTimestamp: false,
    98  		ForceColors:   true,
    99  	})
   100  	log.SetLevel(log.DebugLevel)
   101  
   102  	// configfile path
   103  	usr, err := user.Current()
   104  	if err != nil {
   105  		log.Println("cannot read current user info, consider using -config=/path/to/cfgfile")
   106  	} else {
   107  		// default config file: $HOME/.config/client.conf, make sure chmod 600!
   108  		// use -config=/path/to/cfgfile to override
   109  		iniflags.SetConfigFile(usr.HomeDir + "/.config/client.conf")
   110  		iniflags.SetAllowMissingConfigFile(true)
   111  	}
   112  
   113  	// flags
   114  	flag.StringVar(&username, "username", "", "username")
   115  	flag.StringVar(&password, "password", "", "password")
   116  	flag.StringVar(&ticketFile, "ticketFile", "", "location for caching auth tickets")
   117  	flag.StringVar(&binderFront, "binderFront", "https://www.cdn77.com/v2,https://netlify.com/v2,https://ajax.aspnetcdn.com/v2", "binder domain-fronting hosts, comma separated")
   118  	flag.StringVar(&binderHost, "binderHost", "1680337695.rsc.cdn77.org,loving-bell-981479.netlify.app,gephbinder-vzn.azureedge.net", "real hostname of the binder, comma separated")
   119  	flag.StringVar(&exitName, "exitName", "us-sfo-01.exits.geph.io", "qualified name of the exit node selected")
   120  	flag.StringVar(&exitKey, "exitKey", "2f8571e4795032433098af285c0ce9e43c973ac3ad71bf178e4f2aaa39794aec", "ed25519 pubkey of the selected exit")
   121  	flag.BoolVar(&forceBridges, "forceBridges", false, "force the use of obfuscated bridges")
   122  	flag.StringVar(&socksAddr, "socksAddr", "localhost:9909", "SOCKS5 listening address")
   123  	flag.StringVar(&httpAddr, "httpAddr", "localhost:9910", "HTTP proxy listener")
   124  	flag.StringVar(&statsAddr, "statsAddr", "localhost:9809", "HTTP listener for statistics")
   125  	flag.StringVar(&dnsAddr, "dnsAddr", "localhost:9983", "local DNS listener")
   126  	flag.BoolVar(&fakeDNS, "fakeDNS", true, "return fake results for DNS")
   127  	flag.BoolVar(&loginCheck, "loginCheck", false, "do a login check and immediately exit with code 0")
   128  	flag.StringVar(&binderProxy, "binderProxy", "", "if set, proxy the binder at the given listening address and do nothing else")
   129  	// flag.StringVar(&cachePath, "cachePath", os.TempDir()+"/geph-cache.db", "location of state cache")
   130  	flag.StringVar(&upstreamProxy, "upstreamProxy", "", "upstream SOCKS5 proxy")
   131  	flag.StringVar(&additionalBridges, "additionalBridges", "", "additional bridges, in the form of cookie1@host1;cookie2@host2 etc")
   132  	flag.StringVar(&singleHop, "singleHop", "", "if set in form pk@host:port, location of a single-hop server. OVERRIDES BINDER AND AUTHENTICATION!")
   133  	flag.BoolVar(&bypassChinese, "bypassChinese", false, "bypass proxy for Chinese domains")
   134  	flag.BoolVar(&forceWarpfront, "forceWarpfront", false, "force use of warpfront")
   135  	iniflags.Parse()
   136  	hackDNS()
   137  	if dnsAddr != "" {
   138  		go doDNS()
   139  	}
   140  	go listenStats()
   141  	if GitVersion == "" {
   142  		GitVersion = "NOVER"
   143  	}
   144  	logPipeR, logPipeW, _ := os.Pipe()
   145  	log.SetOutput(logPipeW)
   146  	go func() {
   147  		buffi := bufio.NewReader(logPipeR)
   148  		for {
   149  			line, err := buffi.ReadString('\n')
   150  			if err != nil {
   151  				return
   152  			}
   153  			fmt.Fprint(os.Stderr, line)
   154  			useStats(func(sc *stats) {
   155  				sc.LogLines = append(sc.LogLines,
   156  					stripansi.Strip(strings.TrimSpace(line)))
   157  			})
   158  		}
   159  	}()
   160  	binderRace()
   161  
   162  	log.Println("GephNG version", GitVersion)
   163  	// special actions
   164  	if loginCheck {
   165  		log.Println("loginCheck mode")
   166  		go func() {
   167  			time.Sleep(time.Second * 60)
   168  			os.Exit(10)
   169  		}()
   170  	}
   171  	if singleHop == "" {
   172  		if binderProxy != "" {
   173  			log.Println("binderProxy mode on", binderProxy)
   174  			revProx := &httputil.ReverseProxy{
   175  				Director: func(req *http.Request) {
   176  					bURL, bHost := getBindInfo()
   177  					bURLP, err := url.Parse(bURL)
   178  					if err != nil {
   179  						panic(err)
   180  					}
   181  					log.Println("reverse proxying", req.Method, req.URL)
   182  					req.Host = bHost
   183  					req.URL.Scheme = bURLP.Scheme
   184  					req.URL.Host = bURLP.Host
   185  					req.URL.Path = bURLP.Path + "/" + req.URL.Path
   186  				},
   187  				ModifyResponse: func(resp *http.Response) error {
   188  					resp.Header.Add("Access-Control-Allow-Origin", "*")
   189  					resp.Header.Add("Access-Control-Expose-Headers", "*")
   190  					resp.Header.Add("Access-Control-Allow-Methods", "*")
   191  					resp.Header.Add("Access-Control-Allow-Headers", "*")
   192  					return nil
   193  				},
   194  			}
   195  			if err := http.ListenAndServe(binderProxy, revProx); err != nil {
   196  				panic(err)
   197  			}
   198  		}
   199  
   200  		// connect to bridge
   201  		// automatically pick mode
   202  		if upstreamProxy != "" {
   203  			log.Println("upstream proxy enabled, no bridges")
   204  			direct = true
   205  		} else if !forceBridges {
   206  		retry:
   207  			var country string
   208  			binders.Do(func(b *bdclient.Client) error {
   209  				var cinfo bdclient.ClientInfo
   210  				cinfo, err = b.GetClientInfo()
   211  				country = cinfo.Country
   212  				return err
   213  			})
   214  			if err != nil {
   215  				log.Println("cannot get country", err)
   216  				goto retry
   217  			} else {
   218  				log.Println("country is", country)
   219  				if country == "CN" {
   220  					log.Println("in CHINA, must use bridges")
   221  				} else {
   222  					log.Println("disabling bridges")
   223  					direct = true
   224  				}
   225  			}
   226  		} else {
   227  			direct = false
   228  		}
   229  	}
   230  	sWrap = newMultipool()
   231  
   232  	// confirm we are connected
   233  	func() {
   234  		for {
   235  			rm, _, ok := sWrap.DialCmd("ip")
   236  			if !ok {
   237  				log.Println("FAILED to get IP, retrying...")
   238  				time.Sleep(time.Second)
   239  				continue
   240  			}
   241  			defer rm.Close()
   242  			var ip string
   243  			err := rlp.Decode(rm, &ip)
   244  			if err != nil {
   245  				log.Println("Uh oh, cannot get IP!", err)
   246  				continue
   247  			}
   248  			ip = strings.TrimSpace(ip)
   249  			log.Println("Successfully got external IP", ip)
   250  			useStats(func(sc *stats) {
   251  				sc.Connected = true
   252  				sc.PublicIP = ip
   253  			})
   254  			return
   255  		}
   256  	}()
   257  	go listenHTTP()
   258  	listenSocks()
   259  }
   260  
   261  func dialTun(dest string) (conn net.Conn, err error) {
   262  	sks, err := proxy.SOCKS5("tcp", socksAddr, nil, proxy.Direct)
   263  	if err != nil {
   264  		return
   265  	}
   266  	conn, err = sks.Dial("tcp", dest)
   267  	return
   268  }