├── deploy.sh ├── gen_key_cert.sh ├── .travis.yml ├── hmac.go ├── go.mod ├── hmac_test.go ├── .gitignore ├── handler.go ├── http_test.go ├── proto.go ├── proxy.go ├── cmd ├── local │ └── main.go └── server │ └── main.go ├── Makefile ├── README.md ├── go.sum ├── local_test.go ├── local.go └── http.go /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ve 4 | 5 | if [ ! -z "$TRAVIS_TAG" ];then 6 | echo "the tag is $TRAVIS_TAG, will deploy...." 7 | else 8 | echo "will not deploy..." 9 | exit 0 10 | fi 11 | 12 | make deploy 13 | 14 | ghr -u ls0f -t $GITHUB_TOKEN -r cracker --replace --debug $TRAVIS_TAG dist/ -------------------------------------------------------------------------------- /gen_key_cert.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # openssl 1.1.1 4 | read -p "Enter your ip or domain:" domain 5 | openssl req -subj "/CN=${domain}" \ 6 | -addext "subjectAltName=DNS:${domain},DNS:*.${domain},IP:${domain}" \ 7 | -x509 -sha256 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 1024 -nodes 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | #branches: 4 | # only: 5 | # - master 6 | 7 | go: 8 | - 1.7 9 | env: 10 | - "PATH=/home/travis/gopath/bin:$PATH" 11 | 12 | before_install: 13 | - go get github.com/mitchellh/gox 14 | - go get github.com/tcnksm/ghr 15 | 16 | script: 17 | - make vendor 18 | - make test 19 | 20 | after_success: 21 | - sh ./deploy.sh 22 | -------------------------------------------------------------------------------- /hmac.go: -------------------------------------------------------------------------------- 1 | package cracker 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha1" 6 | "fmt" 7 | ) 8 | 9 | func GenHMACSHA1(key, raw string) string { 10 | k := []byte(key) 11 | mac := hmac.New(sha1.New, k) 12 | mac.Write([]byte(raw)) 13 | return fmt.Sprintf("%x", mac.Sum(nil)) 14 | } 15 | 16 | func VerifyHMACSHA1(key, raw, sign string) bool { 17 | return GenHMACSHA1(key, raw) == sign 18 | } 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ls0f/cracker 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/golang/glog v1.2.4 7 | github.com/ls0f/proxylib v0.0.0-20180928021808-8e114c280464 8 | github.com/pborman/uuid v1.2.1 9 | github.com/stretchr/testify v1.8.2 10 | gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/google/uuid v1.0.0 // indirect 16 | github.com/pmezard/go-difflib v1.0.0 // indirect 17 | golang.org/x/net v0.23.0 // indirect 18 | gopkg.in/yaml.v3 v3.0.1 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /hmac_test.go: -------------------------------------------------------------------------------- 1 | package cracker 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestGenHMACSHA1(t *testing.T) { 10 | assert.Equal(t, GenHMACSHA1("123456", "123456"), "74b55b6ab2b8e438ac810435e369e3047b3951d0") 11 | } 12 | 13 | func TestVerifyHMACSHA1(t *testing.T) { 14 | assert.True(t, VerifyHMACSHA1("123456", "123456", "74b55b6ab2b8e438ac810435e369e3047b3951d0")) 15 | } 16 | 17 | func TestVerifyHMACSHA12(t *testing.T) { 18 | 19 | assert.False(t, VerifyHMACSHA1("123456", "123456", "74b55b6ab2b8e438ac810435e369e304")) 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | # Output of the go coverage tool, specifically when used with LiteIDE 27 | *.out 28 | 29 | # External packages folder 30 | server/server 31 | .idea/* 32 | bin/ 33 | src/github.com/ 34 | src/gopkg.in/ 35 | dist/* 36 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package cracker 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | type Handler struct { 12 | Server string 13 | Secret string 14 | Interval time.Duration 15 | } 16 | 17 | func (h *Handler) Connect(addr string) (io.ReadWriteCloser, error) { 18 | if strings.HasSuffix(h.Server, "/") { 19 | h.Server = h.Server[:len(h.Server)-1] 20 | } 21 | conn := &localProxyConn{server: h.Server, secret: h.Secret, interval: h.Interval} 22 | host := strings.Split(addr, ":")[0] 23 | port := strings.Split(addr, ":")[1] 24 | uuid, err := conn.connect(host, port) 25 | if err != nil { 26 | return nil, errors.New(fmt.Sprintf("connect %s %v", addr, err)) 27 | } 28 | conn.uuid = uuid 29 | if h.Interval == 0 { 30 | err = conn.pull() 31 | if err != nil { 32 | return nil, err 33 | } 34 | } 35 | conn.close = make(chan bool) 36 | go conn.alive() 37 | return conn, nil 38 | } 39 | 40 | func (h *Handler) Clean() {} 41 | -------------------------------------------------------------------------------- /http_test.go: -------------------------------------------------------------------------------- 1 | package cracker 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "sync" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var ( 15 | lock sync.Mutex 16 | serverHaveStart bool 17 | testP *httpProxy 18 | ) 19 | 20 | const ( 21 | testAddr = ":12245" 22 | testSecret = "12345" 23 | ) 24 | 25 | func startProxyServer() { 26 | lock.Lock() 27 | defer lock.Unlock() 28 | if serverHaveStart { 29 | return 30 | } 31 | testP = NewHttpProxy(testAddr, testSecret, false) 32 | go testP.Listen() 33 | time.Sleep(time.Millisecond * 100) 34 | serverHaveStart = true 35 | } 36 | 37 | func TestHandler_Connect(t *testing.T) { 38 | startProxyServer() 39 | 40 | res, err := http.Get(fmt.Sprintf("http://127.0.0.1%s%s", testAddr, CONNECT)) 41 | if err != nil { 42 | assert.NoError(t, err) 43 | } 44 | assert.Equal(t, res.StatusCode, 404) 45 | defer res.Body.Close() 46 | body, _ := ioutil.ReadAll(res.Body) 47 | assert.Equal(t, "404", string(body)) 48 | } 49 | -------------------------------------------------------------------------------- /proto.go: -------------------------------------------------------------------------------- 1 | package cracker 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | const ( 9 | HeadError = 500 10 | HeadOK = 200 11 | HeadData = 201 12 | HeadHeart = 202 13 | HeadQuit = 203 14 | HeadNotFound = 404 15 | ) 16 | 17 | func WriteHTTPError(w http.ResponseWriter, message string) { 18 | w.WriteHeader(HeadError) 19 | fmt.Fprintf(w, "%s", message) 20 | } 21 | 22 | func WriteNotFoundError(w http.ResponseWriter, message string) { 23 | w.WriteHeader(HeadNotFound) 24 | fmt.Fprintf(w, "%s", message) 25 | } 26 | 27 | func WriteHTTPOK(w http.ResponseWriter, data string) { 28 | w.WriteHeader(HeadOK) 29 | fmt.Fprintf(w, "%s", data) 30 | } 31 | 32 | func WriteHTTPData(w http.ResponseWriter, data []byte) { 33 | w.WriteHeader(HeadData) 34 | w.Write(data) 35 | } 36 | 37 | func WriteHTTPQuit(w http.ResponseWriter, data string) { 38 | w.WriteHeader(HeadQuit) 39 | fmt.Fprintf(w, "%s", data) 40 | } 41 | 42 | func WriteHTTPHeart(w http.ResponseWriter, data string) { 43 | w.WriteHeader(HeadHeart) 44 | fmt.Fprintf(w, "%s", data) 45 | } 46 | -------------------------------------------------------------------------------- /proxy.go: -------------------------------------------------------------------------------- 1 | package cracker 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type proxyConn struct { 10 | remote net.Conn 11 | uuid string 12 | close chan struct{} 13 | heart chan struct{} 14 | sync.Mutex 15 | hasClosed bool 16 | } 17 | 18 | func newProxyConn(remote net.Conn, uuid string) *proxyConn { 19 | return &proxyConn{remote: remote, uuid: uuid, 20 | close: make(chan struct{}), 21 | heart: make(chan struct{}), 22 | } 23 | } 24 | 25 | func (pc *proxyConn) Close() { 26 | pc.Lock() 27 | pc.hasClosed = true 28 | pc.Unlock() 29 | select { 30 | case pc.close <- struct{}{}: 31 | default: 32 | 33 | } 34 | } 35 | 36 | func (pc *proxyConn) IsClosed() bool { 37 | pc.Lock() 38 | defer pc.Unlock() 39 | return pc.hasClosed 40 | } 41 | 42 | func (pc *proxyConn) Heart() { 43 | select { 44 | case pc.heart <- struct{}{}: 45 | default: 46 | } 47 | } 48 | 49 | func (pc *proxyConn) Do() { 50 | 51 | defer pc.remote.Close() 52 | 53 | for { 54 | select { 55 | case <-time.After(time.Second * heartTTL): 56 | return 57 | case <-pc.close: 58 | return 59 | case <-pc.heart: 60 | continue 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cmd/local/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | g "github.com/golang/glog" 9 | "github.com/ls0f/cracker" 10 | p "github.com/ls0f/proxylib" 11 | ) 12 | 13 | var ( 14 | GitTag = "2000.01.01.release" 15 | BuildTime = "2000-01-01T00:00:00+0800" 16 | ) 17 | 18 | func main() { 19 | addr := flag.String("addr", "127.0.0.1:1080", "listen addr") 20 | raddr := flag.String("raddr", "", "remote http url(e.g, https://example.com)") 21 | secret := flag.String("secret", "", "secret key") 22 | version := flag.Bool("version", false, "version") 23 | interval := flag.Duration("interval", 0, "interval of pulling, 0 means use http chunked") 24 | cert := flag.String("cert", "", "cert file") 25 | flag.Parse() 26 | 27 | defer g.Flush() 28 | 29 | if *version { 30 | fmt.Printf("GitTag: %s \n", GitTag) 31 | fmt.Printf("BuildTime: %s \n", BuildTime) 32 | os.Exit(0) 33 | } 34 | if *cert != "" { 35 | cracker.Init(*cert) 36 | } 37 | s := p.Server{Addr: *addr} 38 | handler := &cracker.Handler{ 39 | Server: *raddr, 40 | Secret: *secret, 41 | Interval: *interval, 42 | } 43 | s.HTTPHandler = handler 44 | s.Socks5Handler = handler 45 | g.Fatal(s.ListenAndServe()) 46 | } 47 | -------------------------------------------------------------------------------- /cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | g "github.com/golang/glog" 9 | "github.com/ls0f/cracker" 10 | ) 11 | 12 | var ( 13 | GitTag = "2000.01.01.release" 14 | BuildTime = "2000-01-01T00:00:00+0800" 15 | ) 16 | 17 | func main() { 18 | 19 | addr := flag.String("addr", "", "listen addr") 20 | secret := flag.String("secret", "", "secret") 21 | version := flag.Bool("version", false, "version") 22 | https := flag.Bool("https", false, "https") 23 | cert := flag.String("cert", "", "cert file") 24 | key := flag.String("key", "", "private key file") 25 | flag.Parse() 26 | if *version { 27 | fmt.Printf("GitTag: %s \n", GitTag) 28 | fmt.Printf("BuildTime: %s \n", BuildTime) 29 | os.Exit(0) 30 | } 31 | defer g.Flush() 32 | p := cracker.NewHttpProxy(*addr, *secret, *https) 33 | if *https { 34 | f, err := os.Stat(*cert) 35 | if err != nil { 36 | g.Fatal(err) 37 | } 38 | if f.IsDir() { 39 | g.Fatal("cert should be file") 40 | } 41 | f, err = os.Stat(*key) 42 | if err != nil { 43 | g.Fatal(err) 44 | } 45 | if f.IsDir() { 46 | g.Fatal("key should be file") 47 | } 48 | p.ListenHTTPS(*cert, *key) 49 | } else { 50 | p.Listen() 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | MODULES := proxy logger 4 | BIN := cmd/server cmd/local 5 | 6 | GITTAG := `git describe --tags` 7 | VERSION := `git describe --abbrev=0 --tags` 8 | RELEASE := `git rev-list $(shell git describe --abbrev=0 --tags).. --count` 9 | BUILD_TIME := `date +%FT%T%z` 10 | # Setup the -ldflags option for go build here, interpolate the variable values 11 | LDFLAGS := -ldflags "-X main.GitTag=${GITTAG} -X main.BuildTime=${BUILD_TIME}" 12 | 13 | vendor: 14 | go get ./... 15 | go get github.com/stretchr/testify/assert 16 | 17 | 18 | test: 19 | go test ./... --race -cover; 20 | 21 | fmt: 22 | find . -name "*.go" -type f -exec echo {} \; |\ 23 | while IFS= read -r line; \ 24 | do \ 25 | echo "$$line";\ 26 | goimports -w "$$line" "$$line";\ 27 | done 28 | 29 | build: 30 | mkdir -p bin;\ 31 | echo ==================================; \ 32 | for m in $(BIN); do \ 33 | cd $(PWD)/$$m && go build ${LDFLAGS} -o ../../bin/$$m --race ; \ 34 | done 35 | echo ==================================; \ 36 | cd $(PWD) && cp gen_key_cert.sh ./bin 37 | 38 | install: vendor build 39 | 40 | deploy: 41 | for m in $(BIN); do \ 42 | cd $(PWD)/$$m && gox ${LDFLAGS} -osarch="linux/amd64" -output ../../dist/{{.OS}}_{{.Arch}}_{{.Dir}};\ 43 | cd $(PWD)/$$m && gox ${LDFLAGS} -os="windows" -output ../../dist/{{.OS}}_{{.Arch}}_{{.Dir}};\ 44 | cd $(PWD)/$$m && gox ${LDFLAGS} -osarch="darwin/amd64" -output ../../dist/{{.OS}}_{{.Arch}}_{{.Dir}};\ 45 | done 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cracker 2 | proxy over http[s], support http,socks5 proxy. 3 | 4 | ``` 5 | +------------+ +--------------+ 6 | | local app | <=======> |local proxy | <####### 7 | +------------+ +--------------+ # 8 | # 9 | # 10 | # http[s] 11 | # 12 | # 13 | +-------------+ +--------------+ # 14 | | target host | <=======> |http[s] server| <##### 15 | +-------------+ +--------------+ 16 | ``` 17 | 18 | # Install 19 | 20 | Download the latest binaries from this [release page](https://github.com/ls0f/cracker/releases). 21 | 22 | # Usage 23 | 24 | ## Server side (Run on your vps or other application container platform) 25 | 26 | ``` 27 | ./server -addr :8080 -secret -logtostderr 28 | ``` 29 | 30 | ## Local side (Run on your local pc) 31 | 32 | ``` 33 | ./local -raddr http://example.com:8080 -secret -logtostderr 34 | ``` 35 | 36 | ## https 37 | 38 | It is strongly recommended to open the https option on the server side. 39 | 40 | ### Notice 41 | 42 | If you have a ssl certificate, It would be easy. 43 | 44 | ``` 45 | ./server -addr :443 -secret -https -cert /etc/cert.pem -key /etc/key.pem -logtostderr 46 | ``` 47 | 48 | ``` 49 | ./local -raddr https://example.com -secret -logtostderr 50 | ``` 51 | 52 | Of Course, you can create a self-signed ssl certificate by openssl. 53 | 54 | ``` 55 | sh -c "$(curl https://raw.githubusercontent.com/ls0f/cracker/master/gen_key_cert.sh)" 56 | ``` 57 | 58 | ``` 59 | ./server -addr :443 -secret -https -cert /etc/self-signed-cert.pem -key /etc/self-ca-key.pem -logtostderr -v=10 60 | ``` 61 | 62 | ``` 63 | ./local -raddr https://example.com -secret -cert /etc/self-signed-cert.pem -logtostderr -v=10 64 | ``` 65 | 66 | 67 | # Quick Test 68 | 69 | If you don't want to run the server side, I did for you :) you only need to run the local side. 70 | 71 | ``` 72 | ./local -raddr https://lit-citadel-13724.herokuapp.com -secret 123456 -logtostderr 73 | ``` 74 | 75 | [Deploy the server side on heroku](https://github.com/ls0f/cracker-heroku) 76 | 77 | 78 | # Next 79 | 80 | Play with [SwitchyOmega](https://github.com/FelisCatus/SwitchyOmega/releases) 81 | 82 | # ChatGPT 83 | [Free ChatGPT Proxy](https://ls0f.github.io/post/chatgpt%E4%BB%A3%E7%90%86/) 84 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= 5 | github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= 6 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 7 | github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= 8 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 9 | github.com/ls0f/proxylib v0.0.0-20180928021808-8e114c280464 h1:Z3Xc7i8FWEgPtVgAarvmCkuVpQyPicxFZ6Gk8vcwqwE= 10 | github.com/ls0f/proxylib v0.0.0-20180928021808-8e114c280464/go.mod h1:bToRS7zsY68XHkcTAGPB+SqbH6EKF/RKdeAWlY+w9Jw= 11 | github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= 12 | github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 16 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 17 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 18 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 19 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 20 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 21 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 22 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 23 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 24 | gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e h1:wGA78yza6bu/mWcc4QfBuIEHEtc06xdiU0X8sY36yUU= 25 | gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e/go.mod h1:xsQCaysVCudhrYTfzYWe577fCe7Ceci+6qjO2Rdc0Z4= 26 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 27 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 28 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 29 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 30 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 31 | -------------------------------------------------------------------------------- /local_test.go: -------------------------------------------------------------------------------- 1 | package cracker 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestProxyConn(t *testing.T) { 12 | startProxyServer() 13 | 14 | h := &Handler{Server: "http://localhost" + testAddr, Secret: testSecret} 15 | conn, err := h.Connect("localhost" + testAddr) 16 | assert.NoError(t, err) 17 | conn.Write([]byte("GET /ping HTTP/1.1\r\nHost: localhost\r\n\r\n")) 18 | go func() { 19 | time.Sleep(time.Millisecond * 100) 20 | conn.Close() 21 | }() 22 | body, _ := ioutil.ReadAll(conn) 23 | assert.Contains(t, string(body), "pong") 24 | } 25 | 26 | func TestProxyConn2(t *testing.T) { 27 | startProxyServer() 28 | 29 | h := &Handler{Server: "http://localhost" + testAddr, Secret: testSecret, Interval: time.Millisecond * 20} 30 | conn, err := h.Connect("localhost" + testAddr) 31 | assert.NoError(t, err) 32 | conn.Write([]byte("GET /connect HTTP/1.1\r\nHost: localhost\r\n\r\n")) 33 | go func() { 34 | time.Sleep(time.Millisecond * 100) 35 | conn.Close() 36 | }() 37 | body, _ := ioutil.ReadAll(conn) 38 | assert.Contains(t, string(body), "404") 39 | } 40 | 41 | func TestProxyConn3(t *testing.T) { 42 | startProxyServer() 43 | 44 | h := &Handler{Server: "http://localhost" + testAddr, Secret: testSecret} 45 | conn, err := h.Connect("localhost" + testAddr) 46 | assert.NoError(t, err) 47 | p, ok := conn.(*localProxyConn) 48 | assert.True(t, ok) 49 | // wrong uuid 50 | p.uuid = "" 51 | _, err = conn.Write([]byte("GET /connect HTTP/1.1\r\nHost: localhost\r\n\r\n")) 52 | assert.Error(t, err) 53 | assert.Contains(t, err.Error(), "uuid don't exist") 54 | conn.Close() 55 | } 56 | 57 | func TestProxyConn4(t *testing.T) { 58 | startProxyServer() 59 | 60 | h := &Handler{Server: "http://localhost" + testAddr, Secret: testSecret, Interval: time.Millisecond * 20} 61 | conn, err := h.Connect("localhost" + testAddr) 62 | assert.NoError(t, err) 63 | p, ok := conn.(*localProxyConn) 64 | assert.True(t, ok) 65 | // wrong uuid 66 | p.uuid = "" 67 | body, err := ioutil.ReadAll(conn) 68 | assert.Error(t, err) 69 | assert.Contains(t, err.Error(), "uuid don't exist") 70 | conn.Close() 71 | assert.Empty(t, body) 72 | } 73 | 74 | func TestProxyConn5(t *testing.T) { 75 | startProxyServer() 76 | 77 | h := &Handler{Server: "http://localhost" + testAddr, Secret: testSecret, Interval: time.Millisecond * 20} 78 | conn, err := h.Connect("localhost" + testAddr) 79 | assert.NoError(t, err) 80 | p, ok := conn.(*localProxyConn) 81 | assert.True(t, ok) 82 | remote, ok := testP.proxyMap[p.uuid] 83 | assert.True(t, ok) 84 | assert.False(t, remote.IsClosed()) 85 | conn.Close() 86 | } 87 | 88 | func TestProxyConn6(t *testing.T) { 89 | startProxyServer() 90 | 91 | h := &Handler{Server: "http://localhost" + testAddr, Secret: testSecret, Interval: time.Millisecond * 20} 92 | conn, err := h.Connect("localhost" + testAddr) 93 | assert.NoError(t, err) 94 | p, ok := conn.(*localProxyConn) 95 | assert.True(t, ok) 96 | remote, ok := testP.proxyMap[p.uuid] 97 | assert.True(t, ok) 98 | conn.Close() 99 | assert.True(t, remote.IsClosed()) 100 | } 101 | -------------------------------------------------------------------------------- /local.go: -------------------------------------------------------------------------------- 1 | package cracker 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "net/http" 12 | "os" 13 | "time" 14 | 15 | g "github.com/golang/glog" 16 | "gopkg.in/bufio.v1" 17 | ) 18 | 19 | var hc = &http.Client{Transport: http.DefaultTransport} 20 | 21 | func Init(cert string) { 22 | if f, err := os.Stat(cert); err == nil && !f.IsDir() { 23 | var CAPOOL *x509.CertPool 24 | CAPOOL, err := x509.SystemCertPool() 25 | if err != nil { 26 | g.Warning(err) 27 | CAPOOL = x509.NewCertPool() 28 | } 29 | serverCert, err := ioutil.ReadFile(cert) 30 | if err != nil { 31 | g.Errorf("read cert.pem err:%s ", err) 32 | return 33 | } 34 | CAPOOL.AppendCertsFromPEM(serverCert) 35 | tp := hc.Transport.(*http.Transport) 36 | config := &tls.Config{RootCAs: CAPOOL} 37 | tp.TLSClientConfig = config 38 | g.Infof("load %s success ... ", cert) 39 | } else if err != nil { 40 | g.Error(err) 41 | } else { 42 | g.Errorf("%s is a dir", cert) 43 | } 44 | } 45 | 46 | type localProxyConn struct { 47 | uuid string 48 | server string 49 | secret string 50 | source io.ReadCloser 51 | close chan bool 52 | interval time.Duration 53 | dst io.WriteCloser 54 | } 55 | 56 | func (c *localProxyConn) gen_sign(req *http.Request) { 57 | 58 | ts := fmt.Sprintf("%d", time.Now().Unix()) 59 | req.Header.Set("UUID", c.uuid) 60 | req.Header.Set("timestamp", ts) 61 | req.Header.Set("sign", GenHMACSHA1(c.secret, ts)) 62 | } 63 | 64 | func (c *localProxyConn) chunkPush(data []byte, typ string) error { 65 | if c.dst != nil { 66 | _, err := c.dst.Write(data) 67 | return err 68 | } 69 | wr, ww := io.Pipe() 70 | req, _ := http.NewRequest("POST", c.server+PUSH, wr) 71 | req.Header.Set("TYP", typ) 72 | req.Header.Set("Transfer-Encoding", "chunked") 73 | c.gen_sign(req) 74 | req.Header.Set("Content-Type", "image/jpeg") 75 | go func() (err error) { 76 | defer wr.Close() 77 | defer ww.Close() 78 | res, err := hc.Do(req) 79 | if err != nil { 80 | return 81 | } 82 | defer res.Body.Close() 83 | body, _ := ioutil.ReadAll(res.Body) 84 | switch res.StatusCode { 85 | case HeadOK: 86 | return nil 87 | default: 88 | return errors.New(fmt.Sprintf("status code is %d, body is: %s", res.StatusCode, string(body))) 89 | } 90 | return nil 91 | }() 92 | 93 | c.dst = ww 94 | _, err := c.dst.Write(data) 95 | return err 96 | } 97 | 98 | func (c *localProxyConn) push(data []byte, typ string) error { 99 | buf := bufio.NewBuffer(data) 100 | req, _ := http.NewRequest("POST", c.server+PUSH, buf) 101 | req.Header.Set("TYP", typ) 102 | c.gen_sign(req) 103 | req.ContentLength = int64(len(data)) 104 | req.Header.Set("Content-Type", "image/jpeg") 105 | res, err := hc.Do(req) 106 | if err != nil { 107 | return err 108 | } 109 | defer res.Body.Close() 110 | body, _ := ioutil.ReadAll(res.Body) 111 | switch res.StatusCode { 112 | case HeadOK: 113 | return nil 114 | default: 115 | return errors.New(fmt.Sprintf("status code is %d, body is: %s", res.StatusCode, string(body))) 116 | } 117 | } 118 | 119 | func (c *localProxyConn) connect(dstHost, dstPort string) (uuid string, err error) { 120 | req, _ := http.NewRequest("GET", c.server+CONNECT, nil) 121 | c.gen_sign(req) 122 | req.Header.Set("DSTHOST", dstHost) 123 | req.Header.Set("DSTPORT", dstPort) 124 | cxt, cancel := context.WithTimeout(context.Background(), time.Second*timeout) 125 | defer cancel() 126 | req.WithContext(cxt) 127 | res, err := hc.Do(req) 128 | if err != nil { 129 | return "", err 130 | } 131 | body, _ := ioutil.ReadAll(res.Body) 132 | res.Body.Close() 133 | if res.StatusCode != HeadOK { 134 | return "", errors.New(fmt.Sprintf("status code is %d, body is:%s", res.StatusCode, string(body))) 135 | } 136 | return string(body), err 137 | 138 | } 139 | 140 | func (c *localProxyConn) pull() error { 141 | 142 | req, _ := http.NewRequest("GET", c.server+PULL, nil) 143 | req.Header.Set("Interval", fmt.Sprintf("%d", c.interval)) 144 | c.gen_sign(req) 145 | if c.interval > 0 { 146 | cxt, cancel := context.WithTimeout(context.Background(), time.Second*timeout) 147 | defer cancel() 148 | req.WithContext(cxt) 149 | 150 | } 151 | res, err := hc.Do(req) 152 | if err != nil { 153 | return err 154 | } 155 | if res.StatusCode != HeadOK { 156 | body, _ := ioutil.ReadAll(res.Body) 157 | res.Body.Close() 158 | return errors.New(fmt.Sprintf("status code is %d, body is %s", res.StatusCode, string(body))) 159 | } 160 | c.source = res.Body 161 | return nil 162 | } 163 | 164 | func (c *localProxyConn) Read(b []byte) (n int, err error) { 165 | 166 | if c.source == nil { 167 | if c.interval > 0 { 168 | if err = c.pull(); err != nil { 169 | return 170 | } 171 | } else { 172 | return 0, errors.New("pull http connection is not ready") 173 | } 174 | } 175 | n, err = c.source.Read(b) 176 | if err != nil { 177 | c.source.Close() 178 | c.source = nil 179 | } 180 | if err == io.EOF && c.interval > 0 { 181 | err = nil 182 | } 183 | return 184 | } 185 | 186 | func (c *localProxyConn) Write(b []byte) (int, error) { 187 | 188 | var err error 189 | if c.interval > 0 { 190 | err = c.push(b, DATA_TYP) 191 | } else { 192 | //err = c.push(b, DATA_TYP) 193 | err = c.chunkPush(b, DATA_TYP) 194 | } 195 | if err != nil { 196 | g.V(LDEBUG).Infof("push: %v", err) 197 | return 0, err 198 | } 199 | 200 | return len(b), nil 201 | } 202 | 203 | func (c *localProxyConn) alive() { 204 | for { 205 | select { 206 | case <-c.close: 207 | return 208 | case <-time.After(time.Second * heartTTL / 2): 209 | if err := c.push([]byte("alive"), HEART_TYP); err != nil { 210 | return 211 | } 212 | } 213 | } 214 | } 215 | 216 | func (c *localProxyConn) quit() error { 217 | return c.push([]byte("quit"), QUIT_TYP) 218 | } 219 | 220 | func (c *localProxyConn) Close() error { 221 | close(c.close) 222 | return c.quit() 223 | } 224 | -------------------------------------------------------------------------------- /http.go: -------------------------------------------------------------------------------- 1 | package cracker 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | "time" 7 | 8 | "fmt" 9 | "net" 10 | 11 | "errors" 12 | 13 | "sync" 14 | 15 | g "github.com/golang/glog" 16 | 17 | "io" 18 | 19 | "github.com/pborman/uuid" 20 | ) 21 | 22 | const ( 23 | CONNECT = "/connect" 24 | PING = "/ping" 25 | PULL = "/pull" 26 | PUSH = "/push" 27 | DOWNLOAD = "/download" 28 | CHUNK_PULL = "chunk_pull" 29 | CHUNK_PUSH = "chunk_push" 30 | ) 31 | const ( 32 | DATA_TYP = "data" 33 | QUIT_TYP = "quit" 34 | HEART_TYP = "heart" 35 | ) 36 | 37 | const ( 38 | timeout = 10 39 | signTTL = 10 40 | heartTTL = 60 41 | ) 42 | 43 | // Log level for glog 44 | const ( 45 | LFATAL = iota 46 | LERROR 47 | LWARNING 48 | LINFO 49 | LDEBUG 50 | ) 51 | 52 | const ( 53 | version = "20170803" 54 | ) 55 | 56 | type DevZero struct { 57 | } 58 | 59 | func (z DevZero) Read(b []byte) (n int, err error) { 60 | for i := range b { 61 | b[i] = 0 62 | } 63 | 64 | return len(b), nil 65 | } 66 | 67 | var bufPool = &sync.Pool{New: func() interface{} { return make([]byte, 1024*8) }} 68 | 69 | type httpProxy struct { 70 | addr string 71 | secret string 72 | proxyMap map[string]*proxyConn 73 | sync.Mutex 74 | https bool 75 | } 76 | 77 | func NewHttpProxy(addr, secret string, https bool) *httpProxy { 78 | return &httpProxy{addr: addr, 79 | secret: secret, 80 | proxyMap: make(map[string]*proxyConn), 81 | https: https, 82 | } 83 | } 84 | 85 | func (hp *httpProxy) handler() { 86 | http.HandleFunc(CONNECT, hp.connect) 87 | http.HandleFunc(PULL, hp.pull) 88 | http.HandleFunc(PUSH, hp.push) 89 | http.HandleFunc(PING, hp.ping) 90 | http.HandleFunc(CHUNK_PULL, hp.chunkPull) 91 | http.HandleFunc(CHUNK_PUSH, hp.chunkPush) 92 | } 93 | 94 | func (hp *httpProxy) ListenHTTPS(cert, key string) { 95 | hp.handler() 96 | g.Infof("listen at:[%s]", hp.addr) 97 | g.Fatal("ListenAndServe: ", http.ListenAndServeTLS(hp.addr, cert, key, nil)) 98 | } 99 | 100 | func (hp *httpProxy) Listen() { 101 | hp.handler() 102 | g.Infof("listen at:[%s]", hp.addr) 103 | g.Fatal("ListenAndServe: ", http.ListenAndServe(hp.addr, nil)) 104 | } 105 | 106 | func (hp *httpProxy) download(w http.ResponseWriter, r *http.Request) { 107 | w.Header().Set("Content-Length", fmt.Sprintf("%d", 100 << 20)) 108 | io.CopyN(w, DevZero{}, 100 << 20) 109 | } 110 | 111 | func (hp *httpProxy) verify(r *http.Request) error { 112 | ts := r.Header.Get("timestamp") 113 | sign := r.Header.Get("sign") 114 | tm, err := strconv.ParseInt(ts, 10, 0) 115 | if err != nil { 116 | return err 117 | } 118 | now := time.Now().Unix() 119 | if now-tm > signTTL { 120 | return errors.New("timestamp expire") 121 | } 122 | if VerifyHMACSHA1(hp.secret, ts, sign) { 123 | return nil 124 | } else { 125 | return errors.New("sign invalid") 126 | } 127 | } 128 | 129 | func (hp *httpProxy) before(w http.ResponseWriter, r *http.Request) error { 130 | err := hp.verify(r) 131 | if err != nil { 132 | g.V(LDEBUG).Info(err) 133 | WriteNotFoundError(w, "404") 134 | } 135 | return err 136 | } 137 | 138 | func (hp *httpProxy) ping(w http.ResponseWriter, r *http.Request) { 139 | w.Header().Set("Version", version) 140 | w.Write([]byte("pong")) 141 | } 142 | 143 | func (hp *httpProxy) pull(w http.ResponseWriter, r *http.Request) { 144 | if err := hp.before(w, r); err != nil { 145 | return 146 | } 147 | uuid := r.Header.Get("UUID") 148 | hp.Lock() 149 | pc, ok := hp.proxyMap[uuid] 150 | hp.Unlock() 151 | if !ok { 152 | WriteHTTPError(w, "uuid don't exist") 153 | return 154 | } 155 | if pc.IsClosed() { 156 | WriteHTTPError(w, "remote conn is closed") 157 | return 158 | } 159 | w.Header().Set("Content-Type", "application/octet-stream") 160 | interval := r.Header.Get("Interval") 161 | if interval == "" { 162 | interval = "0" 163 | } 164 | buf := bufPool.Get().([]byte) 165 | defer bufPool.Put(buf) 166 | t, _ := strconv.ParseInt(interval, 10, 0) 167 | if t > 0 { 168 | pc.remote.SetReadDeadline(time.Now().Add(time.Duration(t))) 169 | n, err := pc.remote.Read(buf) 170 | if n > 0 { 171 | w.Write(buf[:n]) 172 | } 173 | if err != nil { 174 | if err, ok := err.(net.Error); ok && err.Timeout() { 175 | } else { 176 | if err != io.EOF && !pc.IsClosed() { 177 | g.V(LERROR).Infof("read :%v", err) 178 | } 179 | pc.Close() 180 | } 181 | } 182 | 183 | return 184 | } 185 | flusher, _ := w.(http.Flusher) 186 | w.Header().Set("Transfer-Encoding", "chunked") 187 | defer pc.Close() 188 | for { 189 | flusher.Flush() 190 | n, err := pc.remote.Read(buf) 191 | if n > 0 { 192 | w.Write(buf[:n]) 193 | } 194 | if err != nil { 195 | if err != io.EOF && !pc.IsClosed() { 196 | g.V(LERROR).Info(err) 197 | } 198 | return 199 | } 200 | } 201 | } 202 | 203 | func (hp *httpProxy) push(w http.ResponseWriter, r *http.Request) { 204 | if err := hp.before(w, r); err != nil { 205 | return 206 | } 207 | uuid := r.Header.Get("UUID") 208 | hp.Lock() 209 | pc, ok := hp.proxyMap[uuid] 210 | hp.Unlock() 211 | if !ok { 212 | WriteHTTPError(w, "uuid don't exist") 213 | return 214 | } 215 | if pc.IsClosed() { 216 | WriteHTTPError(w, "remote conn is closed") 217 | return 218 | } 219 | 220 | typ := r.Header.Get("TYP") 221 | switch typ { 222 | default: 223 | case HEART_TYP: 224 | pc.Heart() 225 | case QUIT_TYP: 226 | pc.Close() 227 | case DATA_TYP: 228 | _, err := io.Copy(pc.remote, r.Body) 229 | if err != nil && err != io.EOF { 230 | if !pc.IsClosed() { 231 | g.V(LERROR).Info(err) 232 | } 233 | pc.Close() 234 | } 235 | } 236 | 237 | } 238 | 239 | func (hp *httpProxy) connect(w http.ResponseWriter, r *http.Request) { 240 | 241 | if err := hp.before(w, r); err != nil { 242 | return 243 | } 244 | 245 | host := r.Header.Get("DSTHOST") 246 | port := r.Header.Get("DSTPORT") 247 | addr := fmt.Sprintf("%s:%s", host, port) 248 | remote, err := net.DialTimeout("tcp", addr, time.Second*timeout) 249 | if err != nil { 250 | WriteHTTPError(w, fmt.Sprintf("connect %s %v", addr, err)) 251 | return 252 | } 253 | g.V(LINFO).Infof("connect %s success", addr) 254 | proxyID := uuid.New() 255 | pc := newProxyConn(remote, proxyID) 256 | hp.Lock() 257 | hp.proxyMap[proxyID] = pc 258 | hp.Unlock() 259 | 260 | go func() { 261 | pc.Do() 262 | hp.Lock() 263 | delete(hp.proxyMap, proxyID) 264 | hp.Unlock() 265 | g.V(LINFO).Infof("disconnect %s", addr) 266 | }() 267 | WriteHTTPOK(w, proxyID) 268 | } 269 | 270 | 271 | // not used by now 272 | 273 | func (hp *httpProxy) chunkPush(w http.ResponseWriter, r *http.Request) { 274 | if err := hp.before(w, r); err != nil { 275 | return 276 | } 277 | // 消息不超过8k 278 | chunk := bufPool.Get().([]byte) 279 | defer bufPool.Put(chunk) 280 | for { 281 | n, err := r.Body.Read(chunk) 282 | if n > 0 { 283 | // unpack chunk 284 | } 285 | if err != nil { 286 | g.V(LERROR).Info(err) 287 | break 288 | } 289 | } 290 | } 291 | 292 | // not used by now 293 | 294 | func (hp *httpProxy) chunkPull(w http.ResponseWriter, r *http.Request) { 295 | if err := hp.before(w, r); err != nil { 296 | return 297 | } 298 | w.Header().Set("Content-Type", "application/octet-stream") 299 | w.Header().Set("Transfer-Encoding", "chunked") 300 | flusher, _ := w.(http.Flusher) 301 | flusher.Flush() 302 | buf := make([]byte, 10) 303 | for { 304 | _, err := w.Write(buf) 305 | if err != nil { 306 | g.V(LERROR).Info(err) 307 | break 308 | } 309 | flusher.Flush() 310 | } 311 | } --------------------------------------------------------------------------------