Compare commits

..

49 Commits

Author SHA1 Message Date
fatedier
f804330dbf release v0.5.0 (#24)
[new] Optimize for http services.Support virtual host and custom domain binding.
[new] Support max days of keeping log files.
[fix] Fix a bug when reconnecting.
2016-05-03 22:05:55 +08:00
fatedier
2c39719cc0 Merge branch 'dev' 2016-04-11 17:38:12 +08:00
fatedier
6874688e07 build: for cross-compiles 2016-04-11 17:27:14 +08:00
fatedier
fdd7436736 build: fix 2016-04-11 12:05:24 +08:00
fatedier
0f326449e8 build: fix 2016-04-11 11:58:07 +08:00
fatedier
7c3e00ed28 build: update Makefile to avoid godep warning 2016-04-11 11:21:35 +08:00
fatedier
d5913fc77b all: fix bug when reconnecting 2016-04-07 19:27:39 +08:00
fatedier
2ba84d375a Release v0.3.0
Release v0.3.0
2016-04-06 12:19:31 +08:00
fatedier
6a0d6035cb doc: typo 2016-04-06 11:51:12 +08:00
fatedier
d091e0eac9 doc: update 2016-04-06 11:43:50 +08:00
fatedier
bc176b90f1 all: change passwd to auth_token and improve authentication 2016-04-05 17:18:21 +08:00
fatedier
a729a4fafe pcrypto: update 2016-04-05 16:17:31 +08:00
fatedier
78c770d37d Merge pull request #15 from fatedier/test 2016-04-05 15:43:28 +08:00
fatedier
718e707b77 pcrypto: fix 2016-04-05 15:40:32 +08:00
fatedier
b3ee746be8 optimize compress 2016-04-05 15:25:55 +08:00
fatedier
80fc76da52 all: now messages between frps and frpc can be encryped when use_encryption set true 2016-03-31 18:48:18 +08:00
fatedier
52f99bbc00 all: some optimization 2016-03-31 18:03:44 +08:00
fatedier
45c21b2705 utils/conn: add func joinMore 2016-03-28 11:22:36 +08:00
fatedier
b6212afb03 Merge pull request #11 from vashstorm/new
fix crypto
2016-03-25 14:09:01 +08:00
Gogs
49975c4c1b fix crypto 2016-03-24 15:42:34 +08:00
fatedier
580e75f633 Merge pull request #8 from vashstorm/master
add crypto
2016-03-23 09:59:42 +08:00
Gogs
20afe25ef1 modified: src/frp/models/client/client.go
modified:   src/frp/models/server/server.go
	modified:   src/frp/utils/conn/conn.go
	modified:   src/frp/utils/pcrypto/pcrypto.go
	modified:   src/frp/utils/pcrypto/pcrypto_test.go
2016-03-22 17:18:05 +08:00
fatedier
6e57135533 typo 2016-03-17 17:52:59 +08:00
fatedier
931c102668 fix bug in Dockerfile 2016-03-17 17:47:41 +08:00
fatedier
5700101c0e doc: typo 2016-03-17 11:04:40 +08:00
fatedier
90349a48b0 doc: update version in doc to v0.2.0 2016-03-17 10:49:38 +08:00
fatedier
51114f2afd Merge pull request #7 from fatedier/dev
Release version 0.2.0
2016-03-17 10:45:04 +08:00
fatedier
f130886f69 all: change heartbeat interval time 2016-03-17 10:25:23 +08:00
fatedier
cdd79aee52 add Dockerfile 2016-03-16 17:20:53 +08:00
fatedier
30d79e66be test: typo 2016-03-15 18:53:27 +08:00
fatedier
5c6f03afcf test: typo 2016-03-15 18:50:49 +08:00
fatedier
8ed55e1288 test: improve test case 2016-03-15 18:45:57 +08:00
fatedier
a52e77f6ed doc: update README.md for Contributing 2016-03-14 16:18:29 +08:00
fatedier
d1c3badce2 doc: add quick_start_en.md 2016-03-14 16:11:02 +08:00
fatedier
95ae70234d Update quick_start_zh.md 2016-03-14 15:42:29 +08:00
fatedier
a092af28a6 Update quick_start_zh.md 2016-03-14 15:30:23 +08:00
fatedier
9f5465b08d Update quick_start_zh.md 2016-03-14 15:29:43 +08:00
fatedier
d9bca30c9b Update README.md 2016-03-14 15:27:54 +08:00
fatedier
d03f2753d0 doc: add quick_start_zh.md 2016-03-14 15:26:26 +08:00
fatedier
db3abd304e doc: update architecture.png 2016-03-14 14:31:48 +08:00
fatedier
b4acba9480 doc: add architecture.png 2016-03-14 14:28:37 +08:00
fatedier
6e458229f6 add license info 2016-03-14 11:18:24 +08:00
fatedier
a56b29b153 add support for command line with frpc 2016-03-14 00:48:22 +08:00
fatedier
3218eda481 models/server: fix bug, program will core if listener is nil 2016-03-14 00:39:32 +08:00
fatedier
975c2a97c8 add support for command line 2016-03-14 00:21:40 +08:00
fatedier
838dc10c6e [utils/version] add version.go 2016-03-07 01:19:07 +08:00
fatedier
3cbe432889 cmd/client: if no local_ip set in proxy config, use 127.0.0.1 as default 2016-02-29 14:56:47 +08:00
fatedier
586d63f662 Merge pull request #6 from Hurricanezwf/add_func
enable client to connect non-local [ip:port]
2016-02-29 13:16:40 +08:00
Hurricanezwf
f02ed95ef1 enable client to connect non-local [ip:port] 2016-02-29 12:38:45 +08:00
39 changed files with 4189 additions and 410 deletions

2
.gitignore vendored
View File

@@ -25,7 +25,7 @@ _testmain.go
# Self
bin/
packages/
# Cache
*.swp
*.swo

View File

@@ -3,7 +3,7 @@ language: go
go:
- 1.4.2
- 1.5.1
- 1.5.3
install:
- make

5
Godeps/Godeps.json generated
View File

@@ -10,6 +10,11 @@
"Comment": "v1.5.0-9-gfb7314f",
"Rev": "fb7314f8ac86b83ccd34386518d97cf2363e2ae5"
},
{
"ImportPath": "github.com/docopt/docopt-go",
"Comment": "0.6.2",
"Rev": "784ddc588536785e7299f7272f39101f7faccc3f"
},
{
"ImportPath": "github.com/vaughan0/go-ini",
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"

View File

@@ -0,0 +1,25 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
# coverage droppings
profile.cov

View File

@@ -0,0 +1,31 @@
# Travis CI (http://travis-ci.org/) is a continuous integration
# service for open source projects. This file configures it
# to run unit tests for docopt-go.
language: go
go:
- 1.4
- 1.5
- tip
matrix:
fast_finish: true
before_install:
- go get golang.org/x/tools/cmd/vet
- go get golang.org/x/tools/cmd/cover
- go get github.com/golang/lint/golint
- go get github.com/mattn/goveralls
install:
- go get -d -v ./... && go build -v ./...
script:
- go vet -x ./...
- $HOME/gopath/bin/golint ./...
- go test -v ./...
- go test -covermode=count -coverprofile=profile.cov .
after_script:
- $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 Keith Batten
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,88 @@
docopt-go
=========
[![Build Status](https://travis-ci.org/docopt/docopt.go.svg?branch=master)](https://travis-ci.org/docopt/docopt.go)
[![Coverage Status](https://coveralls.io/repos/docopt/docopt.go/badge.png)](https://coveralls.io/r/docopt/docopt.go)
[![GoDoc](https://godoc.org/github.com/docopt/docopt.go?status.png)](https://godoc.org/github.com/docopt/docopt.go)
An implementation of [docopt](http://docopt.org/) in the
[Go](http://golang.org/) programming language.
**docopt** helps you create *beautiful* command-line interfaces easily:
```go
package main
import (
"fmt"
"github.com/docopt/docopt-go"
)
func main() {
usage := `Naval Fate.
Usage:
naval_fate ship new <name>...
naval_fate ship <name> move <x> <y> [--speed=<kn>]
naval_fate ship shoot <x> <y>
naval_fate mine (set|remove) <x> <y> [--moored|--drifting]
naval_fate -h | --help
naval_fate --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Moored (anchored) mine.
--drifting Drifting mine.`
arguments, _ := docopt.Parse(usage, nil, true, "Naval Fate 2.0", false)
fmt.Println(arguments)
}
```
**docopt** parses command-line arguments based on a help message. Don't
write parser code: a good help message already has all the necessary
information in it.
## Installation
⚠ Use the alias “docopt-go”. To use docopt in your Go code:
```go
import "github.com/docopt/docopt-go"
```
To install docopt according to your `$GOPATH`:
```console
$ go get github.com/docopt/docopt-go
```
## API
```go
func Parse(doc string, argv []string, help bool, version string,
optionsFirst bool, exit ...bool) (map[string]interface{}, error)
```
Parse `argv` based on the command-line interface described in `doc`.
Given a conventional command-line help message, docopt creates a parser and
processes the arguments. See
https://github.com/docopt/docopt#help-message-format for a description of the
help message format. If `argv` is `nil`, `os.Args[1:]` is used.
docopt returns a map of option names to the values parsed from `argv`, and an
error or `nil`.
More documentation for docopt is available at
[GoDoc.org](https://godoc.org/github.com/docopt/docopt.go).
## Testing
All tests from the Python version are implemented and passing
at [Travis CI](https://travis-ci.org/docopt/docopt.go). New
language-agnostic tests have been added
to [test_golang.docopt](test_golang.docopt).
To run tests for docopt-go, use `go test`.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
r"""usage: prog [NAME_-2]..."""
$ prog 10 20
{"NAME_-2": ["10", "20"]}
$ prog 10
{"NAME_-2": ["10"]}
$ prog
{"NAME_-2": []}

View File

@@ -0,0 +1,957 @@
r"""Usage: prog
"""
$ prog
{}
$ prog --xxx
"user-error"
r"""Usage: prog [options]
Options: -a All.
"""
$ prog
{"-a": false}
$ prog -a
{"-a": true}
$ prog -x
"user-error"
r"""Usage: prog [options]
Options: --all All.
"""
$ prog
{"--all": false}
$ prog --all
{"--all": true}
$ prog --xxx
"user-error"
r"""Usage: prog [options]
Options: -v, --verbose Verbose.
"""
$ prog --verbose
{"--verbose": true}
$ prog --ver
{"--verbose": true}
$ prog -v
{"--verbose": true}
r"""Usage: prog [options]
Options: -p PATH
"""
$ prog -p home/
{"-p": "home/"}
$ prog -phome/
{"-p": "home/"}
$ prog -p
"user-error"
r"""Usage: prog [options]
Options: --path <path>
"""
$ prog --path home/
{"--path": "home/"}
$ prog --path=home/
{"--path": "home/"}
$ prog --pa home/
{"--path": "home/"}
$ prog --pa=home/
{"--path": "home/"}
$ prog --path
"user-error"
r"""Usage: prog [options]
Options: -p PATH, --path=<path> Path to files.
"""
$ prog -proot
{"--path": "root"}
r"""Usage: prog [options]
Options: -p --path PATH Path to files.
"""
$ prog -p root
{"--path": "root"}
$ prog --path root
{"--path": "root"}
r"""Usage: prog [options]
Options:
-p PATH Path to files [default: ./]
"""
$ prog
{"-p": "./"}
$ prog -phome
{"-p": "home"}
r"""UsAgE: prog [options]
OpTiOnS: --path=<files> Path to files
[dEfAuLt: /root]
"""
$ prog
{"--path": "/root"}
$ prog --path=home
{"--path": "home"}
r"""usage: prog [options]
options:
-a Add
-r Remote
-m <msg> Message
"""
$ prog -a -r -m Hello
{"-a": true,
"-r": true,
"-m": "Hello"}
$ prog -armyourass
{"-a": true,
"-r": true,
"-m": "yourass"}
$ prog -a -r
{"-a": true,
"-r": true,
"-m": null}
r"""Usage: prog [options]
Options: --version
--verbose
"""
$ prog --version
{"--version": true,
"--verbose": false}
$ prog --verbose
{"--version": false,
"--verbose": true}
$ prog --ver
"user-error"
$ prog --verb
{"--version": false,
"--verbose": true}
r"""usage: prog [-a -r -m <msg>]
options:
-a Add
-r Remote
-m <msg> Message
"""
$ prog -armyourass
{"-a": true,
"-r": true,
"-m": "yourass"}
r"""usage: prog [-armmsg]
options: -a Add
-r Remote
-m <msg> Message
"""
$ prog -a -r -m Hello
{"-a": true,
"-r": true,
"-m": "Hello"}
r"""usage: prog -a -b
options:
-a
-b
"""
$ prog -a -b
{"-a": true, "-b": true}
$ prog -b -a
{"-a": true, "-b": true}
$ prog -a
"user-error"
$ prog
"user-error"
r"""usage: prog (-a -b)
options: -a
-b
"""
$ prog -a -b
{"-a": true, "-b": true}
$ prog -b -a
{"-a": true, "-b": true}
$ prog -a
"user-error"
$ prog
"user-error"
r"""usage: prog [-a] -b
options: -a
-b
"""
$ prog -a -b
{"-a": true, "-b": true}
$ prog -b -a
{"-a": true, "-b": true}
$ prog -a
"user-error"
$ prog -b
{"-a": false, "-b": true}
$ prog
"user-error"
r"""usage: prog [(-a -b)]
options: -a
-b
"""
$ prog -a -b
{"-a": true, "-b": true}
$ prog -b -a
{"-a": true, "-b": true}
$ prog -a
"user-error"
$ prog -b
"user-error"
$ prog
{"-a": false, "-b": false}
r"""usage: prog (-a|-b)
options: -a
-b
"""
$ prog -a -b
"user-error"
$ prog
"user-error"
$ prog -a
{"-a": true, "-b": false}
$ prog -b
{"-a": false, "-b": true}
r"""usage: prog [ -a | -b ]
options: -a
-b
"""
$ prog -a -b
"user-error"
$ prog
{"-a": false, "-b": false}
$ prog -a
{"-a": true, "-b": false}
$ prog -b
{"-a": false, "-b": true}
r"""usage: prog <arg>"""
$ prog 10
{"<arg>": "10"}
$ prog 10 20
"user-error"
$ prog
"user-error"
r"""usage: prog [<arg>]"""
$ prog 10
{"<arg>": "10"}
$ prog 10 20
"user-error"
$ prog
{"<arg>": null}
r"""usage: prog <kind> <name> <type>"""
$ prog 10 20 40
{"<kind>": "10", "<name>": "20", "<type>": "40"}
$ prog 10 20
"user-error"
$ prog
"user-error"
r"""usage: prog <kind> [<name> <type>]"""
$ prog 10 20 40
{"<kind>": "10", "<name>": "20", "<type>": "40"}
$ prog 10 20
{"<kind>": "10", "<name>": "20", "<type>": null}
$ prog
"user-error"
r"""usage: prog [<kind> | <name> <type>]"""
$ prog 10 20 40
"user-error"
$ prog 20 40
{"<kind>": null, "<name>": "20", "<type>": "40"}
$ prog
{"<kind>": null, "<name>": null, "<type>": null}
r"""usage: prog (<kind> --all | <name>)
options:
--all
"""
$ prog 10 --all
{"<kind>": "10", "--all": true, "<name>": null}
$ prog 10
{"<kind>": null, "--all": false, "<name>": "10"}
$ prog
"user-error"
r"""usage: prog [<name> <name>]"""
$ prog 10 20
{"<name>": ["10", "20"]}
$ prog 10
{"<name>": ["10"]}
$ prog
{"<name>": []}
r"""usage: prog [(<name> <name>)]"""
$ prog 10 20
{"<name>": ["10", "20"]}
$ prog 10
"user-error"
$ prog
{"<name>": []}
r"""usage: prog NAME..."""
$ prog 10 20
{"NAME": ["10", "20"]}
$ prog 10
{"NAME": ["10"]}
$ prog
"user-error"
r"""usage: prog [NAME]..."""
$ prog 10 20
{"NAME": ["10", "20"]}
$ prog 10
{"NAME": ["10"]}
$ prog
{"NAME": []}
r"""usage: prog [NAME...]"""
$ prog 10 20
{"NAME": ["10", "20"]}
$ prog 10
{"NAME": ["10"]}
$ prog
{"NAME": []}
r"""usage: prog [NAME [NAME ...]]"""
$ prog 10 20
{"NAME": ["10", "20"]}
$ prog 10
{"NAME": ["10"]}
$ prog
{"NAME": []}
r"""usage: prog (NAME | --foo NAME)
options: --foo
"""
$ prog 10
{"NAME": "10", "--foo": false}
$ prog --foo 10
{"NAME": "10", "--foo": true}
$ prog --foo=10
"user-error"
r"""usage: prog (NAME | --foo) [--bar | NAME]
options: --foo
options: --bar
"""
$ prog 10
{"NAME": ["10"], "--foo": false, "--bar": false}
$ prog 10 20
{"NAME": ["10", "20"], "--foo": false, "--bar": false}
$ prog --foo --bar
{"NAME": [], "--foo": true, "--bar": true}
r"""Naval Fate.
Usage:
prog ship new <name>...
prog ship [<name>] move <x> <y> [--speed=<kn>]
prog ship shoot <x> <y>
prog mine (set|remove) <x> <y> [--moored|--drifting]
prog -h | --help
prog --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Mored (anchored) mine.
--drifting Drifting mine.
"""
$ prog ship Guardian move 150 300 --speed=20
{"--drifting": false,
"--help": false,
"--moored": false,
"--speed": "20",
"--version": false,
"<name>": ["Guardian"],
"<x>": "150",
"<y>": "300",
"mine": false,
"move": true,
"new": false,
"remove": false,
"set": false,
"ship": true,
"shoot": false}
r"""usage: prog --hello"""
$ prog --hello
{"--hello": true}
r"""usage: prog [--hello=<world>]"""
$ prog
{"--hello": null}
$ prog --hello wrld
{"--hello": "wrld"}
r"""usage: prog [-o]"""
$ prog
{"-o": false}
$ prog -o
{"-o": true}
r"""usage: prog [-opr]"""
$ prog -op
{"-o": true, "-p": true, "-r": false}
r"""usage: prog --aabb | --aa"""
$ prog --aa
{"--aabb": false, "--aa": true}
$ prog --a
"user-error" # not a unique prefix
#
# Counting number of flags
#
r"""Usage: prog -v"""
$ prog -v
{"-v": true}
r"""Usage: prog [-v -v]"""
$ prog
{"-v": 0}
$ prog -v
{"-v": 1}
$ prog -vv
{"-v": 2}
r"""Usage: prog -v ..."""
$ prog
"user-error"
$ prog -v
{"-v": 1}
$ prog -vv
{"-v": 2}
$ prog -vvvvvv
{"-v": 6}
r"""Usage: prog [-v | -vv | -vvv]
This one is probably most readable user-friednly variant.
"""
$ prog
{"-v": 0}
$ prog -v
{"-v": 1}
$ prog -vv
{"-v": 2}
$ prog -vvvv
"user-error"
r"""usage: prog [--ver --ver]"""
$ prog --ver --ver
{"--ver": 2}
#
# Counting commands
#
r"""usage: prog [go]"""
$ prog go
{"go": true}
r"""usage: prog [go go]"""
$ prog
{"go": 0}
$ prog go
{"go": 1}
$ prog go go
{"go": 2}
$ prog go go go
"user-error"
r"""usage: prog go..."""
$ prog go go go go go
{"go": 5}
#
# [options] does not include options from usage-pattern
#
r"""usage: prog [options] [-a]
options: -a
-b
"""
$ prog -a
{"-a": true, "-b": false}
$ prog -aa
"user-error"
#
# Test [options] shourtcut
#
r"""Usage: prog [options] A
Options:
-q Be quiet
-v Be verbose.
"""
$ prog arg
{"A": "arg", "-v": false, "-q": false}
$ prog -v arg
{"A": "arg", "-v": true, "-q": false}
$ prog -q arg
{"A": "arg", "-v": false, "-q": true}
#
# Test single dash
#
r"""usage: prog [-]"""
$ prog -
{"-": true}
$ prog
{"-": false}
#
# If argument is repeated, its value should always be a list
#
r"""usage: prog [NAME [NAME ...]]"""
$ prog a b
{"NAME": ["a", "b"]}
$ prog
{"NAME": []}
#
# Option's argument defaults to null/None
#
r"""usage: prog [options]
options:
-a Add
-m <msg> Message
"""
$ prog -a
{"-m": null, "-a": true}
#
# Test options without description
#
r"""usage: prog --hello"""
$ prog --hello
{"--hello": true}
r"""usage: prog [--hello=<world>]"""
$ prog
{"--hello": null}
$ prog --hello wrld
{"--hello": "wrld"}
r"""usage: prog [-o]"""
$ prog
{"-o": false}
$ prog -o
{"-o": true}
r"""usage: prog [-opr]"""
$ prog -op
{"-o": true, "-p": true, "-r": false}
r"""usage: git [-v | --verbose]"""
$ prog -v
{"-v": true, "--verbose": false}
r"""usage: git remote [-v | --verbose]"""
$ prog remote -v
{"remote": true, "-v": true, "--verbose": false}
#
# Test empty usage pattern
#
r"""usage: prog"""
$ prog
{}
r"""usage: prog
prog <a> <b>
"""
$ prog 1 2
{"<a>": "1", "<b>": "2"}
$ prog
{"<a>": null, "<b>": null}
r"""usage: prog <a> <b>
prog
"""
$ prog
{"<a>": null, "<b>": null}
#
# Option's argument should not capture default value from usage pattern
#
r"""usage: prog [--file=<f>]"""
$ prog
{"--file": null}
r"""usage: prog [--file=<f>]
options: --file <a>
"""
$ prog
{"--file": null}
r"""Usage: prog [-a <host:port>]
Options: -a, --address <host:port> TCP address [default: localhost:6283].
"""
$ prog
{"--address": "localhost:6283"}
#
# If option with argument could be repeated,
# its arguments should be accumulated into a list
#
r"""usage: prog --long=<arg> ..."""
$ prog --long one
{"--long": ["one"]}
$ prog --long one --long two
{"--long": ["one", "two"]}
#
# Test multiple elements repeated at once
#
r"""usage: prog (go <direction> --speed=<km/h>)..."""
$ prog go left --speed=5 go right --speed=9
{"go": 2, "<direction>": ["left", "right"], "--speed": ["5", "9"]}
#
# Required options should work with option shortcut
#
r"""usage: prog [options] -a
options: -a
"""
$ prog -a
{"-a": true}
#
# If option could be repeated its defaults should be split into a list
#
r"""usage: prog [-o <o>]...
options: -o <o> [default: x]
"""
$ prog -o this -o that
{"-o": ["this", "that"]}
$ prog
{"-o": ["x"]}
r"""usage: prog [-o <o>]...
options: -o <o> [default: x y]
"""
$ prog -o this
{"-o": ["this"]}
$ prog
{"-o": ["x", "y"]}
#
# Test stacked option's argument
#
r"""usage: prog -pPATH
options: -p PATH
"""
$ prog -pHOME
{"-p": "HOME"}
#
# Issue 56: Repeated mutually exclusive args give nested lists sometimes
#
r"""Usage: foo (--xx=x|--yy=y)..."""
$ prog --xx=1 --yy=2
{"--xx": ["1"], "--yy": ["2"]}
#
# POSIXly correct tokenization
#
r"""usage: prog [<input file>]"""
$ prog f.txt
{"<input file>": "f.txt"}
r"""usage: prog [--input=<file name>]..."""
$ prog --input a.txt --input=b.txt
{"--input": ["a.txt", "b.txt"]}
#
# Issue 85: `[options]` shourtcut with multiple subcommands
#
r"""usage: prog good [options]
prog fail [options]
options: --loglevel=N
"""
$ prog fail --loglevel 5
{"--loglevel": "5", "fail": true, "good": false}
#
# Usage-section syntax
#
r"""usage:prog --foo"""
$ prog --foo
{"--foo": true}
r"""PROGRAM USAGE: prog --foo"""
$ prog --foo
{"--foo": true}
r"""Usage: prog --foo
prog --bar
NOT PART OF SECTION"""
$ prog --foo
{"--foo": true, "--bar": false}
r"""Usage:
prog --foo
prog --bar
NOT PART OF SECTION"""
$ prog --foo
{"--foo": true, "--bar": false}
r"""Usage:
prog --foo
prog --bar
NOT PART OF SECTION"""
$ prog --foo
{"--foo": true, "--bar": false}
#
# Options-section syntax
#
r"""Usage: prog [options]
global options: --foo
local options: --baz
--bar
other options:
--egg
--spam
-not-an-option-
"""
$ prog --baz --egg
{"--foo": false, "--baz": true, "--bar": false, "--egg": true, "--spam": false}

View File

@@ -1,22 +1,22 @@
export PATH := $(GOPATH)/bin:$(PATH)
export NEW_GOPATH := $(shell pwd)
export OLDGOPATH := $(GOPATH)
export GOPATH := $(shell pwd):$(GOPATH)
all: build
build: godep fmt frps frpc
godep:
@go get github.com/tools/godep
godep restore
GOPATH=$(OLDGOPATH) go get github.com/tools/godep
fmt:
@GOPATH=$(NEW_GOPATH) godep go fmt ./...
godep go fmt ./...
frps:
GOPATH=$(NEW_GOPATH) godep go build -o bin/frps ./src/frp/cmd/frps
godep go build -o bin/frps ./src/frp/cmd/frps
frpc:
GOPATH=$(NEW_GOPATH) godep go build -o bin/frpc ./src/frp/cmd/frpc
godep go build -o bin/frpc ./src/frp/cmd/frpc
test:
@GOPATH=$(NEW_GOPATH) godep go test ./...
godep go test -v ./...

15
Makefile.cross-compiles Normal file
View File

@@ -0,0 +1,15 @@
export PATH := $(GOPATH)/bin:$(PATH)
export OLDGOPATH := $(GOPATH)
export GOPATH := $(shell pwd)/Godeps/_workspace:$(shell pwd):$(GOPATH)
export OS_TARGETS=linux windows
export ARCH_TARGETS=386 amd64
all: build
build: godep app
godep:
GOPATH=$(OLDGOPATH) go get github.com/mitchellh/gox
app:
gox -os "$(OS_TARGETS)" -arch="$(ARCH_TARGETS)" ./...

View File

@@ -2,4 +2,45 @@
[![Build Status](https://travis-ci.org/fatedier/frp.svg)](https://travis-ci.org/fatedier/frp)
A fast reverse proxy.
[README](README.md) | [中文文档](README_zh.md)
## What is frp?
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet.
## What can I do with frp?
* Expose any http service behind a NAT or firewall to the internet by a server with public IP address(Name-based Virtual Host Support).
* Expose any tcp service behind a NAT or firewall to the internet by a server with public IP address.
* Inspect all http requests/responses that are transmitted over the tunnel(future).
## Status
frp is under development and you can try it with latest release version.Master branch for releasing stable version when dev branch for developing.
**We may change any protocol and can't promise backward compatible before version 1.x.**
## Quick Start
Read the [QuickStart](/doc/quick_start_en.md)
[Tcp port forwarding](/doc/quick_start_en.md#tcp-port-forwarding)
[Http port forwarding and Custom domain binding](/doc/quick_start_en.md#http-port-forwarding-and-custom-domains-binding)
## Architecture
![architecture](/doc/pic/architecture.png)
## Contributing
Interested in getting involved? We would love to help you!
* Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider submitting a patch
* If you have some wanderful ideas, send email to fatedier@gmail.com.
## Contributors
* [fatedier](https://github.com/fatedier)
* [Hurricanezwf](https://github.com/Hurricanezwf)
* [vashstorm](https://github.com/vashstorm)

45
README_zh.md Normal file
View File

@@ -0,0 +1,45 @@
# frp
[![Build Status](https://travis-ci.org/fatedier/frp.svg)](https://travis-ci.org/fatedier/frp)
[README](README.md) | [中文文档](README_zh.md)
>frp 是一个高性能的反向代理应用,可以帮助你轻松的进行内网穿透,对外网提供服务,对于 http 服务还支持虚拟主机功能访问80端口可以根据域名路由到后端不同的 http 服务。
## frp 的作用?
* 利用处于内网或防火墙后的机器,对外网环境提供 http 服务。
* 对于 http 服务支持基于域名的虚拟主机支持自定义域名绑定使多个域名可以共用一个80端口。
* 利用处于内网或防火墙后的机器,对外网环境提供 tcp 服务,例如在家里通过 ssh 访问公司局域网内的主机。
* 可查看通过代理的所有 http 请求和响应的详细信息。(待开发)
## 开发状态
frp 目前正在前期开发阶段master分支用于发布稳定版本dev分支用于开发您可以尝试下载最新的 release 版本进行测试。
**在 1.0 版本以前,交互协议都可能会被改变,不能保证向后兼容。**
## 快速开始
[使用文档](/doc/quick_start_zh.md)
[tcp 端口转发](/doc/quick_start_zh.md#tcp-端口转发)
[http 端口转发,自定义域名绑定](/doc/quick_start_zh.md#http-端口转发自定义域名绑定)
## 架构
![architecture](/doc/pic/architecture.png)
## 贡献代码
如果您对这个项目感兴趣,我们非常欢迎您参与其中!
* 如果您需要提交问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来完成。
* 如果您有新的功能需求,可以反馈至 fatedier@gmail.com 共同讨论。
## 贡献者
* [fatedier](https://github.com/fatedier)
* [Hurricanezwf](https://github.com/Hurricanezwf)
* [vashstorm](https://github.com/vashstorm)

View File

@@ -1,14 +1,32 @@
# common是必须的section
# [common] is integral section
[common]
server_addr = 127.0.0.1
server_addr = 0.0.0.0
server_port = 7000
# console or real logFile path like ./frpc.log
log_file = ./frpc.log
# debug, info, warn, error
log_level = debug
# file, console
log_way = console
log_level = info
log_max_days = 3
# for authentication
auth_token = 123
# test1即为name
[test1]
passwd = 123
# ssh is the proxy name same as server's configuration
[ssh]
# tcp | http, default is tcp
type = tcp
local_ip = 127.0.0.1
local_port = 22
# true or false, if true, messages between frps and frpc will be encrypted, default is false
use_encryption = true
# Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02, the domains are set in frps.ini
[web01]
type = http
local_ip = 127.0.0.1
local_port = 80
use_encryption = true
[web02]
type = http
local_ip = 127.0.0.1
local_port = 8000

View File

@@ -1,15 +1,29 @@
# common是必须的section
# [common] is integral section
[common]
bind_addr = 0.0.0.0
bind_port = 7000
# optional
vhost_http_port = 80
# console or real logFile path like ./frps.log
log_file = ./frps.log
# debug, info, warn, error
log_level = debug
# file, console
log_way = console
log_level = info
log_max_days = 3
# test1即为name
[test1]
passwd = 123
# ssh is the proxy name, client will use this name and auth_token to connect to server
[ssh]
type = tcp
auth_token = 123
bind_addr = 0.0.0.0
listen_port = 6000
[web01]
type = http
auth_token = 123
# if proxy type equals http, custom_domains must be set separated by commas
custom_domains = web01.yourdomain.com,web01.yourdomain2.com
[web02]
type = http
auth_token = 123
custom_domains = web02.yourdomain.com

45
cross_compiles_package.sh Executable file
View File

@@ -0,0 +1,45 @@
# compile for version
make
if [ $? -ne 0 ]; then
echo "make error"
exit 1
fi
frp_version=`./bin/frps --version`
echo "build version: $frp_version"
# cross_compiles
make -f ./Makefile.cross-compiles
rm -rf ./packages
mkdir ./packages
os_all='linux windows'
arch_all='386 amd64'
for os in $os_all; do
for arch in $arch_all; do
frp_dir_name="frp_${frp_version}_${os}_${arch}"
frp_path="./packages/frp_${frp_version}_${os}_${arch}"
mkdir ${frp_path}
if [ "x${os}" = x"windows" ]; then
mv ./frpc_${os}_${arch}.exe ${frp_path}/frpc.exe
mv ./frps_${os}_${arch}.exe ${frp_path}/frps.exe
else
mv ./frpc_${os}_${arch} ${frp_path}/frpc
mv ./frps_${os}_${arch} ${frp_path}/frps
fi
cp ./LICENSE ${frp_path}
cp ./conf/* ${frp_path}
# packages
cd ./packages
if [ "x${os}" = x"windows" ]; then
zip -rq ${frp_dir_name}.zip ${frp_dir_name}
else
tar -zcf ${frp_dir_name}.tar.gz ${frp_dir_name}
fi
cd ..
rm -rf ${frp_path}
done
done

BIN
doc/pic/architecture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

135
doc/quick_start_en.md Normal file
View File

@@ -0,0 +1,135 @@
# Quick Start
frp is easier to use compared with other similar projects.
We will use two simple demo to demonstrate how to use frp.
1. How to create a connection to **server A**'s **ssh port** by **server B** with **public IP address** x.x.x.x(replace to the real IP address of your server).
2. How to visit web service in **server A**'s **8000 port** and **8001 port** by **web01.yourdomain.com** and **web02.yourdomain.com** through **server B** with public ID address.
### Download SourceCode
`go get github.com/fatedier/frp` is recommended, then the code will be copied to the directory `$GOPATH/src/github.com/fatedier/frp`.
Or you can use `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp`.
If you want to try it quickly, download the compiled program and configuration files from [https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases).
### Compile
Enter the root directory and execute `make`, then wait until finished.
**bin** include all executable programs when **conf** include corresponding configuration files.
### Pre-requirement
* Go environment. Version of go >= 1.4.
* Godep (if not exist, `go get` will be executed to download godep when compiling)
### Deploy
1. Move `./bin/frps` and `./conf/frps.ini` to any directory of **server B**.
2. Move `./bin/frpc` and `./conf/frpc.ini` to any directory of **server A**.
3. Modify all configuration files, details in next paragraph.
4. Execute `nohup ./frps &` or `nohup ./frps -c ./frps.ini &` in **server B**.
5. Execute `nohup ./frpc &` or `nohup ./frpc -c ./frpc.ini &` in **server A**.
6. Use `ssh -oPort=6000 {user}@x.x.x.x` to test if frp is work(replace {user} to real username in **server A**), or visit custom domains by browser.
## Tcp port forwarding
### Configuration files
#### frps.ini
```ini
[common]
bind_addr = 0.0.0.0
# for accept connections from frpc
bind_port = 7000
log_file = ./frps.log
log_level = info
# ssh is the custom name of proxy and there can be many proxies with unique name in one configure file
[ssh]
auth_token = 123
bind_addr = 0.0.0.0
# finally we connect to server A by this port
listen_port = 6000
```
#### frpc.ini
```ini
[common]
# server address of frps
server_addr = x.x.x.x
server_port = 7000
log_file = ./frpc.log
log_level = info
# for authentication
auth_token = 123
# ssh is proxy name same with configure in frps.ini
[ssh]
# local port which need to be transferred
local_port = 22
# if use_encryption equals true, messages between frpc and frps will be encrypted, default is false
use_encryption = true
```
## Http port forwarding and Custom domains binding
If you only want to forward port one by one, you just need refer to [Tcp port forwarding](/doc/quick_start_en.md#Tcp-port-forwarding).If you want to visit different web pages deployed in different web servers by **server B**'s **80 port**, you should specify the type as **http**.
You also need to resolve your **A record** of your custom domain to [server_addr], or resolve your **CNAME record** to [server_addr] if [server_addr] is a domain.
After that, you can visit your web pages in local server by custom domains.
### Configuration files
#### frps.ini
```ini
[common]
bind_addr = 0.0.0.0
bind_port = 7000
# if you want to support vhost, specify one port for http services
vhost_http_port = 80
log_file = ./frps.log
log_level = info
[web01]
type = http
auth_token = 123
# # if proxy type equals http, custom_domains must be set separated by commas
custom_domains = web01.yourdomain.com
[web02]
type = http
auth_token = 123
custom_domains = web02.yourdomain.com
```
#### frpc.ini
```ini
[common]
server_addr = x.x.x.x
server_port = 7000
log_file = ./frpc.log
log_level = info
auth_token = 123
# custom domains are set in frps.ini
[web01]
type = http
local_ip = 127.0.0.1
local_port = 8000
# encryption is optional, default is false
use_encryption = true
[web02]
type = http
local_ip = 127.0.0.1
local_port = 8001
```

137
doc/quick_start_zh.md Normal file
View File

@@ -0,0 +1,137 @@
# frp 使用文档
相比于其他项目而言 frp 更易于部署和使用,这里我们用两个简单的示例来演示 frp 的使用过程。
1. 如何通过一台拥有公网IP地址的**服务器B**,访问处于公司内部网络环境中的**服务器A**的**ssh**端口,**服务器B**的IP地址为 x.x.x.x测试时替换为真实的IP地址
2. 如何利用一台拥有公网IP地址的**服务器B**,使通过 **web01.yourdomain.com** 可以访问内网环境中**服务器A**上**8000端口**的web服务**web02.yourdomain.com** 可以访问**服务器A**上**8001端口**的web服务。
### 下载源码
推荐直接使用 `go get github.com/fatedier/frp` 下载源代码安装,执行命令后代码将会拷贝到 `$GOPATH/src/github.com/fatedier/frp` 目录下。
或者可以使用 `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp` 拷贝到相应目录下。
如果您想快速进行测试,也可以根据您服务器的操作系统及架构直接下载编译好的程序及示例配置文件,[https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases)。
### 编译
进入下载后的源码根目录,执行 `make` 命令,等待编译完成。
编译完成后, **bin** 目录下是编译好的可执行文件,**conf** 目录下是示例配置文件。
### 依赖
* go 1.4 以上版本
* godep (如果检查不存在,编译时会通过 `go get` 命令安装)
### 部署
1. 将 ./bin/frps 和 ./conf/frps.ini 拷贝至**服务器B**任意目录。
2. 将 ./bin/frpc 和 ./conf/frpc.ini 拷贝至**服务器A**任意目录。
3. 根据要实现的功能修改两边的配置文件,详细内容见后续章节说明。
4. 在服务器B执行 `nohup ./frps &` 或者 `nohup ./frps -c ./frps.ini &`
5. 在服务器A执行 `nohup ./frpc &` 或者 `nohup ./frpc -c ./frpc.ini &`
6. 通过 `ssh -oPort=6000 {user}@x.x.x.x` 测试是否能够成功连接**服务器A**{user}替换为**服务器A**上存在的真实用户),或通过浏览器访问自定义域名验证 http 服务是否转发成功。
## tcp 端口转发
转发 tcp 端口需要按照需求修改 frps 和 frpc 的配置文件。
### 配置文件
#### frps.ini
```ini
[common]
bind_addr = 0.0.0.0
# 用于接收 frpc 连接的端口
bind_port = 7000
log_file = ./frps.log
log_level = info
# ssh 为代理的自定义名称可以有多个不能重复和frpc中名称对应
[ssh]
auth_token = 123
bind_addr = 0.0.0.0
# 最后将通过此端口访问后端服务
listen_port = 6000
```
#### frpc.ini
```ini
[common]
# frps 所在服务器绑定的IP地址
server_addr = x.x.x.x
server_port = 7000
log_file = ./frpc.log
log_level = info
# 用于身份验证
auth_token = 123
# ssh 需要和 frps.ini 中配置一致
[ssh]
# 需要转发的本地端口
local_port = 22
# 启用加密frpc与frps之间通信加密默认为 false
use_encryption = true
```
## http 端口转发,自定义域名绑定
如果只需要一对一的转发,例如**服务器B**的**80端口**转发**服务器A**的**8000端口**,则只需要配置 [tcp 端口转发](/doc/quick_start_zh.md#tcp-端口转发) 即可,如果需要使**服务器B**的**80端口**可以转发至**多个**web服务端口则需要指定代理的类型为 http并且在 frps 的配置文件中配置用于提供 http 转发服务的端口。
按照如下的内容修改配置文件后,需要将自定义域名的 **A 记录**解析到 [server_addr],如果 [server_addr] 是域名也可以将自定义域名的 **CNAME 记录**解析到 [server_addr]。
之后就可以通过自定义域名访问到本地的多个 web 服务。
### 配置文件
#### frps.ini
```ini
[common]
bind_addr = 0.0.0.0
bind_port = 7000
# 如果需要支持http类型的代理则需要指定一个端口
vhost_http_port = 80
log_file = ./frps.log
log_level = info
[web01]
# type 默认为 tcp这里需要特别指定为 http
type = http
auth_token = 123
# 自定义域名绑定,如果需要同时绑定多个以英文逗号分隔
custom_domains = web01.yourdomain.com
[web02]
type = http
auth_token = 123
custom_domains = web02.yourdomain.com
```
#### frpc.ini
```ini
[common]
server_addr = x.x.x.x
server_port = 7000
log_file = ./frpc.log
log_level = info
auth_token = 123
# 自定义域名在 frps.ini 中配置,方便做统一管理
[web01]
type = http
local_ip = 127.0.0.1
local_port = 8000
# 可选是否加密
use_encryption = true
[web02]
type = http
local_ip = 127.0.0.1
local_port = 8001
```

View File

@@ -1,3 +1,17 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
@@ -12,66 +26,105 @@ import (
"frp/models/msg"
"frp/utils/conn"
"frp/utils/log"
"frp/utils/pcrypto"
)
var connection *conn.Conn = nil
var heartBeatTimer *time.Timer = nil
func ControlProcess(cli *client.ProxyClient, wait *sync.WaitGroup) {
defer wait.Done()
msgSendChan := make(chan interface{}, 1024)
c, err := loginToServer(cli)
if err != nil {
log.Error("ProxyName [%s], connect to server failed!", cli.Name)
return
}
connection = c
defer connection.Close()
defer c.Close()
go heartbeatSender(c, msgSendChan)
go msgSender(cli, c, msgSendChan)
msgReader(cli, c, msgSendChan)
close(msgSendChan)
}
// loop for reading messages from frpc after control connection is established
func msgReader(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface{}) error {
// for heartbeat
var heartbeatTimeout bool = false
timer := time.AfterFunc(time.Duration(client.HeartBeatTimeout)*time.Second, func() {
heartbeatTimeout = true
c.Close()
log.Error("ProxyName [%s], heartbeatRes from frps timeout", cli.Name)
})
defer timer.Stop()
for {
// ignore response content now
content, err := connection.ReadLine()
if err == io.EOF || nil == connection || connection.IsClosed() {
log.Debug("ProxyName [%s], server close this control conn", cli.Name)
var sleepTime time.Duration = 1
buf, err := c.ReadLine()
if err == io.EOF || c == nil || c.IsClosed() {
c.Close()
log.Warn("ProxyName [%s], frps close this control conn!", cli.Name)
var delayTime time.Duration = 1
// loop until connect to server
// loop until reconnect to frps
for {
log.Debug("ProxyName [%s], try to reconnect to server[%s:%d]...", cli.Name, client.ServerAddr, client.ServerPort)
tmpConn, err := loginToServer(cli)
log.Info("ProxyName [%s], try to reconnect to frps [%s:%d]...", cli.Name, client.ServerAddr, client.ServerPort)
c, err = loginToServer(cli)
if err == nil {
connection.Close()
connection = tmpConn
close(msgSendChan)
msgSendChan = make(chan interface{}, 1024)
go heartbeatSender(c, msgSendChan)
go msgSender(cli, c, msgSendChan)
break
}
if sleepTime < 60 {
sleepTime = sleepTime * 2
if delayTime < 60 {
delayTime = delayTime * 2
}
time.Sleep(sleepTime * time.Second)
time.Sleep(delayTime * time.Second)
}
continue
} else if err != nil {
log.Warn("ProxyName [%s], read from server error, %v", cli.Name, err)
log.Warn("ProxyName [%s], read from frps error: %v", cli.Name, err)
continue
}
clientCtlRes := &msg.ClientCtlRes{}
if err := json.Unmarshal([]byte(content), clientCtlRes); err != nil {
log.Warn("Parse err: %v : %s", err, content)
continue
}
if consts.SCHeartBeatRes == clientCtlRes.GeneralRes.Code {
if heartBeatTimer != nil {
log.Debug("Client rcv heartbeat response")
heartBeatTimer.Reset(time.Duration(client.HeartBeatTimeout) * time.Second)
} else {
log.Error("heartBeatTimer is nil")
}
ctlRes := &msg.ControlRes{}
if err := json.Unmarshal([]byte(buf), &ctlRes); err != nil {
log.Warn("ProxyName [%s], parse msg from frps error: %v : %s", cli.Name, err, buf)
continue
}
cli.StartTunnel(client.ServerAddr, client.ServerPort)
switch ctlRes.Type {
case consts.HeartbeatRes:
log.Debug("ProxyName [%s], receive heartbeat response", cli.Name)
timer.Reset(time.Duration(client.HeartBeatTimeout) * time.Second)
case consts.NoticeUserConn:
log.Debug("ProxyName [%s], new user connection", cli.Name)
cli.StartTunnel(client.ServerAddr, client.ServerPort)
default:
log.Warn("ProxyName [%s}, unsupport msgType [%d]", cli.Name, ctlRes.Type)
}
}
return nil
}
// loop for sending messages from channel to frps
func msgSender(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface{}) {
for {
msg, ok := <-msgSendChan
if !ok {
break
}
buf, _ := json.Marshal(msg)
err := c.Write(string(buf) + "\n")
if err != nil {
log.Warn("ProxyName [%s], write to server error, proxy exit", cli.Name)
c.Close()
break
}
}
}
@@ -82,10 +135,14 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) {
return
}
req := &msg.ClientCtlReq{
Type: consts.CtlConn,
ProxyName: cli.Name,
Passwd: cli.Passwd,
nowTime := time.Now().Unix()
authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime))
req := &msg.ControlReq{
Type: consts.NewCtlConn,
ProxyName: cli.Name,
AuthKey: authKey,
UseEncryption: cli.UseEncryption,
Timestamp: nowTime,
}
buf, _ := json.Marshal(req)
err = c.Write(string(buf) + "\n")
@@ -101,55 +158,34 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) {
}
log.Debug("ProxyName [%s], read [%s]", cli.Name, res)
clientCtlRes := &msg.ClientCtlRes{}
if err = json.Unmarshal([]byte(res), &clientCtlRes); err != nil {
ctlRes := &msg.ControlRes{}
if err = json.Unmarshal([]byte(res), &ctlRes); err != nil {
log.Error("ProxyName [%s], format server response error, %v", cli.Name, err)
return
}
if clientCtlRes.Code != 0 {
log.Error("ProxyName [%s], start proxy error, %s", cli.Name, clientCtlRes.Msg)
return c, fmt.Errorf("%s", clientCtlRes.Msg)
if ctlRes.Code != 0 {
log.Error("ProxyName [%s], start proxy error, %s", cli.Name, ctlRes.Msg)
return c, fmt.Errorf("%s", ctlRes.Msg)
}
go startHeartBeat(c)
log.Debug("ProxyName [%s], connect to server[%s:%d] success!", cli.Name, client.ServerAddr, client.ServerPort)
log.Debug("ProxyName [%s], connect to server [%s:%d] success!", cli.Name, client.ServerAddr, client.ServerPort)
return
}
func startHeartBeat(c *conn.Conn) {
f := func() {
log.Error("HeartBeat timeout!")
if c != nil {
c.Close()
}
func heartbeatSender(c *conn.Conn, msgSendChan chan interface{}) {
heartbeatReq := &msg.ControlReq{
Type: consts.HeartbeatReq,
}
heartBeatTimer = time.AfterFunc(time.Duration(client.HeartBeatTimeout)*time.Second, f)
defer heartBeatTimer.Stop()
clientCtlReq := &msg.ClientCtlReq{
Type: consts.CSHeartBeatReq,
ProxyName: "",
Passwd: "",
}
request, err := json.Marshal(clientCtlReq)
if err != nil {
log.Warn("Serialize clientCtlReq err! Err: %v", err)
}
log.Debug("Start to send heartbeat")
log.Info("Start to send heartbeat to frps")
for {
time.Sleep(time.Duration(client.HeartBeatInterval) * time.Second)
if c != nil && !c.IsClosed() {
err = c.Write(string(request) + "\n")
if err != nil {
log.Error("Send hearbeat to server failed! Err:%v", err)
continue
}
log.Debug("Send heartbeat to server")
msgSendChan <- heartbeatReq
} else {
break
}
}
log.Debug("Heartbeat exit")
log.Debug("Heartbeat goroutine exit")
}

View File

@@ -1,20 +1,94 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"os"
"strconv"
"strings"
"sync"
docopt "github.com/docopt/docopt-go"
"frp/models/client"
"frp/utils/log"
"frp/utils/version"
)
var (
configFile string = "./frpc.ini"
)
var usage string = `frpc is the client of frp
Usage:
frpc [-c config_file] [-L log_file] [--log-level=<log_level>] [--server-addr=<server_addr>]
frpc -h | --help | --version
Options:
-c config_file set config file
-L log_file set output log file, including console
--log-level=<log_level> set log level: debug, info, warn, error
--server-addr=<server_addr> addr which frps is listening for, example: 0.0.0.0:7000
-h --help show this screen
--version show version
`
func main() {
err := client.LoadConf("./frpc.ini")
// the configures parsed from file will be replaced by those from command line if exist
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
if args["-c"] != nil {
configFile = args["-c"].(string)
}
err = client.LoadConf(configFile)
if err != nil {
fmt.Println(err)
os.Exit(-1)
}
log.InitLog(client.LogWay, client.LogFile, client.LogLevel)
if args["-L"] != nil {
if args["-L"].(string) == "console" {
client.LogWay = "console"
} else {
client.LogWay = "file"
client.LogFile = args["-L"].(string)
}
}
if args["--log-level"] != nil {
client.LogLevel = args["--log-level"].(string)
}
if args["--server-addr"] != nil {
addr := strings.Split(args["--server-addr"].(string), ":")
if len(addr) != 2 {
fmt.Println("--server-addr format error: example 0.0.0.0:7000")
os.Exit(1)
}
serverPort, err := strconv.ParseInt(addr[1], 10, 64)
if err != nil {
fmt.Println("--server-addr format error, example 0.0.0.0:7000")
os.Exit(1)
}
client.ServerAddr = addr[0]
client.ServerPort = serverPort
}
log.InitLog(client.LogWay, client.LogFile, client.LogLevel, client.LogMaxDays)
// wait until all control goroutine exit
var wait sync.WaitGroup

View File

@@ -1,3 +1,17 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
@@ -11,95 +25,171 @@ import (
"frp/models/server"
"frp/utils/conn"
"frp/utils/log"
"frp/utils/pcrypto"
)
func ProcessControlConn(l *conn.Listener) {
for {
c, err := l.GetConn()
c, err := l.Accept()
if err != nil {
return
}
log.Debug("Get one new conn, %v", c.GetRemoteAddr())
log.Debug("Get new connection, %v", c.GetRemoteAddr())
go controlWorker(c)
}
}
// connection from every client and server
func controlWorker(c *conn.Conn) {
// the first message is from client to server
// if error, close connection
res, err := c.ReadLine()
// if login message type is NewWorkConn, don't close this connection
var closeFlag bool = true
var s *server.ProxyServer
defer func() {
if closeFlag {
c.Close()
if s != nil {
s.Close()
}
}
}()
// get login message
buf, err := c.ReadLine()
if err != nil {
log.Warn("Read error, %v", err)
return
}
log.Debug("get: %s", res)
log.Debug("Get msg from frpc: %s", buf)
clientCtlReq := &msg.ClientCtlReq{}
clientCtlRes := &msg.ClientCtlRes{}
if err := json.Unmarshal([]byte(res), &clientCtlReq); err != nil {
log.Warn("Parse err: %v : %s", err, res)
cliReq := &msg.ControlReq{}
if err := json.Unmarshal([]byte(buf), &cliReq); err != nil {
log.Warn("Parse msg from frpc error: %v : %s", err, buf)
return
}
// check
succ, info, needRes := checkProxy(clientCtlReq, c)
if !succ {
clientCtlRes.Code = 1
clientCtlRes.Msg = info
// do login when type is NewCtlConn or NewWorkConn
ret, info := doLogin(cliReq, c)
s, ok := server.ProxyServers[cliReq.ProxyName]
if !ok {
log.Warn("ProxyName [%s] is not exist", cliReq.ProxyName)
return
}
if needRes {
defer c.Close()
buf, _ := json.Marshal(clientCtlRes)
err = c.Write(string(buf) + "\n")
// if login type is NewWorkConn, nothing will be send to frpc
if cliReq.Type != consts.NewWorkConn {
cliRes := &msg.ControlRes{
Type: consts.NewCtlConnRes,
Code: ret,
Msg: info,
}
byteBuf, _ := json.Marshal(cliRes)
err = c.Write(string(byteBuf) + "\n")
if err != nil {
log.Warn("Write error, %v", err)
log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name)
time.Sleep(1 * time.Second)
return
}
} else {
// work conn, just return
closeFlag = false
return
}
// other messages is from server to client
s, ok := server.ProxyServers[clientCtlReq.ProxyName]
if !ok {
log.Warn("ProxyName [%s] is not exist", clientCtlReq.ProxyName)
return
}
// create a channel for sending messages
msgSendChan := make(chan interface{}, 1024)
go msgSender(s, c, msgSendChan)
go noticeUserConn(s, msgSendChan)
// read control msg from client
go readControlMsgFromClient(s, c)
serverCtlReq := &msg.ClientCtlReq{}
serverCtlReq.Type = consts.WorkConn
for {
closeFlag := s.WaitUserConn()
if closeFlag {
log.Debug("ProxyName [%s], goroutine for dealing user conn is closed", s.Name)
break
}
buf, _ := json.Marshal(serverCtlReq)
err = c.Write(string(buf) + "\n")
if err != nil {
log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name)
s.Close()
return
}
log.Debug("ProxyName [%s], write to client to add work conn success", s.Name)
}
// loop for reading control messages from frpc and deal with different types
msgReader(s, c, msgSendChan)
close(msgSendChan)
log.Info("ProxyName [%s], I'm dead!", s.Name)
return
}
func checkProxy(req *msg.ClientCtlReq, c *conn.Conn) (succ bool, info string, needRes bool) {
succ = false
needRes = true
// when frps get one new user connection, send NoticeUserConn message to frpc and accept one new WorkConn later
func noticeUserConn(s *server.ProxyServer, msgSendChan chan interface{}) {
for {
closeFlag := s.WaitUserConn()
if closeFlag {
log.Debug("ProxyName [%s], goroutine for noticing user conn is closed", s.Name)
break
}
notice := &msg.ControlRes{
Type: consts.NoticeUserConn,
}
msgSendChan <- notice
log.Debug("ProxyName [%s], notice client to add work conn", s.Name)
}
}
// loop for reading messages from frpc after control connection is established
func msgReader(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}) error {
// for heartbeat
var heartbeatTimeout bool = false
timer := time.AfterFunc(time.Duration(server.HeartBeatTimeout)*time.Second, func() {
heartbeatTimeout = true
s.Close()
c.Close()
log.Error("ProxyName [%s], client heartbeat timeout", s.Name)
})
defer timer.Stop()
for {
buf, err := c.ReadLine()
if err != nil {
if err == io.EOF {
log.Warn("ProxyName [%s], client is dead!", s.Name)
return err
} else if c == nil || c.IsClosed() {
log.Warn("ProxyName [%s], client connection is closed", s.Name)
return err
}
log.Warn("ProxyName [%s], read error: %v", s.Name, err)
continue
}
cliReq := &msg.ControlReq{}
if err := json.Unmarshal([]byte(buf), &cliReq); err != nil {
log.Warn("ProxyName [%s], parse msg from frpc error: %v : %s", s.Name, err, buf)
continue
}
switch cliReq.Type {
case consts.HeartbeatReq:
log.Debug("ProxyName [%s], get heartbeat", s.Name)
timer.Reset(time.Duration(server.HeartBeatTimeout) * time.Second)
heartbeatRes := &msg.ControlRes{
Type: consts.HeartbeatRes,
}
msgSendChan <- heartbeatRes
default:
log.Warn("ProxyName [%s}, unsupport msgType [%d]", s.Name, cliReq.Type)
}
}
return nil
}
// loop for sending messages from channel to frpc
func msgSender(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}) {
for {
msg, ok := <-msgSendChan
if !ok {
break
}
buf, _ := json.Marshal(msg)
err := c.Write(string(buf) + "\n")
if err != nil {
log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name)
s.Close()
break
}
}
}
// if success, ret equals 0, otherwise greater than 0
func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
ret = 1
// check if proxy name exist
s, ok := server.ProxyServers[req.ProxyName]
if !ok {
@@ -108,97 +198,53 @@ func checkProxy(req *msg.ClientCtlReq, c *conn.Conn) (succ bool, info string, ne
return
}
// check password
if req.Passwd != s.Passwd {
info = fmt.Sprintf("ProxyName [%s], password is not correct", req.ProxyName)
// check authKey
nowTime := time.Now().Unix()
authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp))
// authKey avaiable in 15 minutes
if nowTime-req.Timestamp > 15*60 {
info = fmt.Sprintf("ProxyName [%s], authorization timeout", req.ProxyName)
log.Warn(info)
return
} else if req.AuthKey != authKey {
info = fmt.Sprintf("ProxyName [%s], authorization failed", req.ProxyName)
log.Warn(info)
return
}
// control conn
if req.Type == consts.CtlConn {
if s.Status != consts.Idle {
if req.Type == consts.NewCtlConn {
if s.Status == consts.Working {
info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName)
log.Warn(info)
return
}
// start proxy and listen for user conn, no block
// set infomations from frpc
s.UseEncryption = req.UseEncryption
// start proxy and listen for user connections, no block
err := s.Start()
if err != nil {
info = fmt.Sprintf("ProxyName [%s], start proxy error: %v", req.ProxyName, err.Error())
info = fmt.Sprintf("ProxyName [%s], start proxy error: %v", req.ProxyName, err)
log.Warn(info)
return
}
log.Info("ProxyName [%s], start proxy success", req.ProxyName)
} else if req.Type == consts.WorkConn {
} else if req.Type == consts.NewWorkConn {
// work conn
needRes = false
if s.Status != consts.Working {
log.Warn("ProxyName [%s], is not working when it gets one new work conn", req.ProxyName)
log.Warn("ProxyName [%s], is not working when it gets one new work connnection", req.ProxyName)
return
}
s.GetNewCliConn(c)
// the connection will close after join over
s.RecvNewWorkConn(c)
} else {
info = fmt.Sprintf("ProxyName [%s], type [%d] unsupport", req.ProxyName, req.Type)
log.Warn(info)
info = fmt.Sprintf("Unsupport login message type [%d]", req.Type)
log.Warn("Unsupport login message type [%d]", req.Type)
return
}
succ = true
ret = 0
return
}
func readControlMsgFromClient(s *server.ProxyServer, c *conn.Conn) {
isContinueRead := true
f := func() {
isContinueRead = false
s.Close()
log.Error("ProxyName [%s], client heartbeat timeout", s.Name)
}
timer := time.AfterFunc(time.Duration(server.HeartBeatTimeout)*time.Second, f)
defer timer.Stop()
for isContinueRead {
content, err := c.ReadLine()
if err != nil {
if err == io.EOF {
log.Warn("ProxyName [%s], client is dead!", s.Name)
s.Close()
break
} else if nil == c || c.IsClosed() {
log.Warn("ProxyName [%s], client connection is closed", s.Name)
break
}
log.Error("ProxyName [%s], read error: %v", s.Name, err)
continue
}
clientCtlReq := &msg.ClientCtlReq{}
if err := json.Unmarshal([]byte(content), clientCtlReq); err != nil {
log.Warn("Parse err: %v : %s", err, content)
continue
}
if consts.CSHeartBeatReq == clientCtlReq.Type {
log.Debug("ProxyName [%s], get heartbeat", s.Name)
timer.Reset(time.Duration(server.HeartBeatTimeout) * time.Second)
clientCtlRes := &msg.ClientCtlRes{}
clientCtlRes.GeneralRes.Code = consts.SCHeartBeatRes
response, err := json.Marshal(clientCtlRes)
if err != nil {
log.Warn("Serialize ClientCtlRes err! err: %v", err)
continue
}
err = c.Write(string(response) + "\n")
if err != nil {
log.Error("Send heartbeat response to client failed! Err:%v", err)
continue
}
}
}
}

View File

@@ -1,25 +1,114 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"os"
"strconv"
"strings"
"time"
docopt "github.com/docopt/docopt-go"
"frp/models/server"
"frp/utils/conn"
"frp/utils/log"
"frp/utils/version"
"frp/utils/vhost"
)
var (
configFile string = "./frps.ini"
)
var usage string = `frps is the server of frp
Usage:
frps [-c config_file] [-L log_file] [--log-level=<log_level>] [--addr=<bind_addr>]
frps -h | --help | --version
Options:
-c config_file set config file
-L log_file set output log file, including console
--log-level=<log_level> set log level: debug, info, warn, error
--addr=<bind_addr> listen addr for client, example: 0.0.0.0:7000
-h --help show this screen
--version show version
`
func main() {
err := server.LoadConf("./frps.ini")
// the configures parsed from file will be replaced by those from command line if exist
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
if args["-c"] != nil {
configFile = args["-c"].(string)
}
err = server.LoadConf(configFile)
if err != nil {
fmt.Println(err)
os.Exit(-1)
}
log.InitLog(server.LogWay, server.LogFile, server.LogLevel)
if args["-L"] != nil {
if args["-L"].(string) == "console" {
server.LogWay = "console"
} else {
server.LogWay = "file"
server.LogFile = args["-L"].(string)
}
}
if args["--log-level"] != nil {
server.LogLevel = args["--log-level"].(string)
}
if args["--addr"] != nil {
addr := strings.Split(args["--addr"].(string), ":")
if len(addr) != 2 {
fmt.Println("--addr format error: example 0.0.0.0:7000")
os.Exit(1)
}
bindPort, err := strconv.ParseInt(addr[1], 10, 64)
if err != nil {
fmt.Println("--addr format error, example 0.0.0.0:7000")
os.Exit(1)
}
server.BindAddr = addr[0]
server.BindPort = bindPort
}
log.InitLog(server.LogWay, server.LogFile, server.LogLevel, server.LogMaxDays)
l, err := conn.Listen(server.BindAddr, server.BindPort)
if err != nil {
log.Error("Create listener error, %v", err)
os.Exit(-1)
log.Error("Create server listener error, %v", err)
os.Exit(1)
}
// create vhost if VhostHttpPort != 0
if server.VhostHttpPort != 0 {
vhostListener, err := conn.Listen(server.BindAddr, server.VhostHttpPort)
if err != nil {
log.Error("Create vhost http listener error, %v", err)
os.Exit(1)
}
server.VhostMuxer, err = vhost.NewHttpMuxer(vhostListener, 30*time.Second)
if err != nil {
log.Error("Create vhost httpMuxer error, %v", err)
}
}
log.Info("Start frps success")

View File

@@ -1,22 +1,42 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"encoding/json"
"fmt"
"time"
"frp/models/consts"
"frp/models/msg"
"frp/utils/conn"
"frp/utils/log"
"frp/utils/pcrypto"
)
type ProxyClient struct {
Name string
Passwd string
LocalPort int64
Name string
AuthToken string
LocalIp string
LocalPort int64
Type string
UseEncryption bool
}
func (p *ProxyClient) GetLocalConn() (c *conn.Conn, err error) {
c, err = conn.ConnectServer("127.0.0.1", p.LocalPort)
c, err = conn.ConnectServer(p.LocalIp, p.LocalPort)
if err != nil {
log.Error("ProxyName [%s], connect to local port error, %v", p.Name, err)
}
@@ -36,10 +56,13 @@ func (p *ProxyClient) GetRemoteConn(addr string, port int64) (c *conn.Conn, err
return
}
req := &msg.ClientCtlReq{
Type: consts.WorkConn,
nowTime := time.Now().Unix()
authKey := pcrypto.GetAuthKey(p.Name + p.AuthToken + fmt.Sprintf("%d", nowTime))
req := &msg.ControlReq{
Type: consts.NewWorkConn,
ProxyName: p.Name,
Passwd: p.Passwd,
AuthKey: authKey,
Timestamp: nowTime,
}
buf, _ := json.Marshal(req)
@@ -64,8 +87,13 @@ func (p *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err erro
}
// l means local, r means remote
log.Debug("Join two conns, (l[%s] r[%s]) (l[%s] r[%s])", localConn.GetLocalAddr(), localConn.GetRemoteAddr(),
log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", localConn.GetLocalAddr(), localConn.GetRemoteAddr(),
remoteConn.GetLocalAddr(), remoteConn.GetRemoteAddr())
go conn.Join(localConn, remoteConn)
if p.UseEncryption {
go conn.JoinMore(localConn, remoteConn, p.AuthToken)
} else {
go conn.Join(localConn, remoteConn)
}
return nil
}

View File

@@ -1,3 +1,17 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
@@ -11,11 +25,12 @@ import (
var (
ServerAddr string = "0.0.0.0"
ServerPort int64 = 7000
LogFile string = "./frpc.log"
LogLevel string = "warn"
LogWay string = "file"
HeartBeatInterval int64 = 5
HeartBeatTimeout int64 = 30
LogFile string = "console"
LogWay string = "console"
LogLevel string = "info"
LogMaxDays int64 = 3
HeartBeatInterval int64 = 20
HeartBeatTimeout int64 = 90
)
var ProxyClients map[string]*ProxyClient = make(map[string]*ProxyClient)
@@ -43,6 +58,11 @@ func LoadConf(confFile string) (err error) {
tmpStr, ok = conf.Get("common", "log_file")
if ok {
LogFile = tmpStr
if LogFile == "console" {
LogWay = "console"
} else {
LogWay = "file"
}
}
tmpStr, ok = conf.Get("common", "log_level")
@@ -50,22 +70,37 @@ func LoadConf(confFile string) (err error) {
LogLevel = tmpStr
}
tmpStr, ok = conf.Get("common", "log_way")
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
LogWay = tmpStr
LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
}
// servers
var authToken string
tmpStr, ok = conf.Get("common", "auth_token")
if ok {
authToken = tmpStr
} else {
return fmt.Errorf("auth_token not found")
}
// proxies
for name, section := range conf {
if name != "common" {
proxyClient := &ProxyClient{}
// name
proxyClient.Name = name
proxyClient.Passwd, ok = section["passwd"]
// auth_token
proxyClient.AuthToken = authToken
// local_ip
proxyClient.LocalIp, ok = section["local_ip"]
if !ok {
return fmt.Errorf("Parse ini file error: proxy [%s] no passwd found", proxyClient.Name)
// use 127.0.0.1 as default
proxyClient.LocalIp = "127.0.0.1"
}
// local_port
portStr, ok := section["local_port"]
if ok {
proxyClient.LocalPort, err = strconv.ParseInt(portStr, 10, 64)
@@ -76,6 +111,23 @@ func LoadConf(confFile string) (err error) {
return fmt.Errorf("Parse ini file error: proxy [%s] local_port not found", proxyClient.Name)
}
// type
proxyClient.Type = "tcp"
typeStr, ok := section["type"]
if ok {
if typeStr != "tcp" && typeStr != "http" {
return fmt.Errorf("Parse ini file error: proxy [%s] type error", proxyClient.Name)
}
proxyClient.Type = typeStr
}
// use_encryption
proxyClient.UseEncryption = false
useEncryptionStr, ok := section["use_encryption"]
if ok && useEncryptionStr == "true" {
proxyClient.UseEncryption = true
}
ProxyClients[proxyClient.Name] = proxyClient
}
}

View File

@@ -1,23 +1,32 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package consts
// server status
const (
Idle = iota
Working
Closed
)
// connection type
// msg type
const (
CtlConn = iota
WorkConn
)
// msg from client to server
const (
CSHeartBeatReq = 1
)
// msg from server to client
const (
SCHeartBeatRes = 100
NewCtlConn = iota
NewWorkConn
NoticeUserConn
NewCtlConnRes
HeartbeatReq
HeartbeatRes
)

View File

@@ -1,3 +1,17 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
type GeneralRes struct {
@@ -5,16 +19,17 @@ type GeneralRes struct {
Msg string `json:"msg"`
}
type ClientCtlReq struct {
Type int64 `json:"type"`
ProxyName string `json:"proxy_name"`
Passwd string `json:"passwd"`
// messages between control connection of frpc and frps
type ControlReq struct {
Type int64 `json:"type"`
ProxyName string `json:"proxy_name,omitempty"`
AuthKey string `json:"auth_key, omitempty"`
UseEncryption bool `json:"use_encryption, omitempty"`
Timestamp int64 `json:"timestamp, omitempty"`
}
type ClientCtlRes struct {
GeneralRes
}
type ServerCtlReq struct {
Type int64 `json:"type"`
type ControlRes struct {
Type int64 `json:"type"`
Code int64 `json:"code"`
Msg string `json:"msg"`
}

View File

@@ -1,21 +1,42 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"strconv"
"strings"
ini "github.com/vaughan0/go-ini"
"frp/utils/vhost"
)
// common config
var (
BindAddr string = "0.0.0.0"
BindPort int64 = 9527
LogFile string = "./frps.log"
LogLevel string = "warn"
LogWay string = "file"
HeartBeatTimeout int64 = 30
BindPort int64 = 7000
VhostHttpPort int64 = 0 // if VhostHttpPort equals 0, do not listen a public port for http
LogFile string = "console"
LogWay string = "console" // console or file
LogLevel string = "info"
LogMaxDays int64 = 3
HeartBeatTimeout int64 = 90
UserConnTimeout int64 = 10
VhostMuxer *vhost.HttpMuxer
)
var ProxyServers map[string]*ProxyServer = make(map[string]*ProxyServer)
@@ -40,9 +61,21 @@ func LoadConf(confFile string) (err error) {
BindPort, _ = strconv.ParseInt(tmpStr, 10, 64)
}
tmpStr, ok = conf.Get("common", "vhost_http_port")
if ok {
VhostHttpPort, _ = strconv.ParseInt(tmpStr, 10, 64)
} else {
VhostHttpPort = 0
}
tmpStr, ok = conf.Get("common", "log_file")
if ok {
LogFile = tmpStr
if LogFile == "console" {
LogWay = "console"
} else {
LogWay = "file"
}
}
tmpStr, ok = conf.Get("common", "log_level")
@@ -50,35 +83,61 @@ func LoadConf(confFile string) (err error) {
LogLevel = tmpStr
}
tmpStr, ok = conf.Get("common", "log_way")
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
LogWay = tmpStr
LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
}
// servers
for name, section := range conf {
if name != "common" {
proxyServer := &ProxyServer{}
proxyServer.CustomDomains = make([]string, 0)
proxyServer.Name = name
proxyServer.Passwd, ok = section["passwd"]
if !ok {
return fmt.Errorf("Parse ini file error: proxy [%s] no passwd found", proxyServer.Name)
}
proxyServer.BindAddr, ok = section["bind_addr"]
if !ok {
proxyServer.BindAddr = "0.0.0.0"
}
portStr, ok := section["listen_port"]
proxyServer.Type, ok = section["type"]
if ok {
proxyServer.ListenPort, err = strconv.ParseInt(portStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse ini file error: proxy [%s] listen_port error", proxyServer.Name)
if proxyServer.Type != "tcp" && proxyServer.Type != "http" {
return fmt.Errorf("Parse ini file error: proxy [%s] type error", proxyServer.Name)
}
} else {
return fmt.Errorf("Parse ini file error: proxy [%s] listen_port not found", proxyServer.Name)
proxyServer.Type = "tcp"
}
proxyServer.AuthToken, ok = section["auth_token"]
if !ok {
return fmt.Errorf("Parse ini file error: proxy [%s] no auth_token found", proxyServer.Name)
}
// for tcp
if proxyServer.Type == "tcp" {
proxyServer.BindAddr, ok = section["bind_addr"]
if !ok {
proxyServer.BindAddr = "0.0.0.0"
}
portStr, ok := section["listen_port"]
if ok {
proxyServer.ListenPort, err = strconv.ParseInt(portStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse ini file error: proxy [%s] listen_port error", proxyServer.Name)
}
} else {
return fmt.Errorf("Parse ini file error: proxy [%s] listen_port not found", proxyServer.Name)
}
} else if proxyServer.Type == "http" {
// for http
domainStr, ok := section["custom_domains"]
if ok {
var suffix string
if VhostHttpPort != 80 {
suffix = fmt.Sprintf(":%d", VhostHttpPort)
}
proxyServer.CustomDomains = strings.Split(domainStr, ",")
for i, domain := range proxyServer.CustomDomains {
proxyServer.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain)) + suffix
}
}
}
proxyServer.Init()

View File

@@ -1,3 +1,17 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
@@ -10,25 +24,34 @@ import (
"frp/utils/log"
)
type ProxyServer struct {
Name string
Passwd string
BindAddr string
ListenPort int64
Status int64
type Listener interface {
Accept() (*conn.Conn, error)
Close() error
}
listener *conn.Listener // accept new connection from remote users
type ProxyServer struct {
Name string
AuthToken string
Type string
BindAddr string
ListenPort int64
UseEncryption bool
CustomDomains []string
Status int64
listeners []Listener // accept new connection from remote users
ctlMsgChan chan int64 // every time accept a new user conn, put "1" to the channel
cliConnChan chan *conn.Conn // get client conns from control goroutine
workConnChan chan *conn.Conn // get new work conns from control goroutine
userConnList *list.List // store user conns
mutex sync.Mutex
}
func (p *ProxyServer) Init() {
p.Status = consts.Idle
p.cliConnChan = make(chan *conn.Conn)
p.workConnChan = make(chan *conn.Conn)
p.ctlMsgChan = make(chan int64)
p.userConnList = list.New()
p.listeners = make([]Listener, 0)
}
func (p *ProxyServer) Lock() {
@@ -42,60 +65,74 @@ func (p *ProxyServer) Unlock() {
// start listening for user conns
func (p *ProxyServer) Start() (err error) {
p.Init()
p.listener, err = conn.Listen(p.BindAddr, p.ListenPort)
if err != nil {
return err
if p.Type == "tcp" {
l, err := conn.Listen(p.BindAddr, p.ListenPort)
if err != nil {
return err
}
p.listeners = append(p.listeners, l)
} else if p.Type == "http" {
for _, domain := range p.CustomDomains {
l, err := VhostMuxer.Listen(domain)
if err != nil {
return err
}
p.listeners = append(p.listeners, l)
}
}
p.Status = consts.Working
// start a goroutine for listener to accept user connection
go func() {
for {
// block
// if listener is closed, err returned
c, err := p.listener.GetConn()
if err != nil {
log.Info("ProxyName [%s], listener is closed", p.Name)
return
}
log.Debug("ProxyName [%s], get one new user conn [%s]", p.Name, c.GetRemoteAddr())
// insert into list
p.Lock()
if p.Status != consts.Working {
log.Debug("ProxyName [%s] is not working, new user conn close", p.Name)
c.Close()
p.Unlock()
return
}
p.userConnList.PushBack(c)
p.Unlock()
// put msg to control conn
p.ctlMsgChan <- 1
// set timeout
time.AfterFunc(time.Duration(UserConnTimeout)*time.Second, func() {
p.Lock()
defer p.Unlock()
element := p.userConnList.Front()
if element == nil {
for _, listener := range p.listeners {
go func(l Listener) {
for {
// block
// if listener is closed, err returned
c, err := l.Accept()
if err != nil {
log.Info("ProxyName [%s], listener is closed", p.Name)
return
}
log.Debug("ProxyName [%s], get one new user conn [%s]", p.Name, c.GetRemoteAddr())
userConn := element.Value.(*conn.Conn)
if userConn == c {
log.Warn("ProxyName [%s], user conn [%s] timeout", p.Name, c.GetRemoteAddr())
// insert into list
p.Lock()
if p.Status != consts.Working {
log.Debug("ProxyName [%s] is not working, new user conn close", p.Name)
c.Close()
p.Unlock()
return
}
})
}
}()
p.userConnList.PushBack(c)
p.Unlock()
// start another goroutine for join two conns from client and user
// put msg to control conn
p.ctlMsgChan <- 1
// set timeout
time.AfterFunc(time.Duration(UserConnTimeout)*time.Second, func() {
p.Lock()
element := p.userConnList.Front()
p.Unlock()
if element == nil {
return
}
userConn := element.Value.(*conn.Conn)
if userConn == c {
log.Warn("ProxyName [%s], user conn [%s] timeout", p.Name, c.GetRemoteAddr())
userConn.Close()
}
})
}
}(listener)
}
// start another goroutine for join two conns from frpc and user
go func() {
for {
cliConn, ok := <-p.cliConnChan
workConn, ok := <-p.workConnChan
if !ok {
return
}
@@ -108,7 +145,7 @@ func (p *ProxyServer) Start() (err error) {
userConn = element.Value.(*conn.Conn)
p.userConnList.Remove(element)
} else {
cliConn.Close()
workConn.Close()
p.Unlock()
continue
}
@@ -116,9 +153,14 @@ func (p *ProxyServer) Start() (err error) {
// msg will transfer to another without modifying
// l means local, r means remote
log.Debug("Join two conns, (l[%s] r[%s]) (l[%s] r[%s])", cliConn.GetLocalAddr(), cliConn.GetRemoteAddr(),
log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", workConn.GetLocalAddr(), workConn.GetRemoteAddr(),
userConn.GetLocalAddr(), userConn.GetRemoteAddr())
go conn.Join(cliConn, userConn)
if p.UseEncryption {
go conn.JoinMore(userConn, workConn, p.AuthToken)
} else {
go conn.Join(userConn, workConn)
}
}
}()
@@ -127,11 +169,19 @@ func (p *ProxyServer) Start() (err error) {
func (p *ProxyServer) Close() {
p.Lock()
p.Status = consts.Idle
p.listener.Close()
close(p.ctlMsgChan)
close(p.cliConnChan)
p.userConnList = list.New()
if p.Status != consts.Closed {
p.Status = consts.Closed
if len(p.listeners) != 0 {
for _, l := range p.listeners {
if l != nil {
l.Close()
}
}
}
close(p.ctlMsgChan)
close(p.workConnChan)
p.userConnList = list.New()
}
p.Unlock()
}
@@ -145,6 +195,6 @@ func (p *ProxyServer) WaitUserConn() (closeFlag bool) {
return
}
func (p *ProxyServer) GetNewCliConn(c *conn.Conn) {
p.cliConnChan <- c
func (p *ProxyServer) RecvNewWorkConn(c *conn.Conn) {
p.workConnChan <- c
}

View File

@@ -1,3 +1,17 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package broadcast
type Broadcast struct {

View File

@@ -1,3 +1,17 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package broadcast
import (
@@ -15,7 +29,7 @@ var (
func TestBroadcast(t *testing.T) {
b := NewBroadcast()
if b == nil {
t.Errorf("New Broadcast error, nil return")
t.Fatalf("New Broadcast error, nil return")
}
defer b.Close()
@@ -31,7 +45,7 @@ func TestBroadcast(t *testing.T) {
wait.Wait()
if succNum != totalNum {
t.Errorf("TotalNum %d, FailNum(timeout) %d", totalNum, totalNum-succNum)
t.Fatalf("TotalNum %d, FailNum(timeout) %d", totalNum, totalNum-succNum)
}
}

View File

@@ -1,3 +1,17 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package conn
import (
@@ -6,14 +20,16 @@ import (
"io"
"net"
"sync"
"time"
"frp/utils/log"
"frp/utils/pcrypto"
)
type Listener struct {
addr net.Addr
l *net.TCPListener
conns chan *Conn
accept chan *Conn
closeFlag bool
}
@@ -27,7 +43,7 @@ func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
l = &Listener{
addr: listener.Addr(),
l: listener,
conns: make(chan *Conn),
accept: make(chan *Conn),
closeFlag: false,
}
@@ -46,7 +62,7 @@ func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
closeFlag: false,
}
c.Reader = bufio.NewReader(c.TcpConn)
l.conns <- c
l.accept <- c
}
}()
return l, err
@@ -54,30 +70,38 @@ func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
// wait util get one new connection or listener is closed
// if listener is closed, err returned
func (l *Listener) GetConn() (conn *Conn, err error) {
var ok bool
conn, ok = <-l.conns
func (l *Listener) Accept() (*Conn, error) {
conn, ok := <-l.accept
if !ok {
return conn, fmt.Errorf("channel close")
}
return conn, nil
}
func (l *Listener) Close() {
func (l *Listener) Close() error {
if l.l != nil && l.closeFlag == false {
l.closeFlag = true
l.l.Close()
close(l.conns)
close(l.accept)
}
return nil
}
// wrap for TCPConn
type Conn struct {
TcpConn *net.TCPConn
TcpConn net.Conn
Reader *bufio.Reader
closeFlag bool
}
func NewConn(conn net.Conn) (c *Conn) {
c = &Conn{}
c.TcpConn = conn
c.Reader = bufio.NewReader(c.TcpConn)
c.closeFlag = false
return c
}
func ConnectServer(host string, port int64) (c *Conn, err error) {
c = &Conn{}
servertAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", host, port))
@@ -113,6 +137,12 @@ func (c *Conn) ReadLine() (buff string, err error) {
func (c *Conn) Write(content string) (err error) {
_, err = c.TcpConn.Write([]byte(content))
return err
}
func (c *Conn) SetDeadline(t time.Time) error {
err := c.TcpConn.SetDeadline(t)
return err
}
func (c *Conn) Close() {
@@ -137,7 +167,7 @@ func Join(c1 *Conn, c2 *Conn) {
var err error
_, err = io.Copy(to.TcpConn, from.TcpConn)
if err != nil {
log.Warn("join conns error, %v", err)
log.Warn("join connections error, %v", err)
}
}
@@ -147,3 +177,93 @@ func Join(c1 *Conn, c2 *Conn) {
wait.Wait()
return
}
// messages from c1 to c2 will be encrypted
// and from c2 to c1 will be decrypted
func JoinMore(c1 *Conn, c2 *Conn, cryptKey string) {
var wait sync.WaitGroup
encryptPipe := func(from *Conn, to *Conn, key string) {
defer from.Close()
defer to.Close()
defer wait.Done()
// we don't care about errors here
PipeEncrypt(from.TcpConn, to.TcpConn, key)
}
decryptPipe := func(to *Conn, from *Conn, key string) {
defer from.Close()
defer to.Close()
defer wait.Done()
// we don't care about errors here
PipeDecrypt(to.TcpConn, from.TcpConn, key)
}
wait.Add(2)
go encryptPipe(c1, c2, cryptKey)
go decryptPipe(c2, c1, cryptKey)
wait.Wait()
log.Debug("One tunnel stopped")
return
}
// decrypt msg from reader, then write into writer
func PipeDecrypt(r net.Conn, w net.Conn, key string) error {
laes := new(pcrypto.Pcrypto)
if err := laes.Init([]byte(key)); err != nil {
log.Error("Pcrypto Init error: %v", err)
return fmt.Errorf("Pcrypto Init error: %v", err)
}
nreader := bufio.NewReader(r)
for {
buf, err := nreader.ReadBytes('\n')
if err != nil {
return err
}
res, err := laes.Decrypt(buf)
if err != nil {
log.Error("Decrypt [%s] error, %v", string(buf), err)
return fmt.Errorf("Decrypt [%s] error: %v", string(buf), err)
}
_, err = w.Write(res)
if err != nil {
return err
}
}
return nil
}
// recvive msg from reader, then encrypt msg into write
func PipeEncrypt(r net.Conn, w net.Conn, key string) error {
laes := new(pcrypto.Pcrypto)
if err := laes.Init([]byte(key)); err != nil {
log.Error("Pcrypto Init error: %v", err)
return fmt.Errorf("Pcrypto Init error: %v", err)
}
nreader := bufio.NewReader(r)
buf := make([]byte, 10*1024)
for {
n, err := nreader.Read(buf)
if err != nil {
return err
}
res, err := laes.Encrypt(buf[:n])
if err != nil {
log.Error("Encrypt error: %v", err)
return fmt.Errorf("Encrypt error: %v", err)
}
res = append(res, '\n')
_, err = w.Write(res)
if err != nil {
return err
}
}
return nil
}

View File

@@ -1,6 +1,21 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package log
import (
"fmt"
"github.com/astaxie/beego/logs"
)
@@ -12,24 +27,24 @@ func init() {
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
}
func InitLog(logWay string, logFile string, logLevel string) {
SetLogFile(logWay, logFile)
func InitLog(logWay string, logFile string, logLevel string, maxdays int64) {
SetLogFile(logWay, logFile, maxdays)
SetLogLevel(logLevel)
}
// logWay: such as file or console
func SetLogFile(logWay string, logFile string) {
// logWay: file or console
func SetLogFile(logWay string, logFile string, maxdays int64) {
if logWay == "console" {
Log.SetLogger("console", "")
} else {
Log.SetLogger("file", `{"filename": "`+logFile+`"}`)
params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
Log.SetLogger("file", params)
}
}
// value: error, warning, info, debug
func SetLogLevel(logLevel string) {
level := 4 // warning
switch logLevel {
case "error":
level = 3
@@ -42,7 +57,6 @@ func SetLogLevel(logLevel string) {
default:
level = 4
}
Log.SetLevel(level)
}

View File

@@ -1,3 +1,17 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pcrypto
import (
@@ -5,6 +19,7 @@ import (
"compress/gzip"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"encoding/base64"
"encoding/hex"
"errors"
@@ -19,43 +34,40 @@ type Pcrypto struct {
func (pc *Pcrypto) Init(key []byte) error {
var err error
pc.pkey = PKCS7Padding(key, aes.BlockSize)
pc.pkey = pKCS7Padding(key, aes.BlockSize)
pc.paes, err = aes.NewCipher(pc.pkey)
return err
}
func (pc *Pcrypto) Encrypto(src []byte) ([]byte, error) {
func (pc *Pcrypto) Encrypt(src []byte) ([]byte, error) {
// gzip
var zbuf bytes.Buffer
zwr, err := gzip.NewWriterLevel(&zbuf, -1)
if err != nil {
return nil, err
}
defer zwr.Close()
zwr.Write(src)
zwr.Flush()
// aes
src = PKCS7Padding(src, aes.BlockSize)
src = pKCS7Padding(zbuf.Bytes(), aes.BlockSize)
blockMode := cipher.NewCBCEncrypter(pc.paes, pc.pkey)
crypted := make([]byte, len(src))
blockMode.CryptBlocks(crypted, src)
// gzip
var zbuf bytes.Buffer
zwr := gzip.NewWriter(&zbuf)
defer zwr.Close()
zwr.Write(crypted)
zwr.Flush()
// base64
return []byte(base64.StdEncoding.EncodeToString(zbuf.Bytes())), nil
return []byte(base64.StdEncoding.EncodeToString(crypted)), nil
}
func (pc *Pcrypto) Decrypto(str []byte) ([]byte, error) {
func (pc *Pcrypto) Decrypt(str []byte) ([]byte, error) {
// base64
data, err := base64.StdEncoding.DecodeString(string(str))
if err != nil {
return nil, err
}
// gunzip
zbuf := bytes.NewBuffer(data)
zrd, _ := gzip.NewReader(zbuf)
defer zrd.Close()
data, _ = ioutil.ReadAll(zrd)
// aes
decryptText, err := hex.DecodeString(fmt.Sprintf("%x", data))
if err != nil {
@@ -69,19 +81,35 @@ func (pc *Pcrypto) Decrypto(str []byte) ([]byte, error) {
blockMode := cipher.NewCBCDecrypter(pc.paes, pc.pkey)
blockMode.CryptBlocks(decryptText, decryptText)
decryptText = PKCS7UnPadding(decryptText)
decryptText = pKCS7UnPadding(decryptText)
return decryptText, nil
// gunzip
zbuf := bytes.NewBuffer(decryptText)
zrd, err := gzip.NewReader(zbuf)
if err != nil {
return nil, err
}
defer zrd.Close()
data, _ = ioutil.ReadAll(zrd)
return data, nil
}
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
func pKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func PKCS7UnPadding(origData []byte) []byte {
func pKCS7UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func GetAuthKey(str string) (authKey string) {
md5Ctx := md5.New()
md5Ctx.Write([]byte(str))
md5Str := md5Ctx.Sum(nil)
return hex.EncodeToString(md5Str)
}

View File

@@ -1,47 +1,47 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pcrypto
import (
"crypto/aes"
"fmt"
"testing"
)
func TestEncrypto(t *testing.T) {
func TestEncrypt(t *testing.T) {
pp := new(Pcrypto)
pp.Init([]byte("Hana"))
res, err := pp.Encrypto([]byte("Just One Test!"))
res, err := pp.Encrypt([]byte("Just One Test!"))
if err != nil {
t.Error(err)
t.Fatal(err)
}
fmt.Printf("[%x]\n", res)
}
func TestDecrypto(t *testing.T) {
func TestDecrypt(t *testing.T) {
pp := new(Pcrypto)
pp.Init([]byte("Hana"))
res, err := pp.Encrypto([]byte("Just One Test!"))
res, err := pp.Encrypt([]byte("Just One Test!"))
if err != nil {
t.Error(err)
t.Fatal(err)
}
res, err = pp.Decrypto(res)
res, err = pp.Decrypt(res)
if err != nil {
t.Error(err)
t.Fatal(err)
}
fmt.Printf("[%s]\n", string(res))
}
func TestPKCS7Padding(t *testing.T) {
ltt := []byte("Test_PKCS7Padding")
ltt = PKCS7Padding(ltt, aes.BlockSize)
fmt.Printf("[%x]\n", (ltt))
}
func TestPKCS7UnPadding(t *testing.T) {
ltt := []byte("Test_PKCS7Padding")
ltt = PKCS7Padding(ltt, aes.BlockSize)
ltt = PKCS7UnPadding(ltt)
fmt.Printf("[%x]\n", ltt)
}

View File

@@ -0,0 +1,58 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package version
import (
"strconv"
"strings"
)
var version string = "0.5.0"
func Full() string {
return version
}
func Proto(v string) int64 {
arr := strings.Split(v, ".")
if len(arr) < 2 {
return 0
}
res, _ := strconv.ParseInt(arr[0], 10, 64)
return res
}
func Major(v string) int64 {
arr := strings.Split(v, ".")
if len(arr) < 2 {
return 0
}
res, _ := strconv.ParseInt(arr[1], 10, 64)
return res
}
func Minor(v string) int64 {
arr := strings.Split(v, ".")
if len(arr) < 2 {
return 0
}
res, _ := strconv.ParseInt(arr[2], 10, 64)
return res
}
// add every case there if server will not accept client's protocol and return false
func Compat(client string, server string) bool {
return true
}

View File

@@ -0,0 +1,56 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package version
import (
"fmt"
"strconv"
"strings"
"testing"
)
func TestFull(t *testing.T) {
version := Full()
arr := strings.Split(version, ".")
if len(arr) != 3 {
t.Fatalf("Version string error: %s", version)
}
proto, err := strconv.ParseInt(arr[0], 10, 64)
if err != nil || proto < 0 {
t.Fatalf("Version proto error")
}
major, err := strconv.ParseInt(arr[1], 10, 64)
if err != nil || major < 0 {
t.Fatalf("Version major error")
}
minor, err := strconv.ParseInt(arr[2], 10, 64)
if err != nil || minor < 0 {
t.Fatalf("Version minor error")
}
}
func TestVersion(t *testing.T) {
proto := Proto(Full())
major := Major(Full())
minor := Minor(Full())
parseVerion := fmt.Sprintf("%d.%d.%d", proto, major, minor)
version := Full()
if parseVerion != version {
t.Fatalf("Get version incorrect, version [%s], parseVerion [%s]", version, parseVerion)
}
}

View File

@@ -0,0 +1,193 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package vhost
import (
"bufio"
"bytes"
"fmt"
"io"
"net"
"net/http"
"strings"
"sync"
"time"
"frp/utils/conn"
)
type muxFunc func(*conn.Conn) (net.Conn, string, error)
type VhostMuxer struct {
listener *conn.Listener
timeout time.Duration
vhostFunc muxFunc
registryMap map[string]*Listener
mutex sync.RWMutex
}
func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
mux = &VhostMuxer{
listener: listener,
timeout: timeout,
vhostFunc: vhostFunc,
registryMap: make(map[string]*Listener),
}
go mux.run()
return mux, nil
}
func (v *VhostMuxer) Listen(name string) (l *Listener, err error) {
v.mutex.Lock()
defer v.mutex.Unlock()
if _, exist := v.registryMap[name]; exist {
return nil, fmt.Errorf("name %s is already bound", name)
}
l = &Listener{
name: name,
mux: v,
accept: make(chan *conn.Conn),
}
v.registryMap[name] = l
return l, nil
}
func (v *VhostMuxer) getListener(name string) (l *Listener, exist bool) {
v.mutex.RLock()
defer v.mutex.RUnlock()
l, exist = v.registryMap[name]
return l, exist
}
func (v *VhostMuxer) unRegister(name string) {
v.mutex.Lock()
defer v.mutex.Unlock()
delete(v.registryMap, name)
}
func (v *VhostMuxer) run() {
for {
conn, err := v.listener.Accept()
if err != nil {
return
}
go v.handle(conn)
}
}
func (v *VhostMuxer) handle(c *conn.Conn) {
if err := c.SetDeadline(time.Now().Add(v.timeout)); err != nil {
return
}
sConn, name, err := v.vhostFunc(c)
if err != nil {
return
}
name = strings.ToLower(name)
l, ok := v.getListener(name)
if !ok {
return
}
if err = sConn.SetDeadline(time.Time{}); err != nil {
return
}
c.TcpConn = sConn
l.accept <- c
}
type HttpMuxer struct {
*VhostMuxer
}
func GetHttpHostname(c *conn.Conn) (_ net.Conn, routerName string, err error) {
sc, rd := newShareConn(c.TcpConn)
request, err := http.ReadRequest(bufio.NewReader(rd))
if err != nil {
return sc, "", err
}
routerName = request.Host
request.Body.Close()
return sc, routerName, nil
}
func NewHttpMuxer(listener *conn.Listener, timeout time.Duration) (*HttpMuxer, error) {
mux, err := NewVhostMuxer(listener, GetHttpHostname, timeout)
return &HttpMuxer{mux}, err
}
type Listener struct {
name string
mux *VhostMuxer // for closing VhostMuxer
accept chan *conn.Conn
}
func (l *Listener) Accept() (*conn.Conn, error) {
conn, ok := <-l.accept
if !ok {
return nil, fmt.Errorf("Listener closed")
}
return conn, nil
}
func (l *Listener) Close() error {
l.mux.unRegister(l.name)
close(l.accept)
return nil
}
func (l *Listener) Name() string {
return l.name
}
type sharedConn struct {
net.Conn
sync.Mutex
buff *bytes.Buffer
}
func newShareConn(conn net.Conn) (*sharedConn, io.Reader) {
sc := &sharedConn{
Conn: conn,
buff: bytes.NewBuffer(make([]byte, 0, 1024)),
}
return sc, io.TeeReader(conn, sc.buff)
}
func (sc *sharedConn) Read(p []byte) (n int, err error) {
sc.Lock()
if sc.buff == nil {
sc.Unlock()
return sc.Conn.Read(p)
}
n, err = sc.buff.Read(p)
if err == io.EOF {
sc.buff = nil
var n2 int
n2, err = sc.Conn.Read(p[n:])
n += n2
}
sc.Unlock()
return
}