├── .gitignore ├── .goxc.json ├── .mailmap ├── .travis.yml ├── CHANGELOG ├── CODEOWNERS ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── Jenkinsfile ├── LICENSE ├── Makefile ├── README.md ├── VERSION ├── appveyor.yml ├── config.darwin.yaml ├── config.linux.yaml ├── config.windows.yaml ├── config.yaml ├── configure ├── daemon.go ├── daemon_test.go ├── debian ├── changelog ├── compat ├── control ├── copyright ├── dirs ├── postinst ├── rules ├── source │ └── format ├── subutai-p2p.service └── tree │ └── etc │ ├── default │ └── subutai-p2p │ └── p2p.yaml ├── debug.go ├── dht_connection.go ├── dht_router.go ├── dht_test.go ├── instance.go ├── lib ├── comm.go ├── comm_test.go ├── conf.go ├── conf_darwin.go ├── conf_linux.go ├── conf_test.go ├── conf_windows.go ├── crypto.go ├── crypto_test.go ├── dht.go ├── dht_callbacks.go ├── dht_callbacks_test.go ├── dht_test.go ├── endpoint.go ├── endpoint_test.go ├── errors.go ├── errors_test.go ├── log.go ├── log_test.go ├── net.go ├── net_test.go ├── p2p.go ├── p2p_test.go ├── packet.go ├── packet_handler.go ├── packet_handler_test.go ├── packet_test.go ├── peer.go ├── peer_stats.go ├── peer_stats_test.go ├── peer_test.go ├── platform_posix.go ├── platform_posix_test.go ├── platform_windows.go ├── pmtu.go ├── proxy_manager.go ├── proxy_manager_test.go ├── proxy_server.go ├── proxy_server_test.go ├── swarm.go ├── swarm_test.go ├── tuntap.go ├── tuntap_darwin.go ├── tuntap_darwin_test.go ├── tuntap_linux.go ├── tuntap_linux_test.go ├── tuntap_windows.go ├── utils.go ├── utils_test.go └── vars.go ├── main.go ├── main_test.go ├── protocol ├── dht.pb.go ├── dht.proto └── proxy.proto ├── rest.go ├── rest └── swagger.yml ├── restore.go ├── restore_test.go ├── service_posix.go ├── service_windows.go ├── set.go ├── show.go ├── start.go ├── status.go ├── stop.go └── upload-ipfs.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # vim 2 | *.swp 3 | *.swo 4 | *.suo 5 | 6 | # vscode 7 | .vscode 8 | debug 9 | 10 | # Build 11 | config.make 12 | main 13 | /p2p 14 | tests/* 15 | /p2p-* 16 | /p2p_osx 17 | *.exe* 18 | *.zip 19 | *.tmp 20 | *.msi 21 | /bin/* 22 | *.bat 23 | 24 | # Logs 25 | *.log 26 | 27 | # Debian 28 | *.docs 29 | *.substvars 30 | *.deb 31 | darwin/*.pkg 32 | darwin/flat 33 | darwin/root 34 | 35 | # tests 36 | lib/MarshalBinary* 37 | *workdir* 38 | *out 39 | coverage.txt 40 | -------------------------------------------------------------------------------- /.goxc.json: -------------------------------------------------------------------------------- 1 | { 2 | "BuildConstraints": "darwin,linux", 3 | "ConfigVersion": "0.9" 4 | } 5 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Altynai Baytulakova baytulakova <34105118+baytulakova@users.noreply.github.com> 2 | Altynai Baytulakova baytulakova 3 | Altynai Baytulakova baytulakova 4 | Altynai Baytulakova baytulakova 5 | 6 | Aron Xu Aron Xu 7 | Aron Xu Aron Xu 8 | 9 | Azret Kenzhaliev Azret Kenzhaliev 10 | Azret Kenzhaliev azretkenzhaliev 11 | 12 | Dmitry Shihovtsev Dmitry Shihovtsev 13 | Dmitry Shihovtsev Dmitry Shihovtsev 14 | Dmitry Shihovtsev Dmitry Shihovtsev 15 | Dmitry Shihovtsev soffokl 16 | 17 | Eldar Tursunbaev Eldar Tursunbaev 18 | Eldar Tursunbaev etursunbaev 19 | 20 | Marat Bediev Marat Bediev 21 | Marat Bediev Marat Bediev 22 | Marat Bediev Marat Bediev 23 | 24 | Mikhail Savochkin Mikhail Savochkin 25 | Mikhail Savochkin Mike Savochkin 26 | Mikhail Savochkin Mike Savochkin 27 | Mikhail Savochkin Mikhail Savochkin 28 | Mikhail Savochkin Mikhail Savochkin 29 | Mikhail Savochkin Mike 30 | Mikhail Savochkin Mikhail Savochkin 31 | Mikhail Savochkin Mike 32 | Mikhail Savochkin Mike Savochkin 33 | Mikhail Savochkin Mikhail Savochkin 34 | Mikhail Savochkin crioto 35 | 36 | Oleg Katkov Oleg Katkov 37 | Oleg Katkov okatkov 38 | 39 | Shahrizada Tagaeva Shahrizada <31571523+Shahrizada@users.noreply.github.com> 40 | 41 | Tilek Asankulov Tilek Asankulov 42 | Tilek Asankulov Tilek_A <33412251+tasankulov@users.noreply.github.com> 43 | 44 | Timur Zununbekov Timur Zununbekov 45 | Timur Zununbekov Timur Zununbekov 46 | Timur Zununbekov tzununbekov 47 | Timur Zununbekov tzununbekov 48 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 1.13 3 | matrix: 4 | include: 5 | - os: linux 6 | dist: bionic 7 | sudo: required 8 | before_install: 9 | - sudo apt-get update -qq 10 | - sudo apt-get install -qq build-essential devscripts debhelper 11 | - git clone https://github.com/subutai-io/p2p-packages.git /tmp/p2p-packages 12 | - go get github.com/google/gofuzz 13 | script: 14 | - make linux 15 | - make test 16 | - make coverage 17 | after_success: 18 | - bash <(curl -s https://codecov.io/bash) 19 | - os: osx 20 | before_install: 21 | - git clone https://github.com/subutai-io/p2p-packages.git /tmp/p2p-packages 22 | script: 23 | - make macos 24 | - make test 25 | 26 | branches: 27 | only: 28 | - master 29 | - dev 30 | - sysnet 31 | 32 | install: 33 | - go get 34 | - if [ ! -z "$TRAVIS_TAG" ] ; 35 | then ./configure --branch=HEAD ; 36 | else ./configure --branch=$TRAVIS_BRANCH ; 37 | fi 38 | 39 | notifications: 40 | slack: 41 | secure: oshkIoOJGMJ8iwmtgaSvkelCM5GIMld9jy7ZjUy9vATpsiOntAovhEFtlpmxnok1mD53SV1m45mPY7JnNOKjFOMQA3hFANEN79RSGjQKI5JNqGTRLsDuFmbgUPoS3lyV8qTdyytKOpi1moaVOsSD8s3byzmdWHMtnkS6M8iS5+3L/t3o6W7lNDSdFJCPDtVxIFrH+CBRRMbbFFkhPOxmJSxBaxEN7EEpNAwgw6z2pOqSj6fD5vwXG5YcuH6/cWEmAiVC4hIfxERh2cMGzCkgl4ZpO12AQvJBLw9ufhLvu2ER03fhqkKuHAo7myF0PcqSpOFhdamO05MtiWN/6RLGHDMIWRfBmaYmOfPyaodSUF4DZrhFYmMXDXhWkrpeBpYzknPQqPkwouYO6OtHsdMITdo27n1ENg1QuIth8kjiPbVcz/gjsUTzkcbbbQQw4LGcdkj6NawC3oq7+JXUE4gIGiqpuMEIxaKIe+P/77nIxVqGOQUQJzVx2IQKG8dBKUpBeFkTnDoJxbT/u7Q2cIMtv/Tmv+m7uEvii/kwrsk/XJpBWL3xzSgmiCiTi346bbJ71lFeuBpiYEN9P3Afi/573Bxddj7eA23ricF81QrBHTpo7OnrwKPz+dz8g7/297rWJC80olemkdFyp3bjgd158TKepYU+YP/LLugRF+AVLJQ= 42 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @crioto @shahrizada @akarasulu 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Branching and Releasing 2 | ========= 3 | 4 | **TL;DR** Create your Pull Requests targeting `dev` branch! 5 | 6 | We are using git tags for releases. Version numbers doesn't have any suffix/prefix, for example: **6.2.12** 7 | 8 | New features should be tested by developers in `sysnet` branch which represents an isolated subutai ecosystem and can be used by project maintainers for integration tests. 9 | 10 | After isolated tests code from `sysnet` branch must be merged into `dev` branch. In a dev branch developers, contributors and project maintainers can test p2p using real-life applications and environments. 11 | 12 | `master` branch should be stable, containing all the hotfixes along with the new features that was tested and merged from `dev` branch. Master is used by Subutai QA team for their tests. 13 | 14 | Once in a while code in `master` is being tagged. 15 | 16 | Coding style and tests 17 | =========== 18 | 19 | Try to give clean names to all your variables, functions and methods, structs, types and everything else. Do not forget to use linter. 20 | 21 | And always try to write a test function for your code! 22 | 23 | Contributors 24 | ============ 25 | 26 | Contributors will be listed in alphabet order. If we forgot to mention you - please contact us directly. 27 | 28 | * [chennqqi](https://github.com/chennqqi) -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * What's your Operating System? 2 | 3 | * Describe your problem and steps to reproduce it 4 | 5 | * Show output of `p2p debug` 6 | 7 | * Attach p2p log or sensible part of it -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=go 2 | PACK=goupx 3 | BUILD=$(shell git describe) 4 | VERSION=$(shell cat VERSION) 5 | OS=$(shell uname -s) 6 | ARCH=$(shell uname -m) 7 | BRANCH=$(shell git rev-parse --abbrev-ref HEAD) 8 | NAME_PREFIX=p2p 9 | NAME_BASE=p2p 10 | SOURCES=instance.go restore.go main.go rest.go start.go stop.go show.go set.go status.go debug.go daemon.go dht_connection.go dht_router.go 11 | DOMAIN=subutai.io 12 | 13 | sinclude config.make 14 | ifdef DHT_ENDPOINTS 15 | DHT=$(DHT_ENDPOINTS) 16 | else 17 | DHT=dht 18 | endif 19 | APP=$(NAME_PREFIX) 20 | 21 | build: directories 22 | build: bin/$(APP) 23 | linux: bin/$(APP) 24 | windows: bin/$(APP).exe 25 | macos: bin/$(APP)_osx 26 | all: linux windows macos 27 | 28 | bin/$(APP): $(SOURCES) service_posix.go 29 | @if [ ! -d "$(GOPATH)/src/github.com/subutai-io/p2p" ]; then mkdir -p $(GOPATH)/src/github.com/subutai-io/; ln -s $(shell pwd) $(GOPATH)/src/github.com/subutai-io/p2p; fi 30 | GOOS=linux $(CC) build -ldflags="-w -s -X main.AppVersion=$(VERSION)$(BRANCH_POSTFIX) -X main.TargetURL=$(DHT) -X main.BuildID=$(BUILD) -X main.DefaultLog=$(LOG_LEVEL)" -o $@ -v $^ 31 | 32 | bin/$(APP).exe: $(SOURCES) service_windows.go 33 | @if [ ! -d "$(GOPATH)/src/github.com/subutai-io/p2p" ]; then mkdir -p $(GOPATH)/src/github.com/subutai-io/; ln -s $(shell pwd) $(GOPATH)/src/github.com/subutai-io/p2p; fi 34 | GOOS=windows $(CC) build -ldflags="-w -s -X main.AppVersion=$(VERSION)$(BRANCH_POSTFIX) -X main.TargetURL=$(DHT) -X main.BuildID=$(BUILD) -X main.DefaultLog=$(LOG_LEVEL)" -o $@ -v $^ 35 | 36 | bin/$(APP)_osx: $(SOURCES) service_posix.go 37 | @if [ ! -d "$(GOPATH)/src/github.com/subutai-io/p2p" ]; then mkdir -p $(GOPATH)/src/github.com/subutai-io/; ln -s $(shell pwd) $(GOPATH)/src/github.com/subutai-io/p2p; fi 38 | GOOS=darwin $(CC) build -ldflags="-w -s -X main.AppVersion=$(VERSION)$(BRANCH_POSTFIX) -X main.TargetURL=$(DHT) -X main.BuildID=$(BUILD) -X main.DefaultLog=$(LOG_LEVEL)" -o $@ -v $^ 39 | 40 | clean: 41 | -rm -f bin/$(APP) 42 | -rm -f bin/$(APP).exe 43 | -rm -f bin/$(APP)_osx 44 | -rm -f $(APP) 45 | -rm -f $(APP)_osx 46 | -rm -f $(APP).exe 47 | -rm -f $(APP)-$(OS)* 48 | -rm -f $(NAME_PREFIX) 49 | -rm -f $(NAME_PREFIX)_osx 50 | -rm -f $(NAME_PREFIX).exe 51 | -rm -f $(NAME_PREFIX)-$(OS)* 52 | -rm -rf debian/extra-code/* 53 | 54 | mrproper: clean 55 | mrproper: 56 | -rm -rf bin 57 | -rm -f config.make 58 | 59 | test: 60 | go test github.com/subutai-io/p2p 61 | go test github.com/subutai-io/p2p/lib 62 | # go test --bench . ./... 63 | 64 | coverage: 65 | go test -coverprofile=coverage.txt -covermode=atomic github.com/subutai-io/p2p/lib 66 | 67 | htmlcover: 68 | go test -coverprofile=/tmp/coverage.out github.com/subutai-io/p2p/lib 69 | go tool cover -html=/tmp/coverage.out 70 | 71 | release: build 72 | release: 73 | -mv $(APP) $(APP)-$(OS)-$(ARCH)-$(VERSION) 74 | 75 | install: 76 | @mkdir -p $(DESTDIR)/opt/subutai/bin 77 | @cp $(APP) $(DESTDIR)/opt/subutai/bin/$(NAME_PREFIX) 78 | 79 | uninstall: 80 | @rm -f $(DESTDIR)/bin/$(NAME_PREFIX) 81 | 82 | directories: 83 | @mkdir -p bin 84 | 85 | proto: 86 | protoc --go_out=import_path=protocol:. protocol/dht.proto 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | P2P Cloud 2 | =================== 3 | 4 | [![Build Status - master](https://api.travis-ci.org/subutai-io/p2p.png?branch=master)](https://travis-ci.org/subutai-io/p2p) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/1qyikpu9x3ecn8ay/branch/master?svg=true)](https://ci.appveyor.com/project/crioto/p2p/branch/master) 6 | [![codecov](https://codecov.io/gh/subutai-io/p2p/branch/master/graph/badge.svg)](https://codecov.io/gh/subutai-io/p2p) 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/subutai-io/p2p)](https://goreportcard.com/report/github.com/subutai-io/p2p) 8 | [![GoDoc](https://godoc.org/github.com/subutai-io/p2p?status.svg)](https://godoc.org/github.com/subutai-io/p2p) 9 | 10 | P2P Cloud project allows users to build their private networks. 11 | 12 | Building 13 | ------------------- 14 | 15 | p2p is shipped with a Makefile, so building it a pretty easy task. You just run 16 | ``` 17 | make 18 | ``` 19 | command to buld a single binary for current platform or you can try to 20 | ``` 21 | make all 22 | ``` 23 | in order to build p2p for linux, windows and macos 24 | 25 | Running 26 | ------------------- 27 | 28 | > **MacOS** users should install [TUN/TAP driver](http://tuntaposx.sourceforge.net) and create a config.yaml file with the following line: ``` iptool: /sbin/ifconfig ``` 29 | 30 | > **Windows** users should install [TAP-windows NDIS6](https://openvpn.net/index.php/open-source/downloads.html) driver from OpenVPN suite 31 | 32 | p2p is managed by a daemon that controls every instance of your private networks (if you're participating in a different networks at the same time). To start a daemon simply run *p2p daemon* command. Note, that application will run in a foreground mode. 33 | 34 | ``` 35 | p2p daemon 36 | ``` 37 | 38 | Now you can start manage the daemon with p2p command line interface. To start a new network or join existing you should run p2p application with a -start flag. 39 | 40 | ``` 41 | p2p start -ip 10.10.10.1 -hash UNIQUE_STRING_IDENTIFIER 42 | ``` 43 | 44 | You should specify an IP address which will be used by your virtual network interface. All the participants should have an agreement on ranges of IP addresses they're using. In the future this will become unnecessary, because DHCP-like service will be implemented. 45 | 46 | With a -hash flag user should specify a unique name of his network. 47 | 48 | Instance of P2P network can be stopped with use of stop command 49 | 50 | ``` 51 | p2p stop -hash UNIQUE_STRING_IDENTIFIER 52 | ``` 53 | 54 | To learn more about available commands run 55 | 56 | ``` 57 | p2p help 58 | ``` 59 | 60 | or append name of command to print detailed help about this command. For example: 61 | 62 | ``` 63 | p2p help daemon 64 | ``` 65 | 66 | will display detailed information about *daemon* command 67 | 68 | Development & Branching Model 69 | ------------------- 70 | 71 | * 'master' is always stable. 72 | * 'dev' contains latest development snapshot that is under heavy testing 73 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 8.3.1 2 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | clone_folder: c:\root\src\github.com\subutai-io\p2p 4 | 5 | image: Visual Studio 2015 6 | 7 | install: 8 | - nuget install secure-file -ExcludeVersion 9 | - rmdir c:\go /s /q 10 | - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.msi 11 | - msiexec /i go%GOVERSION%.windows-amd64.msi /q 12 | - choco install bzr 13 | - set Path=c:\go\bin;c:\root\bin;C:\Program Files (x86)\Bazaar\;C:\Program Files\Mercurial\%Path% 14 | - go version 15 | - go env 16 | - git clone --depth 1 https://github.com/subutai-io/p2p-packages.git c:\root\p2p-packages 17 | - git clone --depth 1 https://github.com/subutai-io/devops.git c:\root\devops 18 | # - curl -fsSLk https://eu0.cdn.subutai.io:8338/kurjun/rest/raw/get?name=tap-windows-9.21.2.exe -o c:\root\p2p-packages\tap-windows-9.21.2.exe 19 | - gpg --list-keys 20 | # - secure-file\tools\secure-file -decrypt c:\root\p2p-packages\appveyor\sign.enc -secret %GPG_KEY_SECRET% 21 | # - secure-file\tools\secure-file -decrypt c:\root\p2p-packages\appveyor\gpg.enc -secret %GPG_KEY_SECRET% 22 | # - gpg --import c:\root\p2p-packages\appveyor\gpg 23 | 24 | before_build: 25 | - C:\msys64\usr\bin\bash -lc "$(cygpath ${APPVEYOR_BUILD_FOLDER})/configure --os=windows --branch=%APPVEYOR_REPO_BRANCH%" 26 | 27 | build_script: 28 | - cmd: >- 29 | build.bat 30 | 31 | # %signtool% sign /tr http://timestamp.comodoca.com/authenticode /f c:\root\p2p-packages\appveyor\sign /p %SIGN_KEY% c:\root\src\github.com\subutai-io\p2p\p2p.exe 32 | 33 | # c:\root\src\github.com\subutai-io\p2p\p2p.exe -v 34 | 35 | # copy c:\root\src\github.com\subutai-io\p2p\p2p.exe c:\root\p2p-packages\p2p.exe 36 | 37 | # "C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\Common7\\IDE\\devenv.exe" c:\\root\\p2p-packages\\windows\\win.sln /Rebuild Release 38 | 39 | # %signtool% sign /tr http://timestamp.comodoca.com/authenticode /f c:\root\p2p-packages\appveyor\sign /p %SIGN_KEY% c:\\root\\p2p-packages\\windows\\P2PInstaller\\Release\\P2PInstaller.msi 40 | 41 | #deploy_script: 42 | # - C:\msys64\usr\bin\bash -lc "/c/root/p2p-packages/deploy.sh /c/root/p2p-packages/p2p.exe %APPVEYOR_REPO_BRANCH% -appveyor-test" 43 | 44 | #notifications: 45 | # - provider: Slack 46 | # incoming_webhook: 47 | # secure: EH0gGIsLRLevwZRtCfJVMO6Bw+OP7p+1eyWatC0h6IorO7ksOj+PsENn5duSz59F45IIrPB0c318ZAzXGEhiUhD/Huor6zS/mQfejsO5RTg= 48 | -------------------------------------------------------------------------------- /config.darwin.yaml: -------------------------------------------------------------------------------- 1 | iptool: /sbin/ifconfig 2 | pmtu: false 3 | mtu: 1500 -------------------------------------------------------------------------------- /config.linux.yaml: -------------------------------------------------------------------------------- 1 | iptool: /sbin/ip 2 | pmtu: false 3 | mtu: 1500 -------------------------------------------------------------------------------- /config.windows.yaml: -------------------------------------------------------------------------------- 1 | iptool: netsh.exe 2 | taptool: C:\\Program Files\\TAP-Windows\\bin\\tapinstall.exe 3 | inf_file: C:\\Program Files\\TAP-Windows\\driver\\OemVista.inf 4 | pmtu: false 5 | mtu: 1500 -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | iptool: /sbin/ip 2 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | showhelp() 4 | { 5 | cat << ENDHELP 6 | 7 | usage: configure [options] 8 | Configure p2p 9 | 10 | Options: 11 | --dht= 12 | Default DHT url 13 | 14 | --branch= 15 | Specify a branch name. By default configure script will try to determine branch name 16 | using command line. Setting this option will modify version output and target DHT 17 | 18 | --os= 19 | Specify operating system handle: linux, darwin, windows 20 | 21 | --help 22 | Display this help screen 23 | ENDHELP 24 | } 25 | 26 | name_prefix=p2p 27 | gopath= 28 | branch= 29 | dht="" 30 | argOS="" 31 | targetURL="subutai.io" 32 | 33 | while [ $# -ge 1 ]; do 34 | case "$1" in 35 | --branch=*) 36 | branch="`echo ${1} | awk '{print substr($0,10)}'`" ;; 37 | --dht=*) 38 | dht="`echo ${1} | awk '{print substr($0,7)}'`" ;; 39 | --os=*) 40 | argOS="`echo ${1} | awk '{print substr($0,6)}'`" ;; 41 | --help) 42 | showhelp 43 | exit 0 44 | ;; 45 | *) 46 | echo "ERROR: Unknown argument: $1" 47 | showhelp 48 | exit 1 49 | ;; 50 | esac 51 | 52 | shift 53 | done 54 | 55 | 56 | if [ "$argOS" != "linux" ] && [ "$argOS" != "darwin" ] && [ "$argOS" != "windows" ]; then 57 | argOS="" 58 | fi 59 | 60 | if [ -z "$argOS" ]; then 61 | argOS=$(uname -s | tr '[:upper:]' '[:lower:]') 62 | fi 63 | 64 | # Workaround for AppVeyor 65 | if [ "$argOS" == "windows" ]; then 66 | location="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 67 | cd $location 68 | fi 69 | 70 | if [ -z "$branch" ]; then 71 | echo "Branch was not specified. Trying to determine" 72 | branch=`git rev-parse --abbrev-ref HEAD` 73 | if [ $? -ne 0 ]; then 74 | echo "Failed to determine branch using git command. Exiting" 75 | exit 25 76 | fi 77 | fi 78 | 79 | echo "Preparing \"${branch}\" build for ${argOS}" 80 | 81 | version=`cat VERSION` 82 | build=`git describe` 83 | source_list=`ls | grep .go` 84 | source_files="${source_list//$'\n'/ }" 85 | 86 | name_base="$name_prefix" 87 | if [ "$branch" != "HEAD" ]; then 88 | name_base=$name_prefix-$branch 89 | fi 90 | 91 | echo "Building $name_base" 92 | 93 | if [ "$dht" == "" ]; then 94 | echo "Determining DHT address" 95 | if [ "$branch" == "dev" ]; then 96 | dht="devdht" 97 | elif [ "$branch" == "master" ]; then 98 | dht="masterdht" 99 | elif [ "$branch" == "sysnet" ]; then 100 | dht="sysnetdht" 101 | else 102 | dht="dht" 103 | fi 104 | echo "DHT is set to $dht" 105 | else 106 | echo "DHT is set to $dht" 107 | fi 108 | 109 | log_level="INFO" 110 | if [ "$branch" == "HEAD" ]; then 111 | log_level="WARNING" 112 | elif [ "$branch" == "master" ]; then 113 | log_level="DEBUG" 114 | elif [ "$branch" == "dev" ]; then 115 | log_level="DEBUG" 116 | elif [ "$branch" == "sysnet" ]; then 117 | log_level="TRACE" 118 | fi 119 | 120 | if [ "$argOS" != "windows" ]; then 121 | 122 | echo "Checking go environment" 123 | if [ "$GOPATH" == "" ]; then 124 | echo "GOPATH is not set. Setting to /tmp/go-path-tmp directory" 125 | gopath=/tmp/go-path-tmp 126 | fi 127 | 128 | echo "Downloading necessary packages" 129 | if [ ! -z $gopath ]; then 130 | export GOPATH=$HOME 131 | export GOBIN=$HOME/go-bin 132 | fi 133 | 134 | go get 135 | go get -u github.com/golang/protobuf/proto 136 | 137 | fi 138 | 139 | if [ "$argOS" == "windows" ]; then 140 | output_file="build.bat" 141 | echo ":: ${output_file} generated by configure script" > $output_file 142 | echo "go get" >> $output_file 143 | echo "go build -ldflags=\"-w -s -X main.AppVersion=${version}-${branch} -X main.DefaultDHT=${dht} -X main.BuildID=${build} -X main.DefaultLog=${log_level}\" -o p2p.exe github.com/subutai-io/p2p" >> $output_file 144 | else 145 | # generating config.make file 146 | echo "# config.make generated by configure script" > config.make 147 | echo "NAME_BASE = $name_base" >> config.make 148 | echo "NAME_PREFIX = $name_prefix" >> config.make 149 | echo "DHT_ENDPOINTS = $dht" >> config.make 150 | echo "LOG_LEVEL = $log_level" >> config.make 151 | if [ "$dht" != "" ]; then 152 | echo "DHT = $dht" >> config.make 153 | fi 154 | if [ "$branch" != "HEAD" ]; then 155 | echo "BRANCH_POSTFIX = -$branch" >> config.make 156 | fi 157 | # 158 | echo "export NAME_BASE" >> config.make 159 | echo "export NAME_PREFIX" >> config.make 160 | echo "export DHT_ENDPOINTS" >> config.make 161 | echo "export LOG_LEVEL" >> config.make 162 | if [ ! -z "$gopath" ]; then 163 | echo "GOPATH = $HOME" >> config.make 164 | echo "export GOPATH" >> config.make 165 | fi 166 | if [ "$branch" != "HEAD" ]; then 167 | echo "export BRANCH_POSTFIX" >> config.make 168 | fi 169 | fi -------------------------------------------------------------------------------- /daemon.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "os" 8 | "os/signal" 9 | "runtime/pprof" 10 | "strings" 11 | "time" 12 | 13 | ptp "github.com/subutai-io/p2p/lib" 14 | ) 15 | 16 | var ( 17 | errEmptyDHTEndpoint = errors.New("DHT endpoint wasn't specified") 18 | errBadDHTEndpoint = errors.New("Endpoint have wrong format") 19 | ) 20 | 21 | // DaemonArgs arguments used by daemon to manipulate 22 | // p2p behaviour 23 | type DaemonArgs struct { 24 | IP string `json:"ip"` 25 | Mac string `json:"mac"` 26 | Dev string `json:"dev"` 27 | Hash string `json:"hash"` 28 | Dht string `json:"dht"` 29 | Keyfile string `json:"keyfile"` 30 | Key string `json:"key"` 31 | TTL string `json:"ttl"` 32 | Fwd bool `json:"fwd"` 33 | Port int `json:"port"` 34 | Interfaces bool `json:"interfaces"` // show only 35 | All bool `json:"all"` // show only 36 | Command string `json:"command"` 37 | Args string `json:"args"` 38 | Log string `json:"log"` 39 | Bind bool `json:"bind"` 40 | MTU bool `json:"mtu"` 41 | } 42 | 43 | var bootstrap DHTConnection 44 | var UsePMTU bool 45 | 46 | func processConfigFile(configFile string) (*ptp.Conf, error) { 47 | if configFile == "" { 48 | return nil, fmt.Errorf("no config file provided") 49 | } 50 | 51 | conf := new(ptp.Conf) 52 | err := conf.Load(configFile) 53 | if err != nil { 54 | return conf, err 55 | } 56 | return conf, nil 57 | } 58 | 59 | func configureMTU(conf *ptp.Conf, mtu int, pmtu bool) { 60 | if conf == nil { 61 | ptp.GlobalMTU = ptp.DefaultMTU 62 | ptp.UsePMTU = false 63 | ptp.Log(ptp.Error, "Couldn't configure MTU: conf nil") 64 | return 65 | } 66 | ptp.GlobalMTU = conf.GetMTU(mtu) 67 | ptp.Log(ptp.Info, "MTU is set to %d", ptp.GlobalMTU) 68 | if pmtu == false && conf.GetPMTU() == false { 69 | ptp.Log(ptp.Info, "PMTU disabled") 70 | ptp.UsePMTU = false 71 | } else { 72 | ptp.Log(ptp.Info, "PMTU enabled") 73 | ptp.UsePMTU = true 74 | } 75 | } 76 | 77 | // ExecDaemon starts P2P daemon 78 | func ExecDaemon(port int, targetURL, sFile, profiling, syslog, logLevel, configFile string, mtu int, pmtu bool) { 79 | ptp.Log(ptp.Info, "Initializing P2P Daemon") 80 | if logLevel == "" { 81 | ptp.SetMinLogLevelString(DefaultLog) 82 | } else { 83 | ptp.SetMinLogLevelString(logLevel) 84 | } 85 | 86 | var err error 87 | if configFile == "" { 88 | configFile = ptp.DefaultConfigLocation 89 | } 90 | config, err := processConfigFile(configFile) 91 | if err != nil { 92 | ptp.Log(ptp.Error, "Failed to load config file %s: %s", configFile, err.Error()) 93 | } else { 94 | ptp.Log(ptp.Info, "Loaded configuration from %s", configFile) 95 | } 96 | 97 | if targetURL == "" { 98 | targetURL = "subutai.io" 99 | } 100 | if syslog != "" { 101 | ptp.SetSyslogSocket(syslog) 102 | } 103 | StartProfiling(profiling) 104 | ptp.InitPlatform() 105 | ptp.InitErrors() 106 | 107 | configureMTU(config, mtu, pmtu) 108 | 109 | if !ptp.HavePrivileges(ptp.GetPrivilegesLevel()) { 110 | os.Exit(1) 111 | } 112 | StartTime = time.Now() 113 | 114 | ReadyToServe = false 115 | 116 | bootstrapConnected := false 117 | bootstrapLastConnection := time.Unix(0, 0) 118 | 119 | for !bootstrapConnected { 120 | if time.Since(bootstrapLastConnection) > time.Duration(time.Second*5) { 121 | bootstrapLastConnection = time.Now() 122 | err := bootstrap.init(targetURL) 123 | if err == nil { 124 | bootstrapConnected = true 125 | } else { 126 | ptp.Log(ptp.Error, "Failed to connect to %s", targetURL) 127 | } 128 | } 129 | time.Sleep(time.Millisecond * 100) 130 | } 131 | 132 | go bootstrap.run() 133 | go waitOutboundIP() 134 | 135 | proc := new(Daemon) 136 | proc.init(sFile) 137 | setupRESTHandlers(port, proc) 138 | 139 | go restoreInstances(proc) 140 | 141 | ReadyToServe = true 142 | 143 | SignalChannel = make(chan os.Signal, 1) 144 | signal.Notify(SignalChannel, os.Interrupt) 145 | 146 | go waitActiveBootstrap() 147 | 148 | go func() { 149 | for sig := range SignalChannel { 150 | fmt.Println("Received signal: ", sig) 151 | pprof.StopCPUProfile() 152 | os.Exit(0) 153 | } 154 | }() 155 | 156 | // main loop 157 | for { 158 | for id, inst := range proc.Instances.get() { 159 | if inst == nil || inst.PTP == nil { 160 | continue 161 | } 162 | if inst.PTP.ReadyToStop { 163 | err := proc.Stop(&DaemonArgs{Hash: id}, &Response{}) 164 | if err != nil { 165 | ptp.Log(ptp.Error, "Failed to stop instance: %s", err) 166 | } 167 | } 168 | } 169 | time.Sleep(time.Millisecond * 100) 170 | } 171 | } 172 | 173 | func waitOutboundIP() { 174 | for _, r := range bootstrap.routers { 175 | if r != nil { 176 | go r.run() 177 | go r.keepAlive() 178 | } 179 | } 180 | for !bootstrap.isActive { 181 | for _, r := range bootstrap.routers { 182 | if r.running && r.handshaked { 183 | bootstrap.isActive = true 184 | break 185 | } 186 | } 187 | time.Sleep(time.Millisecond * 100) 188 | } 189 | for bootstrap.ip == "" { 190 | time.Sleep(time.Millisecond * 100) 191 | } 192 | OutboundIP = net.ParseIP(bootstrap.ip) 193 | } 194 | 195 | func waitActiveBootstrap() { 196 | for { 197 | active := 0 198 | for _, r := range bootstrap.routers { 199 | if !r.stop { 200 | active++ 201 | } 202 | } 203 | if active == 0 { 204 | ptp.Log(ptp.Info, "No active bootstrap nodes") 205 | os.Exit(0) 206 | } 207 | time.Sleep(time.Millisecond * 100) 208 | } 209 | } 210 | 211 | func restoreInstances(daemon *Daemon) { 212 | for !bootstrap.isActive { 213 | time.Sleep(100 * time.Millisecond) 214 | } 215 | if daemon.Restore != nil && daemon.Restore.isActive() { 216 | ptp.Log(ptp.Info, "Restore subsystem initialized") 217 | 218 | // loading from restore file 219 | err := daemon.Restore.load() 220 | if err != nil { 221 | ptp.Log(ptp.Error, "Failed to restore from file") 222 | return 223 | } 224 | 225 | entries := daemon.Restore.get() 226 | if len(entries) == 0 { 227 | return 228 | } 229 | 230 | ptp.Log(ptp.Info, "Attempt to restore %d instances", len(entries)) 231 | 232 | restored := 0 233 | 234 | for _, e := range entries { 235 | err := daemon.run(&RunArgs{ 236 | IP: e.IP, 237 | Mac: e.Mac, 238 | Dev: e.Dev, 239 | Hash: e.Hash, 240 | Keyfile: e.Keyfile, 241 | Key: e.Key, 242 | TTL: e.TTL, 243 | }, new(Response)) 244 | if err != nil { 245 | ptp.Log(ptp.Error, "Failed to start instance %s during restore: %s", e.Hash, err.Error()) 246 | continue 247 | } else { 248 | restored++ 249 | daemon.Restore.bumpInstance(e.Hash) 250 | } 251 | } 252 | err = daemon.Restore.save() 253 | if err != nil { 254 | ptp.Log(ptp.Error, "Failed to save restore file") 255 | } 256 | ptp.Log(ptp.Info, "Restored %d of %d instances", restored, len(entries)) 257 | } 258 | } 259 | 260 | func validateDHT(dht string) error { 261 | if dht == "" { 262 | ptp.Log(ptp.Error, "Empty bootstrap list") 263 | return errEmptyDHTEndpoint 264 | } 265 | eps := strings.Split(dht, ",") 266 | for _, ep := range eps { 267 | _, err := net.ResolveTCPAddr("tcp4", ep) 268 | if err != nil { 269 | ptp.Log(ptp.Error, "Bootstrap %s have bad format or wrong address: %s", ep, err) 270 | return errBadDHTEndpoint 271 | } 272 | } 273 | return nil 274 | } 275 | -------------------------------------------------------------------------------- /daemon_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestValidateDHT(t *testing.T) { 8 | if validateDHT("") != errEmptyDHTEndpoint { 9 | t.Fatalf("DHT Validate: providing empty list doesn't generate proper error") 10 | } 11 | if validateDHT("google.com") != errBadDHTEndpoint { 12 | t.Fatalf("Providing URL without port doesn't generate expected error") 13 | } 14 | if validateDHT("iamnotexist.atall:80") != errBadDHTEndpoint { 15 | t.Fatalf("Providing non existing URL doesn't generate expected error") 16 | } 17 | if validateDHT("google.com:80") != nil { 18 | t.Fatalf("Providing correct endpoint generates error") 19 | } 20 | if validateDHT("google.com:80,yandex.ru:80") != nil { 21 | t.Fatalf("Providing correct endpoints generates error") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | subutai-p2p (6.3.1+20180419-1) unstable; urgency=medium 2 | 3 | * Initial release. 4 | 5 | -- Aron Xu Sat, 31 Mar 2018 17:03:46 +0800 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: subutai-p2p 2 | Section: utils 3 | Priority: optional 4 | Maintainer: Aron Xu 5 | Build-Depends: debhelper (>= 10), 6 | dh-golang, 7 | dh-systemd, 8 | golang-any, 9 | golang-github-ccding-go-stun-dev, 10 | golang-github-google-gofuzz-dev, 11 | golang-github-mdlayher-ethernet-dev, 12 | golang-github-nebulouslabs-fastrand-dev, 13 | golang-github-nebulouslabs-go-upnp-dev, 14 | golang-github-urfave-cli-dev (>= 1.20.0~), 15 | golang-github-wayn3h0-go-uuid-dev, 16 | golang-goprotobuf-dev 17 | Standards-Version: 4.1.3 18 | Homepage: http://github.com/subutai-io/p2p 19 | XS-Go-Import-Path: github.com/subutai-io/p2p 20 | 21 | Package: subutai-p2p 22 | Architecture: any 23 | Depends: ${misc:Depends}, ${shlibs:Depends} 24 | Description: p2p component of subutai 25 | P2P Cloud project allows users to build their private networks. 26 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: subutai-p2p 3 | Source: https://github.com/subutai-io/p2p 4 | 5 | Files: * 6 | Copyright: 2018 OptDyn Coporation 7 | License: GPL-3 8 | 9 | License: GPL-3 10 | This program is free software; you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation; either version 3 of the License. 13 | . 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | . 19 | You should have received a copy of the GNU General Public License 20 | along with this program; if not, write to the 21 | Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 22 | MA 02110-1301, USA. 23 | . 24 | On Debian systems, the full text of GPL-3 could be found at 25 | `/usr/share/common-licenses/GPL-3'. 26 | -------------------------------------------------------------------------------- /debian/dirs: -------------------------------------------------------------------------------- 1 | var/lib/subutai/data/ 2 | -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | systemctl status p2p.service >/dev/null 2>/dev/null 4 | if [ $? -eq 0 ]; then 5 | systemctl stop p2p.service 6 | systemctl disable p2p.service 7 | fi 8 | systemctl unmask subutai-p2p.service 9 | systemctl restart subutai-p2p.service 10 | systemctl enable subutai-p2p.service 11 | 12 | exit 0 -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | export DH_VERBOSE=1 3 | include /usr/share/dpkg/default.mk 4 | 5 | BUILDER := $(shell sed -ne 's,^Maintainer: .[^<]*<\([^>]*\)>,\1,p' debian/control) 6 | #BUILDPKG := github.com/subutai-io/p2p/build 7 | BUILDPKG := main 8 | VERSION := $(shell dpkg-parsechangelog | sed -n 's/^Version: //p') 9 | BUILDID := $(shell git describe) 10 | LOG_LEVEL := DEFAULT_LOG_LEVEL 11 | BUILDFLAGS := -ldflags\ 12 | "-X $(BUILDPKG).AppVersion=$(VERSION)\ 13 | -X $(BUILDPKG).TargetURL=DHT_ENDPOINT\ 14 | -X $(BUILDPKG).BuildID=$(BUILDID)\ 15 | -X $(BUILDPKG).DefaultLog=$(LOG_LEVEL)" 16 | 17 | %: 18 | dh $@ --buildsystem=golang --with=golang --with-systemd 19 | 20 | override_dh_auto_build: 21 | dh_auto_build -- $(BUILDFLAGS) 22 | 23 | override_dh_auto_install: 24 | dh_auto_install -- --no-source 25 | mkdir -p debian/subutai-p2p/etc/default/ 26 | cp debian/tree/etc/default/* debian/subutai-p2p/etc/default 27 | cp debian/tree/etc/p2p.yaml debian/subutai-p2p/etc/p2p.yaml 28 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/subutai-p2p.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Subutai P2P Daemon 3 | OnFailure=subutai-cop.service 4 | 5 | [Service] 6 | Type=simple 7 | EnvironmentFile=-/etc/default/subutai-p2p 8 | ExecStart=/usr/bin/p2p daemon -save /var/lib/subutai/data/p2p.save -syslog 127.0.0.1:1514 --mtu=${P2P_MTU} 9 | Restart=always 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /debian/tree/etc/default/subutai-p2p: -------------------------------------------------------------------------------- 1 | # This is an environment file for Subutai P2P daemon 2 | # Default location is: /etc/default/p2p-service 3 | P2P_MTU=1500 -------------------------------------------------------------------------------- /debian/tree/etc/p2p.yaml: -------------------------------------------------------------------------------- 1 | iptool: /sbin/ip 2 | pmtu: false 3 | mtu: 1500 -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "os" 8 | "runtime" 9 | "time" 10 | 11 | ptp "github.com/subutai-io/p2p/lib" 12 | ) 13 | 14 | // CommandDebug prints debug information 15 | func CommandDebug(restPort int) { 16 | out, err := sendRequest(restPort, "debug", &DaemonArgs{}) 17 | if err != nil { 18 | fmt.Fprintln(os.Stderr, err.Error()) 19 | os.Exit(1) 20 | } 21 | 22 | fmt.Println(out.Message) 23 | os.Exit(out.Code) 24 | } 25 | 26 | func (d *Daemon) execRESTDebug(w http.ResponseWriter, r *http.Request) { 27 | if !ReadyToServe { 28 | resp, _ := getResponse(105, "P2P Daemon is in initialization state") 29 | w.Write(resp) 30 | return 31 | } 32 | if !bootstrap.isActive { 33 | resp, _ := getResponse(106, "Not connected to DHT nodes") 34 | w.Write(resp) 35 | return 36 | } 37 | if bootstrap.ip == "" { 38 | resp, _ := getResponse(107, "Didn't received outbound IP yet") 39 | w.Write(resp) 40 | return 41 | } 42 | args := new(DaemonArgs) 43 | err := getJSON(r.Body, args) 44 | if handleMarshalError(err, w) != nil { 45 | return 46 | } 47 | response := new(Response) 48 | d.Debug(&Args{ 49 | Command: args.Command, 50 | Args: args.Args, 51 | }, response) 52 | resp, err := getResponse(response.ExitCode, response.Output) 53 | if err != nil { 54 | ptp.Log(ptp.Error, "Internal error: %s", err) 55 | return 56 | } 57 | w.Write(resp) 58 | } 59 | 60 | // Debug output debug information 61 | func (p *Daemon) Debug(args *Args, resp *Response) error { 62 | resp.Output = fmt.Sprintf("Version: %s Build: %s\n", AppVersion, BuildID) 63 | resp.Output += fmt.Sprintf("Uptime: %d h %d m %d s\n", int(time.Since(StartTime).Hours()), int(time.Since(StartTime).Minutes())%60, int(time.Since(StartTime).Seconds())%60) 64 | resp.Output += fmt.Sprintf("Number of gouroutines: %d\n", runtime.NumGoroutine()) 65 | if ptp.UsePMTU { 66 | resp.Output += fmt.Sprintf("PMTU: Enabled\n") 67 | } else { 68 | resp.Output += fmt.Sprintf("PMTU: Disabled\n") 69 | } 70 | resp.Output += fmt.Sprintf("Bootstrap nodes information:\n") 71 | for _, node := range bootstrap.routers { 72 | if node != nil { 73 | resp.Output += fmt.Sprintf(" %s Rx: %d Tx: %d Version: %s Packet version: %s\n", node.addr.String(), node.rx, node.tx, node.version, node.packetVersion) 74 | } 75 | } 76 | resp.Output += fmt.Sprintf("Instances information:\n") 77 | instances := p.Instances.get() 78 | for _, inst := range instances { 79 | resp.Output += fmt.Sprintf("Hash: %s\n", inst.ID) 80 | resp.Output += fmt.Sprintf("ID: %s\n", inst.PTP.Dht.ID) 81 | resp.Output += fmt.Sprintf("UDP Port: %d\n", inst.PTP.UDPSocket.GetPort()) 82 | resp.Output += fmt.Sprintf("Network interfaces: ") 83 | for _, ip := range inst.PTP.LocalIPs { 84 | resp.Output += fmt.Sprintf("%s ", ip.String()) 85 | } 86 | resp.Output += "\n" 87 | resp.Output += fmt.Sprintf("P2P Interface %s, HW Addr: %s, IP: %s\n", inst.PTP.Interface.GetName(), inst.PTP.Interface.GetHardwareAddress().String(), inst.PTP.Interface.GetIP().String()) 88 | resp.Output += fmt.Sprintf("Proxies: ") 89 | proxyList := inst.PTP.ProxyManager.GetList() 90 | if len(proxyList) == 0 { 91 | resp.Output += fmt.Sprintf("No proxies in use") 92 | } 93 | for _, proxy := range proxyList { 94 | if proxy.Addr == nil || proxy.Endpoint == nil { 95 | continue 96 | } 97 | resp.Output += fmt.Sprintf("%s/%d [%d] ", proxy.Addr.String(), proxy.Endpoint.Port, ptp.NanoToMilliseconds(proxy.Latency.Nanoseconds())) 98 | } 99 | resp.Output += "\n" 100 | resp.Output += fmt.Sprintf("Peers:\n") 101 | 102 | peers := inst.PTP.Swarm.Get() 103 | for _, peer := range peers { 104 | resp.Output += fmt.Sprintf("\t--- %s ---\n", peer.ID) 105 | resp.Output += fmt.Sprintf("\tStates: %s | %s\n", ptp.StringifyState(peer.State), ptp.StringifyState(peer.RemoteState)) 106 | if peer.PeerLocalIP == nil { 107 | resp.Output += "\tNo IP assigned\n" 108 | } else if peer.PeerHW == nil { 109 | resp.Output += "\tNo MAC assigned\n" 110 | } else { 111 | resp.Output += fmt.Sprintf("\tNetwork: %s %s\n", peer.PeerLocalIP.String(), peer.PeerHW.String()) 112 | resp.Output += fmt.Sprintf("\tEndpoint: %s\n", peer.Endpoint) 113 | resp.Output += fmt.Sprintf("\tAll Endpoints: ") 114 | for _, ep := range peer.EndpointsHeap { 115 | resp.Output += fmt.Sprintf("%s [%d] ", ep.Addr.String(), ptp.NanoToMilliseconds(ep.Latency.Nanoseconds())) 116 | } 117 | resp.Output += "\n" 118 | c := peer.Stat.GetConnectionsNum() 119 | r := peer.Stat.GetReconnectsNum() 120 | hp := peer.Stat.GetHolePunchNum() 121 | cdelta := peer.Stat.GetConnectionTimeDelta() 122 | rdelta := peer.Stat.GetReconnectionTimeDelta() 123 | resp.Output += fmt.Sprintf("Stats: [C: %d] [R: %d] [HP: %d] [CDelta: %d] [RDelta: %d]\n", c, r, hp, cdelta, rdelta) 124 | } 125 | resp.Output += fmt.Sprintf("\tEndpoints pool: ") 126 | pool := []*net.UDPAddr{} 127 | pool = append(pool, peer.KnownIPs...) 128 | pool = append(pool, peer.Proxies...) 129 | for _, v := range pool { 130 | resp.Output += fmt.Sprintf("%s ", v.String()) 131 | } 132 | resp.Output += "\n" 133 | resp.Output += fmt.Sprintf("\t--- End of %s ---\n", peer.ID) 134 | } 135 | } 136 | return nil 137 | } 138 | -------------------------------------------------------------------------------- /dht_connection.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "sync" 8 | 9 | "github.com/golang/protobuf/proto" 10 | ptp "github.com/subutai-io/p2p/lib" 11 | "github.com/subutai-io/p2p/protocol" 12 | ) 13 | 14 | // DHT Errors 15 | var ( 16 | ErrorNoRouters = errors.New("Routers wasn't specified") 17 | ErrorBadRouterAddress = errors.New("Bad router address") 18 | ) 19 | 20 | // DHTConnection to a DHT bootstrap node 21 | type DHTConnection struct { 22 | routers []*DHTRouter // Bootstrap nodes 23 | routersList map[int]string // List of bootstrap nodes received from SRV lookup 24 | lock sync.Mutex // Mutex for register/unregister 25 | instances map[string]*P2PInstance // Instances 26 | registered []string // List of registered swarm IDs 27 | incoming chan *protocol.DHTPacket // Packets received by routers 28 | ip string // Our outbound IP 29 | isActive bool // Whether DHT connection is active or not 30 | } 31 | 32 | func (dht *DHTConnection) init(target string) error { 33 | ptp.Log(ptp.Debug, "Initializing connection to a bootstrap nodes") 34 | dht.incoming = make(chan *protocol.DHTPacket) 35 | var err error 36 | dht.routersList, err = ptp.SrvLookup(target, "tcp", "subutai.io") 37 | if err != nil { 38 | ptp.Log(ptp.Debug, "Failed to get bootstrap nodes: %s", err.Error()) 39 | dht.routersList = make(map[int]string) 40 | } 41 | if len(dht.routersList) == 0 { 42 | return ErrorNoRouters 43 | } 44 | for _, r := range dht.routersList { 45 | if r == "" { 46 | continue 47 | } 48 | addr, err := net.ResolveTCPAddr("tcp4", r) 49 | if err != nil { 50 | ptp.Log(ptp.Error, "Bad router address provided [%s]: %s", r, err) 51 | return ErrorBadRouterAddress 52 | } 53 | router := new(DHTRouter) 54 | router.addr = addr 55 | router.router = r 56 | router.data = dht.incoming 57 | dht.routers = append(dht.routers, router) 58 | } 59 | dht.instances = make(map[string]*P2PInstance) 60 | return nil 61 | } 62 | 63 | func (dht *DHTConnection) registerInstance(hash string, inst *P2PInstance) error { 64 | dht.lock.Lock() 65 | defer dht.lock.Unlock() 66 | ptp.Log(ptp.Debug, "Registering instance %s on bootstrap", hash) 67 | 68 | exists := false 69 | for ihash, _ := range dht.instances { 70 | if hash == ihash { 71 | exists = true 72 | break 73 | } 74 | } 75 | for _, ihash := range dht.registered { 76 | if ihash == hash { 77 | exists = true 78 | break 79 | } 80 | } 81 | if exists { 82 | return fmt.Errorf("Hash already registered on bootstrap") 83 | } 84 | dht.instances[hash] = inst 85 | dht.registered = append(dht.registered, hash) 86 | inst.PTP.Dht.IncomingData = make(chan *protocol.DHTPacket) 87 | inst.PTP.Dht.OutgoingData = make(chan *protocol.DHTPacket) 88 | go func() { 89 | for { 90 | packet := <-inst.PTP.Dht.OutgoingData 91 | if packet == nil { 92 | break 93 | } 94 | dht.send(packet) 95 | } 96 | }() 97 | ptp.Log(ptp.Debug, "Instance was registered with bootstrap client") 98 | return nil 99 | } 100 | 101 | func (dht *DHTConnection) send(packet *protocol.DHTPacket) { 102 | if packet == nil { 103 | return 104 | } 105 | ptp.Log(ptp.Trace, "Sending DHT packet %+v", packet) 106 | data, err := proto.Marshal(packet) 107 | if err != nil { 108 | ptp.Log(ptp.Error, "Failed to marshal DHT Packet: %s", err) 109 | } 110 | ptp.Log(ptp.Trace, "Sending marshaled DHT Packet of size [%d]", len(data)) 111 | for i, router := range dht.routers { 112 | if router.running && router.handshaked { 113 | n, err := router.sendRaw(data) 114 | if err != nil { 115 | ptp.Log(ptp.Error, "Failed to send data to %s", router.addr.String()) 116 | continue 117 | } 118 | if n >= 0 { 119 | dht.routers[i].tx += uint64(n) 120 | } 121 | } 122 | } 123 | } 124 | 125 | func (dht *DHTConnection) run() { 126 | for { 127 | packet := <-dht.incoming 128 | if packet == nil { 129 | continue 130 | } 131 | ptp.Log(ptp.Trace, "Routing DHT Packet %+v", packet) 132 | // Ping should always provide us with outbound IP value 133 | if packet.Type == protocol.DHTPacketType_Ping && packet.Data != "" { 134 | ptp.Log(ptp.Info, "Received outbound IP: %s", packet.Data) 135 | dht.ip = packet.Data 136 | 137 | if packet.Extra != "" && packet.Query == "handshaked" { 138 | // Resend our IP 139 | for _, inst := range dht.instances { 140 | if inst == nil || inst.PTP == nil { 141 | continue 142 | } 143 | inst.PTP.ReportIP(inst.PTP.Interface.GetIP().String(), inst.PTP.Interface.GetHardwareAddress().String(), inst.PTP.Interface.GetName()) 144 | } 145 | } 146 | 147 | continue 148 | } 149 | if packet.Infohash == "" { 150 | continue 151 | } 152 | i, e := dht.instances[packet.Infohash] 153 | if e && i != nil && i.PTP != nil && !i.PTP.Shutdown && i.PTP.Dht != nil && i.PTP.Dht.IncomingData != nil { 154 | i.PTP.Dht.IncomingData <- packet 155 | } else { 156 | ptp.Log(ptp.Debug, "DHT received data for unknown instance %s: %+v", packet.Infohash, packet) 157 | } 158 | } 159 | } 160 | 161 | func (dht *DHTConnection) unregisterInstance(hash string) error { 162 | dht.lock.Lock() 163 | defer dht.lock.Unlock() 164 | ptp.Log(ptp.Debug, "Unregistering instance %s from bootstrap", hash) 165 | inst, e := dht.instances[hash] 166 | if !e { 167 | return fmt.Errorf("Can't unregister hash %s: Instance doesn't exists", hash) 168 | } 169 | if inst != nil && inst.PTP != nil && inst.PTP.Dht != nil { 170 | err := inst.PTP.Dht.Close() 171 | if err != nil { 172 | ptp.Log(ptp.Error, "Failed to stop DHT on instance %s", hash) 173 | } 174 | } 175 | delete(dht.instances, hash) 176 | for i, ihash := range dht.registered { 177 | if ihash == hash { 178 | dht.registered = append(dht.registered[:i], dht.registered[i+1:]...) 179 | break 180 | } 181 | } 182 | return nil 183 | } 184 | -------------------------------------------------------------------------------- /dht_router.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net" 7 | "time" 8 | 9 | "github.com/golang/protobuf/proto" 10 | ptp "github.com/subutai-io/p2p/lib" 11 | "github.com/subutai-io/p2p/protocol" 12 | ) 13 | 14 | // DHTRouter represents a connection to a router 15 | type DHTRouter struct { 16 | conn *net.TCPConn // TCP connection to a bootsrap node 17 | addr *net.TCPAddr // TCP address of a bootstrap node 18 | router string // Address of a bootstrap node 19 | running bool // Whether router is running or not 20 | handshaked bool // Whether handshake has been completed or not 21 | stop bool // Whether service should be terminated 22 | fails int // Number of connection fails 23 | tx uint64 // Transferred bytes 24 | rx uint64 // Received bytes 25 | data chan *protocol.DHTPacket // Payload channel 26 | lastContact time.Time // Last communication 27 | packetVersion string // Version of packet on DHT 28 | version string // Version of DHT 29 | } 30 | 31 | func (dht *DHTRouter) run() { 32 | dht.running = false 33 | dht.handshaked = false 34 | dht.version = "Unknown" 35 | dht.packetVersion = "Unknown" 36 | 37 | for !dht.stop { 38 | for !dht.running { 39 | dht.connect() 40 | if dht.running || dht.stop { 41 | break 42 | } 43 | dht.sleep() 44 | } 45 | data := make([]byte, ptp.DHTBufferSize) 46 | n, err := dht.conn.Read(data) 47 | if err != nil { 48 | ptp.Log(ptp.Warning, "BSN socket closed: %s", err) 49 | dht.running = false 50 | dht.handshaked = false 51 | continue 52 | } 53 | dht.lastContact = time.Now() 54 | go dht.handleData(data[:n]) 55 | } 56 | } 57 | 58 | func (dht *DHTRouter) handleData(data []byte) { 59 | length := len(data) 60 | dht.rx += uint64(length) 61 | i := 0 62 | handled := 0 63 | ptp.Log(ptp.Trace, "Handling data: data length is [%d]", len(data)) 64 | for i >= 0 { 65 | i = bytes.Index(data, []byte{0x0a, 0x0b, 0x0c, 0x0a}) 66 | if i <= 0 { 67 | break 68 | } 69 | handled++ 70 | dht.routeData(data[:i]) 71 | if i+4 < len(data) { 72 | data = data[i+4:] 73 | } else { 74 | break 75 | } 76 | } 77 | if handled == 0 { 78 | dht.routeData(data[:length]) 79 | } 80 | } 81 | 82 | func (dht *DHTRouter) routeData(data []byte) { 83 | packet := &protocol.DHTPacket{} 84 | err := proto.Unmarshal(data, packet) 85 | ptp.Log(ptp.Trace, "DHTPacket size: [%d]", len(data)) 86 | ptp.Log(ptp.Trace, "DHTPacket contains: %+v --- %+v", bytes.NewBuffer(data).String(), packet) 87 | if err != nil { 88 | ptp.Log(ptp.Warning, "Corrupted data from DHT: %s [%d]", err, len(data)) 89 | return 90 | } 91 | ptp.Log(ptp.Trace, "Received DHT packet: %+v", packet) 92 | if packet.Type == protocol.DHTPacketType_Ping && dht.handshaked == false { 93 | supported := false 94 | for _, v := range ptp.SupportedVersion { 95 | if v == packet.Version { 96 | supported = true 97 | } 98 | } 99 | if !supported { 100 | ptp.Log(ptp.Error, "Version mismatch. Server have %d. We have %d", packet.Version, ptp.PacketVersion) 101 | dht.stop = true 102 | if dht.conn != nil { 103 | dht.conn.Close() 104 | } 105 | } else { 106 | dht.handshaked = true 107 | ptp.Log(ptp.Info, "Connected to a bootstrap node: %s [%s]", dht.addr.String(), packet.Data) 108 | dht.packetVersion = fmt.Sprintf("%d", packet.Version) 109 | if packet.Extra != "" { 110 | ptp.Log(ptp.Info, "DHT Version: %s", packet.Extra) 111 | dht.version = packet.Extra 112 | } 113 | packet.Query = "handshaked" 114 | dht.data <- packet 115 | return 116 | } 117 | } 118 | if !dht.handshaked { 119 | ptp.Log(ptp.Trace, "Skipping packet: not handshaked") 120 | return 121 | } 122 | dht.data <- packet 123 | } 124 | 125 | func (dht *DHTRouter) connect() { 126 | dht.handshaked = false 127 | dht.running = false 128 | 129 | if dht.conn != nil { 130 | dht.conn.Close() 131 | dht.conn = nil 132 | } 133 | 134 | var err error 135 | dht.conn, err = net.DialTCP("tcp4", nil, dht.addr) 136 | if err != nil { 137 | dht.fails++ 138 | ptp.Log(ptp.Error, "Failed to establish connection with %s: %s", dht.addr.String(), err) 139 | return 140 | } 141 | dht.lastContact = time.Now() 142 | dht.fails = 0 143 | dht.running = true 144 | } 145 | 146 | func (dht *DHTRouter) sleep() { 147 | multiplier := dht.fails * 5 148 | if multiplier > 30 { 149 | multiplier = 30 150 | } 151 | ptp.Log(ptp.Info, "Waiting for %d second before reconnecting", multiplier) 152 | started := time.Now() 153 | timeout := time.Duration(time.Second * time.Duration(multiplier)) 154 | for time.Since(started) < timeout { 155 | time.Sleep(time.Millisecond * 200) 156 | } 157 | } 158 | 159 | func (dht *DHTRouter) keepAlive() { 160 | lastPing := time.Now() 161 | dht.lastContact = time.Now() 162 | for { 163 | if time.Since(lastPing) > time.Duration(time.Millisecond*30000) && time.Since(dht.lastContact) > time.Duration(time.Millisecond*40) { 164 | lastPing = time.Now() 165 | if dht.ping() != nil { 166 | ptp.Log(ptp.Error, "DHT router ping failed") 167 | } 168 | } 169 | if time.Since(dht.lastContact) > time.Duration(time.Millisecond*60000) && dht.running { 170 | ptp.Log(ptp.Warning, "Disconnected from DHT router %s by timeout", dht.addr.String()) 171 | dht.handshaked = false 172 | dht.running = false 173 | dht.conn.Close() 174 | dht.conn = nil 175 | } 176 | time.Sleep(time.Millisecond * 100) 177 | } 178 | } 179 | 180 | func (dht *DHTRouter) sendRaw(data []byte) (int, error) { 181 | if dht.conn == nil { 182 | return -1, fmt.Errorf("Can't send: connection is nil") 183 | } 184 | return dht.conn.Write(data) 185 | } 186 | 187 | func (dht *DHTRouter) ping() error { 188 | ptp.Log(ptp.Trace, "Sending ping to dht %s", dht.addr.String()) 189 | packet := &protocol.DHTPacket{ 190 | Type: protocol.DHTPacketType_Ping, 191 | Query: "req", 192 | Version: ptp.PacketVersion, 193 | } 194 | data, err := proto.Marshal(packet) 195 | if err != nil { 196 | return err 197 | } 198 | _, err = dht.sendRaw(data) 199 | if err != nil { 200 | return err 201 | } 202 | return nil 203 | } 204 | -------------------------------------------------------------------------------- /dht_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/golang/protobuf/proto" 7 | ptp "github.com/subutai-io/p2p/lib" 8 | "github.com/subutai-io/p2p/protocol" 9 | ) 10 | 11 | func TestRouteData(t *testing.T) { 12 | 13 | dataStr := "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 14 | dataArr := []string{} 15 | i := 0 16 | for i < 99 { 17 | dataArr = append(dataArr, dataStr) 18 | i++ 19 | } 20 | 21 | data := []protocol.DHTPacket{ 22 | protocol.DHTPacket{}, 23 | protocol.DHTPacket{ 24 | Type: protocol.DHTPacketType_Ping, 25 | }, 26 | protocol.DHTPacket{ 27 | Type: protocol.DHTPacketType_Ping, 28 | Arguments: dataArr, 29 | }, 30 | } 31 | 32 | router := new(DHTRouter) 33 | for _, d := range data { 34 | b, _ := proto.Marshal(&d) 35 | if len(b) > ptp.DHTBufferSize { 36 | b = b[:ptp.DHTBufferSize] 37 | } 38 | router.routeData(b) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/comm.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "net" 8 | ) 9 | 10 | // Cross-peer communication handlers 11 | 12 | // commPacketCheck is a common packet checker that checks for 13 | // incoming data length 14 | func commPacketCheck(data []byte) error { 15 | if len(data) < 36 { 16 | return fmt.Errorf("data is too small for communication packet") 17 | } 18 | return nil 19 | } 20 | 21 | // commStatusReportHandler handles status reports from another peer 22 | func commStatusReportHandler(data []byte, p *PeerToPeer) ([]byte, error) { 23 | err := commPacketCheck(data) 24 | if err != nil { 25 | return nil, err 26 | } 27 | return nil, nil 28 | } 29 | 30 | // commSubnetInfoHandler request/response of network subnet. Data format is as follows: 31 | // id[36] - subnet[?] 32 | // If subnet is empty, that means that this is a request. Hash is a mandatory, but just for a sanity check 33 | func commSubnetInfoHandler(data []byte, p *PeerToPeer) ([]byte, error) { 34 | if p.Interface == nil { 35 | return nil, fmt.Errorf("nil interface") 36 | } 37 | if p.Dht == nil { 38 | return nil, fmt.Errorf("nil dht") 39 | } 40 | //hash := data[0:36] 41 | err := commPacketCheck(data) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | if len(data) == 36 { 47 | // This is a request 48 | // We are not allowed to reply, if we use automatic IP 49 | if p.Interface.IsAuto() { 50 | return nil, nil 51 | } 52 | response := make([]byte, 42) 53 | binary.BigEndian.PutUint16(response[0:2], CommIPSubnet) 54 | copy(response[2:38], p.Dht.ID) 55 | copy(response[38:42], p.Interface.GetIP().Mask(net.CIDRMask(24, 32)).To4()) 56 | return response, nil 57 | } 58 | 59 | if len(data) != 40 { 60 | return nil, fmt.Errorf("wrong payload size: %d", len(data)) 61 | } 62 | 63 | // This is a response. We just a subnet on the interface 64 | p.Interface.SetSubnet(net.IP(data[36:40])) 65 | 66 | return nil, nil 67 | } 68 | 69 | // commIPInfoHandler will check if we know this IP or not 70 | // id[36] ip[4] res[1] 71 | // When res is empty - packet is a request 72 | // res can be 0 - IP unknown 73 | // res can be 1 - IP known 74 | func commIPInfoHandler(data []byte, p *PeerToPeer) ([]byte, error) { 75 | err := commPacketCheck(data) 76 | if err != nil { 77 | return nil, err 78 | } 79 | if p == nil { 80 | return nil, fmt.Errorf("nil ptp") 81 | } 82 | if p.Swarm == nil { 83 | return nil, fmt.Errorf("nil peer list") 84 | } 85 | if p.Interface == nil { 86 | return nil, fmt.Errorf("nil interface") 87 | } 88 | if p.Dht == nil { 89 | return nil, fmt.Errorf("nil dht") 90 | } 91 | if len(data) < 40 { 92 | return nil, fmt.Errorf("payload it soo small: %d", len(data)) 93 | } 94 | 95 | //hash := data[0:36] 96 | ip := net.IP(data[36:40]) 97 | if len(data) == 42 { 98 | result := binary.BigEndian.Uint16(data[40:42]) 99 | if result == 0 && p.Interface.GetIP() == nil { 100 | Log(Info, "IP %s is unknown to this swarm. Setting it", ip.String()) 101 | p.Interface.SetIP(ip) 102 | p.Interface.Configure(false) 103 | p.Interface.MarkConfigured() 104 | go p.notifyIP() 105 | return nil, nil 106 | } 107 | Log(Info, "IP %s is already known to this swarm. Ignoring it", ip.String()) 108 | return nil, nil 109 | } 110 | if len(data) != 40 { 111 | return nil, fmt.Errorf("wrong data length: %d", len(data)) 112 | } 113 | 114 | var result uint16 115 | 116 | if bytes.Equal(p.Interface.GetIP(), ip) { 117 | result = 1 118 | } else { 119 | for _, peer := range p.Swarm.Get() { 120 | if bytes.Equal(peer.PeerLocalIP.To4(), ip) { 121 | result = 1 122 | break 123 | } 124 | } 125 | } 126 | 127 | if result == 1 { 128 | Log(Debug, "Peer requested info about IP %s. That IP is known to us", ip.String()) 129 | } else { 130 | Log(Debug, "Peer requested info about IP %s. We don't know that IP", ip.String()) 131 | } 132 | 133 | response := make([]byte, 44) 134 | binary.BigEndian.PutUint16(response[0:2], CommIPInfo) 135 | copy(response[2:38], p.Dht.ID) 136 | copy(response[38:42], ip) 137 | binary.BigEndian.PutUint16(response[42:44], result) 138 | return response, nil 139 | } 140 | 141 | // commIPSetHandler will handle notification from another peer that he 142 | // is now uses specified ip 143 | // id[36] ip[4] 144 | func commIPSetHandler(data []byte, p *PeerToPeer) ([]byte, error) { 145 | if p.Swarm == nil { 146 | return nil, fmt.Errorf("nil swarm") 147 | } 148 | err := commPacketCheck(data) 149 | if err != nil { 150 | return nil, err 151 | } 152 | 153 | if len(data) < 40 { 154 | return nil, fmt.Errorf("data is too small") 155 | } 156 | 157 | id := string(data[0:36]) 158 | ip := net.IP(data[36:40]) 159 | 160 | for _, peer := range p.Swarm.Get() { 161 | if bytes.Equal(peer.PeerLocalIP, ip) && peer.Endpoint != nil { 162 | // That IP already set on other peer. Call a conflict 163 | Log(Info, "Reporting IP conflict") 164 | payload := make([]byte, 42) 165 | binary.BigEndian.PutUint16(payload[0:2], CommIPConflict) 166 | copy(payload[2:38], p.Dht.ID) 167 | copy(payload[38:42], ip) 168 | msg, _ := p.CreateMessage(MsgTypeComm, payload, 0, true) 169 | p.UDPSocket.SendMessage(msg, peer.Endpoint) 170 | return nil, nil 171 | } 172 | } 173 | 174 | for _, peer := range p.Swarm.Get() { 175 | if peer.ID == id { 176 | peer.PeerLocalIP = ip 177 | return nil, nil 178 | } 179 | } 180 | 181 | return nil, fmt.Errorf("Can't update peer IP. Peer %s not found", id) 182 | } 183 | 184 | func commIPConflictHandler(data []byte, p *PeerToPeer) ([]byte, error) { 185 | if p.Interface == nil { 186 | return nil, fmt.Errorf("nil interface") 187 | } 188 | 189 | err := commPacketCheck(data) 190 | if err != nil { 191 | return nil, err 192 | } 193 | 194 | ip := net.IP(data[36:40]) 195 | 196 | if p.Interface.IsAuto() && p.Interface.IsConfigured() && bytes.Equal(ip, p.Interface.GetIP()) { 197 | p.Interface.Deconfigure() 198 | return nil, nil 199 | } 200 | 201 | return nil, nil 202 | } 203 | -------------------------------------------------------------------------------- /lib/comm_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "encoding/binary" 5 | "net" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func Test_commPacketCheck(t *testing.T) { 11 | type args struct { 12 | data []byte 13 | } 14 | 15 | ut := "123e4567-e89b-12d3-a456-426655440000" 16 | 17 | tests := []struct { 18 | name string 19 | args args 20 | wantErr bool 21 | }{ 22 | {"nil case", args{}, true}, 23 | {"small size", args{[]byte{0x01}}, true}, 24 | {"passing", args{[]byte(ut)}, false}, 25 | } 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | if err := commPacketCheck(tt.args.data); (err != nil) != tt.wantErr { 29 | t.Errorf("commPacketCheck() error = %v, wantErr %v", err, tt.wantErr) 30 | } 31 | }) 32 | } 33 | } 34 | 35 | func Test_commStatusReportHandler(t *testing.T) { 36 | type args struct { 37 | data []byte 38 | p *PeerToPeer 39 | } 40 | 41 | ut := "123e4567-e89b-12d3-a456-426655440000" 42 | 43 | ptp := new(PeerToPeer) 44 | 45 | tests := []struct { 46 | name string 47 | args args 48 | want []byte 49 | wantErr bool 50 | }{ 51 | {"nil case", args{nil, ptp}, nil, true}, 52 | {"small size", args{[]byte{0x01}, ptp}, nil, true}, 53 | {"passing", args{[]byte(ut), ptp}, nil, false}, 54 | } 55 | for _, tt := range tests { 56 | t.Run(tt.name, func(t *testing.T) { 57 | got, err := commStatusReportHandler(tt.args.data, tt.args.p) 58 | if (err != nil) != tt.wantErr { 59 | t.Errorf("commStatusReportHandler() error = %v, wantErr %v", err, tt.wantErr) 60 | return 61 | } 62 | if !reflect.DeepEqual(got, tt.want) { 63 | t.Errorf("commStatusReportHandler() = %v, want %v", got, tt.want) 64 | } 65 | }) 66 | } 67 | } 68 | 69 | func Test_commSubnetInfoHandler(t *testing.T) { 70 | type args struct { 71 | data []byte 72 | p *PeerToPeer 73 | } 74 | 75 | ut := "123e4567-e89b-12d3-a456-426655440000" 76 | 77 | ptp0 := new(PeerToPeer) 78 | 79 | ptp1 := new(PeerToPeer) 80 | ptp1.Interface, _ = newTAP("ip", "10.10.10.1", "00:11:22:33:44:55", "255.255.255.0", 1500, false) 81 | 82 | ptp2 := new(PeerToPeer) 83 | ptp2.Interface, _ = newTAP("ip", "10.10.10.1", "00:11:22:33:44:55", "255.255.255.0", 1500, false) 84 | ptp2.Dht = new(DHTClient) 85 | ptp2.Dht.ID = ut 86 | 87 | resp := []byte{0x0, 0xa} 88 | resp = append(resp, []byte(ut)...) 89 | resp = append(resp, []byte{0xa, 0xa, 0xa, 0x0}...) 90 | 91 | tests := []struct { 92 | name string 93 | args args 94 | want []byte 95 | wantErr bool 96 | }{ 97 | {"nil interface", args{nil, ptp0}, nil, true}, 98 | {"nil dht", args{nil, ptp1}, nil, true}, 99 | {"small size", args{[]byte{0x01}, ptp2}, nil, true}, 100 | {"passing", args{[]byte(ut), ptp2}, resp, false}, 101 | } 102 | for _, tt := range tests { 103 | t.Run(tt.name, func(t *testing.T) { 104 | got, err := commSubnetInfoHandler(tt.args.data, tt.args.p) 105 | if (err != nil) != tt.wantErr { 106 | t.Errorf("commSubnetInfoHandler() error = %v, wantErr %v", err, tt.wantErr) 107 | return 108 | } 109 | if !reflect.DeepEqual(got, tt.want) { 110 | t.Errorf("commSubnetInfoHandler() = %v, want %v", got, tt.want) 111 | } 112 | }) 113 | } 114 | } 115 | 116 | func Test_commIPInfoHandler(t *testing.T) { 117 | type args struct { 118 | data []byte 119 | p *PeerToPeer 120 | } 121 | 122 | ip := net.ParseIP("127.0.0.1") 123 | 124 | ut := "123e4567-e89b-12d3-a456-426655440000" 125 | 126 | ptp0 := new(PeerToPeer) 127 | 128 | ptp1 := new(PeerToPeer) 129 | ptp1.Swarm = new(Swarm) 130 | ptp1.Swarm.Init() 131 | 132 | ptp2 := new(PeerToPeer) 133 | ptp2.Swarm = new(Swarm) 134 | ptp2.Swarm.Init() 135 | ptp2.Interface, _ = newTAP("ip", "10.10.10.1", "00:11:22:33:44:55", "255.255.255.0", 1500, false) 136 | 137 | ptp3 := new(PeerToPeer) 138 | ptp3.Swarm = new(Swarm) 139 | ptp3.Swarm.Init() 140 | ptp3.Interface, _ = newTAP("ip", "10.10.10.1", "00:11:22:33:44:55", "255.255.255.0", 1500, false) 141 | ptp3.Dht = new(DHTClient) 142 | ptp3.Dht.ID = ut 143 | 144 | ptp4 := new(PeerToPeer) 145 | ptp4.Swarm = new(Swarm) 146 | ptp4.Swarm.Init() 147 | ptp4.Swarm.peers["127.0.0.1"] = &NetworkPeer{ 148 | PeerLocalIP: ip, 149 | } 150 | ptp4.Interface, _ = newTAP("ip", "10.10.10.1", "00:11:22:33:44:55", "255.255.255.0", 1500, false) 151 | ptp4.Dht = new(DHTClient) 152 | ptp4.Dht.ID = ut 153 | 154 | d0 := append([]byte{0x00, 0x01}, []byte(ut)...) 155 | 156 | d1 := make([]byte, 42) 157 | copy(d1[0:36], ut) 158 | copy(d1[36:40], ip.To4()) 159 | binary.BigEndian.PutUint16(d1[40:42], uint16(0)) 160 | 161 | d2 := make([]byte, 42) 162 | copy(d2[0:36], ut) 163 | copy(d2[36:40], ip.To4()) 164 | binary.BigEndian.PutUint16(d2[40:42], uint16(1)) 165 | 166 | d3 := make([]byte, 41) 167 | copy(d3[0:36], ut) 168 | copy(d3[36:40], ip.To4()) 169 | d3[40] = 0x0f 170 | 171 | d4 := make([]byte, 40) 172 | copy(d4[0:36], ut) 173 | copy(d4[36:40], ip.To4()) 174 | 175 | result := ut + string(ip.To4()) 176 | 177 | res := []byte{0x0, 0xb} 178 | res = append(res, []byte(result)...) 179 | res = append(res, []byte{0x00, 0x01}...) 180 | 181 | tests := []struct { 182 | name string 183 | args args 184 | want []byte 185 | wantErr bool 186 | }{ 187 | {"nil case", args{}, nil, true}, 188 | {"nil ptp", args{d0, nil}, nil, true}, 189 | {"nil peer list", args{d0, ptp0}, nil, true}, 190 | {"nil interface", args{d0, ptp1}, nil, true}, 191 | {"nil dht", args{d0, ptp2}, nil, true}, 192 | {"too small", args{d0, ptp3}, nil, true}, 193 | {"42 size>0", args{d1, ptp3}, nil, false}, 194 | {"42 size>1", args{d2, ptp3}, nil, false}, 195 | {"41 size", args{d3, ptp3}, nil, true}, 196 | {"40 size", args{d4, ptp4}, res, false}, 197 | } 198 | for _, tt := range tests { 199 | t.Run(tt.name, func(t *testing.T) { 200 | got, err := commIPInfoHandler(tt.args.data, tt.args.p) 201 | if (err != nil) != tt.wantErr { 202 | t.Errorf("commIPInfoHandler() error = %v, wantErr %v", err, tt.wantErr) 203 | return 204 | } 205 | if !reflect.DeepEqual(got, tt.want) { 206 | t.Errorf("commIPInfoHandler() = %v, want %v", got, tt.want) 207 | } 208 | }) 209 | } 210 | } 211 | 212 | func Test_commIPSetHandler(t *testing.T) { 213 | type args struct { 214 | data []byte 215 | p *PeerToPeer 216 | } 217 | 218 | ut := "123e4567-e89b-12d3-a456-426655440000" + "10.10.10.01" 219 | 220 | ptp := new(PeerToPeer) 221 | 222 | ptp1 := new(PeerToPeer) 223 | ptp1.Swarm = new(Swarm) 224 | 225 | tests := []struct { 226 | name string 227 | args args 228 | want []byte 229 | wantErr bool 230 | }{ 231 | {"nil swarm", args{nil, ptp}, nil, true}, 232 | {"small size", args{[]byte{0x01}, ptp1}, nil, true}, 233 | {"passing", args{[]byte(ut), ptp1}, nil, true}, 234 | } 235 | for _, tt := range tests { 236 | t.Run(tt.name, func(t *testing.T) { 237 | got, err := commIPSetHandler(tt.args.data, tt.args.p) 238 | if (err != nil) != tt.wantErr { 239 | t.Errorf("commIPSetHandler() error = %v, wantErr %v", err, tt.wantErr) 240 | return 241 | } 242 | if !reflect.DeepEqual(got, tt.want) { 243 | t.Errorf("commIPSetHandler() = %v, want %v", got, tt.want) 244 | } 245 | }) 246 | } 247 | } 248 | 249 | func Test_commIPConflictHandler(t *testing.T) { 250 | type args struct { 251 | data []byte 252 | p *PeerToPeer 253 | } 254 | 255 | ut := "123e4567-e89b-12d3-a456-426655440000" 256 | 257 | ptp := new(PeerToPeer) 258 | 259 | ptp1 := new(PeerToPeer) 260 | ptp1.Interface, _ = newTAP("ip", "10.10.10.1", "00:11:22:33:44:55", "255.255.255.0", 1500, false) 261 | 262 | tests := []struct { 263 | name string 264 | args args 265 | want []byte 266 | wantErr bool 267 | }{ 268 | {"nil case", args{nil, ptp}, nil, true}, 269 | {"small size", args{[]byte{0x01}, ptp1}, nil, true}, 270 | {"passing", args{[]byte(ut), ptp1}, nil, false}, 271 | } 272 | for _, tt := range tests { 273 | t.Run(tt.name, func(t *testing.T) { 274 | got, err := commIPConflictHandler(tt.args.data, tt.args.p) 275 | if (err != nil) != tt.wantErr { 276 | t.Errorf("commIPConflictHandler() error = %v, wantErr %v", err, tt.wantErr) 277 | return 278 | } 279 | if !reflect.DeepEqual(got, tt.want) { 280 | t.Errorf("commIPConflictHandler() = %v, want %v", got, tt.want) 281 | } 282 | }) 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /lib/conf.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | 7 | yaml "gopkg.in/yaml.v2" 8 | ) 9 | 10 | type Conf struct { 11 | IPTool string `yaml:"iptool"` 12 | TAPTool string `yaml:"taptool"` 13 | INFFile string `yaml:"inf_file"` 14 | MTU int `yaml:"mtu"` 15 | PMTU bool `yaml:"pmtu"` 16 | } 17 | 18 | func (c *Conf) Load(filepath string) error { 19 | c.SetDefaults() 20 | if filepath == "" { 21 | return nil 22 | } 23 | yamlFile, err := ioutil.ReadFile(filepath) 24 | if err != nil { 25 | return err 26 | } 27 | err = yaml.Unmarshal(yamlFile, c) 28 | if err != nil { 29 | return fmt.Errorf("config parse failed: %s", err.Error()) 30 | } 31 | return nil 32 | } 33 | 34 | func (c *Conf) SetDefaults() { 35 | c.IPTool = DefaultIPTool 36 | c.TAPTool = DefaultTAPTool 37 | c.INFFile = DefaultINFFile 38 | c.MTU = DefaultMTU 39 | c.PMTU = DefaultPMTU 40 | } 41 | 42 | func (c *Conf) GetIPTool(preset string) string { 43 | if preset != "" { 44 | return preset 45 | } 46 | return c.IPTool 47 | } 48 | 49 | func (c *Conf) GetTAPTool(preset string) string { 50 | if preset != "" { 51 | return preset 52 | } 53 | return c.TAPTool 54 | } 55 | 56 | func (c *Conf) GetINFFile(preset string) string { 57 | if preset != "" { 58 | return preset 59 | } 60 | return c.INFFile 61 | } 62 | 63 | func (c *Conf) GetMTU(preset int) int { 64 | if preset != 0 { 65 | return preset 66 | } 67 | return c.MTU 68 | } 69 | 70 | func (c *Conf) GetPMTU() bool { 71 | return c.PMTU 72 | } 73 | -------------------------------------------------------------------------------- /lib/conf_darwin.go: -------------------------------------------------------------------------------- 1 | // +build darwin 2 | 3 | package ptp 4 | 5 | const DefaultConfigLocation = "/Applications/SubutaiP2P.app/Contents/Resources/p2p.yaml" 6 | 7 | // Platform specific defaults 8 | const ( 9 | DefaultIPTool = "/sbin/ifconfig" // Default network interface configuration tool for Darwin OS 10 | DefaultTAPTool = "" // Default path to TAP configuration tool on Windows OS 11 | DefaultINFFile = "" // Default path to TAP INF file used by Windows OS 12 | DefaultMTU = 1500 // Default MTU value 13 | DefaultPMTU = false // Default PMTU switch 14 | ) 15 | -------------------------------------------------------------------------------- /lib/conf_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package ptp 4 | 5 | const DefaultConfigLocation = "/etc/p2p.yaml" 6 | 7 | // Platform specific defaults 8 | const ( 9 | DefaultIPTool = "/sbin/ip" // Default network interface configuration tool for Darwin OS 10 | DefaultTAPTool = "" // Default path to TAP configuration tool on Windows OS 11 | DefaultINFFile = "" // Default path to TAP INF file used by Windows OS 12 | DefaultMTU = 1500 // Default MTU value 13 | DefaultPMTU = false // Default PMTU switch 14 | ) 15 | -------------------------------------------------------------------------------- /lib/conf_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | ) 7 | 8 | func Test_Conf_Load(t *testing.T) { 9 | type fields struct { 10 | IPTool string 11 | TAPTool string 12 | INFFile string 13 | MTU int 14 | PMTU bool 15 | } 16 | type args struct { 17 | filepath string 18 | } 19 | 20 | d1 := []byte("-") 21 | ioutil.WriteFile("/tmp/test-yaml-Config-p2p-bad", d1, 0777) 22 | 23 | d2 := []byte("iptool: /sbin/ip") 24 | ioutil.WriteFile("/tmp/test-yaml-Config-p2p-ok", d2, 0777) 25 | 26 | tests := []struct { 27 | name string 28 | fields fields 29 | args args 30 | wantErr bool 31 | }{ 32 | {"empty filepath", fields{}, args{""}, false}, 33 | {"wrong filepath", fields{}, args{"/"}, true}, 34 | {"bad yaml", fields{}, args{"/tmp/test-yaml-Config-p2p-bad"}, true}, 35 | {"normal yaml", fields{}, args{"/tmp/test-yaml-Config-p2p-ok"}, false}, 36 | } 37 | for _, tt := range tests { 38 | t.Run(tt.name, func(t *testing.T) { 39 | c := &Conf{ 40 | IPTool: tt.fields.IPTool, 41 | TAPTool: tt.fields.TAPTool, 42 | INFFile: tt.fields.INFFile, 43 | MTU: tt.fields.MTU, 44 | PMTU: tt.fields.PMTU, 45 | } 46 | if err := c.Load(tt.args.filepath); (err != nil) != tt.wantErr { 47 | t.Errorf("Conf.Load() error = %v, wantErr %v", err, tt.wantErr) 48 | } 49 | }) 50 | } 51 | } 52 | 53 | func Test_Conf_getIPTool(t *testing.T) { 54 | type fields struct { 55 | IPTool string 56 | TAPTool string 57 | INFFile string 58 | MTU int 59 | PMTU bool 60 | } 61 | type args struct { 62 | preset string 63 | } 64 | 65 | c1 := new(Conf) 66 | c1.Load("/") 67 | 68 | f1 := fields{ 69 | IPTool: c1.IPTool, 70 | TAPTool: c1.TAPTool, 71 | INFFile: c1.INFFile, 72 | MTU: c1.MTU, 73 | PMTU: c1.PMTU, 74 | } 75 | 76 | tests := []struct { 77 | name string 78 | fields fields 79 | args args 80 | want string 81 | }{ 82 | {"empty string test", fields{}, args{}, ""}, 83 | {"default value", f1, args{""}, c1.IPTool}, 84 | {"preset value", f1, args{"preset-val"}, "preset-val"}, 85 | } 86 | for _, tt := range tests { 87 | t.Run(tt.name, func(t *testing.T) { 88 | c := &Conf{ 89 | IPTool: tt.fields.IPTool, 90 | TAPTool: tt.fields.TAPTool, 91 | INFFile: tt.fields.INFFile, 92 | MTU: tt.fields.MTU, 93 | PMTU: tt.fields.PMTU, 94 | } 95 | if got := c.GetIPTool(tt.args.preset); got != tt.want { 96 | t.Errorf("Conf.getIPTool() = %v, want %v", got, tt.want) 97 | } 98 | }) 99 | } 100 | } 101 | 102 | func Test_Conf_getTAPTool(t *testing.T) { 103 | type fields struct { 104 | IPTool string 105 | TAPTool string 106 | INFFile string 107 | MTU int 108 | PMTU bool 109 | } 110 | type args struct { 111 | preset string 112 | } 113 | 114 | c1 := new(Conf) 115 | c1.Load("/") 116 | 117 | f1 := fields{ 118 | IPTool: c1.IPTool, 119 | TAPTool: c1.TAPTool, 120 | INFFile: c1.INFFile, 121 | MTU: c1.MTU, 122 | PMTU: c1.PMTU, 123 | } 124 | 125 | tests := []struct { 126 | name string 127 | fields fields 128 | args args 129 | want string 130 | }{ 131 | {"empty string test", fields{}, args{}, ""}, 132 | {"default value", f1, args{""}, c1.TAPTool}, 133 | {"preset value", f1, args{"preset-val"}, "preset-val"}, 134 | } 135 | for _, tt := range tests { 136 | t.Run(tt.name, func(t *testing.T) { 137 | c := &Conf{ 138 | IPTool: tt.fields.IPTool, 139 | TAPTool: tt.fields.TAPTool, 140 | INFFile: tt.fields.INFFile, 141 | MTU: tt.fields.MTU, 142 | PMTU: tt.fields.PMTU, 143 | } 144 | if got := c.GetTAPTool(tt.args.preset); got != tt.want { 145 | t.Errorf("Conf.getTAPTool() = %v, want %v", got, tt.want) 146 | } 147 | }) 148 | } 149 | } 150 | 151 | func Test_Conf_getINFFile(t *testing.T) { 152 | type fields struct { 153 | IPTool string 154 | TAPTool string 155 | INFFile string 156 | MTU int 157 | PMTU bool 158 | } 159 | type args struct { 160 | preset string 161 | } 162 | 163 | c1 := new(Conf) 164 | c1.Load("/") 165 | 166 | f1 := fields{ 167 | IPTool: c1.IPTool, 168 | TAPTool: c1.TAPTool, 169 | INFFile: c1.INFFile, 170 | MTU: c1.MTU, 171 | PMTU: c1.PMTU, 172 | } 173 | 174 | tests := []struct { 175 | name string 176 | fields fields 177 | args args 178 | want string 179 | }{ 180 | {"empty string test", fields{}, args{}, ""}, 181 | {"default value", f1, args{""}, c1.INFFile}, 182 | {"preset value", f1, args{"preset-val"}, "preset-val"}, 183 | } 184 | for _, tt := range tests { 185 | t.Run(tt.name, func(t *testing.T) { 186 | c := &Conf{ 187 | IPTool: tt.fields.IPTool, 188 | TAPTool: tt.fields.TAPTool, 189 | INFFile: tt.fields.INFFile, 190 | MTU: tt.fields.MTU, 191 | PMTU: tt.fields.PMTU, 192 | } 193 | if got := c.GetINFFile(tt.args.preset); got != tt.want { 194 | t.Errorf("Conf.getINFFile() = %v, want %v", got, tt.want) 195 | } 196 | }) 197 | } 198 | } 199 | 200 | func Test_Conf_getMTU(t *testing.T) { 201 | type fields struct { 202 | IPTool string 203 | TAPTool string 204 | INFFile string 205 | MTU int 206 | PMTU bool 207 | } 208 | type args struct { 209 | preset int 210 | } 211 | 212 | c1 := new(Conf) 213 | c1.Load("/") 214 | 215 | f1 := fields{ 216 | IPTool: c1.IPTool, 217 | TAPTool: c1.TAPTool, 218 | INFFile: c1.INFFile, 219 | MTU: c1.MTU, 220 | PMTU: c1.PMTU, 221 | } 222 | 223 | tests := []struct { 224 | name string 225 | fields fields 226 | args args 227 | want int 228 | }{ 229 | {"empty string test", fields{}, args{}, 0}, 230 | {"default value", f1, args{0}, c1.MTU}, 231 | {"preset value", f1, args{256}, 256}, 232 | } 233 | for _, tt := range tests { 234 | t.Run(tt.name, func(t *testing.T) { 235 | c := &Conf{ 236 | IPTool: tt.fields.IPTool, 237 | TAPTool: tt.fields.TAPTool, 238 | INFFile: tt.fields.INFFile, 239 | MTU: tt.fields.MTU, 240 | PMTU: tt.fields.PMTU, 241 | } 242 | if got := c.GetMTU(tt.args.preset); got != tt.want { 243 | t.Errorf("Conf.getMTU() = %v, want %v", got, tt.want) 244 | } 245 | }) 246 | } 247 | } 248 | 249 | func Test_Conf_getPMTU(t *testing.T) { 250 | type fields struct { 251 | IPTool string 252 | TAPTool string 253 | INFFile string 254 | MTU int 255 | PMTU bool 256 | } 257 | type args struct { 258 | preset bool 259 | } 260 | 261 | tests := []struct { 262 | name string 263 | fields fields 264 | args args 265 | want bool 266 | }{ 267 | {"empty pmtu val", fields{}, args{}, false}, 268 | } 269 | for _, tt := range tests { 270 | t.Run(tt.name, func(t *testing.T) { 271 | c := &Conf{ 272 | IPTool: tt.fields.IPTool, 273 | TAPTool: tt.fields.TAPTool, 274 | INFFile: tt.fields.INFFile, 275 | MTU: tt.fields.MTU, 276 | PMTU: tt.fields.PMTU, 277 | } 278 | if got := c.GetPMTU(); got != tt.want { 279 | t.Errorf("Conf.getPMTU() = %v, want %v", got, tt.want) 280 | } 281 | }) 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /lib/conf_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package ptp 4 | 5 | const DefaultConfigLocation = "C:\\ProgramData\\subutai\\bin\\p2p.yaml" 6 | 7 | // Platform specific defaults 8 | const ( 9 | DefaultIPTool = "netsh.exe" // Default network interface configuration tool for Darwin OS 10 | DefaultTAPTool = "C:\\Program Files\\TAP-Windows\\bin\\tapinstall.exe" // Default path to TAP configuration tool on Windows OS 11 | DefaultINFFile = "C:\\Program Files\\TAP-Windows\\driver\\OemVista.inf" // Default path to TAP INF file used by Windows OS 12 | DefaultMTU = 1500 // Default MTU value 13 | DefaultPMTU = false // Default PMTU switch 14 | ) 15 | -------------------------------------------------------------------------------- /lib/crypto.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/rand" 8 | "fmt" 9 | "io/ioutil" 10 | "strconv" 11 | "time" 12 | 13 | "gopkg.in/yaml.v2" 14 | ) 15 | 16 | // CryptoKey represents a key and it's expiration date 17 | type CryptoKey struct { 18 | TTLConfig string `yaml:"ttl"` 19 | KeyConfig string `yaml:"key"` 20 | Until time.Time 21 | Key []byte 22 | } 23 | 24 | // Crypto is a object used by crypto subsystem 25 | type Crypto struct { 26 | Keys []CryptoKey 27 | ActiveKey CryptoKey 28 | Active bool 29 | } 30 | 31 | // EnrichKeyValues update information about current and feature keys 32 | func (c Crypto) EnrichKeyValues(ckey CryptoKey, key, datetime string) CryptoKey { 33 | var err error 34 | i, err := strconv.ParseInt(datetime, 10, 64) 35 | ckey.Until = time.Now() 36 | // Default value is +1 hour 37 | ckey.Until = ckey.Until.Add(60 * time.Minute) 38 | if err != nil { 39 | Log(Warning, "Failed to parse TTL. Falling back to default value of 1 hour") 40 | } else { 41 | ckey.Until = time.Unix(i, 0) 42 | } 43 | ckey.Key = []byte(key) 44 | if err != nil { 45 | Log(Error, "Failed to parse provided TTL value: %v", err) 46 | return ckey 47 | } 48 | return ckey 49 | } 50 | 51 | // ReadKeysFromFile read a file stored in a file system and extracts keys to be used 52 | func (c Crypto) ReadKeysFromFile(filepath string) { 53 | yamlFile, err := ioutil.ReadFile(filepath) 54 | if err != nil { 55 | Log(Error, "Failed to read key file yaml: %v", err) 56 | c.Active = false 57 | return 58 | } 59 | var ckey CryptoKey 60 | err = yaml.Unmarshal(yamlFile, ckey) 61 | if err != nil { 62 | Log(Error, "Failed to parse config: %v", err) 63 | c.Active = false 64 | return 65 | } 66 | ckey = c.EnrichKeyValues(ckey, ckey.KeyConfig, ckey.TTLConfig) 67 | c.Active = true 68 | c.Keys = append(c.Keys, ckey) 69 | } 70 | 71 | // Encrypt encrypts data 72 | func (c Crypto) encrypt(key []byte, data []byte) ([]byte, error) { 73 | block, err := aes.NewCipher(key) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | if len(data) != aes.BlockSize { 79 | padding := aes.BlockSize - len(data)%aes.BlockSize 80 | data = append(data, bytes.Repeat([]byte{byte(padding)}, padding)...) 81 | } 82 | 83 | encData := make([]byte, aes.BlockSize+len(data)) 84 | iv := encData[:aes.BlockSize] 85 | if _, err = rand.Read(iv); err != nil { 86 | return nil, err 87 | } 88 | 89 | mode := cipher.NewCBCEncrypter(block, iv) 90 | mode.CryptBlocks(encData[aes.BlockSize:], data) 91 | 92 | return encData, nil 93 | } 94 | 95 | // Decrypt decrypts data 96 | func (c Crypto) decrypt(key []byte, data []byte) ([]byte, error) { 97 | block, err := aes.NewCipher(key) 98 | if err != nil { 99 | return nil, err 100 | } 101 | encData := data[aes.BlockSize:] 102 | if len(data)%aes.BlockSize != 0 { 103 | return nil, fmt.Errorf("Input not full blocks: %s", string(data)) 104 | } 105 | mode := cipher.NewCBCDecrypter(block, data[:aes.BlockSize]) 106 | mode.CryptBlocks(encData, encData) 107 | 108 | return encData, nil 109 | } 110 | -------------------------------------------------------------------------------- /lib/crypto_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | //"crypto/rand" 5 | "math/rand" 6 | "reflect" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestEncrypt(t *testing.T) { 12 | crypto := new(Crypto) 13 | _, err := crypto.encrypt([]byte{}, []byte{}) 14 | if err == nil { 15 | t.Errorf("Encrypt didn't return error on empty key") 16 | } 17 | var key CryptoKey 18 | crypto.EnrichKeyValues(key, "keylessthan32", "1") 19 | } 20 | 21 | func RandomString(size int) string { 22 | var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") 23 | rand.Seed(time.Now().UnixNano()) 24 | b := make([]rune, size) 25 | for i := range b { 26 | b[i] = letters[rand.Intn(len(letters))] 27 | } 28 | return string(b) 29 | } 30 | 31 | func BenchmarkEncrypt(b *testing.B) { 32 | var data []string 33 | for i := 1; i < 10; i++ { 34 | data = append(data, RandomString(i*10)) 35 | } 36 | crypto := new(Crypto) 37 | var key CryptoKey 38 | crypto.EnrichKeyValues(key, "keylessthan32", "1") 39 | for i := 0; i < b.N; i++ { 40 | for _, str := range data { 41 | crypto.encrypt(crypto.ActiveKey.Key, []byte(str)) 42 | } 43 | } 44 | } 45 | 46 | func TestCrypto_encrypt(t *testing.T) { 47 | type fields struct { 48 | Keys []CryptoKey 49 | ActiveKey CryptoKey 50 | Active bool 51 | } 52 | type args struct { 53 | key []byte 54 | data []byte 55 | } 56 | tests := []struct { 57 | name string 58 | fields fields 59 | args args 60 | want []byte 61 | wantErr bool 62 | }{ 63 | {"broken key", fields{}, args{}, nil, true}, 64 | } 65 | for _, tt := range tests { 66 | t.Run(tt.name, func(t *testing.T) { 67 | c := Crypto{ 68 | Keys: tt.fields.Keys, 69 | ActiveKey: tt.fields.ActiveKey, 70 | Active: tt.fields.Active, 71 | } 72 | got, err := c.encrypt(tt.args.key, tt.args.data) 73 | if (err != nil) != tt.wantErr { 74 | t.Errorf("Crypto.encrypt() error = %v, wantErr %v", err, tt.wantErr) 75 | return 76 | } 77 | if !reflect.DeepEqual(got, tt.want) { 78 | t.Errorf("Crypto.encrypt() = %v, want %v", got, tt.want) 79 | } 80 | }) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/endpoint.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "net" 7 | "time" 8 | ) 9 | 10 | // Endpoint reprsents a UDP address endpoint that instance 11 | // may use for connection with a peer 12 | type Endpoint struct { 13 | Addr *net.UDPAddr 14 | LastContact time.Time 15 | LastPing time.Time 16 | broken bool 17 | Latency time.Duration 18 | LastLatencyQuery time.Time 19 | } 20 | 21 | // Measure will prepare and send latency packet to the endpoint 22 | // id is an ID of this peer 23 | func (e *Endpoint) Measure(n *Network, id string) { 24 | if e.broken { 25 | return 26 | } 27 | 28 | if e.Addr == nil { 29 | return 30 | } 31 | 32 | if n == nil { 33 | return 34 | } 35 | 36 | if time.Since(e.LastLatencyQuery) < EndpointLatencyRequestInterval { 37 | return 38 | } 39 | 40 | e.LastLatencyQuery = time.Now() 41 | 42 | ts, _ := time.Now().MarshalBinary() 43 | ba := e.addrToBytes() 44 | 45 | if ba == nil { 46 | return 47 | } 48 | 49 | payload := []byte{} 50 | payload = append(payload, LatencyRequestHeader...) 51 | payload = append(payload, ba...) 52 | payload = append(payload, []byte(id)...) 53 | payload = append(payload, ts...) 54 | 55 | msg, err := CreateMessageStatic(MsgTypeLatency, payload) 56 | if err != nil { 57 | Log(Error, "Failed to create latency measurement packet for endpoint: %s", err.Error()) 58 | e.LastLatencyQuery = time.Now() 59 | return 60 | } 61 | Log(Trace, "Measuring latency with endpoint %s", e.Addr.String()) 62 | n.SendMessage(msg, e.Addr) 63 | } 64 | 65 | func (e *Endpoint) addrToBytes() []byte { 66 | if e.Addr == nil { 67 | return nil 68 | } 69 | 70 | // 4 bytes of IP and 2 bytes of port 71 | ipfield := make([]byte, 6) 72 | 73 | ip4 := e.Addr.IP.To4() 74 | if len(ip4) != 4 { 75 | return nil 76 | } 77 | port := e.Addr.Port 78 | 79 | copy(ipfield[0:4], ip4[:4]) 80 | binary.BigEndian.PutUint16(ipfield[4:6], uint16(port)) 81 | 82 | // Data extract 83 | // net.IP{ipfield[0], ipfield[1], ipfield[2], ipfield[3]} 84 | // binary.BigEndian.Uint16(ipfield[4:6]) 85 | return ipfield 86 | } 87 | 88 | func (e *Endpoint) ping(ptpc *PeerToPeer, id string) error { 89 | if ptpc == nil { 90 | return fmt.Errorf("nil ptp") 91 | } 92 | if ptpc.UDPSocket == nil { 93 | return fmt.Errorf("nil socket") 94 | } 95 | e.LastPing = time.Now() 96 | if e.Addr == nil { 97 | return fmt.Errorf("nil addr") 98 | } 99 | payload := append([]byte("q"+id), []byte(e.Addr.String())...) 100 | msg, err := ptpc.CreateMessage(MsgTypeXpeerPing, payload, 0, true) 101 | if err != nil { 102 | return err 103 | } 104 | Log(Trace, "Sending ping to endpoint: %s", e.Addr.String()) 105 | _, err = ptpc.UDPSocket.SendMessage(msg, e.Addr) 106 | return err 107 | } 108 | 109 | func (e *Endpoint) updateLastContact() error { 110 | e.LastContact = time.Now() 111 | e.LastPing = time.Now() 112 | return nil 113 | } 114 | -------------------------------------------------------------------------------- /lib/endpoint_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "encoding/binary" 5 | "net" 6 | "reflect" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestEndpoint_addrToBytes(t *testing.T) { 12 | type fields struct { 13 | Addr *net.UDPAddr 14 | LastContact time.Time 15 | LastPing time.Time 16 | broken bool 17 | Latency time.Duration 18 | LastLatencyQuery time.Time 19 | MeasureInProgress bool 20 | } 21 | 22 | a1, _ := net.ResolveUDPAddr("udp4", "127.0.0.1:1111") 23 | r1 := []byte{127, 0, 0, 1, 0, 0} 24 | binary.BigEndian.PutUint16(r1[4:6], uint16(1111)) 25 | 26 | a2, _ := net.ResolveUDPAddr("udp4", "0.0.0.0:0000") 27 | r2 := []byte{0, 0, 0, 0, 0, 0} 28 | binary.BigEndian.PutUint16(r2[4:6], uint16(0)) 29 | 30 | a3, _ := net.ResolveUDPAddr("udp4", "255.255.255.255:65535") 31 | r3 := []byte{255, 255, 255, 255, 255, 255} 32 | 33 | a4, _ := net.ResolveUDPAddr("udp4", "254.254.254.254:65534") 34 | r4 := []byte{254, 254, 254, 254, 0, 0} 35 | binary.BigEndian.PutUint16(r4[4:6], uint16(65534)) 36 | 37 | tests := []struct { 38 | name string 39 | fields fields 40 | want []byte 41 | }{ 42 | {"nil addr", fields{}, nil}, 43 | {"Testing 127.0.0.1:1111", fields{Addr: a1}, r1}, 44 | {"Testing 0.0.0.0:0000", fields{Addr: a2}, r2}, 45 | {"Testing 255.255.255.255:65535", fields{Addr: a3}, r3}, 46 | {"Testing 254.254.254.254:65534", fields{Addr: a4}, r4}, 47 | } 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | e := &Endpoint{ 51 | Addr: tt.fields.Addr, 52 | LastContact: tt.fields.LastContact, 53 | LastPing: tt.fields.LastPing, 54 | broken: tt.fields.broken, 55 | Latency: tt.fields.Latency, 56 | LastLatencyQuery: tt.fields.LastLatencyQuery, 57 | } 58 | if got := e.addrToBytes(); !reflect.DeepEqual(got, tt.want) { 59 | t.Errorf("Endpoint.addrToBytes() = %v, want %v", got, tt.want) 60 | } 61 | }) 62 | } 63 | } 64 | 65 | func TestEndpoint_Measure(t *testing.T) { 66 | type fields struct { 67 | Addr *net.UDPAddr 68 | LastContact time.Time 69 | LastPing time.Time 70 | broken bool 71 | Latency time.Duration 72 | LastLatencyQuery time.Time 73 | } 74 | type args struct { 75 | n *Network 76 | id string 77 | } 78 | addr, _ := net.ResolveUDPAddr("udp4", "127.0.0.1:1234") 79 | tests := []struct { 80 | name string 81 | fields fields 82 | args args 83 | }{ 84 | {"Broken EP", fields{broken: true}, args{}}, 85 | {"nil addr", fields{}, args{}}, 86 | {"nil network", fields{Addr: new(net.UDPAddr)}, args{}}, 87 | {"time not passed", fields{Addr: new(net.UDPAddr), LastLatencyQuery: time.Now()}, args{new(Network), ""}}, 88 | {"address bytes is nil", fields{Addr: new(net.UDPAddr), LastLatencyQuery: time.Unix(0, 0)}, args{new(Network), ""}}, 89 | {"proper addr", fields{Addr: addr, LastLatencyQuery: time.Unix(0, 0)}, args{new(Network), ""}}, 90 | } 91 | for _, tt := range tests { 92 | t.Run(tt.name, func(t *testing.T) { 93 | e := &Endpoint{ 94 | Addr: tt.fields.Addr, 95 | LastContact: tt.fields.LastContact, 96 | LastPing: tt.fields.LastPing, 97 | broken: tt.fields.broken, 98 | Latency: tt.fields.Latency, 99 | LastLatencyQuery: tt.fields.LastLatencyQuery, 100 | } 101 | e.Measure(tt.args.n, tt.args.id) 102 | }) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/errors.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // ErrorType is a type of an error 8 | type ErrorType string 9 | 10 | var ( 11 | // ErrorList stores known errors 12 | ErrorList map[ErrorType]error 13 | ) 14 | 15 | // Types of errors 16 | const ( 17 | ErrUnknownError ErrorType = "unknownerror" 18 | ErrIncopatibleVersion ErrorType = "unsupported" 19 | ErrMalformedHandshake ErrorType = "badhandshake" 20 | ErrPortParseFailed ErrorType = "badport" 21 | ErrBadUDPAddr ErrorType = "badudpaddr" 22 | ErrBadIDReceived ErrorType = "badid" 23 | ErrBadDHCPData ErrorType = "baddhcp" 24 | ) 25 | 26 | // TError -Struct for errors 27 | type TError struct { 28 | Type ErrorType 29 | } 30 | 31 | // InitErrors populates ErrorList with error types 32 | func InitErrors() { 33 | ErrorList = make(map[ErrorType]error) 34 | ErrorList[ErrIncopatibleVersion] = errors.New("DHT received incompatible packet") 35 | ErrorList[ErrMalformedHandshake] = errors.New("DHT received malformed handshake") 36 | ErrorList[ErrPortParseFailed] = errors.New("DHT failed to extract port from handshake") 37 | ErrorList[ErrBadUDPAddr] = errors.New("DHT failed to extract UDP address from handshake") 38 | ErrorList[ErrBadIDReceived] = errors.New("DHT received invalid ID from client") 39 | ErrorList[ErrBadDHCPData] = errors.New("DHT failed to parse provided DHCP packet") 40 | } 41 | -------------------------------------------------------------------------------- /lib/errors_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import "testing" 4 | 5 | func TestInitErrors(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | }{ 9 | {"init"}, 10 | } 11 | for _, tt := range tests { 12 | t.Run(tt.name, func(t *testing.T) { 13 | InitErrors() 14 | }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/log.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "strings" 7 | "fmt" 8 | ) 9 | 10 | // LogLevel is a level of the log message 11 | type LogLevel int32 12 | 13 | // Log Levels 14 | const ( 15 | Trace LogLevel = iota 16 | Debug 17 | Info 18 | Warning 19 | Error 20 | ) 21 | 22 | var logPrefixes = [...]string{"[TRACE] ", "[DEBUG] ", "[INFO] ", "[WARNING] ", "[ERROR] "} 23 | var logFlags = [...]int{log.Ldate | log.Ltime, 24 | log.Ldate | log.Ltime, 25 | log.Ldate | log.Ltime, 26 | log.Ldate | log.Ltime, 27 | log.Ldate | log.Ltime} 28 | 29 | var logLevelMin = Info 30 | var syslogSocket = "" 31 | var stdLoggers = [...]*log.Logger{log.New(os.Stdout, logPrefixes[Trace], logFlags[Trace]), 32 | log.New(os.Stdout, logPrefixes[Debug], logFlags[Debug]), 33 | log.New(os.Stdout, logPrefixes[Info], logFlags[Info]), 34 | log.New(os.Stdout, logPrefixes[Warning], logFlags[Warning]), 35 | log.New(os.Stdout, logPrefixes[Error], logFlags[Error])} 36 | 37 | // SetMinLogLevel sets a minimal logging level. Accepts a LogLevel constant for setting 38 | func SetMinLogLevel(level LogLevel) { 39 | logLevelMin = level 40 | } 41 | 42 | // SetMinLogLevel sets a minimal logging level. Accepts a string for setting 43 | func SetMinLogLevelString(level string) error { 44 | level = strings.ToLower(level) 45 | if level == "trace" { 46 | SetMinLogLevel(Trace) 47 | } else if level == "debug" { 48 | SetMinLogLevel(Debug) 49 | } else if level == "info" { 50 | SetMinLogLevel(Info) 51 | } else if level == "warning" { 52 | SetMinLogLevel(Warning) 53 | } else if level == "error" { 54 | SetMinLogLevel(Error) 55 | } else { 56 | Log(Warning, "Unknown log level %s was provided. Supported log levels are:\ntrace\ndebug\ninfo\nwarning\nerror\n", level) 57 | return fmt.Errorf("Could not set provided log level") 58 | } 59 | Log(Info, "Logging level has switched to %s level", level) 60 | return nil 61 | } 62 | 63 | // MinLogLevel returns minimal log level 64 | func MinLogLevel() LogLevel { return logLevelMin } 65 | 66 | // Log writes a log message 67 | func Log(level LogLevel, format string, v ...interface{}) { 68 | if level < logLevelMin { 69 | return 70 | } 71 | stdLoggers[level].Printf(format, v...) 72 | if level != Trace && len(syslogSocket) != 0 { 73 | go Syslog(level, format, v...) 74 | } 75 | } 76 | 77 | // SetSyslogSocket sets an adders of the syslog server 78 | func SetSyslogSocket(socket string) { 79 | syslogSocket = socket 80 | } 81 | -------------------------------------------------------------------------------- /lib/log_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import "testing" 4 | 5 | // import "testing" 6 | 7 | // func TestSetMinLogLevel(t *testing.T) { 8 | // var i LogLevel 9 | // for i = 0; i < 10; i++ { 10 | // SetMinLogLevel(i) 11 | // if logLevelMin != i { 12 | // t.Errorf("Error. Wait %v, get %v", i, logLevelMin) 13 | // } 14 | // } 15 | // } 16 | 17 | // func TestMinLogLevel(t *testing.T) { 18 | // var level LogLevel 19 | // for level = 0; level < 10; level++ { 20 | // SetMinLogLevel(level) 21 | // get := MinLogLevel() 22 | // if get != level { 23 | // t.Errorf("Error. Wait %v, get %v", level, get) 24 | // } 25 | // } 26 | // } 27 | 28 | // func TestSetSyslogSocket(t *testing.T) { 29 | // syslogs := [...]string{ 30 | // "socket", 31 | // "12345", 32 | // "", 33 | // } 34 | // for i := 0; i < len(syslogs); i++ { 35 | // SetSyslogSocket(syslogs[i]) 36 | // if syslogSocket != syslogs[i] { 37 | // t.Errorf("Error. Wait %v, get %v", syslogs[i], syslogSocket) 38 | // } 39 | // } 40 | // } 41 | 42 | func TestSetMinLogLevel(t *testing.T) { 43 | type args struct { 44 | level LogLevel 45 | } 46 | tests := []struct { 47 | name string 48 | args args 49 | }{ 50 | {"empty test", args{}}, 51 | } 52 | for _, tt := range tests { 53 | t.Run(tt.name, func(t *testing.T) { 54 | SetMinLogLevel(tt.args.level) 55 | }) 56 | } 57 | } 58 | 59 | func TestSetMinLogLevelString(t *testing.T) { 60 | type args struct { 61 | level string 62 | } 63 | tests := []struct { 64 | name string 65 | args args 66 | wantErr bool 67 | }{ 68 | {"trace", args{"trace"}, false}, 69 | {"debug", args{"debug"}, false}, 70 | {"info", args{"info"}, false}, 71 | {"warning", args{"warning"}, false}, 72 | {"error", args{"error"}, false}, 73 | {"unknown", args{"unknown"}, true}, 74 | } 75 | for _, tt := range tests { 76 | t.Run(tt.name, func(t *testing.T) { 77 | if err := SetMinLogLevelString(tt.args.level); (err != nil) != tt.wantErr { 78 | t.Errorf("SetMinLogLevelString() error = %v, wantErr %v", err, tt.wantErr) 79 | } 80 | }) 81 | } 82 | } 83 | 84 | func TestMinLogLevel(t *testing.T) { 85 | SetMinLogLevel(Info) 86 | tests := []struct { 87 | name string 88 | want LogLevel 89 | }{ 90 | {"empty test", 2}, 91 | } 92 | for _, tt := range tests { 93 | t.Run(tt.name, func(t *testing.T) { 94 | if got := MinLogLevel(); got != tt.want { 95 | t.Errorf("MinLogLevel() = %v, want %v", got, tt.want) 96 | } 97 | }) 98 | } 99 | } 100 | 101 | func TestLog(t *testing.T) { 102 | type args struct { 103 | level LogLevel 104 | format string 105 | v []interface{} 106 | } 107 | syslogSocket = "test" 108 | tests := []struct { 109 | name string 110 | args args 111 | }{ 112 | {"lower level", args{Trace, "%s", nil}}, 113 | {"normal level", args{Info, "%s", nil}}, 114 | } 115 | for _, tt := range tests { 116 | t.Run(tt.name, func(t *testing.T) { 117 | Log(tt.args.level, tt.args.format, tt.args.v...) 118 | }) 119 | } 120 | } 121 | 122 | func TestSetSyslogSocket(t *testing.T) { 123 | type args struct { 124 | socket string 125 | } 126 | tests := []struct { 127 | name string 128 | args args 129 | }{ 130 | {"empty test", args{""}}, 131 | } 132 | for _, tt := range tests { 133 | t.Run(tt.name, func(t *testing.T) { 134 | SetSyslogSocket(tt.args.socket) 135 | }) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /lib/net.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "time" 9 | ) 10 | 11 | // P2PMessageHeader is header used in cross-peer packets 12 | type P2PMessageHeader struct { 13 | Magic uint16 14 | Type uint16 15 | Length uint16 16 | SerializedLen uint16 17 | NetProto uint16 18 | } 19 | 20 | // P2PMessage is a cross-peer message packet 21 | type P2PMessage struct { 22 | Header *P2PMessageHeader 23 | Data []byte 24 | } 25 | 26 | // Serialize does a header serialization 27 | func (v *P2PMessageHeader) Serialize() []byte { 28 | resBuf := make([]byte, HeaderSize) 29 | binary.BigEndian.PutUint16(resBuf[0:2], v.Magic) 30 | binary.BigEndian.PutUint16(resBuf[2:4], v.Type) 31 | binary.BigEndian.PutUint16(resBuf[4:6], v.Length) 32 | binary.BigEndian.PutUint16(resBuf[6:8], v.NetProto) 33 | binary.BigEndian.PutUint16(resBuf[8:10], v.SerializedLen) 34 | return resBuf 35 | } 36 | 37 | // P2PMessageHeaderFromBytes extracts message header from received packet 38 | func P2PMessageHeaderFromBytes(bytes []byte) (*P2PMessageHeader, error) { 39 | if len(bytes) < HeaderSize { 40 | if len(bytes) == 2 { 41 | return nil, nil 42 | } 43 | return nil, errors.New("P2PMessageHeaderFromBytes_error : less then 14 bytes") 44 | } 45 | 46 | result := new(P2PMessageHeader) 47 | result.Magic = binary.BigEndian.Uint16(bytes[0:2]) 48 | result.Type = binary.BigEndian.Uint16(bytes[2:4]) 49 | result.Length = binary.BigEndian.Uint16(bytes[4:6]) 50 | result.NetProto = binary.BigEndian.Uint16(bytes[6:8]) 51 | result.SerializedLen = binary.BigEndian.Uint16(bytes[8:10]) 52 | return result, nil 53 | } 54 | 55 | // Serialize constructs a P2P message 56 | func (v *P2PMessage) Serialize() []byte { 57 | v.Header.SerializedLen = uint16(len(v.Data)) 58 | resBuf := v.Header.Serialize() 59 | resBuf = append(resBuf, v.Data...) 60 | return resBuf 61 | } 62 | 63 | // P2PMessageFromBytes extract a payload from received packet 64 | func P2PMessageFromBytes(bytes []byte) (*P2PMessage, error) { 65 | res := new(P2PMessage) 66 | var err error 67 | res.Header, err = P2PMessageHeaderFromBytes(bytes) 68 | if err != nil { 69 | return nil, err 70 | } 71 | if res.Header == nil { 72 | return nil, nil 73 | } 74 | if res.Header.Magic != MagicCookie { 75 | return nil, errors.New("magic cookie not presented") 76 | } 77 | res.Data = make([]byte, res.Header.SerializedLen) 78 | copy(res.Data[:], bytes[HeaderSize:]) 79 | return res, err 80 | } 81 | 82 | // CreateMessage create internal P2P Message 83 | func (p *PeerToPeer) CreateMessage(msgType MsgType, payload []byte, proto uint16, encrypt bool) (*P2PMessage, error) { 84 | msg := new(P2PMessage) 85 | msg.Header = new(P2PMessageHeader) 86 | msg.Header.Magic = MagicCookie 87 | msg.Header.Type = uint16(msgType) 88 | msg.Header.NetProto = proto 89 | msg.Header.Length = uint16(len(payload)) 90 | if p.Crypter.Active && encrypt { 91 | var err error 92 | msg.Data, err = p.Crypter.encrypt(p.Crypter.ActiveKey.Key, payload) 93 | if err != nil { 94 | return nil, err 95 | } 96 | } else { 97 | msg.Data = payload 98 | } 99 | return msg, nil 100 | } 101 | 102 | // CreateMessageStatic is a static method for a P2P Message 103 | func CreateMessageStatic(msgType MsgType, payload []byte) (*P2PMessage, error) { 104 | p := PeerToPeer{} 105 | return p.CreateMessage(msgType, payload, 0, false) 106 | } 107 | 108 | /////////////////////////////////////////////////////////////////////////////////////////// 109 | 110 | // Network is a network subsystem 111 | type Network struct { 112 | host string 113 | port int 114 | remotePort int 115 | addr *net.UDPAddr 116 | conn *net.UDPConn 117 | inBuffer [4096]byte 118 | disposed bool 119 | } 120 | 121 | // Close will terminate packet reader 122 | func (uc *Network) Close() error { 123 | uc.disposed = true 124 | if uc.conn != nil { 125 | err := uc.conn.Close() 126 | uc.conn = nil 127 | return err 128 | } 129 | return fmt.Errorf("Nil Connection") 130 | } 131 | 132 | // Disposed returns whether service is willing to stop or not 133 | func (uc *Network) Disposed() bool { 134 | return uc.disposed 135 | } 136 | 137 | // Addr returns assigned address 138 | func (uc *Network) Addr() *net.UDPAddr { 139 | if uc.addr != nil { 140 | return uc.addr 141 | } 142 | return nil 143 | } 144 | 145 | // Init creates a UDP connection 146 | func (uc *Network) Init(host string, port int) error { 147 | var err error 148 | uc.host = host 149 | uc.port = port 150 | uc.disposed = true 151 | 152 | //todo check if we need Host and Port 153 | uc.addr, err = net.ResolveUDPAddr("udp4", fmt.Sprintf(":%d", port)) 154 | if err != nil { 155 | return err 156 | } 157 | uc.conn, err = net.ListenUDP("udp4", uc.addr) 158 | if err != nil { 159 | return err 160 | } 161 | uc.disposed = false 162 | return nil 163 | } 164 | 165 | // KeepAlive will send keep alive packet periodically to keep 166 | // UDP port bind 167 | func (uc *Network) KeepAlive(target string) error { 168 | if uc.conn == nil { 169 | return fmt.Errorf("Nil Connection") 170 | } 171 | 172 | addresses, err := SrvLookup(target, "udp", "subutai.io") 173 | if err != nil { 174 | return fmt.Errorf("Failed to lookup address for keep alive session: %s", err.Error()) 175 | } 176 | if len(addresses) == 0 { 177 | return fmt.Errorf("No suitable address for keep alive") 178 | } 179 | 180 | firstAddr, e := addresses[0] 181 | if !e { 182 | return fmt.Errorf("Failed to retrieve keep alive address at index 0") 183 | } 184 | 185 | addr, err := net.ResolveUDPAddr("udp4", firstAddr) 186 | if err != nil { 187 | return fmt.Errorf("Failed to resolve UDP addr for keep alive session: %s", err.Error()) 188 | } 189 | 190 | data := []byte{0x0D, 0x0A} 191 | keepAlive := time.Now() 192 | Log(Debug, "Started keep alive session with %s", addr) 193 | i := 0 194 | for i < 20 { 195 | uc.SendRawBytes(data, addr) 196 | i++ 197 | time.Sleep(time.Millisecond * 500) 198 | } 199 | for !uc.disposed { 200 | if time.Duration(time.Second*3) < time.Since(keepAlive) { 201 | keepAlive = time.Now() 202 | uc.SendRawBytes(data, addr) 203 | } 204 | time.Sleep(100 * time.Millisecond) 205 | } 206 | return nil 207 | } 208 | 209 | // GetPort return a port assigned 210 | func (uc *Network) GetPort() int { 211 | if uc.conn == nil { 212 | return -1 213 | } 214 | addr, _ := net.ResolveUDPAddr("udp4", uc.conn.LocalAddr().String()) 215 | return addr.Port 216 | } 217 | 218 | // UDPReceivedCallback is executed when message is received 219 | type UDPReceivedCallback func(count int, src_addr *net.UDPAddr, err error, buff []byte) error 220 | 221 | // Listen is a main listener of a network traffic 222 | func (uc *Network) Listen(receivedCallback UDPReceivedCallback) error { 223 | Log(Info, "Started UDP listener") 224 | if uc.conn == nil { 225 | return fmt.Errorf("Nil connection") 226 | } 227 | for !uc.Disposed() { 228 | n, src, err := uc.conn.ReadFromUDP(uc.inBuffer[:]) 229 | receivedCallback(n, src, err, uc.inBuffer[:]) 230 | } 231 | Log(Info, "Stopping UDP Listener") 232 | return nil 233 | } 234 | 235 | // SendMessage sends message over network 236 | func (uc *Network) SendMessage(msg *P2PMessage, dstAddr *net.UDPAddr) (int, error) { 237 | if uc.conn == nil { 238 | return -1, fmt.Errorf("Nil connection") 239 | } 240 | if msg == nil { 241 | return 0, fmt.Errorf("Nil message") 242 | } 243 | n, err := uc.conn.WriteToUDP(msg.Serialize(), dstAddr) 244 | if err != nil { 245 | return 0, err 246 | } 247 | return n, nil 248 | } 249 | 250 | // SendRawBytes sends bytes over network 251 | func (uc *Network) SendRawBytes(bytes []byte, dstAddr *net.UDPAddr) (int, error) { 252 | if uc.conn == nil { 253 | return -1, fmt.Errorf("Nil connection") 254 | } 255 | n, err := uc.conn.WriteToUDP(bytes, dstAddr) 256 | if err != nil { 257 | return 0, err 258 | } 259 | return n, nil 260 | } 261 | -------------------------------------------------------------------------------- /lib/peer_stats.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import "time" 4 | 5 | // PeerStats represents different peer statistics 6 | // localNum, internetNum and proxyNum are the number of endpoints in local network, internet and over proxy 7 | // connectionsNum and reconnctsNum represents number of connection attempts made during the lifetime of the peer 8 | // PeerStats also keeps different timestamps related to connections 9 | type PeerStats struct { 10 | localNum int // Number of local network connections 11 | internetNum int // Number of internet connections 12 | proxyNum int // Number of proxy connections 13 | connectionsNum int // Number of connections attempts in a single connection cyclce (not reconnect after connection was established) 14 | reconnectsNum int // Number of reconnects 15 | startedAt time.Time // Time when peer was started 16 | connectedAt time.Time // Time when peer was connected for the first time 17 | connectionLostAt time.Time // Time when connection was lost and reconnection cycle was initialized 18 | reconnectedAt time.Time // Time when connection to the peer was reeestablished 19 | holePunchNum int // Number of hole punch attempts made during peer lifetime 20 | } 21 | 22 | // updateConnectionTime will update timestamp of the `connectedAt` 23 | // if this is the first time when connection was established or 24 | // `reconnectedAt` if connection was established after reconnect 25 | func (p *PeerStats) updateConnectionTime() { 26 | if p.reconnectsNum == 0 { 27 | p.connectedAt = time.Now() 28 | return 29 | } 30 | p.reconnectedAt = time.Now() 31 | } 32 | 33 | // reconnect must be called when peer join a new connection cycle 34 | // after connection was already established 35 | func (p *PeerStats) reconnect() { 36 | p.reconnectsNum++ 37 | p.connectionLostAt = time.Now() 38 | } 39 | 40 | // connectionAttempt must be called when peer initiates new connection attempt in the 41 | // connection cycle 42 | func (p *PeerStats) connectionAttempt() { 43 | p.connectionsNum++ 44 | } 45 | 46 | func (p *PeerStats) holePunchAttempt() { 47 | p.holePunchNum++ 48 | } 49 | 50 | // GetStartedAt returns the time when peer was started 51 | func (p *PeerStats) GetStartedAt() time.Time { 52 | return p.startedAt 53 | } 54 | 55 | // GetConnectedAt returns time when peer was connected for the first time 56 | func (p *PeerStats) GetConnectedAt() time.Time { 57 | return p.connectedAt 58 | } 59 | 60 | // GetConnectionTimeDelta returns difference between `connectedAt` and `startedAt` in seconds 61 | func (p *PeerStats) GetConnectionTimeDelta() int { 62 | return int(p.connectedAt.Sub(p.startedAt).Seconds()) % 60 63 | } 64 | 65 | // GetConnectionLostAt returns time when connection with the peer was lost for the first time 66 | func (p *PeerStats) GetConnectionLostAt() time.Time { 67 | return p.connectionLostAt 68 | } 69 | 70 | // GetReconnectedAt returns the time when connection with the peer was reestablished for the last time 71 | func (p *PeerStats) GetReconnectedAt() time.Time { 72 | return p.reconnectedAt 73 | } 74 | 75 | // GetReconnectionTimeDelta returns difference between `connectionsLostAt` and `reconnectedAt` in seconds 76 | func (p *PeerStats) GetReconnectionTimeDelta() int { 77 | return int(p.reconnectedAt.Sub(p.connectionLostAt).Seconds()) % 60 78 | } 79 | 80 | // GetHolePunchNum returns number of hole punch attempts during peer lifetime 81 | func (p *PeerStats) GetHolePunchNum() int { 82 | return p.holePunchNum 83 | } 84 | 85 | // GetConnectionsNum returns number of connection attempts during first connection cycle 86 | func (p *PeerStats) GetConnectionsNum() int { 87 | return p.connectionsNum 88 | } 89 | 90 | // GetReconnectsNum returns the number of reconnection cycles 91 | func (p *PeerStats) GetReconnectsNum() int { 92 | return p.reconnectsNum 93 | } 94 | -------------------------------------------------------------------------------- /lib/platform_posix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package ptp 4 | 5 | import ( 6 | "fmt" 7 | "log/syslog" 8 | "os" 9 | ) 10 | 11 | const ( 12 | MaximumInterfaceNameLength int = 12 13 | ) 14 | 15 | var syslogLevel = [...]syslog.Priority{syslog.LOG_DEBUG, syslog.LOG_DEBUG, syslog.LOG_INFO, syslog.LOG_WARNING, syslog.LOG_ERR} 16 | 17 | // InitPlatform does a platform specific preparation 18 | func InitPlatform() { 19 | 20 | } 21 | 22 | // CheckPermissions validates platform specific permissions to run TUNTAP utilities 23 | func HavePrivileges(level int) bool { 24 | if level != 0 { 25 | Log(Error, "P2P cannot run in daemon mode without root privileges") 26 | return false 27 | } 28 | return true 29 | } 30 | 31 | func GetPrivilegesLevel() int { 32 | return os.Getuid() 33 | } 34 | 35 | // Syslog provides additional logging to the syslog server 36 | func Syslog(level LogLevel, format string, v ...interface{}) { 37 | if l3, err := syslog.Dial("udp", syslogSocket, syslogLevel[level], "p2p"); err == nil { 38 | l3.Write([]byte(fmt.Sprintf(format, v...))) 39 | l3.Close() 40 | } 41 | } 42 | 43 | // SetupPlatform runs platform specific preparations during p2p daemon creation 44 | func SetupPlatform(remove bool) { 45 | // Not used on POSIX 46 | } 47 | -------------------------------------------------------------------------------- /lib/platform_posix_test.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package ptp 4 | 5 | import "testing" 6 | 7 | func TestInitPlatform(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | }{ 11 | {"empty test"}, 12 | } 13 | for _, tt := range tests { 14 | t.Run(tt.name, func(t *testing.T) { 15 | InitPlatform() 16 | }) 17 | } 18 | } 19 | 20 | func TestHavePrivileges(t *testing.T) { 21 | type args struct { 22 | level int 23 | } 24 | tests := []struct { 25 | name string 26 | args args 27 | want bool 28 | }{ 29 | {"have privileges", args{0}, true}, 30 | {"doesn't have privileges", args{1}, false}, 31 | } 32 | for _, tt := range tests { 33 | t.Run(tt.name, func(t *testing.T) { 34 | if got := HavePrivileges(tt.args.level); got != tt.want { 35 | t.Errorf("HavePrivileges() = %v, want %v", got, tt.want) 36 | } 37 | }) 38 | } 39 | } 40 | 41 | func TestGetPrivilegesLevel(t *testing.T) { 42 | tests := []struct { 43 | name string 44 | want int 45 | }{ 46 | {"simple test", 0}, 47 | } 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | // Converted from != to == 51 | if got := GetPrivilegesLevel(); got == tt.want { 52 | t.Errorf("GetPrivilegesLevel() = %v, want %v", got, tt.want) 53 | } 54 | }) 55 | } 56 | } 57 | 58 | func TestSyslog(t *testing.T) { 59 | type args struct { 60 | level LogLevel 61 | format string 62 | v []interface{} 63 | } 64 | tests := []struct { 65 | name string 66 | args args 67 | }{ 68 | {"empty test", args{}}, 69 | } 70 | for _, tt := range tests { 71 | t.Run(tt.name, func(t *testing.T) { 72 | Syslog(tt.args.level, tt.args.format, tt.args.v...) 73 | }) 74 | } 75 | } 76 | 77 | func TestSetupPlatform(t *testing.T) { 78 | type args struct { 79 | remove bool 80 | } 81 | tests := []struct { 82 | name string 83 | args args 84 | }{ 85 | {"empty test", args{}}, 86 | } 87 | for _, tt := range tests { 88 | t.Run(tt.name, func(t *testing.T) { 89 | SetupPlatform(tt.args.remove) 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/platform_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package ptp 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "strconv" 12 | "syscall" 13 | "time" 14 | 15 | "golang.org/x/sys/windows/svc" 16 | "golang.org/x/sys/windows/svc/eventlog" 17 | "golang.org/x/sys/windows/svc/mgr" 18 | ) 19 | 20 | // Windows Platform specific constants 21 | const ( 22 | TapTool = "C:\\Program Files\\TAP-Windows\\bin\\tapinstall.exe" 23 | DriverInf = "C:\\Program Files\\TAP-Windows\\driver\\OemVista.inf" 24 | TapSuffix = ".tap" 25 | TapID = "tap0901" 26 | MaximumInterfaceNameLength = 128 27 | ) 28 | 29 | var ( 30 | errorTAPIsNotInstalled = errors.New("TAP-Windows 9.2x is not installed") 31 | errorFailedToRemoveInterfaces = errors.New("Failed to remove TAP interfaces") 32 | errorFailedToCreateInterface = errors.New("Failed to create interface") 33 | errorObjectCreationFailed = errors.New("Failed to create TAP object") 34 | errorFailedToRetrieveNetworkKey = errors.New("Failed to retrieve network key from registry") 35 | errorFailedToQueryInterface = errors.New("Failed to query network interface") 36 | errorPreconfigurationFailed = errors.New("Interface pre-configuration failed") 37 | ) 38 | 39 | // InitPlatform initializes Windows platform-specific parameters 40 | func InitPlatform() error { 41 | Log(Info, "Initializing Windows Platform") 42 | if _, err := os.Stat(TapTool); os.IsNotExist(err) { 43 | Log(Error, "TAP-Windows 9.2x is not installed. Go to https://openvpn.net/index.php/open-source/downloads.html and download the latest version. Close P2P now as it will not run properly") 44 | return errorTAPIsNotInstalled 45 | } 46 | // Remove interfaces 47 | remove := exec.Command(TapTool, "remove", TapID) 48 | err := remove.Run() 49 | if err != nil { 50 | return errorFailedToRemoveInterfaces 51 | } 52 | for i := 0; i < 10; i++ { 53 | adddev := exec.Command(TapTool, "install", DriverInf, TapID) 54 | err := adddev.Run() 55 | if err != nil { 56 | Log(Error, "Failed to add TUN/TAP Device: %v", err) 57 | return errorFailedToCreateInterface 58 | } 59 | } 60 | 61 | tap, err := newTAP(GetConfigurationTool(), "127.0.0.1", "00:00:00:00:00:00", "255.255.255.0", DefaultMTU, UsePMTU) 62 | if err != nil { 63 | Log(Error, "Failed to create TAP object: %s", err) 64 | return errorObjectCreationFailed 65 | } 66 | 67 | for i := 0; i < 10; i++ { 68 | key, err := tap.queryNetworkKey() 69 | if err != nil { 70 | Log(Error, "Couldn't open Registry Key %s: %s", NetworkKey, err) 71 | continue 72 | //return errorFailedToRetrieveNetworkKey 73 | } 74 | err = tap.queryAdapters(key) 75 | if err != nil { 76 | Log(Error, "Failed to query adapters: %s", err) 77 | syscall.CloseHandle(tap.file) 78 | continue 79 | //return errorFailedToQueryInterface 80 | } 81 | // Dummy IP address for the interface 82 | ip := "172." + strconv.Itoa(i) + ".4.100" 83 | setip := exec.Command("netsh") 84 | setip.SysProcAttr = &syscall.SysProcAttr{} 85 | 86 | cmd := fmt.Sprintf(`netsh interface ip set address "%s" static %s %s`, tap.Interface, ip, "255.255.255.0") 87 | Log(Debug, "Executing: %s", cmd) 88 | 89 | setip.SysProcAttr.CmdLine = cmd 90 | err = setip.Run() 91 | err2 := syscall.CloseHandle(tap.file) 92 | if err != nil { 93 | continue 94 | //return errorPreconfigurationFailed 95 | } 96 | if err2 != nil { 97 | Log(Error, "Failed to close handle: %s", err) 98 | } 99 | } 100 | UsedInterfaces = UsedInterfaces[:0] 101 | return nil 102 | } 103 | 104 | // CheckPermissions return true if started as root/administrator 105 | func HavePrivileges(level int) bool { 106 | if level != 0 { 107 | Log(Error, "P2P cannot run in daemon mode without Administrator privileges") 108 | return false 109 | } 110 | return true 111 | } 112 | 113 | func GetPrivilegesLevel() int { 114 | _, err := os.Open("\\\\.\\PHYSICALDRIVE0") 115 | if err != nil { 116 | return 1 117 | } 118 | return 0 119 | } 120 | 121 | // Syslog provides additional logging to the syslog server 122 | func Syslog(level LogLevel, format string, v ...interface{}) { 123 | Log(Info, "Syslog is not supported on this platform. Please do not use syslog flag.") 124 | } 125 | 126 | // func closeInterface(file syscall.Handle) { 127 | // err := syscall.CloseHandle(file) 128 | // if err != nil { 129 | 130 | // } 131 | // } 132 | 133 | // SetupPlatform will install Windows Service and exit immediatelly 134 | func SetupPlatform(remove bool) { 135 | // Opening log 136 | // f, err := os.OpenFile("C:\\ProgramData\\subutai\\log\\service-setup.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 137 | // if err != nil { 138 | // Log(Error, "Failed to open log file") 139 | // f = nil 140 | // } 141 | // if f != nil { 142 | // defer f.Close() 143 | // log.SetOutput(f) 144 | // } 145 | 146 | name := "Subutai P2P" 147 | desc := "Subutai networking service" 148 | Log(Info, "Setting up Windows Service") 149 | 150 | p2pApp, err := exePath() 151 | if err != nil { 152 | Log(Error, "Failed to determine path to executable") 153 | p2pApp = os.Args[0] 154 | } 155 | Log(Info, "Application: %s", p2pApp) 156 | 157 | manager, err := mgr.Connect() 158 | if err != nil { 159 | Log(Error, "Failed to open service manager: %s", err) 160 | os.Exit(1) 161 | } 162 | defer manager.Disconnect() 163 | 164 | Log(Info, "Opening service manager") 165 | service, err := manager.OpenService("Subutai P2P") 166 | if err == nil { 167 | // Service exists 168 | if remove { 169 | restartWindowsService(service, name, true) 170 | removeWindowsService(service, name) 171 | } else { 172 | restartWindowsService(service, name, false) 173 | } 174 | } else { 175 | if !remove { 176 | installWindowsService(manager, name, p2pApp, desc) 177 | } 178 | } 179 | os.Exit(0) 180 | } 181 | 182 | func removeWindowsService(service *mgr.Service, name string) { 183 | Log(Info, "Removing service") 184 | err := service.Delete() 185 | if err != nil { 186 | Log(Error, "Failed to remove service: %s", err) 187 | service.Close() 188 | os.Exit(15) 189 | } 190 | err = eventlog.Remove(name) 191 | if err != nil { 192 | Log(Error, "Failed to unregister eventlog: %s", err) 193 | service.Close() 194 | os.Exit(16) 195 | } 196 | Log(Info, "Service removed") 197 | os.Exit(0) 198 | } 199 | 200 | func installWindowsService(manager *mgr.Mgr, name, app, desc string) { 201 | Log(Info, "Creating service") 202 | service, err := manager.CreateService(name, app, mgr.Config{DisplayName: name, Description: desc, StartType: mgr.StartAutomatic}, "service") 203 | if err != nil { 204 | Log(Error, "Failed to create P2P service: %s", err) 205 | os.Exit(6) 206 | } 207 | defer service.Close() 208 | Log(Info, "Installing service") 209 | err = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info) 210 | if err != nil { 211 | service.Delete() 212 | Log(Error, "SetupEventLogSource() failed: %s", err) 213 | os.Exit(7) 214 | } 215 | Log(Info, "Installation complete") 216 | err = service.Start("service") 217 | if err != nil { 218 | Log(Error, "Failed to start service: %s", err) 219 | return 220 | } 221 | Log(Info, "Service started") 222 | } 223 | 224 | func restartWindowsService(service *mgr.Service, name string, noStart bool) { 225 | Log(Info, "Service exists. Stopping") 226 | status, err := service.Control(svc.Stop) 227 | if err != nil { 228 | Log(Error, "Failed to get service status on stop: %s", err) 229 | 230 | } else { 231 | timeout := time.Now().Add(30 * time.Second) 232 | for status.State != svc.Stopped { 233 | if timeout.Before(time.Now()) { 234 | Log(Error, "Failed to stop p2p service after timeout") 235 | service.Close() 236 | os.Exit(3) 237 | } 238 | time.Sleep(time.Millisecond * 300) 239 | status, err = service.Query() 240 | if err != nil { 241 | Log(Error, "Couldn't retrieve service status: %s", err) 242 | service.Close() 243 | os.Exit(4) 244 | } 245 | } 246 | } 247 | if !noStart { 248 | Log(Info, "Starting service") 249 | // Service stopped. Now start it. 250 | err = service.Start("service") 251 | if err != nil { 252 | Log(Error, "Failed to start service on restart: %s", err) 253 | service.Close() 254 | // TODO Make this non-zero when fix problems with service start 255 | os.Exit(0) 256 | } 257 | service.Close() 258 | os.Exit(0) 259 | } 260 | } 261 | 262 | func exePath() (string, error) { 263 | prog := os.Args[0] 264 | p, err := filepath.Abs(prog) 265 | if err != nil { 266 | return "", err 267 | } 268 | fi, err := os.Stat(p) 269 | if err == nil { 270 | if !fi.Mode().IsDir() { 271 | return p, nil 272 | } 273 | err = fmt.Errorf("%s is directory", p) 274 | } 275 | if filepath.Ext(p) == "" { 276 | p += ".exe" 277 | fi, err := os.Stat(p) 278 | if err == nil { 279 | if !fi.Mode().IsDir() { 280 | return p, nil 281 | } 282 | err = fmt.Errorf("%s is directory", p) 283 | } 284 | } 285 | return "", err 286 | } 287 | -------------------------------------------------------------------------------- /lib/pmtu.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "hash/crc32" 7 | 8 | "github.com/mdlayher/ethernet" 9 | "golang.org/x/net/icmp" 10 | "golang.org/x/net/ipv4" 11 | ) 12 | 13 | func checksum(bytes []byte) uint16 { 14 | // Clear checksum bytes 15 | bytes[10] = 0 16 | bytes[11] = 0 17 | 18 | // Compute checksum 19 | var csum uint32 20 | for i := 0; i < len(bytes); i += 2 { 21 | csum += uint32(bytes[i]) << 8 22 | csum += uint32(bytes[i+1]) 23 | } 24 | for { 25 | // Break when sum is less or equals to 0xFFFF 26 | if csum <= 65535 { 27 | break 28 | } 29 | // Add carry to the sum 30 | csum = (csum >> 16) + uint32(uint16(csum)) 31 | } 32 | // Flip all the bits 33 | return ^uint16(csum) 34 | } 35 | 36 | func pmtu(data []byte, tap TAP) (bool, error) { 37 | protocol := int(binary.BigEndian.Uint16(data[12:14])) 38 | length := len(data) 39 | 40 | if protocol == int(PacketIPv4) && length > GlobalMTU-150 { 41 | header, err := ipv4.ParseHeader(data[14:]) 42 | if err != nil { 43 | Log(Error, "Failed to parse IPv4 packet: %s", err.Error()) 44 | return false, nil 45 | } 46 | 47 | // Don't fragment flag is set. We need to respond with ICMP Destination Unreachable 48 | if header.Flags == ipv4.DontFragment { 49 | // Extract packet contents as an ethernet frame for later re-use 50 | f := new(ethernet.Frame) 51 | if err := f.UnmarshalBinary(data); err != nil { 52 | Log(Error, "Failed to Unmarshal IPv4") 53 | return false, nil 54 | } 55 | 56 | // Build "Fragmentation needed" ICMP message 57 | packetICMP := &icmp.Message{ 58 | Type: ipv4.ICMPTypeDestinationUnreachable, 59 | Code: 4, 60 | Body: &icmp.PacketTooBig{ 61 | MTU: GlobalMTU - 200, // Next-hop MTU 62 | Data: data[14 : 14+20+8], // Original header and 64-bits of datagram 63 | }, 64 | } 65 | payloadICMP, err := packetICMP.Marshal(nil) 66 | if err != nil { 67 | Log(Error, "Failed to marshal ICMP: %s", err.Error()) 68 | return false, errICMPMarshalFailed 69 | } 70 | 71 | // Build IPv4 Header 72 | iph := &ipv4.Header{ 73 | Version: 4, 74 | Len: 20, // Precalculated header length 75 | TOS: 0, 76 | TotalLen: len(payloadICMP) + 20, 77 | ID: 25, 78 | TTL: 64, 79 | Protocol: 1, 80 | Dst: header.Src, 81 | Src: header.Dst, 82 | Checksum: 0, 83 | } 84 | ipHeader, err := iph.Marshal() 85 | if err != nil { 86 | Log(Error, "Failed to marshal header: %s", err.Error()) 87 | return false, nil 88 | } 89 | 90 | // Calculate IPv4 header checksum 91 | hcsum := checksum(ipHeader) 92 | binary.BigEndian.PutUint16(ipHeader[10:], hcsum) 93 | 94 | // Build new ethernet frame. Swap dst/src 95 | pl := append(ipHeader, payloadICMP...) 96 | nf := new(ethernet.Frame) 97 | nf.Destination = f.Source 98 | nf.Source = f.Destination 99 | nf.EtherType = ethernet.EtherTypeIPv4 100 | nf.Payload = pl 101 | rpacket, err := nf.MarshalBinary() 102 | if err != nil { 103 | Log(Error, "Failed to marshal ethernet") 104 | return false, nil 105 | } 106 | 107 | // Calculate CRC32 checksum for ethernet frame 108 | crc := make([]byte, 4) 109 | binary.LittleEndian.PutUint32(crc, crc32.ChecksumIEEE(rpacket)) 110 | rpacket = append(rpacket, crc...) 111 | 112 | // Send frame to the interface 113 | // P2P will drop packet afterwards 114 | tap.WritePacket(&Packet{int(PacketIPv4), rpacket}) 115 | return true, nil 116 | } 117 | } 118 | return false, fmt.Errorf("Unsupported protocol for PMTU") 119 | } 120 | -------------------------------------------------------------------------------- /lib/proxy_manager.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type proxyStatus uint8 11 | 12 | const ( 13 | proxyConnecting proxyStatus = 0 14 | proxyActive proxyStatus = 1 15 | proxyDisconnected proxyStatus = 2 16 | ) 17 | 18 | // ProxyManager manages TURN servers 19 | type ProxyManager struct { 20 | proxies map[string]*proxyServer 21 | lock sync.RWMutex 22 | hasChanges bool 23 | } 24 | 25 | func (p *ProxyManager) init() error { 26 | p.proxies = make(map[string]*proxyServer) 27 | return nil 28 | } 29 | 30 | func (p *ProxyManager) operate(operation ListOperation, addr string, proxy *proxyServer) { 31 | p.lock.Lock() 32 | defer p.lock.Unlock() 33 | if operation == OperateUpdate { 34 | p.proxies[addr] = proxy 35 | } else if operation == OperateDelete { 36 | _, exists := p.proxies[addr] 37 | if exists { 38 | delete(p.proxies, addr) 39 | } 40 | } 41 | } 42 | 43 | func (p *ProxyManager) get() map[string]*proxyServer { 44 | p.lock.RLock() 45 | result := make(map[string]*proxyServer) 46 | for i, v := range p.proxies { 47 | result[i] = v 48 | } 49 | p.lock.RUnlock() 50 | return result 51 | } 52 | 53 | // GetList will return a slice of proxyServers 54 | func (p *ProxyManager) GetList() []*proxyServer { 55 | list := []*proxyServer{} 56 | proxies := p.get() 57 | for _, v := range proxies { 58 | list = append(list, v) 59 | } 60 | return list 61 | } 62 | 63 | func (p *ProxyManager) new(endpoint *net.UDPAddr) error { 64 | proxies := p.get() 65 | _, exists := proxies[endpoint.String()] 66 | if exists { 67 | return fmt.Errorf("Proxy %s already exists", endpoint.String()) 68 | } 69 | proxy := new(proxyServer) 70 | proxy.Init(endpoint) 71 | p.operate(OperateUpdate, endpoint.String(), proxy) 72 | return nil 73 | } 74 | 75 | func (p *ProxyManager) check() { 76 | proxies := p.get() 77 | for id, proxy := range proxies { 78 | if proxy.Status == proxyConnecting && time.Since(proxy.Created) > time.Duration(10*time.Second) { 79 | err := proxy.Close() 80 | if err != nil { 81 | Log(Debug, "Failed to close proxy: %s", err) 82 | } 83 | Log(Debug, "Failed to connect to proxy %s", id) 84 | } 85 | if proxy.Status == proxyActive && time.Since(proxy.LastUpdate) > time.Duration(90*time.Second) { 86 | err := proxy.Close() 87 | if err != nil { 88 | Log(Debug, "Failed to close proxy: %s", err) 89 | } 90 | Log(Debug, "Proxy %s has been disconnected by timeout", id) 91 | } 92 | if proxy.Status == proxyDisconnected { 93 | Log(Debug, "Removing proxy %s", id) 94 | p.operate(OperateDelete, id, nil) 95 | p.hasChanges = true 96 | } 97 | } 98 | } 99 | 100 | func (p *ProxyManager) touch(id string) bool { 101 | proxies := p.get() 102 | for pid, proxy := range proxies { 103 | if pid == id { 104 | proxy.LastUpdate = time.Now() 105 | p.operate(OperateUpdate, id, proxy) 106 | return true 107 | } 108 | } 109 | return false 110 | } 111 | 112 | func (p *ProxyManager) activate(id string, endpoint *net.UDPAddr) bool { 113 | proxies := p.get() 114 | for pid, proxy := range proxies { 115 | if pid == id && proxy.Status == proxyConnecting { 116 | p.hasChanges = true 117 | proxy.Status = proxyActive 118 | proxy.LastUpdate = time.Now() 119 | proxy.Endpoint = endpoint 120 | p.operate(OperateUpdate, id, proxy) 121 | return true 122 | } 123 | } 124 | return false 125 | } 126 | 127 | func (p *ProxyManager) setLatency(l time.Duration, addr *net.UDPAddr) error { 128 | proxies := p.get() 129 | for id, proxy := range proxies { 130 | if proxy.Addr.String() == addr.String() { 131 | proxy.Latency = l 132 | proxy.LastLatencyQuery = time.Now() 133 | proxy.MeasureInProgress = false 134 | Log(Trace, "Proxy %s is now on latency %d", addr.String(), NanoToMilliseconds(l.Nanoseconds())) 135 | p.operate(OperateUpdate, id, proxy) 136 | return nil 137 | } 138 | } 139 | 140 | return fmt.Errorf("latency set failed: proxy not found: %s", addr.String()) 141 | } 142 | 143 | // getBestProxy will return best proxy server based on latency 144 | func (p *ProxyManager) getBestProxy() *proxyServer { 145 | var bp *proxyServer 146 | var min int64 = 0 147 | for _, proxy := range p.get() { 148 | if proxy.Status != proxyActive { 149 | continue 150 | } 151 | if min == 0 { 152 | min = proxy.Latency.Nanoseconds() 153 | bp = proxy 154 | continue 155 | } 156 | if min > proxy.Latency.Nanoseconds() { 157 | bp = proxy 158 | min = proxy.Latency.Nanoseconds() 159 | continue 160 | } 161 | } 162 | return bp 163 | } 164 | -------------------------------------------------------------------------------- /lib/proxy_server.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | type proxyServer struct { 9 | Addr *net.UDPAddr // Address of the proxy 10 | Endpoint *net.UDPAddr // Endpoint provided by proxy 11 | Status proxyStatus // Current proxy status 12 | LastUpdate time.Time // Last ping 13 | Created time.Time // Creation timestamp 14 | Latency time.Duration // Measured latency 15 | LastLatencyQuery time.Time // Last latency request 16 | MeasureInProgress bool // Whether or not this proxy is measuring latency currently 17 | } 18 | 19 | // Init will initialize Proxy Server 20 | func (p *proxyServer) Init(addr *net.UDPAddr) error { 21 | p.Addr = addr 22 | p.Endpoint = nil 23 | p.Status = proxyConnecting 24 | p.Created = time.Now() 25 | p.LastLatencyQuery = time.Unix(0, 0) 26 | return nil 27 | } 28 | 29 | // Close will stop proxy 30 | func (p *proxyServer) Close() error { 31 | Log(Info, "Stopping proxy %s, Endpoint: %s", p.Addr.String(), p.Endpoint.String()) 32 | p.Addr = nil 33 | p.Endpoint = nil 34 | p.Status = proxyDisconnected 35 | return nil 36 | } 37 | 38 | // Measure will send request to a proxy peer with timestamp in it and 39 | // proxy peer must response with the same message 40 | func (p *proxyServer) Measure(n *Network) { 41 | if p.Status != proxyActive { 42 | return 43 | } 44 | 45 | if time.Since(p.LastLatencyQuery) < ProxyLatencyRequestInterval { 46 | return 47 | } 48 | 49 | if p.MeasureInProgress { 50 | return 51 | } 52 | 53 | p.MeasureInProgress = true 54 | ts, _ := time.Now().MarshalBinary() 55 | msg, err := CreateMessageStatic(MsgTypeLatency, append(LatencyProxyHeader, ts...)) 56 | if err != nil { 57 | Log(Error, "Failed to create latency measurement packet for proxy: %s", err.Error()) 58 | p.LastLatencyQuery = time.Now() 59 | p.MeasureInProgress = false 60 | return 61 | } 62 | Log(Trace, "Measuring latency with proxy %s", p.Addr.String()) 63 | n.SendMessage(msg, p.Addr) 64 | } 65 | -------------------------------------------------------------------------------- /lib/proxy_server_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func Test_proxyServer_Measure(t *testing.T) { 10 | type fields struct { 11 | Addr *net.UDPAddr 12 | Endpoint *net.UDPAddr 13 | Status proxyStatus 14 | LastUpdate time.Time 15 | Created time.Time 16 | Latency time.Duration 17 | LastLatencyQuery time.Time 18 | MeasureInProgress bool 19 | } 20 | type args struct { 21 | n *Network 22 | } 23 | tests := []struct { 24 | name string 25 | fields fields 26 | args args 27 | }{ 28 | {"t1", fields{}, args{}}, 29 | } 30 | for _, tt := range tests { 31 | t.Run(tt.name, func(t *testing.T) { 32 | p := &proxyServer{ 33 | Addr: tt.fields.Addr, 34 | Endpoint: tt.fields.Endpoint, 35 | Status: tt.fields.Status, 36 | LastUpdate: tt.fields.LastUpdate, 37 | Created: tt.fields.Created, 38 | Latency: tt.fields.Latency, 39 | LastLatencyQuery: tt.fields.LastLatencyQuery, 40 | MeasureInProgress: tt.fields.MeasureInProgress, 41 | } 42 | p.Measure(tt.args.n) 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/swarm.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "sync" 7 | ) 8 | 9 | // ListOperation will specify which operation is performed on peer list 10 | type ListOperation int 11 | 12 | // List operations 13 | const ( 14 | OperateDelete ListOperation = 0 // Delete entry from map 15 | OperateUpdate ListOperation = 1 // Add/Update entry in map 16 | ) 17 | 18 | // Swarm is for handling list of peers with all mappings 19 | type Swarm struct { 20 | peers map[string]*NetworkPeer // Map of peers in this swarm 21 | tableIPID map[string]string // Mapping for IP->ID 22 | tableMacID map[string]string // Mapping for MAC->ID 23 | lock sync.RWMutex // Mutex for the tables 24 | } 25 | 26 | // Init will initialize Swarm's maps 27 | func (l *Swarm) Init() { 28 | l.peers = make(map[string]*NetworkPeer) 29 | l.tableIPID = make(map[string]string) 30 | l.tableMacID = make(map[string]string) 31 | } 32 | 33 | func (l *Swarm) operate(action ListOperation, id string, peer *NetworkPeer) error { 34 | if l.peers == nil { 35 | return fmt.Errorf("peers is nil - not initialized") 36 | } 37 | if l.tableIPID == nil { 38 | return fmt.Errorf("IP-ID table is nil - not initialized") 39 | } 40 | if l.tableMacID == nil { 41 | return fmt.Errorf("Mac-ID table is nil - not initialized") 42 | } 43 | l.lock.Lock() 44 | defer l.lock.Unlock() 45 | if action == OperateUpdate { 46 | l.peers[id] = peer 47 | ip := "" 48 | mac := "" 49 | if peer.PeerLocalIP != nil { 50 | ip = peer.PeerLocalIP.String() 51 | } 52 | if peer.PeerHW != nil { 53 | mac = peer.PeerHW.String() 54 | } 55 | l.updateTables(id, ip, mac) 56 | return nil 57 | } else if action == OperateDelete { 58 | peer, exists := l.peers[id] 59 | if !exists { 60 | return fmt.Errorf("can't delete peer: entry doesn't exists") 61 | } 62 | l.deleteTables(peer.PeerLocalIP.String(), peer.PeerHW.String()) 63 | delete(l.peers, id) 64 | return nil 65 | } 66 | return nil 67 | } 68 | 69 | func (l *Swarm) updateTables(id, ip, mac string) { 70 | if ip != "" { 71 | l.tableIPID[ip] = id 72 | } 73 | if mac != "" { 74 | l.tableMacID[mac] = id 75 | } 76 | } 77 | 78 | func (l *Swarm) deleteTables(ip, mac string) { 79 | if ip != "" { 80 | _, exists := l.tableIPID[ip] 81 | if exists { 82 | delete(l.tableIPID, ip) 83 | } 84 | } 85 | if mac != "" { 86 | _, exists := l.tableMacID[mac] 87 | if exists { 88 | delete(l.tableMacID, mac) 89 | } 90 | } 91 | } 92 | 93 | // Delete will remove entry with specified ID from peer list 94 | func (l *Swarm) Delete(id string) { 95 | l.operate(OperateDelete, id, nil) 96 | } 97 | 98 | // Update will append/edit peer in list 99 | func (l *Swarm) Update(id string, peer *NetworkPeer) { 100 | l.operate(OperateUpdate, id, peer) 101 | } 102 | 103 | // Get returns copy of map with all peers 104 | func (l *Swarm) Get() map[string]*NetworkPeer { 105 | result := make(map[string]*NetworkPeer) 106 | l.lock.RLock() 107 | for id, peer := range l.peers { 108 | result[id] = peer 109 | } 110 | l.lock.RUnlock() 111 | return result 112 | } 113 | 114 | // GetPeer returns single peer by id 115 | func (l *Swarm) GetPeer(id string) *NetworkPeer { 116 | l.lock.RLock() 117 | peer, exists := l.peers[id] 118 | l.lock.RUnlock() 119 | if exists { 120 | return peer 121 | } 122 | return nil 123 | } 124 | 125 | // GetEndpoint returns endpoint address and proxy id 126 | func (l *Swarm) GetEndpoint(mac string) (*net.UDPAddr, error) { 127 | l.lock.RLock() 128 | defer l.lock.RUnlock() 129 | id, exists := l.tableMacID[mac] 130 | if exists { 131 | peer, exists := l.peers[id] 132 | if exists && peer.Endpoint != nil { 133 | return peer.Endpoint, nil 134 | } 135 | } 136 | return nil, fmt.Errorf("Specified hardware address was not found in table") 137 | } 138 | 139 | // GetID returns ID by specified IP 140 | func (l *Swarm) GetID(ip string) (string, error) { 141 | l.lock.RLock() 142 | defer l.lock.RUnlock() 143 | id, exists := l.tableIPID[ip] 144 | if exists { 145 | return id, nil 146 | } 147 | return "", fmt.Errorf("Specified IP was not found in table") 148 | } 149 | 150 | // Length returns size of peer list map 151 | func (l *Swarm) Length() int { 152 | return len(l.peers) 153 | } 154 | 155 | // RunPeer should be called once on each peer when added 156 | // to list 157 | func (l *Swarm) RunPeer(id string, p *PeerToPeer) { 158 | Log(Info, "Running peer %s", id) 159 | l.lock.RLock() 160 | defer l.lock.RUnlock() 161 | if !l.peers[id].IsRunning() { 162 | go l.peers[id].Run(p) 163 | } else { 164 | Log(Info, "Peer %s is already running", id) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /lib/swarm_test.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "net" 5 | "reflect" 6 | "sync" 7 | "testing" 8 | ) 9 | 10 | func TestUpdateTables(t *testing.T) { 11 | l := new(Swarm) 12 | l.Init() 13 | l.updateTables("500", "192.168.1.1", "01:02:03:04:05:06") 14 | _, exists := l.tableIPID["500"] 15 | _, exist := l.tableMacID["01:02:03:04:05:06"] 16 | if !exists && !exist { 17 | t.Error("Error. Can't update peer") 18 | } 19 | } 20 | 21 | func TestDeleteTables(t *testing.T) { 22 | l := new(Swarm) 23 | l.Init() 24 | l.tableIPID["800"] = "192.168.8.8" 25 | l.tableMacID["800"] = "01:02:03:04:05:06" 26 | l.deleteTables("800", "800") 27 | _, exists := l.tableIPID["800"] 28 | _, exist := l.tableMacID["800"] 29 | if exist && exists { 30 | t.Error("Error") 31 | } 32 | } 33 | 34 | func TestGet(t *testing.T) { 35 | l := new(Swarm) 36 | np1 := new(NetworkPeer) 37 | np2 := new(NetworkPeer) 38 | l.Init() 39 | l.peers["444"] = np1 40 | l.peers["445"] = np2 41 | get := l.Get() 42 | var wait map[string]*NetworkPeer 43 | wait = make(map[string]*NetworkPeer) 44 | wait["444"] = np1 45 | wait["445"] = np2 46 | if !reflect.DeepEqual(get, wait) { 47 | t.Error("wait, get", wait, get) 48 | } 49 | } 50 | 51 | func TestLength(t *testing.T) { 52 | l := new(Swarm) 53 | l.Init() 54 | l.peers["77"] = new(NetworkPeer) 55 | l.peers["78"] = new(NetworkPeer) 56 | count := 0 57 | for i := 0; i < len(l.peers); i++ { 58 | count++ 59 | } 60 | get := l.Length() 61 | if get != count { 62 | t.Errorf("Error. Wait: %v, get: %v", count, get) 63 | } 64 | } 65 | 66 | func TestGetPeer(t *testing.T) { 67 | l := new(Swarm) 68 | l.Init() 69 | l.peers["9"] = new(NetworkPeer) 70 | l.peers["99"] = new(NetworkPeer) 71 | 72 | get1 := l.GetPeer("9") 73 | if get1 != l.peers["9"] { 74 | t.Error("Error") 75 | } 76 | get2 := l.GetPeer("-1") 77 | if get2 != nil { 78 | t.Error("Error") 79 | } 80 | } 81 | 82 | func TestSwarm_GetID(t *testing.T) { 83 | type fields struct { 84 | peers map[string]*NetworkPeer 85 | tableIPID map[string]string 86 | tableMacID map[string]string 87 | lock sync.RWMutex 88 | } 89 | type args struct { 90 | ip string 91 | } 92 | 93 | data := make(map[string]string) 94 | data["127.0.0.1"] = "test_id" 95 | 96 | tests := []struct { 97 | name string 98 | fields fields 99 | args args 100 | want string 101 | wantErr bool 102 | }{ 103 | {"t1", fields{tableIPID: data}, args{"127.0.0.1"}, "test_id", false}, 104 | {"t1", fields{tableIPID: data}, args{"127.0.0.2"}, "", true}, 105 | } 106 | for _, tt := range tests { 107 | t.Run(tt.name, func(t *testing.T) { 108 | l := &Swarm{ 109 | peers: tt.fields.peers, 110 | tableIPID: tt.fields.tableIPID, 111 | tableMacID: tt.fields.tableMacID, 112 | lock: tt.fields.lock, 113 | } 114 | got, err := l.GetID(tt.args.ip) 115 | if (err != nil) != tt.wantErr { 116 | t.Errorf("Swarm.GetID() error = %v, wantErr %v", err, tt.wantErr) 117 | return 118 | } 119 | if got != tt.want { 120 | t.Errorf("Swarm.GetID() = %v, want %v", got, tt.want) 121 | } 122 | }) 123 | } 124 | } 125 | 126 | func TestSwarm_GetEndpoint(t *testing.T) { 127 | type fields struct { 128 | peers map[string]*NetworkPeer 129 | tableIPID map[string]string 130 | tableMacID map[string]string 131 | lock sync.RWMutex 132 | } 133 | type args struct { 134 | mac string 135 | } 136 | 137 | pl := new(Swarm) 138 | pl.Init() 139 | data := make(map[string]string) 140 | peers := make(map[string]*NetworkPeer) 141 | data["00:01:02:03:04:05"] = "id0" 142 | data["01:01:02:03:04:05"] = "id1" 143 | data["02:01:02:03:04:05"] = "id2" 144 | data["03:01:02:03:04:05"] = "id3" 145 | p1 := new(NetworkPeer) 146 | p1.Endpoint, _ = net.ResolveUDPAddr("udp4", "127.0.0.1:2000") 147 | p2 := new(NetworkPeer) 148 | p2.Endpoint, _ = net.ResolveUDPAddr("udp4", "127.0.0.1:2001") 149 | p3 := new(NetworkPeer) 150 | p3.Endpoint, _ = net.ResolveUDPAddr("udp4", "127.0.0.1:2002") 151 | p4 := new(NetworkPeer) 152 | p4.Endpoint, _ = net.ResolveUDPAddr("udp4", "127.0.0.1:2003") 153 | peers["id0"] = p1 154 | peers["id1"] = p2 155 | peers["id2"] = p3 156 | peers["id3"] = p4 157 | 158 | tests := []struct { 159 | name string 160 | fields fields 161 | args args 162 | want *net.UDPAddr 163 | wantErr bool 164 | }{ 165 | {"GetEndpoint", fields{peers: peers, tableMacID: data}, args{"00:01:02:03:04:05"}, p1.Endpoint, false}, 166 | {"GetEndpoint", fields{peers: peers, tableMacID: data}, args{"01:01:02:03:04:05"}, p2.Endpoint, false}, 167 | {"GetEndpoint", fields{peers: peers, tableMacID: data}, args{"02:01:02:03:04:05"}, p3.Endpoint, false}, 168 | {"GetEndpoint", fields{peers: peers, tableMacID: data}, args{"03:01:02:03:04:05"}, p4.Endpoint, false}, 169 | {"GetEndpoint/Failing", fields{peers: peers, tableMacID: data}, args{"04:01:02:03:04:05"}, nil, true}, 170 | } 171 | for _, tt := range tests { 172 | t.Run(tt.name, func(t *testing.T) { 173 | l := &Swarm{ 174 | peers: tt.fields.peers, 175 | tableIPID: tt.fields.tableIPID, 176 | tableMacID: tt.fields.tableMacID, 177 | lock: tt.fields.lock, 178 | } 179 | got, err := l.GetEndpoint(tt.args.mac) 180 | if (err != nil) != tt.wantErr { 181 | t.Errorf("Swarm.GetEndpoint() error = %v, wantErr %v", err, tt.wantErr) 182 | return 183 | } 184 | if !reflect.DeepEqual(got, tt.want) { 185 | t.Errorf("Swarm.GetEndpoint() = %v, want %v", got, tt.want) 186 | } 187 | }) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /lib/tuntap.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | ) 7 | 8 | const ( 9 | flagMF = 0x10 10 | flagDF = 0x1 11 | iffTun = 0x1 12 | iffTap = 0x2 13 | iffOneQueue = 0x2000 14 | iffnopi = 0x1000 15 | ) 16 | 17 | var ( 18 | errPacketTooBig = errors.New("Packet exceeds MTU") 19 | errICMPMarshalFailed = errors.New("Failed to marshal ICMP") 20 | errPacketTooSmall = errors.New("Packet is too small") 21 | ) 22 | 23 | type ifReq struct { 24 | Name [0x10]byte 25 | Flags uint16 26 | pad [0x28 - 0x10 - 2]byte 27 | } 28 | 29 | // Packet represents a packet received on TUN/TAP interface 30 | type Packet struct { 31 | Protocol int 32 | Packet []byte 33 | } 34 | 35 | // InterfaceStatus holds Status of the network Interface 36 | type InterfaceStatus uint8 37 | 38 | // Interface Statuses 39 | const ( 40 | InterfaceWaiting InterfaceStatus = 0 41 | InterfaceConfiguring InterfaceStatus = 1 42 | InterfaceConfigured InterfaceStatus = 2 43 | InterfaceDeconfigured InterfaceStatus = 3 44 | InterfaceRunning InterfaceStatus = 4 45 | InterfaceBroken InterfaceStatus = 5 46 | InterfaceShutdown InterfaceStatus = 6 47 | ) 48 | 49 | // TAP interface 50 | type TAP interface { 51 | GetName() string 52 | GetHardwareAddress() net.HardwareAddr 53 | GetIP() net.IP 54 | GetSubnet() net.IP 55 | GetMask() net.IPMask 56 | GetBasename() string 57 | SetName(string) 58 | SetHardwareAddress(net.HardwareAddr) 59 | SetIP(net.IP) 60 | SetSubnet(net.IP) 61 | SetMask(net.IPMask) 62 | Init(string) error 63 | Open() error 64 | Close() error 65 | Configure(bool) error 66 | Deconfigure() error 67 | ReadPacket() (*Packet, error) 68 | WritePacket(*Packet) error 69 | Run() 70 | IsConfigured() bool 71 | MarkConfigured() 72 | EnablePMTU() 73 | DisablePMTU() 74 | IsPMTUEnabled() bool 75 | IsBroken() bool 76 | SetAuto(bool) 77 | IsAuto() bool 78 | GetStatus() InterfaceStatus 79 | } 80 | -------------------------------------------------------------------------------- /lib/tuntap_darwin.go: -------------------------------------------------------------------------------- 1 | // +build darwin 2 | 3 | package ptp 4 | 5 | import ( 6 | "encoding/binary" 7 | "fmt" 8 | "io" 9 | "net" 10 | "os" 11 | "os/exec" 12 | ) 13 | 14 | func GetDeviceBase() string { 15 | return "tun" 16 | } 17 | 18 | // GetConfigurationTool function will return path to configuration tool on specific platform 19 | func GetConfigurationTool() string { 20 | path, err := exec.LookPath("ifconfig") 21 | if err != nil { 22 | Log(Error, "Failed to find `ifconfig` in path. Returning default /sbin/ifconfig") 23 | return "/sbin/ifconfig" 24 | } 25 | Log(Info, "Network configuration tool found: %s", path) 26 | return path 27 | } 28 | 29 | func newTAP(tool, ip, mac, mask string, mtu int, pmtu bool) (*TAPDarwin, error) { 30 | Log(Info, "Acquiring TAP interface [Darwin]") 31 | nip := net.ParseIP(ip) 32 | if nip == nil { 33 | return nil, fmt.Errorf("Failed to parse IP during TAP creation") 34 | } 35 | nmac, err := net.ParseMAC(mac) 36 | if err != nil { 37 | return nil, fmt.Errorf("Failed to parse MAC during TAP creation: %s", err) 38 | } 39 | return &TAPDarwin{ 40 | Tool: tool, 41 | IP: nip, 42 | Mac: nmac, 43 | Mask: net.IPv4Mask(255, 255, 255, 0), // Unused yet 44 | MTU: DefaultMTU, 45 | PMTU: pmtu, 46 | }, nil 47 | } 48 | 49 | func newEmptyTAP() *TAPDarwin { 50 | return &TAPDarwin{} 51 | } 52 | 53 | // TAPDarwin is an interface for TAP device on Linux platform 54 | type TAPDarwin struct { 55 | IP net.IP // IP 56 | Subnet net.IP // Subnet 57 | Mask net.IPMask // Mask 58 | Mac net.HardwareAddr // Hardware Address 59 | Name string // Network interface name 60 | Tool string // Path to `ip` 61 | MTU int // MTU value 62 | file *os.File // Interface descriptor 63 | Configured bool 64 | PMTU bool 65 | Auto bool 66 | Status InterfaceStatus 67 | } 68 | 69 | // GetName returns a name of interface 70 | func (t *TAPDarwin) GetName() string { 71 | return t.Name 72 | } 73 | 74 | // GetHardwareAddress returns a MAC address of the interface 75 | func (t *TAPDarwin) GetHardwareAddress() net.HardwareAddr { 76 | return t.Mac 77 | } 78 | 79 | // GetIP returns IP addres of the interface 80 | func (t *TAPDarwin) GetIP() net.IP { 81 | return t.IP 82 | } 83 | 84 | func (t *TAPDarwin) GetSubnet() net.IP { 85 | return t.Subnet 86 | } 87 | 88 | // GetMask returns an IP mask of the interface 89 | func (t *TAPDarwin) GetMask() net.IPMask { 90 | return t.Mask 91 | } 92 | 93 | // GetBasename returns a prefix for automatically generated interface names 94 | func (t *TAPDarwin) GetBasename() string { 95 | return "tap" 96 | } 97 | 98 | // SetName will set interface name 99 | func (t *TAPDarwin) SetName(name string) { 100 | t.Name = name 101 | } 102 | 103 | // SetHardwareAddress will set MAC 104 | func (t *TAPDarwin) SetHardwareAddress(mac net.HardwareAddr) { 105 | t.Mac = mac 106 | } 107 | 108 | // SetIP will set IP 109 | func (t *TAPDarwin) SetIP(ip net.IP) { 110 | t.IP = ip 111 | } 112 | 113 | func (t *TAPDarwin) SetSubnet(subnet net.IP) { 114 | t.Subnet = subnet 115 | } 116 | 117 | // SetMask will set mask 118 | func (t *TAPDarwin) SetMask(mask net.IPMask) { 119 | t.Mask = mask 120 | } 121 | 122 | // Init will initialize TAP interface creation process 123 | func (t *TAPDarwin) Init(name string) error { 124 | if name == "" { 125 | return fmt.Errorf("Failed to configure interface: empty name") 126 | } 127 | t.Name = name 128 | return nil 129 | } 130 | 131 | // Open will open a file descriptor for a new interface 132 | func (t *TAPDarwin) Open() error { 133 | var err error 134 | t.file, err = os.OpenFile("/dev/"+t.Name, os.O_RDWR, 0) 135 | if err != nil { 136 | return err 137 | } 138 | return nil 139 | } 140 | func (t *TAPDarwin) Close() error { 141 | if t.file == nil { 142 | return fmt.Errorf("nil interface file descriptor") 143 | } 144 | Log(Info, "Closing network interface %s", t.GetName()) 145 | err := t.file.Close() 146 | if err != nil { 147 | return fmt.Errorf("Failed to close network interface: %s", err) 148 | } 149 | Log(Info, "Interface closed") 150 | return nil 151 | } 152 | 153 | func (t *TAPDarwin) Configure(lazy bool) error { 154 | if t.Tool == "" { 155 | return fmt.Errorf("TAP.Configure: Configuration tool wasn't specified") 156 | } 157 | t.Status = InterfaceConfiguring 158 | // if lazy { 159 | // return nil 160 | // } 161 | Log(Info, "Setting hardware address to %s", t.Mac.String()) 162 | setmac := exec.Command(t.Tool, t.Name, "ether", t.Mac.String()) 163 | err := setmac.Run() 164 | if err != nil { 165 | Log(Error, "Failed to set MAC: %v", err) 166 | } 167 | 168 | if t.IP == nil { 169 | return nil 170 | } 171 | 172 | // TODO: remove hardcoded mask 173 | linkup := exec.Command(t.Tool, t.Name, t.IP.String(), "netmask", "255.255.255.0", "up") 174 | err = linkup.Run() 175 | if err != nil { 176 | t.Status = InterfaceBroken 177 | Log(Error, "Failed to up link: %v", err) 178 | return err 179 | } 180 | t.Status = InterfaceConfigured 181 | return nil 182 | } 183 | 184 | func (t *TAPDarwin) Deconfigure() error { 185 | t.Status = InterfaceDeconfigured 186 | t.Configured = false 187 | return nil 188 | } 189 | 190 | // ReadPacket will read single packet from network interface 191 | func (t *TAPDarwin) ReadPacket() (*Packet, error) { 192 | buf := make([]byte, 4096) 193 | 194 | n, err := t.file.Read(buf) 195 | if err != nil { 196 | return nil, err 197 | } 198 | 199 | var pkt *Packet 200 | pkt = &Packet{Packet: buf[0:n]} 201 | pkt.Protocol = int(binary.BigEndian.Uint16(buf[12:14])) 202 | 203 | if !t.IsPMTUEnabled() { 204 | return pkt, nil 205 | } 206 | 207 | if pkt.Protocol == int(PacketIPv4) { 208 | // Return packet 209 | skip, err := pmtu(buf, t) 210 | if skip { 211 | return nil, err 212 | } 213 | } 214 | return pkt, nil 215 | } 216 | 217 | // WritePacket will write a single packet to interface 218 | func (t *TAPDarwin) WritePacket(packet *Packet) error { 219 | n, err := t.file.Write(packet.Packet) 220 | if err != nil { 221 | return err 222 | } 223 | if n != len(packet.Packet) { 224 | return io.ErrShortWrite 225 | } 226 | return nil 227 | } 228 | 229 | // Run will start TAP processes 230 | func (t *TAPDarwin) Run() { 231 | t.Status = InterfaceRunning 232 | } 233 | 234 | func (t *TAPDarwin) IsConfigured() bool { 235 | return t.Configured 236 | } 237 | 238 | func (t *TAPDarwin) MarkConfigured() { 239 | t.Configured = true 240 | } 241 | 242 | func (t *TAPDarwin) EnablePMTU() { 243 | t.PMTU = true 244 | } 245 | 246 | func (t *TAPDarwin) DisablePMTU() { 247 | t.PMTU = false 248 | } 249 | 250 | func (t *TAPDarwin) IsPMTUEnabled() bool { 251 | return t.PMTU 252 | } 253 | 254 | func (t *TAPDarwin) IsBroken() bool { 255 | return false 256 | } 257 | 258 | func (t *TAPDarwin) SetAuto(auto bool) { 259 | t.Auto = auto 260 | } 261 | 262 | func (t *TAPDarwin) IsAuto() bool { 263 | return t.Auto 264 | } 265 | 266 | func (t *TAPDarwin) GetStatus() InterfaceStatus { 267 | return t.Status 268 | } 269 | 270 | // FilterInterface will return true if this interface needs to be filtered out 271 | func FilterInterface(infName, infIP string) bool { 272 | if len(infIP) > 4 && infIP[0:3] == "172" { 273 | return true 274 | } 275 | for _, ip := range ActiveInterfaces { 276 | if ip.String() == infIP { 277 | return true 278 | } 279 | } 280 | Log(Trace, "ping -t 1 -c 1 -S %s ptest.subutai.io", infIP) 281 | ping := exec.Command("ping", "-t", "1", "-c", "1", "-S", infIP, "ptest.subutai.io") 282 | if ping.Run() != nil { 283 | Log(Debug, "Filtered %s %s", infName, infIP) 284 | return true 285 | } 286 | return false 287 | } 288 | -------------------------------------------------------------------------------- /lib/utils.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | "net" 7 | "strings" 8 | "time" 9 | 10 | "github.com/google/uuid" 11 | ) 12 | 13 | // Different utility functions 14 | 15 | // GenerateMAC will generate a new MAC address 16 | // Function returns both string representation of MAC and it golang equalient. 17 | // First octet is always 06 18 | func GenerateMAC() (string, net.HardwareAddr) { 19 | buf := make([]byte, 6) 20 | _, err := rand.Read(buf) 21 | if err != nil { 22 | Log(Error, "Failed to generate MAC: %v", err) 23 | return "", nil 24 | } 25 | buf[0] |= 2 26 | mac := fmt.Sprintf("06:%02x:%02x:%02x:%02x:%02x", buf[1], buf[2], buf[3], buf[4], buf[5]) 27 | hw, err := net.ParseMAC(mac) 28 | if err != nil { 29 | Log(Error, "Corrupted MAC address generated: %v", err) 30 | return "", nil 31 | } 32 | return mac, hw 33 | } 34 | 35 | // GenerateToken produces UUID string that will be used during handshake 36 | // with DHT server. Since we don't have an ID on start - we will use token 37 | // and wait from DHT server to respond with ID and our Token, so later 38 | // we will replace Token with received ID 39 | func GenerateToken() string { 40 | result := "" 41 | id, err := uuid.NewUUID() 42 | if err != nil { 43 | Log(Error, "Failed to generate token for peer") 44 | return result 45 | } 46 | result = id.String() 47 | Log(Debug, "Token generated: %s", result) 48 | return result 49 | } 50 | 51 | // This method compares given IP to known private IP address spaces 52 | // and return true if IP is private, false otherwise 53 | func isPrivateIP(ip net.IP) (bool, error) { 54 | if ip == nil { 55 | return false, fmt.Errorf("Missing IP") 56 | } 57 | _, private24, _ := net.ParseCIDR("10.0.0.0/8") 58 | _, private20, _ := net.ParseCIDR("172.16.0.0/12") 59 | _, private16, _ := net.ParseCIDR("192.168.0.0/16") 60 | isPrivate := private24.Contains(ip) || private20.Contains(ip) || private16.Contains(ip) 61 | return isPrivate, nil 62 | } 63 | 64 | // StringifyState extracts human-readable word that represents a peer status 65 | func StringifyState(state PeerState) string { 66 | switch state { 67 | case PeerStateInit: 68 | return "INITIALIZING" 69 | case PeerStateRequestedIP: 70 | return "WAITING_IP" 71 | case PeerStateRequestingProxy: 72 | return "REQUESTING_PROXIES" 73 | case PeerStateWaitingForProxy: 74 | return "WAITING_PROXIES" 75 | case PeerStateWaitingToConnect: 76 | return "WAITING_CONNECTION" 77 | case PeerStateConnecting: 78 | return "INITIALIZING_CONNECTION" 79 | case PeerStateConnected: 80 | return "CONNECTED" 81 | case PeerStateDisconnect: 82 | return "DISCONNECTED" 83 | case PeerStateStop: 84 | return "STOPPED" 85 | case PeerStateCooldown: 86 | return "COOLDOWN" 87 | } 88 | return "UNKNOWN" 89 | } 90 | 91 | // IsInterfaceLocal will return true if specified IP is in list of 92 | // local network interfaces 93 | func IsInterfaceLocal(ip net.IP) bool { 94 | for _, localIP := range ActiveInterfaces { 95 | if localIP.Equal(ip) { 96 | return true 97 | } 98 | } 99 | return false 100 | } 101 | 102 | // FindNetworkAddresses method lists interfaces available in the system and retrieves their 103 | // IP addresses 104 | func (p *PeerToPeer) FindNetworkAddresses() error { 105 | Log(Debug, "Looking for available network interfaces") 106 | interfaces, err := net.Interfaces() 107 | if err != nil { 108 | Log(Error, "Failed to retrieve list of network interfaces: %s", err.Error()) 109 | return fmt.Errorf("Failed to retrieve list of network interfaces: %s", err.Error()) 110 | } 111 | p.LocalIPs = p.LocalIPs[:0] 112 | p.LocalIPs = p.ParseInterfaces(interfaces) 113 | Log(Trace, "%d interfaces were saved", len(p.LocalIPs)) 114 | return nil 115 | } 116 | 117 | // ParseInterfaces accepts list of network interfaces (net.Interface), 118 | // parse their addresses, check they CIDRs and cast type. 119 | // Returns list of IPs 120 | func (p *PeerToPeer) ParseInterfaces(interfaces []net.Interface) []net.IP { 121 | ips := []net.IP{} 122 | // We use reserve to collect all multicast interfaces and use them as a fallback 123 | // in a case when we don't find any interfaces with outbound traffic enabled 124 | reserve := []net.IP{} 125 | for _, i := range interfaces { 126 | addresses, err := i.Addrs() 127 | if err != nil { 128 | Log(Error, "Failed to retrieve address for interface: %s", err.Error()) 129 | continue 130 | } 131 | if len(addresses) == 0 { 132 | Log(Warning, "No IPs assigned to interface %s", i.Name) 133 | continue 134 | } 135 | for _, addr := range addresses { 136 | ip, _, err := net.ParseCIDR(addr.String()) 137 | if err != nil { 138 | Log(Error, "Failed to parse CIDR notation: %v", err) 139 | continue 140 | } 141 | 142 | if ip.IsGlobalUnicast() && p.IsIPv4(ip.String()) { 143 | if !FilterInterface(i.Name, ip.String()) { 144 | ips = append(ips, ip) 145 | } else { 146 | reserve = append(reserve, ip) 147 | } 148 | } 149 | } 150 | } 151 | if len(ips) == 0 { 152 | return reserve 153 | } 154 | return ips 155 | } 156 | 157 | func min(a, b int) int { 158 | if a <= b { 159 | return a 160 | } 161 | return b 162 | } 163 | 164 | // SrvLookup will search for specified service under provided domain 165 | // and return a map of net.Addr sorted by priority 166 | func SrvLookup(name, proto, domain string) (map[int]string, error) { 167 | cname, addrs, err := net.LookupSRV(name, proto, domain) 168 | if err != nil { 169 | return nil, err 170 | } 171 | Log(Debug, "SRV lookup for name cname: %s addrs: %+v", cname, addrs) 172 | result := make(map[int]string) 173 | i := 0 174 | for _, addr := range addrs { 175 | Log(Trace, "Lookup result: %s:%d", addr.Target, addr.Port) 176 | result[i] = fmt.Sprintf("%s:%d", addr.Target, addr.Port) 177 | i++ 178 | } 179 | 180 | return result, nil 181 | } 182 | 183 | // NanoToMilliseconds will convert nanoseconds to milliseconds 184 | func NanoToMilliseconds(nano int64) int64 { 185 | return nano / int64(time.Millisecond) 186 | } 187 | 188 | // isDeviceExists - checks whether interface with the given name exists in the system or not 189 | func isDeviceExists(name string) bool { 190 | inf, err := net.Interfaces() 191 | if err != nil { 192 | Log(Error, "Failed to retrieve list of network interfaces") 193 | return true 194 | } 195 | for _, i := range inf { 196 | if i.Name == name { 197 | return true 198 | } 199 | } 200 | return false 201 | } 202 | 203 | // ParseIntroString receives a comma-separated string with ID, MAC and IP of a peer 204 | // and returns this data 205 | func ParseIntroString(intro string) (*PeerHandshake, error) { 206 | hs := &PeerHandshake{} 207 | parts := strings.Split(intro, ",") 208 | if len(parts) != 4 { 209 | return nil, fmt.Errorf("Failed to parse introduction string: %s", intro) 210 | } 211 | hs.ID = parts[0] 212 | // Extract MAC 213 | var err error 214 | hs.HardwareAddr, err = net.ParseMAC(parts[1]) 215 | if err != nil { 216 | return nil, fmt.Errorf("Failed to parse MAC address from introduction packet: %v", err) 217 | } 218 | // Extract IP 219 | if parts[2] == "auto" { 220 | hs.AutoIP = true 221 | } else { 222 | hs.AutoIP = false 223 | hs.IP = net.ParseIP(parts[2]) 224 | if hs.IP == nil { 225 | return nil, fmt.Errorf("Failed to parse IP address from introduction packet") 226 | } 227 | } 228 | hs.Endpoint, err = net.ResolveUDPAddr("udp4", parts[3]) 229 | if err != nil { 230 | return nil, fmt.Errorf("Failed to parse handshake endpoint: %s", parts[3]) 231 | } 232 | 233 | return hs, nil 234 | } 235 | -------------------------------------------------------------------------------- /lib/vars.go: -------------------------------------------------------------------------------- 1 | package ptp 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // PacketVersion is a version of packet used in DHT communication 8 | const PacketVersion int32 = 20005 9 | 10 | // SupportedVersion is a list of versions supported by DHT server 11 | var SupportedVersion = [...]int32{20005, 200006} 12 | 13 | // DHTBufferSize is a size of DHT buffer 14 | const DHTBufferSize = 4096 15 | 16 | // MsgType is a type of the message 17 | type MsgType uint16 18 | 19 | // Internal network packet type 20 | const ( 21 | MsgTypeString MsgType = 0 // String 22 | MsgTypeIntro = 1 // Introduction packet 23 | MsgTypeIntroReq = 2 // Request for introduction packet 24 | MsgTypeNenc = 3 // Not encrypted message 25 | MsgTypeEnc = 4 // Encrypted message 26 | MsgTypePing = 5 // Internal ping message for Proxies 27 | MsgTypeXpeerPing = 6 // Crosspeer ping message 28 | MsgTypeTest = 7 // Packet tests established connection 29 | MsgTypeProxy = 8 // Information about proxy (forwarder) 30 | MsgTypeBadTun = 9 // Notifies about dead tunnel 31 | MsgTypeConf = 10 // Confirmation 32 | MsgTypeLatency = 11 // Latency measurement 33 | MsgTypeComm = 12 // Internal cross peer communication 34 | ) 35 | 36 | // Common communication packet types 37 | const ( 38 | CommStatusReport uint16 = 0 // Status report between peers 39 | CommPing = 1 // Ping packet 40 | CommLatency = 2 // Latency packet 41 | ) 42 | 43 | // IP communication packets 44 | const ( 45 | CommIPSubnet uint16 = 10 // Request subnet information from peer 46 | CommIPInfo = 11 // Ask peer if it knows specified IP 47 | CommIPSet = 12 // Notify peer that this peer is now available over specified IP 48 | CommIPConflict = 13 // Notify peer that his IP is in conflict 49 | ) 50 | 51 | // Discovery communication packets 52 | const ( 53 | CommDiscoveryInit uint16 = 20 // Initiate connection with discovery service 54 | CommDiscoveryFind = 21 // Find request 55 | CommDiscoveryNode = 22 // Node request 56 | CommDiscoveryPing = 23 // Ping request/response 57 | CommDiscoveryProxy = 24 // Proxy request/response/registration 58 | CommDiscoveryStatus = 25 // Status notification 59 | CommDiscoveryIP = 26 // IP request/response 60 | CommDiscoveryUnsupported = 27 // Unsupported packet version 61 | ) 62 | 63 | // Network Constants 64 | const ( 65 | MagicCookie uint16 = 0xabcd 66 | HeaderSize int = 10 67 | ) 68 | 69 | // Network Variables 70 | 71 | // LatencyProxyHeader used as a header of proxy request 72 | var LatencyProxyHeader = []byte{0xfa, 0xca, 0x13, 0x15} 73 | 74 | // LatencyRequestHeader used as a header when sending latency request 75 | var LatencyRequestHeader = []byte{0xde, 0xad, 0xde, 0xda} 76 | 77 | // LatencyResponseHeader used as a header when sending latency response 78 | var LatencyResponseHeader = []byte{0xad, 0xde, 0xad, 0xde} 79 | 80 | // List of commands used in DHT 81 | const ( 82 | DhtCmdConn string = "conn" 83 | DhtCmdFrwd string = "frwd" 84 | DhtCmdFind string = "find" 85 | DhtCmdNode string = "node" 86 | DhtCmdPing string = "ping" 87 | DhtCmdRegProxy string = "regcp" 88 | DhtCmdBadProxy string = "badcp" 89 | DhtCmdProxy string = "cp" 90 | DhtCmdNotify string = "notify" 91 | DhtCmdLoad string = "load" 92 | DhtCmdStop string = "stop" 93 | DhtCmdUnknown string = "unk" 94 | DhtCmdDhcp string = "dhcp" 95 | DhtCmdError string = "error" 96 | DhtCmdUnsupported string = "unsupported" 97 | DhtCmdState string = "state" 98 | ) 99 | 100 | const ( 101 | // DhtErrorUnsupported - Unsupported version 102 | DhtErrorUnsupported string = "unsupported" 103 | ) 104 | 105 | // PeerState - current state of the peer 106 | type PeerState int 107 | 108 | // Peer state 109 | const ( 110 | PeerStateInit PeerState = 1 // Peer has been added recently. 111 | PeerStateRequestedIP = 2 // We know ID of a peer, but don't know it's IPs 112 | PeerStateRequestingProxy = 3 // Requesting proxies for this peer 113 | PeerStateWaitingForProxy = 4 // Waiting for proxies 114 | PeerStateWaitingToConnect = 5 // Waiting for other peer to start establishing connection 115 | PeerStateConnecting = 6 // Trying to establish connection 116 | PeerStateConnected = 7 // Connected, handshaked and operating normally 117 | PeerStateDisconnect = 8 // We're disconnecting 118 | PeerStateStop = 9 // Peer has been stopped and now can be removed from list of peers 119 | PeerStateCooldown = 10 // Peer is in cooldown mode 120 | ) 121 | 122 | // Timeouts and retries 123 | const ( 124 | DHTMaxRetries int = 10 125 | DHCPMaxRetries int = 10 126 | PeerPingTimeout time.Duration = time.Second * 1 127 | WaitProxyTimeout time.Duration = time.Second * 5 128 | HandshakeProxyTimeout time.Duration = time.Second * 3 129 | EndpointPingInterval time.Duration = time.Millisecond * 7000 130 | EndpointTimeout time.Duration = time.Millisecond * 15000 // Must be greater than EndpointPingInterval 131 | ProxyLatencyRequestInterval time.Duration = time.Second * 15 // How often we should update latency with proxies 132 | EndpointLatencyRequestInterval time.Duration = time.Second * 15 // How often we should update latency with endpoints 133 | UDPHolePunchTimeout time.Duration = time.Millisecond * 20000 // How long we will for udp hole punching to finish 134 | ) 135 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestStateRestore(t *testing.T) { 8 | // daemon := new(Daemon) 9 | // daemon.init("t.file") 10 | 11 | // i1 := new(P2PInstance) 12 | // i2 := new(P2PInstance) 13 | // i1.Args.IP = "10.10.10.10" 14 | // i1.Args.Dev = "vptp1" 15 | // daemon.Instances.update("1", i1) 16 | // i2.Args.IP = "127.0.0.1" 17 | // i2.Args.Dev = "vptp2" 18 | // daemon.Instances.update("2", i2) 19 | 20 | // _, err := daemon.Instances.saveInstances("t.file") 21 | // if err != nil { 22 | // t.Errorf("%v", err) 23 | // } 24 | 25 | // loaded, err := daemon.Instances.loadInstances("t.file") 26 | // if err != nil { 27 | // t.Errorf("Failed to load instances: %v", err) 28 | // } 29 | // if len(loaded) != 2 { 30 | // t.Errorf("Resulting instances size doesn't match saved. Expecting 2, Received: %d", len(loaded)) 31 | // } 32 | // if loaded[0].IP != "10.10.10.10" && loaded[0].IP != "127.0.0.1" { 33 | // t.Errorf("Loaded IP doesn't match saved: %s", loaded[0].IP) 34 | // } 35 | // if loaded[1].IP != "127.0.0.1" && loaded[1].IP != "10.10.10.10" { 36 | // t.Errorf("Loaded IP doesn't match saved: %s", loaded[1].IP) 37 | // } 38 | // if loaded[0].Dev != "vptp1" && loaded[0].Dev != "vptp2" { 39 | // t.Errorf("Loaded device name doesn't match saved: %s", loaded[0].Dev) 40 | // } 41 | // if loaded[1].Dev != "vptp2" && loaded[1].Dev != "vptp1" { 42 | // t.Errorf("Loaded device name doesn't match saved: %s", loaded[1].Dev) 43 | // } 44 | // os.Remove("t.file") 45 | } 46 | -------------------------------------------------------------------------------- /protocol/dht.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package main; 3 | 4 | enum DHTPacketType { 5 | Undefined = 0; 6 | Connect = 1; 7 | Forward = 2; 8 | Find = 3; 9 | Node = 4; 10 | Ping = 5; 11 | RegisterProxy = 6; 12 | RequestProxy = 7; 13 | ReportProxy = 8; 14 | BadProxy = 9; 15 | Proxy = 10; 16 | Notify = 11; 17 | ReportLoad = 12; 18 | Stop = 13; 19 | Unknown = 14; 20 | DHCP = 15; 21 | Error = 16; 22 | Unsupported = 17; 23 | State = 18; 24 | }; 25 | 26 | message DHTPacket { 27 | DHTPacketType type = 1; 28 | string id = 2; // Node ID 29 | string infohash = 3; // Infohash 30 | string data = 4; 31 | string query = 5; 32 | repeated string arguments = 6; // Can hold list of IPs 33 | repeated string proxies = 7; // List of proxies associated with particular peer 34 | string extra = 8; // Can contain error message, report or any other data 35 | bytes payload = 9; // Payload for forwarded/distributed message 36 | int32 version = 10; // Version number 37 | } 38 | -------------------------------------------------------------------------------- /protocol/proxy.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package main; 3 | 4 | enum ProxyPacketType { 5 | Ping = 0; 6 | Register = 1; 7 | Discover = 2; 8 | }; 9 | 10 | message ProxyPacket { 11 | ProxyPacketType type = 1; 12 | uint32 id = 2; 13 | string data = 3; 14 | } 15 | -------------------------------------------------------------------------------- /rest.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "net/http" 11 | "os" 12 | 13 | ptp "github.com/subutai-io/p2p/lib" 14 | ) 15 | 16 | var ( 17 | errorFailedToMarshal = errors.New("Failed to marshal JSON request") 18 | errorFailedToCreatePOSTRequest = errors.New("Failed to create POST request") 19 | errorFailedToExecuteRequest = errors.New("Failed to execute request") 20 | ) 21 | 22 | type request struct { 23 | IP string `json:"ip"` 24 | Mac string `json:"mac"` 25 | Dev string `json:"dev"` 26 | Hash string `json:"hash"` 27 | Dht string `json:"dht"` 28 | Keyfile string `json:"keyfile"` 29 | Key string `json:"key"` 30 | TTL string `json:"ttl"` 31 | Fwd bool `json:"fwd"` 32 | Port int `json:"port"` 33 | Interfaces bool `json:"interfaces"` // Used for show request 34 | All bool `json:"all"` // Used for show request 35 | Bind bool `json:"bind"` // Used for show request 36 | MTU bool `json:"mtu"` // Used for MTU show request 37 | } 38 | 39 | type RESTResponse struct { 40 | Code int `json:"code"` 41 | Message string `json:"message"` 42 | } 43 | 44 | // ErrorOutput is a JSON object for output 45 | type ErrorOutput struct { 46 | Error string `json:"error"` 47 | Code int `json:"code"` 48 | } 49 | 50 | func setupRESTHandlers(port int, d *Daemon) { 51 | http.HandleFunc("/rest/v1/start", d.execRESTStart) 52 | http.HandleFunc("/rest/v1/stop", d.execRESTStop) 53 | http.HandleFunc("/rest/v1/show", d.execRESTShow) 54 | http.HandleFunc("/rest/v1/status", d.execRESTStatus) 55 | http.HandleFunc("/rest/v1/debug", d.execRESTDebug) 56 | http.HandleFunc("/rest/v1/set", d.execRESTSet) 57 | 58 | go func() { 59 | err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil) 60 | if err != nil { 61 | fmt.Printf("Failed to start HTTP listener: %s", err) 62 | os.Exit(98) 63 | } 64 | }() 65 | } 66 | 67 | func sendRequest(port int, command string, args *DaemonArgs) (*RESTResponse, error) { 68 | data, err := json.Marshal(args) 69 | if err != nil { 70 | return nil, fmt.Errorf("Failed to marshal request: %s", err) 71 | } 72 | 73 | req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/rest/v1/%s", port, command), bytes.NewBuffer(data)) 74 | if err != nil { 75 | return nil, fmt.Errorf("Failed to create request: %s", err) 76 | } 77 | 78 | client := &http.Client{} 79 | resp, err := client.Do(req) 80 | if err != nil { 81 | return nil, fmt.Errorf("Couldn't execute command. Check if p2p daemon is running.") 82 | } 83 | defer resp.Body.Close() 84 | 85 | body, _ := ioutil.ReadAll(resp.Body) 86 | out := &RESTResponse{} 87 | err = json.Unmarshal(body, out) 88 | if err != nil { 89 | return nil, fmt.Errorf("Failed to unmarshal response: %s", err) 90 | } 91 | return out, nil 92 | } 93 | 94 | func sendRequestRaw(port int, command string, r *request) ([]byte, error) { 95 | data, err := json.Marshal(r) 96 | if err != nil { 97 | ptp.Log(ptp.Error, "%s: %s", errorFailedToMarshal, err) 98 | return nil, errorFailedToMarshal 99 | } 100 | 101 | req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/rest/v1/%s", port, command), bytes.NewBuffer(data)) 102 | if err != nil { 103 | ptp.Log(ptp.Error, "%s: %s", errorFailedToCreatePOSTRequest, err) 104 | return nil, errorFailedToCreatePOSTRequest 105 | } 106 | 107 | client := &http.Client{} 108 | resp, err := client.Do(req) 109 | if err != nil { 110 | ptp.Log(ptp.Error, "%s. Check if p2p daemon is running", errorFailedToExecuteRequest) 111 | return nil, errorFailedToExecuteRequest 112 | } 113 | defer resp.Body.Close() 114 | return ioutil.ReadAll(resp.Body) 115 | } 116 | 117 | func getJSON(body io.ReadCloser, args *DaemonArgs) error { 118 | data, err := ioutil.ReadAll(body) 119 | if err != nil { 120 | return err 121 | } 122 | //args := new(RunArgs) 123 | if len(data) == 0 { 124 | return nil 125 | } 126 | err = json.Unmarshal(data, args) 127 | if err != nil { 128 | return err 129 | } 130 | return nil 131 | } 132 | 133 | func getResponse(exitCode int, outputMessage string) ([]byte, error) { 134 | resp := &RESTResponse{ 135 | Code: exitCode, 136 | Message: outputMessage, 137 | } 138 | out, err := json.Marshal(resp) 139 | if err != nil { 140 | return nil, fmt.Errorf("Failed to marshal response: %s", err) 141 | } 142 | return out, nil 143 | } 144 | 145 | func handleMarshalError(err error, w http.ResponseWriter) error { 146 | if err != nil { 147 | errText := fmt.Sprintf("Failed to read request body: %s", err) 148 | ptp.Log(ptp.Error, "%s", errText) 149 | resp, err := getResponse(1, errText) 150 | if err != nil { 151 | ptp.Log(ptp.Error, "Internal error: %s", err) 152 | return err 153 | } 154 | w.Write(resp) 155 | return err 156 | } 157 | return nil 158 | } 159 | -------------------------------------------------------------------------------- /rest/swagger.yml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | description: "Subutai P2P operations" 4 | version: "6.3.0" 5 | title: "Subutai P2P" 6 | termsOfService: "https://subutai.io" 7 | contact: 8 | email: "msavochkin@optimal-dynamics.com" 9 | license: 10 | name: "GPLv3" 11 | url: "https://www.gnu.org/licenses/gpl-3.0.en.html" 12 | host: "localhost" 13 | basePath: "/v1" 14 | schemes: 15 | - "http" 16 | - "https" 17 | tags: 18 | - name: "instances" 19 | description: "P2P Instances manipulation" 20 | - name: "swarm" 21 | description: "Manipulate single swarms" 22 | - name: "daemon" 23 | description: "Modify daemon behaviour" 24 | paths: 25 | /instance: 26 | post: 27 | tags: 28 | - "instances" 29 | summary: "Create new P2P instance" 30 | description: "" 31 | operationId: "CreateInstance" 32 | consumes: 33 | - "application/json" 34 | - "application/xml" 35 | produces: 36 | - "application/json" 37 | - "application/xml" 38 | parameters: 39 | - in: "body" 40 | name: "body" 41 | description: "Instance configuration" 42 | required: true 43 | schema: 44 | $ref: "#/definitions/Instance" 45 | responses: 46 | 200: 47 | description: "Sucessfully created" 48 | 400: 49 | description: "Bad request" 50 | 503: 51 | description: "Service unavailable" 52 | get: 53 | tags: 54 | - "instances" 55 | summary: "List P2P instances" 56 | description: "List all p2p instances" 57 | operationId: "ListInstances" 58 | produces: 59 | - "application/json" 60 | - "application/xml" 61 | responses: 62 | 200: 63 | description: "Sucessful operation" 64 | schema: 65 | $ref: "#/definitions/Instances" 66 | 503: 67 | description: "Service unavailable" 68 | delete: 69 | tags: 70 | - "instances" 71 | summary: "Destroy P2P instance" 72 | description: "This command will shutdown P2P instance" 73 | operationId: "CloseInstance" 74 | consumes: 75 | - "application/json" 76 | - "application/xml" 77 | produces: 78 | - "application/json" 79 | - "application/xml" 80 | parameters: 81 | - in: "query" 82 | name: "hash" 83 | description: "Instance configuration" 84 | required: true 85 | schema: 86 | $ref: "#/definitions/Instance" 87 | responses: 88 | 200: 89 | description: "Sucessfully created" 90 | 400: 91 | description: "Bad request" 92 | 503: 93 | description: "Service unavailable" 94 | /swarm: 95 | get: 96 | tags: 97 | - "swarm" 98 | summary: "Display instance information" 99 | description: "Display detailed information about specified instance" 100 | operationId: "SwarmStatus" 101 | produces: 102 | - "application/json" 103 | - "application/xml" 104 | parameters: 105 | - in: "query" 106 | name: "hash" 107 | description: "Instance hash" 108 | required: true 109 | type: "string" 110 | responses: 111 | 200: 112 | description: "Sucessful operation" 113 | schema: 114 | $ref: "#/definitions/InstanceDetails" 115 | 404: 116 | description: "Hash not found" 117 | 503: 118 | description: "Service unavailable" 119 | post: 120 | tags: 121 | - "swarm" 122 | summary: "Update swarm keys" 123 | description: "Add new crypto keys to an existing swarm" 124 | operationId: "SwarmOptions" 125 | consumes: 126 | - "application/json" 127 | - "application/xml" 128 | produces: 129 | - "application/json" 130 | - "application/xml" 131 | parameters: 132 | - in: "body" 133 | name: "body" 134 | description: "Instance configuration" 135 | required: true 136 | schema: 137 | $ref: "#/definitions/Key" 138 | - in: "query" 139 | name: "hash" 140 | description: "Instance hash" 141 | required: true 142 | type: "string" 143 | responses: 144 | 200: 145 | description: "Sucessful operation" 146 | 400: 147 | description: "Bad request" 148 | 503: 149 | description: "Service unavailable" 150 | /daemon: 151 | get: 152 | tags: 153 | - "daemon" 154 | summary: "Get daemon information" 155 | description: "Returns information about P2P daemon" 156 | operationId: "DaemonInfo" 157 | consumes: 158 | - "application/json" 159 | - "application/xml" 160 | produces: 161 | - "application/json" 162 | - "application/xml" 163 | responses: 164 | 200: 165 | description: "Sucessful operation" 166 | schema: 167 | $ref: "#/definitions/Daemon" 168 | 503: 169 | description: "Service unavailable" 170 | post: 171 | tags: 172 | - "daemon" 173 | summary: "Modify daemon" 174 | description: "Modify daemon options on runtime" 175 | operationId: "DaemonOptions" 176 | consumes: 177 | - "application/json" 178 | - "application/xml" 179 | produces: 180 | - "application/json" 181 | - "application/xml" 182 | parameters: 183 | - in: "body" 184 | name: "body" 185 | description: "Instance configuration" 186 | required: true 187 | schema: 188 | $ref: "#/definitions/Log" 189 | responses: 190 | 200: 191 | description: "Sucessful operation" 192 | 503: 193 | description: "Service unavailable" 194 | definitions: 195 | Instance: 196 | type: object 197 | properties: 198 | hash: 199 | type: "string" 200 | required: true 201 | interface: 202 | $ref: "#/definitions/Interface" 203 | port: 204 | type: "string" 205 | description: "Specific UDP port or port range [from-to]" 206 | key: 207 | $ref: "#/definitions/Key" 208 | xml: 209 | name: "Instance" 210 | Instances: 211 | type: array 212 | items: 213 | $ref: "#/definitions/Instance" 214 | InstanceDetails: 215 | type: object 216 | properties: 217 | id: 218 | type: "string" 219 | hash: 220 | type: "string" 221 | interface: 222 | $ref: "#/definitions/Interface" 223 | port: 224 | type: "integer" 225 | proxies: 226 | type: "array" 227 | items: 228 | $ref: "#/definitions/Proxy" 229 | peers: 230 | type: "array" 231 | items: 232 | $ref: "#/definitions/Peer" 233 | Key: 234 | type: object 235 | properties: 236 | key: 237 | type: "string" 238 | keyfile: 239 | type: "string" 240 | until: 241 | type: "string" 242 | xml: 243 | name: "Key" 244 | Log: 245 | type: object 246 | properties: 247 | level: 248 | type: "string" 249 | default: "info" 250 | required: true 251 | Daemon: 252 | type: object 253 | properties: 254 | version: 255 | type: "string" 256 | build: 257 | type: "string" 258 | os: 259 | type: "string" 260 | dht: 261 | type: array 262 | items: 263 | $ref: "#/definitions/DHT" 264 | uptime: 265 | type: "string" 266 | DHT: 267 | type: object 268 | properties: 269 | endpoint: 270 | type: "string" 271 | rx: 272 | type: "string" 273 | tx: 274 | type: "string" 275 | Interface: 276 | type: object 277 | properties: 278 | name: 279 | type: "string" 280 | ip: 281 | type: "string" 282 | mac: 283 | type: "string" 284 | Proxy: 285 | type: object 286 | properties: 287 | addr: 288 | type: "string" 289 | description: "UDP address of proxy this instance is connected to" 290 | endpoint: 291 | type: "string" 292 | description: "UDP address of proxy that was binded for current instance" 293 | Peer: 294 | type: object 295 | properties: 296 | id: 297 | type: "string" 298 | description: "Unique ID of this peer" 299 | state: 300 | type: "string" 301 | description: "State of the peer on our end" 302 | rstate: 303 | type: "string" 304 | description: "State of our peer on remote end" 305 | interface: 306 | $ref: "#/definitions/Interface" 307 | endpoint: 308 | type: "string" 309 | description: "Active endpoint" 310 | endpoint_pool: 311 | type: "array" 312 | items: 313 | type: "string" 314 | endpoint_list: 315 | type: "array" 316 | items: 317 | type: "string" 318 | -------------------------------------------------------------------------------- /restore.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "sync" 10 | "time" 11 | 12 | "github.com/subutai-io/p2p/lib" 13 | 14 | yaml "gopkg.in/yaml.v2" 15 | ) 16 | 17 | // Restore represents dump/restore subsystem for instances 18 | // This class keeps a list of so-called "save entries", which is 19 | // a binding for instance information 20 | // Restore system saves entries in a YAML-formatted save file, specified 21 | // as an argument on daemon launch using `--save` 22 | type Restore struct { 23 | entries []saveEntry 24 | filepath string 25 | lock sync.RWMutex 26 | active bool 27 | } 28 | 29 | // saveEntry is a YAML binding for data save file 30 | type saveEntry struct { 31 | IP string `yaml:"ip"` 32 | Mac string `yaml:"mac"` 33 | Dev string `yaml:"dev"` 34 | Hash string `yaml:"hash"` 35 | Keyfile string `yaml:"keyfile"` 36 | Key string `yaml:"key"` 37 | TTL string `yaml:"ttl"` 38 | LastSuccess string `yaml:"last_success"` 39 | Enabled bool 40 | } 41 | 42 | // init will initialize restore subsystem by checking if 43 | // file exists and can be modified 44 | func (r *Restore) init(filepath string) error { 45 | if filepath == "" { 46 | r.active = false 47 | return nil 48 | } 49 | file, err := os.OpenFile(filepath, os.O_CREATE|os.O_RDWR, 0700) 50 | if err != nil { 51 | return err 52 | } 53 | file.Close() 54 | r.filepath = filepath 55 | r.active = true 56 | return nil 57 | } 58 | 59 | // save will write dump of entries into a save file 60 | func (r *Restore) save() error { 61 | if r.filepath == "" { 62 | return nil 63 | } 64 | r.lock.Lock() 65 | file, err := os.OpenFile(r.filepath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700) 66 | if err != nil { 67 | r.lock.Unlock() 68 | return err 69 | } 70 | data, err := r.encode() 71 | if err != nil { 72 | r.lock.Unlock() 73 | return err 74 | } 75 | ptp.Log(ptp.Info, "Saving instances") 76 | _, err = file.Write(data) 77 | if err != nil { 78 | r.lock.Unlock() 79 | return err 80 | } 81 | r.lock.Unlock() 82 | return nil 83 | } 84 | 85 | // load will read save file and unmarshal saved entries 86 | func (r *Restore) load() error { 87 | if r.filepath == "" { 88 | return nil 89 | } 90 | r.lock.Lock() 91 | data, err := ioutil.ReadFile(r.filepath) 92 | if err != nil { 93 | r.lock.Unlock() 94 | return err 95 | } 96 | r.lock.Unlock() 97 | data = bytes.Trim(data, "\x00") // TODO: add more security to this 98 | if len(data) == 0 { 99 | return nil 100 | } 101 | if string(data[0]) == "-" || string(data[0]) == "[" { 102 | r.decode(data) 103 | return nil 104 | } 105 | // TODO: This code is deprecated and must be removed in version 9 106 | return r.decodeInstances(data) 107 | } 108 | 109 | // addInstance will create new save file entry from instance 110 | func (r *Restore) addInstance(inst *P2PInstance) error { 111 | ls, _ := inst.Args.LastSuccess.MarshalText() 112 | return r.addEntry(saveEntry{ 113 | IP: inst.Args.IP, 114 | Mac: inst.Args.Mac, 115 | Dev: inst.Args.Dev, 116 | Hash: inst.Args.Hash, 117 | Keyfile: inst.Args.Keyfile, 118 | Key: inst.Args.Key, 119 | TTL: inst.Args.TTL, 120 | LastSuccess: string(ls), 121 | Enabled: true, 122 | }) 123 | } 124 | 125 | // addEntry will create new save entry if it's unique by hash 126 | func (r *Restore) addEntry(entry saveEntry) error { 127 | r.lock.Lock() 128 | defer r.lock.Unlock() 129 | for _, e := range r.entries { 130 | if e.Hash == entry.Hash { 131 | return fmt.Errorf("Instance %s already in list of saved entries", entry.Hash) 132 | } 133 | } 134 | 135 | r.entries = append(r.entries, entry) 136 | return nil 137 | } 138 | 139 | func (r *Restore) removeEntry(hash string) error { 140 | r.lock.Lock() 141 | defer r.lock.Unlock() 142 | for i, e := range r.entries { 143 | if e.Hash == hash { 144 | r.entries = append(r.entries[:i], r.entries[i+1:]...) 145 | return nil 146 | } 147 | } 148 | return fmt.Errorf("Can't delete save entry: %s not found", hash) 149 | } 150 | 151 | func (r *Restore) bumpInstance(hash string) error { 152 | r.lock.Lock() 153 | defer r.lock.Unlock() 154 | for i, e := range r.entries { 155 | if e.Hash == hash { 156 | ls, _ := time.Now().MarshalText() 157 | r.entries[i].LastSuccess = string(ls) 158 | r.entries[i].Enabled = true 159 | return nil 160 | } 161 | } 162 | return fmt.Errorf("Can't update last success date for the instance: %s", hash) 163 | } 164 | 165 | func (r *Restore) disableStaleInstances(inst *P2PInstance) error { 166 | r.lock.RLock() 167 | defer r.lock.RUnlock() 168 | 169 | for i, e := range r.entries { 170 | var t time.Time 171 | err := t.UnmarshalText([]byte(e.LastSuccess)) 172 | if err != nil { 173 | ptp.Log(ptp.Error, "Failed to unmarshal date for save file entry %s. Disabling it", e.Hash) 174 | r.entries[i].Enabled = false 175 | continue 176 | } 177 | if time.Since(t) > time.Duration(time.Hour*24*20) { 178 | ptp.Log(ptp.Warning, "Instance %s was active more than 20 days ago", e.Hash) 179 | r.entries[i].Enabled = false 180 | } 181 | } 182 | return nil 183 | } 184 | 185 | // encode will generate YAML 186 | func (r *Restore) encode() ([]byte, error) { 187 | if len(r.entries) == 0 { 188 | return nil, nil 189 | } 190 | var data []saveEntry 191 | for _, e := range r.entries { 192 | if e.Enabled { 193 | data = append(data, e) 194 | } 195 | } 196 | output, err := yaml.Marshal(data) 197 | if err != nil { 198 | return nil, err 199 | } 200 | return output, nil 201 | } 202 | 203 | // decode will accept YAML and generate a slice of RunArgs 204 | func (r *Restore) decode(data []byte) error { 205 | var saved []saveEntry 206 | err := yaml.Unmarshal(data, &saved) 207 | if err != nil { 208 | return err 209 | } 210 | r.lock.Lock() 211 | r.entries = saved 212 | r.lock.Unlock() 213 | return nil 214 | } 215 | 216 | // decodeInstances is an obsolet variant of instances unmarshal 217 | // TODO: Remove in version 10 218 | func (r *Restore) decodeInstances(data []byte) error { 219 | if len(data) == 0 { 220 | return nil 221 | } 222 | var args []saveEntry 223 | b := bytes.Buffer{} 224 | b.Write(data) 225 | d := gob.NewDecoder(&b) 226 | err := d.Decode(&args) 227 | if err != nil { 228 | blocksOfInstancesOld := bytes.Split(data, bytes.NewBufferString("|~|").Bytes()) 229 | blocksOfInstances := bytes.Split(data, bytes.NewBufferString("|||").Bytes()) 230 | if len(blocksOfInstancesOld) == len(blocksOfInstances) { 231 | if len(blocksOfInstancesOld) != 1 || len(blocksOfInstances) != 1 { 232 | return fmt.Errorf("Unexpected error in decoding process") 233 | } 234 | } else { 235 | if len(blocksOfInstancesOld) > len(blocksOfInstances) { 236 | blocksOfInstances = blocksOfInstancesOld 237 | } 238 | } 239 | for _, str := range blocksOfInstances { 240 | blocksOfArguments := bytes.Split(str, bytes.NewBufferString("~").Bytes()) 241 | if len(blocksOfArguments) != 10 { 242 | return fmt.Errorf("Couldn't decode the instances") 243 | } 244 | var item saveEntry 245 | item.IP = string(blocksOfArguments[0]) 246 | item.Mac = string(blocksOfArguments[1]) 247 | item.Dev = string(blocksOfArguments[2]) 248 | item.Hash = string(blocksOfArguments[3]) 249 | //item.Dht = string(blocksOfArguments[4]) 250 | item.Keyfile = string(blocksOfArguments[5]) 251 | item.Key = string(blocksOfArguments[6]) 252 | item.TTL = string(blocksOfArguments[7]) 253 | 254 | // Force this env to be nor marked as failed 255 | item.Enabled = true 256 | ls, _ := time.Now().MarshalText() 257 | item.LastSuccess = string(ls) 258 | //item.Fwd = false 259 | // if string(blocksOfArguments[8]) == "1" { 260 | // item.Fwd = true 261 | // } 262 | //item.Port, err = strconv.Atoi(string(blocksOfArguments[9])) 263 | // if err != nil { 264 | // return fmt.Errorf("Couldn't decode the Port: %v", err) 265 | // } 266 | args = append(args, item) 267 | } 268 | } 269 | 270 | r.lock.Lock() 271 | ptp.Log(ptp.Info, "Decoded %d entries from the old format", len(args)) 272 | r.entries = args 273 | r.lock.Unlock() 274 | return nil 275 | } 276 | 277 | // get will return slice of entries 278 | func (r *Restore) get() []saveEntry { 279 | r.lock.Lock() 280 | defer r.lock.Unlock() 281 | return r.entries 282 | } 283 | 284 | func (r *Restore) isActive() bool { 285 | return r.active 286 | } 287 | -------------------------------------------------------------------------------- /service_posix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package main 4 | 5 | func ExecService() error { 6 | return nil 7 | } 8 | -------------------------------------------------------------------------------- /service_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | 8 | ptp "github.com/subutai-io/p2p/lib" 9 | "golang.org/x/sys/windows/svc" 10 | "golang.org/x/sys/windows/svc/debug" 11 | "golang.org/x/sys/windows/svc/eventlog" 12 | ) 13 | 14 | type P2PService struct{} 15 | 16 | func (m *P2PService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { 17 | go ExecDaemon(52523, TargetURL, "", "", "", DefaultLog, "", ptp.DefaultMTU, ptp.UsePMTU) 18 | const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue 19 | // changes <- svc.Status{State: svc.StartPending} 20 | changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} 21 | loop: 22 | for { 23 | select { 24 | case c := <-r: 25 | switch c.Cmd { 26 | case svc.Interrogate: 27 | changes <- c.CurrentStatus 28 | case svc.Stop, svc.Shutdown: 29 | // changes <- svc.Status{State: svc.StopPending} 30 | changes <- svc.Status{State: svc.Stopped, Accepts: cmdsAccepted} 31 | break loop 32 | case svc.Pause: 33 | // changes <- svc.Status{State: svc.PausePending} 34 | changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted} 35 | case svc.Continue: 36 | // changes <- svc.Status{State: svc.ContinuePending} 37 | changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} 38 | default: 39 | ptp.Log(ptp.Error, "Unexpected control request #%d", c) 40 | } 41 | } 42 | } 43 | return 44 | } 45 | 46 | func ExecService() error { 47 | isIntSess, err := svc.IsAnInteractiveSession() 48 | if err != nil { 49 | ptp.Log(ptp.Error, "Failed to determine if we are running in an interactive session: %v", err) 50 | os.Exit(106) 51 | return nil 52 | } 53 | if isIntSess { 54 | ptp.Log(ptp.Info, "Running in an interactive session") 55 | elog := debug.New("Subutai P2P") 56 | defer elog.Close() 57 | elog.Info(1, fmt.Sprintf("Debug mode ON")) 58 | run := debug.Run 59 | err = run("Subutai P2P", &P2PService{}) 60 | if err != nil { 61 | elog.Info(1, fmt.Sprintf("Failed to run service: %s", err)) 62 | return nil 63 | } 64 | } else { 65 | elog, err := eventlog.Open("Subutai P2P") 66 | if err != nil { 67 | ptp.Log(ptp.Error, "Failed to get access to event logger") 68 | return nil 69 | } 70 | defer elog.Close() 71 | elog.Info(1, fmt.Sprintf("Running in a non-interactive mode")) 72 | run := svc.Run 73 | err = run("Subutai P2P", &P2PService{}) 74 | if err != nil { 75 | elog.Info(1, fmt.Sprintf("Failed to run service: %s", err)) 76 | return nil 77 | } 78 | } 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /set.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "os" 8 | "strings" 9 | 10 | ptp "github.com/subutai-io/p2p/lib" 11 | ) 12 | 13 | // Set modifies different options of P2P daemon 14 | func CommandSet(rpcPort int, log, hash, keyfile, key, ttl, ip string) { 15 | out, err := sendRequest(rpcPort, "set", &DaemonArgs{Log: log, Keyfile: keyfile, Key: key, TTL: ttl, IP: ip, Hash: hash}) 16 | if err != nil { 17 | fmt.Fprintln(os.Stderr, err.Error()) 18 | os.Exit(1) 19 | } 20 | 21 | fmt.Println(out.Message) 22 | os.Exit(out.Code) 23 | } 24 | 25 | func (d *Daemon) execRESTSet(w http.ResponseWriter, r *http.Request) { 26 | if !ReadyToServe { 27 | resp, _ := getResponse(105, "P2P Daemon is in initialization state") 28 | w.Write(resp) 29 | return 30 | } 31 | if !bootstrap.isActive { 32 | resp, _ := getResponse(106, "Not connected to DHT nodes") 33 | w.Write(resp) 34 | return 35 | } 36 | if bootstrap.ip == "" { 37 | resp, _ := getResponse(107, "Didn't received outbound IP yet") 38 | w.Write(resp) 39 | return 40 | } 41 | args := new(DaemonArgs) 42 | err := getJSON(r.Body, args) 43 | if handleMarshalError(err, w) != nil { 44 | return 45 | } 46 | response := new(Response) 47 | if args.Log != "" { 48 | // User modifying log level 49 | d.SetLog(&NameValueArg{ 50 | Name: "log", 51 | Value: args.Log, 52 | }, response) 53 | } else if args.IP != "" && args.Hash != "" { 54 | // User modifying IP of the hash 55 | ptp.Log(ptp.Info, "Request IP change for %s: %s", args.Hash, args.IP) 56 | d.setIP(&NameValueArg{ 57 | Name: args.Hash, 58 | Value: args.IP, 59 | }, response) 60 | } else { 61 | response.ExitCode = 0 62 | response.Output = "Unknown command" 63 | } 64 | resp, err := getResponse(response.ExitCode, response.Output) 65 | if err != nil { 66 | ptp.Log(ptp.Error, "Internal error: %s", err) 67 | return 68 | } 69 | ptp.Log(ptp.Info, "RESPONSE: %s", string(resp)) 70 | w.Write(resp) 71 | } 72 | 73 | // setIP will change IP of specified hash 74 | func (d *Daemon) setIP(args *NameValueArg, resp *Response) error { 75 | 76 | hash := args.Name 77 | if len(hash) == 0 { 78 | resp.ExitCode = 11 79 | resp.Output = "Empty hash specified" 80 | return fmt.Errorf("empty hash") 81 | } 82 | 83 | ip := net.ParseIP(args.Value) 84 | if ip == nil { 85 | resp.ExitCode = 12 86 | resp.Output = "Couldn't parse IP " + args.Value 87 | return fmt.Errorf("Failed to parse IP") 88 | } 89 | 90 | instance := d.Instances.getInstance(hash) 91 | if instance == nil { 92 | resp.ExitCode = 4 93 | resp.Output = "Instance " + hash + " wasn't found" 94 | return fmt.Errorf("Instance %s not found", hash) 95 | } 96 | 97 | instance.PTP.Interface.SetIP(ip) 98 | err := instance.PTP.Interface.Configure(false) 99 | if err != nil { 100 | resp.ExitCode = 2 101 | resp.Output = "Failed to reconfigure network: " + err.Error() 102 | return fmt.Errorf("Failed to configure network: %s", err.Error()) 103 | } 104 | 105 | resp.ExitCode = 0 106 | resp.Output = "Request for IP change was sent. No errors reported." 107 | 108 | return nil 109 | } 110 | 111 | // SetLog modifies specific option 112 | func (d *Daemon) SetLog(args *NameValueArg, resp *Response) error { 113 | args.Value = strings.ToLower(args.Value) 114 | ptp.Log(ptp.Info, "Setting option %s to %s", args.Name, args.Value) 115 | resp.ExitCode = 0 116 | if args.Name == "log" { 117 | resp.Output = "Logging level has switched to " + args.Value + " level" 118 | err := ptp.SetMinLogLevelString(args.Value) 119 | if err == nil { 120 | resp.Output = "Logging level has switched to " + args.Value + " level" 121 | } else { 122 | resp.ExitCode = 1 123 | resp.Output = "Unknown log level was specified. Supported log levels is:\n" 124 | resp.Output = resp.Output + "TRACE\n" 125 | resp.Output = resp.Output + "DEBUG\n" 126 | resp.Output = resp.Output + "INFO\n" 127 | resp.Output = resp.Output + "WARNING\n" 128 | resp.Output = resp.Output + "ERROR\n" 129 | } 130 | } 131 | return nil 132 | } 133 | 134 | // AddKey adds a new crypto-key 135 | func (p *Daemon) AddKey(args *RunArgs, resp *Response) error { 136 | resp.ExitCode = 0 137 | if args.Hash == "" { 138 | resp.ExitCode = 1 139 | resp.Output = "You have not specified hash" 140 | } 141 | if args.Key == "" { 142 | resp.ExitCode = 1 143 | resp.Output = "You have not specified key" 144 | } 145 | inst := p.Instances.getInstance(args.Hash) 146 | if inst == nil { 147 | resp.ExitCode = 1 148 | resp.Output = "No instances with specified hash were found" 149 | } 150 | if resp.ExitCode == 0 { 151 | resp.Output = "New key added" 152 | var newKey ptp.CryptoKey 153 | 154 | newKey = inst.PTP.Crypter.EnrichKeyValues(newKey, args.Key, args.TTL) 155 | inst.PTP.Crypter.Keys = append(inst.PTP.Crypter.Keys, newKey) 156 | p.Instances.update(args.Hash, inst) 157 | } 158 | return nil 159 | } 160 | -------------------------------------------------------------------------------- /show.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | 9 | ptp "github.com/subutai-io/p2p/lib" 10 | ) 11 | 12 | // ShowOutput is a JSON object for output from `show` command 13 | type ShowOutput struct { 14 | ID string `json:"id"` 15 | IP string `json:"ip"` 16 | Endpoint string `json:"endpoint"` 17 | HardwareAddress string `json:"hw"` 18 | Error string `json:"error"` 19 | Text string `json:"text"` 20 | Code int `json:"code"` 21 | InterfaceName string `json:"interface"` 22 | Hash string `json:"hash"` 23 | MTU string `json:"mtu"` 24 | } 25 | 26 | // Show outputs information about P2P instances and interfaces 27 | func CommandShow(queryPort int, hash, ip string, interfaces, all, bind, mtu bool) { 28 | req := &request{} 29 | if hash != "" { 30 | req.Hash = hash 31 | } else { 32 | req.Hash = "" 33 | } 34 | req.IP = ip 35 | req.Interfaces = interfaces 36 | req.All = all 37 | req.Bind = bind 38 | req.MTU = mtu 39 | 40 | out, err := sendRequestRaw(queryPort, "show", req) 41 | if err != nil { 42 | fmt.Println(err.Error()) 43 | if err == errorFailedToMarshal { 44 | os.Exit(112) 45 | } else if err == errorFailedToCreatePOSTRequest { 46 | os.Exit(113) 47 | } else if err == errorFailedToExecuteRequest { 48 | os.Exit(115) 49 | } 50 | os.Exit(1) 51 | } 52 | show := []ShowOutput{} 53 | err = json.Unmarshal(out, &show) 54 | if err != nil { 55 | fmt.Fprintf(os.Stderr, "Failed to unmarshal JSON. Error %s\n", err) 56 | os.Exit(99) 57 | } 58 | 59 | if req.Hash != "" { 60 | if req.IP != "" { 61 | for _, m := range show { 62 | if m.Code != 0 { 63 | fmt.Fprintln(os.Stderr, m.Error) 64 | } else { 65 | fmt.Println(m.Text) 66 | } 67 | os.Exit(m.Code) 68 | } 69 | fmt.Println("No data available") 70 | os.Exit(102) 71 | } else { 72 | fmt.Println("< Peer ID >\t< IP >\t< Endpoint >\t< HW >") 73 | for _, m := range show { 74 | if m.Code != 0 { 75 | fmt.Println(m.Error) 76 | os.Exit(m.Code) 77 | } 78 | fmt.Printf("%s\t%s\t%s\t%s\n", m.ID, m.IP, m.Endpoint, m.HardwareAddress) 79 | } 80 | os.Exit(0) 81 | } 82 | } 83 | if req.Interfaces { 84 | if req.Bind { 85 | for _, m := range show { 86 | fmt.Printf("%s|%s\n", m.Hash, m.InterfaceName) 87 | } 88 | os.Exit(0) 89 | } else { 90 | for _, m := range show { 91 | if m.Code != 0 { 92 | fmt.Println(m.Error) 93 | os.Exit(m.Code) 94 | } 95 | fmt.Println(m.InterfaceName) 96 | } 97 | os.Exit(0) 98 | } 99 | } 100 | if req.MTU { 101 | for _, s := range show { 102 | fmt.Println(s.MTU) 103 | os.Exit(0) 104 | } 105 | fmt.Fprintln(os.Stderr, "Failed to retrieve MTU value") 106 | os.Exit(114) 107 | } 108 | 109 | for _, m := range show { 110 | if m.Code != 0 { 111 | fmt.Fprintln(os.Stderr, m.Error) 112 | os.Exit(m.Code) 113 | } 114 | fmt.Printf("%s\t%s\t%s\n", m.HardwareAddress, m.IP, m.Hash) 115 | } 116 | os.Exit(0) 117 | } 118 | 119 | func (d *Daemon) execRESTShow(w http.ResponseWriter, r *http.Request) { 120 | args := new(DaemonArgs) 121 | err := getJSON(r.Body, args) 122 | if handleMarshalError(err, w) != nil { 123 | return 124 | } 125 | output, err := d.Show(&request{ 126 | Hash: args.Hash, 127 | IP: args.IP, 128 | Interfaces: args.Interfaces, 129 | Bind: args.Bind, 130 | MTU: args.MTU, 131 | All: args.All, 132 | }) 133 | if err != nil { 134 | ptp.Log(ptp.Error, "Internal error: %s", err) 135 | return 136 | } 137 | w.Write(output) 138 | } 139 | 140 | // Show is used to output information about instances 141 | func (d *Daemon) Show(args *request) ([]byte, error) { 142 | if !ReadyToServe { 143 | out := []ShowOutput{ShowOutput{Error: "P2P Daemon is in initialization mode. Can't handle request", Code: 105}} 144 | return d.showOutput(out) 145 | } 146 | if !bootstrap.isActive { 147 | out := []ShowOutput{ShowOutput{Error: "Not connected to DHT nodes", Code: 106}} 148 | return d.showOutput(out) 149 | } 150 | if bootstrap.ip == "" { 151 | out := []ShowOutput{ShowOutput{Error: "Didn't received outbound IP yet", Code: 107}} 152 | return d.showOutput(out) 153 | } 154 | 155 | if args.Hash != "" { 156 | inst := d.Instances.getInstance(args.Hash) 157 | if inst != nil { 158 | if args.IP != "" { 159 | out, err := d.showIP(args.IP, inst) 160 | return out, err 161 | } 162 | out, err := d.showHash(inst) 163 | return out, err 164 | } 165 | return d.showOutput([]ShowOutput{ 166 | ShowOutput{ 167 | Error: "Specified environment was not found", 168 | Code: 15, 169 | }, 170 | }) 171 | } else if args.Interfaces { 172 | if args.All { 173 | return d.showAllInterfaces() 174 | } 175 | if args.Bind { 176 | return d.showBindInterfaces() 177 | } 178 | return d.showInterfaces() 179 | } else if args.MTU { 180 | return d.showMTU() 181 | } else { 182 | return d.showInstances() 183 | } 184 | return nil, nil 185 | } 186 | 187 | func (d *Daemon) showOutput(data []ShowOutput) ([]byte, error) { 188 | return json.Marshal(data) 189 | } 190 | 191 | func (d *Daemon) showIP(ip string, instance *P2PInstance) ([]byte, error) { 192 | peers := instance.PTP.Swarm.Get() 193 | for _, peer := range peers { 194 | if peer.PeerLocalIP.String() == ip { 195 | if peer.State == ptp.PeerStateConnected { 196 | out := []ShowOutput{ 197 | ShowOutput{ 198 | Text: "Integrated with " + ip, 199 | Code: 0, 200 | }, 201 | } 202 | return d.showOutput(out) 203 | } 204 | } 205 | } 206 | out := []ShowOutput{ 207 | ShowOutput{ 208 | Error: "Not yet integrated with " + ip, 209 | Code: 12, 210 | }, 211 | } 212 | return d.showOutput(out) 213 | } 214 | 215 | func (d *Daemon) showHash(instance *P2PInstance) ([]byte, error) { 216 | peers := instance.PTP.Swarm.Get() 217 | out := []ShowOutput{} 218 | for _, peer := range peers { 219 | s := ShowOutput{ 220 | ID: peer.ID, 221 | IP: peer.PeerLocalIP.String(), 222 | Endpoint: peer.Endpoint.String(), 223 | HardwareAddress: peer.PeerHW.String(), 224 | } 225 | out = append(out, s) 226 | } 227 | return d.showOutput(out) 228 | } 229 | 230 | func (d *Daemon) showInterfaces() ([]byte, error) { 231 | instances := d.Instances.get() 232 | out := []ShowOutput{} 233 | for _, inst := range instances { 234 | if inst.PTP != nil { 235 | s := ShowOutput{InterfaceName: inst.PTP.Interface.GetName()} 236 | out = append(out, s) 237 | } 238 | } 239 | return d.showOutput(out) 240 | } 241 | 242 | func (d *Daemon) showAllInterfaces() ([]byte, error) { 243 | out := []ShowOutput{} 244 | for _, inf := range InterfaceNames { 245 | s := ShowOutput{InterfaceName: inf} 246 | out = append(out, s) 247 | } 248 | return d.showOutput(out) 249 | } 250 | 251 | func (d *Daemon) showBindInterfaces() ([]byte, error) { 252 | instances := d.Instances.get() 253 | out := []ShowOutput{} 254 | for _, inst := range instances { 255 | if inst.PTP != nil && inst.PTP.Interface != nil { 256 | s := ShowOutput{Hash: inst.PTP.Dht.NetworkHash, InterfaceName: inst.PTP.Interface.GetName()} 257 | out = append(out, s) 258 | } 259 | } 260 | return d.showOutput(out) 261 | } 262 | 263 | func (d *Daemon) showInstances() ([]byte, error) { 264 | instances := d.Instances.get() 265 | out := []ShowOutput{} 266 | for key, inst := range instances { 267 | if inst.PTP != nil { 268 | s := ShowOutput{ 269 | HardwareAddress: inst.PTP.Interface.GetHardwareAddress().String(), 270 | IP: inst.PTP.Interface.GetIP().String(), 271 | Hash: key, 272 | } 273 | out = append(out, s) 274 | } else { 275 | s := ShowOutput{ 276 | HardwareAddress: "Unknown", 277 | IP: "Unknown", 278 | Hash: key, 279 | } 280 | out = append(out, s) 281 | } 282 | } 283 | return d.showOutput(out) 284 | } 285 | 286 | func (d *Daemon) showMTU() ([]byte, error) { 287 | ptp.Log(ptp.Trace, "Retrieving MTU value: %d", ptp.GlobalMTU) 288 | return d.showOutput([]ShowOutput{{MTU: fmt.Sprintf("%d", ptp.GlobalMTU)}}) 289 | } 290 | -------------------------------------------------------------------------------- /start.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "os" 9 | "strings" 10 | "time" 11 | 12 | ptp "github.com/subutai-io/p2p/lib" 13 | ) 14 | 15 | // CommandStart will create new P2P instance 16 | func CommandStart(restPort int, ip, hash, mac, dev, keyfile, key, ttl string, fwd bool, port int) { 17 | args := &DaemonArgs{} 18 | args.IP = ip 19 | if hash == "" { 20 | fmt.Fprintln(os.Stderr, "Hash cannot be empty. Please start new instances with -hash VALUE argument") 21 | os.Exit(12) 22 | } 23 | if strings.Index(hash, "~") != -1 { 24 | fmt.Fprintln(os.Stderr, "Hash cannot contain the ~. Please start new instances with hash value that doesn't contain it") 25 | os.Exit(17) 26 | } 27 | args.Hash = hash 28 | if mac != "" { 29 | _, err := net.ParseMAC(mac) 30 | if err != nil { 31 | fmt.Fprintln(os.Stderr, "Invalid MAC address provided") 32 | os.Exit(13) 33 | } 34 | } 35 | args.Mac = mac 36 | args.Dev = dev 37 | args.Keyfile = keyfile 38 | args.Key = key 39 | args.TTL = ttl 40 | args.Fwd = fwd 41 | args.Port = port 42 | 43 | out, err := sendRequest(restPort, "start", args) 44 | if err != nil { 45 | fmt.Fprintln(os.Stderr, err.Error()) 46 | os.Exit(1) 47 | } 48 | 49 | if out.Code > 0 { 50 | fmt.Fprintln(os.Stderr, out.Message) 51 | } else { 52 | fmt.Println(out.Message) 53 | } 54 | os.Exit(out.Code) 55 | } 56 | 57 | func (d *Daemon) execRESTStart(w http.ResponseWriter, r *http.Request) { 58 | if !ReadyToServe { 59 | resp, _ := getResponse(105, "P2P Daemon is in initialization state") 60 | w.Write(resp) 61 | return 62 | } 63 | if !bootstrap.isActive { 64 | resp, _ := getResponse(106, "Not connected to DHT nodes") 65 | w.Write(resp) 66 | return 67 | } 68 | if bootstrap.ip == "" { 69 | resp, _ := getResponse(107, "Didn't received outbound IP yet") 70 | w.Write(resp) 71 | return 72 | } 73 | args := new(DaemonArgs) 74 | err := getJSON(r.Body, args) 75 | if handleMarshalError(err, w) != nil { 76 | return 77 | } 78 | 79 | ptp.Log(ptp.Debug, "Executing start command: %+v", args) 80 | response := new(Response) 81 | err = d.run(&RunArgs{ 82 | IP: args.IP, 83 | Mac: args.Mac, 84 | Dev: args.Dev, 85 | Hash: args.Hash, 86 | Dht: args.Dht, 87 | Keyfile: args.Keyfile, 88 | Key: args.Key, 89 | TTL: args.TTL, 90 | Fwd: args.Fwd, 91 | Port: args.Port, 92 | }, response) 93 | 94 | ls, _ := time.Unix(0, 0).MarshalText() 95 | 96 | // We add new save entry. If save entry already exists with 97 | // hash specified, we will just update it's last success timestamp 98 | if d.Restore.addEntry(saveEntry{ 99 | IP: args.IP, 100 | Mac: args.Mac, 101 | Dev: args.Dev, 102 | Hash: args.Hash, 103 | Keyfile: args.Keyfile, 104 | Key: args.Key, 105 | TTL: args.TTL, 106 | LastSuccess: string(ls), 107 | Enabled: true, 108 | }) != nil { 109 | d.Restore.bumpInstance(args.Hash) 110 | } 111 | err = d.Restore.save() 112 | if err != nil { 113 | ptp.Log(ptp.Error, "Failed to save instance information: %s", err.Error()) 114 | } 115 | resp, err := getResponse(response.ExitCode, response.Output) 116 | if err != nil { 117 | ptp.Log(ptp.Error, "Internal error: %s", err) 118 | return 119 | } 120 | w.Write(resp) 121 | } 122 | 123 | // Run starts a P2P instance 124 | func (d *Daemon) run(args *RunArgs, resp *Response) error { 125 | resp.ExitCode = 0 126 | resp.Output = "Running new P2P instance for " + args.Hash + "\n" 127 | 128 | ptp.Log(ptp.Trace, "Requested new P2P instance: %+v", args) 129 | 130 | // Validate if interface name is unique 131 | if args.Dev != "" { 132 | instances := d.Instances.get() 133 | for _, inst := range instances { 134 | if inst.PTP.Interface.GetName() == args.Dev { 135 | resp.ExitCode = 1 136 | resp.Output = "Device with specified name is already in use" 137 | return errors.New(resp.Output) 138 | } 139 | } 140 | } 141 | 142 | inst := d.Instances.getInstance(args.Hash) 143 | if inst == nil { 144 | resp.Output = resp.Output + "Lookup finished\n" 145 | if args.Key != "" { 146 | if len(args.Key) < 16 { 147 | args.Key += "0000000000000000"[:16-len(args.Key)] 148 | } else if len(args.Key) > 16 && len(args.Key) < 24 { 149 | args.Key += "000000000000000000000000"[:24-len(args.Key)] 150 | } else if len(args.Key) > 24 && len(args.Key) < 32 { 151 | args.Key += "00000000000000000000000000000000"[:32-len(args.Key)] 152 | } else if len(args.Key) > 32 { 153 | args.Key = args.Key[:32] 154 | } 155 | } 156 | 157 | newInst := new(P2PInstance) 158 | newInst.ID = args.Hash 159 | newInst.Args = *args 160 | newInst.PTP = ptp.New(args.Mac, args.Hash, args.Keyfile, args.Key, args.TTL, TargetURL, args.Fwd, args.Port, OutboundIP) 161 | if newInst.PTP == nil { 162 | resp.Output = resp.Output + "Failed to create P2P Instance" 163 | resp.ExitCode = 1 164 | return errors.New("Failed to create P2P Instance") 165 | } 166 | 167 | err := bootstrap.registerInstance(newInst.ID, newInst) 168 | if err != nil { 169 | ptp.Log(ptp.Error, "Failed to register instance with bootstrap nodes: %s", err.Error()) 170 | if newInst.PTP != nil { 171 | newInst.PTP.Close() 172 | newInst.PTP = nil 173 | } 174 | resp.Output = resp.Output + "Failed to register instance: %s" + err.Error() 175 | resp.ExitCode = 601 176 | return errors.New("Failed to register instance") 177 | } 178 | 179 | go newInst.PTP.ReadDHT() 180 | newInst.PTP.Dht.LocalPort = newInst.PTP.UDPSocket.GetPort() 181 | newInst.PTP.FindNetworkAddresses() 182 | err = newInst.PTP.Dht.Connect(newInst.PTP.LocalIPs, newInst.PTP.ProxyManager.GetList()) 183 | if err != nil { 184 | if newInst.PTP != nil { 185 | newInst.PTP.Close() 186 | newInst.PTP = nil 187 | } 188 | bootstrap.unregisterInstance(newInst.ID) 189 | resp.Output = resp.Output + err.Error() 190 | resp.ExitCode = 602 191 | return err 192 | } 193 | 194 | err = newInst.PTP.PrepareInterfaces(args.IP, args.Dev) 195 | if err != nil { 196 | ptp.Log(ptp.Error, "Failed to configure network interface: %s", err) 197 | if newInst.PTP != nil { 198 | newInst.PTP.Close() 199 | newInst.PTP = nil 200 | } 201 | bootstrap.unregisterInstance(newInst.ID) 202 | resp.Output = resp.Output + "Failed to configure network: " + err.Error() 203 | resp.ExitCode = 603 204 | return errors.New("Failed to configure network interface") 205 | } 206 | go newInst.PTP.ListenInterface() 207 | 208 | // Saving interface name 209 | infFound := false 210 | for _, inf := range InterfaceNames { 211 | if inf == newInst.PTP.Interface.GetName() { 212 | infFound = true 213 | } 214 | } 215 | if !infFound && newInst.PTP.Interface.GetName() != "" { 216 | InterfaceNames = append(InterfaceNames, newInst.PTP.Interface.GetName()) 217 | } 218 | 219 | usedIPs = append(usedIPs, newInst.PTP.Interface.GetIP().String()) 220 | ptp.Log(ptp.Info, "Instance created") 221 | newInst.Args.LastSuccess = time.Now() 222 | d.Instances.update(args.Hash, newInst) 223 | 224 | go newInst.PTP.Run() 225 | resp.Output = resp.Output + "Instance created: " + args.Hash + "\n" 226 | } else { 227 | resp.ExitCode = 119 228 | resp.Output = resp.Output + "Hash already in use\n" 229 | } 230 | return nil 231 | } 232 | -------------------------------------------------------------------------------- /status.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | 9 | ptp "github.com/subutai-io/p2p/lib" 10 | ) 11 | 12 | type statusResponse struct { 13 | Instances []*statusInstance `json:"instances"` 14 | Code int `json:"code"` 15 | } 16 | 17 | type statusInstance struct { 18 | ID string `json:"id"` 19 | IP string `json:"ip"` 20 | Peers []*statusPeer `json:"peers"` 21 | } 22 | 23 | type statusPeer struct { 24 | ID string `json:"id"` 25 | IP string `json:"ip"` 26 | State string `json:"state"` 27 | LastError string `json:"lastError"` 28 | } 29 | 30 | // CommandStatus outputs connectivity status of each peer 31 | func CommandStatus(restPort int, hash string) { 32 | out, err := sendRequestRaw(restPort, "status", &request{Hash: hash}) 33 | if err != nil { 34 | fmt.Fprintln(os.Stderr, err.Error()) 35 | os.Exit(1) 36 | } 37 | 38 | response := new(statusResponse) 39 | err = json.Unmarshal(out, response) 40 | if err != nil { 41 | fmt.Fprintf(os.Stderr, "Failed to unmarshal status response: %s", err) 42 | os.Exit(125) 43 | } 44 | 45 | if response.Code != 0 { 46 | fmt.Fprintln(os.Stderr, "Failed to execute `status` command") 47 | os.Exit(response.Code) 48 | } 49 | 50 | if len(hash) == 0 { 51 | for _, instance := range response.Instances { 52 | if len(hash) == 0 { 53 | fmt.Printf("%s|%s\n", instance.ID, instance.IP) 54 | } 55 | for _, peer := range instance.Peers { 56 | if len(hash) == 0 { 57 | fmt.Printf("%s|", peer.ID) 58 | } 59 | fmt.Printf("%s|State:%s|", peer.IP, peer.State) 60 | if peer.LastError != "" { 61 | fmt.Printf("LastError:%s", peer.LastError) 62 | } 63 | fmt.Printf("\n") 64 | } 65 | } 66 | } else { 67 | fmt.Printf("[\n") 68 | for _, instance := range response.Instances { 69 | i := 0 70 | for _, peer := range instance.Peers { 71 | i++ 72 | fmt.Printf("\t{\n") 73 | fmt.Printf("\t\t\"ip\": \"%s\",\n", peer.IP) 74 | fmt.Printf("\t\t\"state\": \"%s\"", peer.State) 75 | if peer.LastError != "" { 76 | fmt.Printf(",\n") 77 | fmt.Printf("\t\t\"last_error\": \"%s\"\n", peer.IP) 78 | } else { 79 | fmt.Printf("\n") 80 | } 81 | fmt.Printf("\t}") 82 | if i != len(instance.Peers) { 83 | fmt.Printf(",") 84 | } 85 | fmt.Printf("\n") 86 | } 87 | } 88 | fmt.Printf("]\n") 89 | } 90 | os.Exit(0) 91 | } 92 | 93 | func (d *Daemon) execRESTStatus(w http.ResponseWriter, r *http.Request) { 94 | if !ReadyToServe { 95 | resp, _ := getResponse(105, "P2P Daemon is in initialization state") 96 | w.Write(resp) 97 | return 98 | } 99 | if !bootstrap.isActive { 100 | resp, _ := getResponse(106, "Not connected to DHT nodes") 101 | w.Write(resp) 102 | return 103 | } 104 | if bootstrap.ip == "" { 105 | resp, _ := getResponse(107, "Didn't received outbound IP yet") 106 | w.Write(resp) 107 | return 108 | } 109 | args := new(DaemonArgs) 110 | err := getJSON(r.Body, args) 111 | if handleMarshalError(err, w) != nil { 112 | return 113 | } 114 | response, err := d.Status(args.Hash) 115 | if err != nil { 116 | ptp.Log(ptp.Error, "Internal error: %s", err) 117 | return 118 | } 119 | output, err := json.Marshal(response) 120 | if err != nil { 121 | ptp.Log(ptp.Error, "Failed to marshal status response: %s", err) 122 | return 123 | } 124 | w.Write(output) 125 | } 126 | 127 | // Status displays information about instances, peers and their statuses 128 | func (d *Daemon) Status(hash string) (*statusResponse, error) { 129 | response := &statusResponse{} 130 | if !ReadyToServe { 131 | response.Code = 105 132 | return response, nil 133 | } 134 | response.Instances = []*statusInstance{} 135 | instances := d.Instances.get() 136 | for _, inst := range instances { 137 | id := inst.ID 138 | if hash != "" { 139 | if hash != inst.ID { 140 | continue 141 | } 142 | id = "" 143 | } 144 | instance := &statusInstance{ 145 | ID: id, 146 | IP: inst.PTP.Interface.GetIP().String(), 147 | } 148 | peers := inst.PTP.Swarm.Get() 149 | for _, peer := range peers { 150 | instance.Peers = append(instance.Peers, &statusPeer{ 151 | ID: peer.ID, 152 | IP: peer.PeerLocalIP.String(), 153 | State: ptp.StringifyState(peer.State), 154 | LastError: peer.LastError, 155 | }) 156 | } 157 | response.Instances = append(response.Instances, instance) 158 | } 159 | return response, nil 160 | } 161 | -------------------------------------------------------------------------------- /stop.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | 8 | ptp "github.com/subutai-io/p2p/lib" 9 | ) 10 | 11 | // CommandStop will terminate P2P instance 12 | // Function will send a request to the /stop/ REST endpoint with 13 | // specified hash that is needed to stop or interface name that's 14 | // needed to be removed from saved interfaces list 15 | func CommandStop(rpcPort int, hash, dev string) { 16 | args := &DaemonArgs{} 17 | if hash != "" { 18 | args.Hash = hash 19 | args.Dev = "" 20 | } else if dev != "" { 21 | args.Dev = dev 22 | args.Hash = "" 23 | } else { 24 | fmt.Printf("Not enough parameters for stop command") 25 | return 26 | } 27 | out, err := sendRequest(rpcPort, "stop", args) 28 | if err != nil { 29 | fmt.Fprintln(os.Stderr, err.Error()) 30 | os.Exit(1) 31 | } 32 | 33 | fmt.Println(out.Message) 34 | os.Exit(out.Code) 35 | } 36 | 37 | func (d *Daemon) execRESTStop(w http.ResponseWriter, r *http.Request) { 38 | if !ReadyToServe { 39 | resp, _ := getResponse(105, "P2P Daemon is in initialization state") 40 | w.Write(resp) 41 | return 42 | } 43 | if !bootstrap.isActive { 44 | resp, _ := getResponse(106, "Not connected to DHT nodes") 45 | w.Write(resp) 46 | return 47 | } 48 | if bootstrap.ip == "" { 49 | resp, _ := getResponse(107, "Didn't received outbound IP yet") 50 | w.Write(resp) 51 | return 52 | } 53 | args := new(DaemonArgs) 54 | err := getJSON(r.Body, args) 55 | if handleMarshalError(err, w) != nil { 56 | return 57 | } 58 | ptp.Log(ptp.Debug, "Executing stop command: %+v", args) 59 | response := new(Response) 60 | d.Stop(&DaemonArgs{ 61 | Hash: args.Hash, 62 | Dev: args.Dev, 63 | }, response) 64 | resp, err := getResponse(response.ExitCode, response.Output) 65 | if err != nil { 66 | ptp.Log(ptp.Error, "Internal error: %s", err) 67 | return 68 | } 69 | w.Write(resp) 70 | } 71 | 72 | // Stop is used to terminate a specific P2P instance 73 | func (p *Daemon) Stop(args *DaemonArgs, resp *Response) error { 74 | resp.ExitCode = 0 75 | if args.Hash != "" { 76 | inst := p.Instances.getInstance(args.Hash) 77 | if inst == nil { 78 | resp.ExitCode = 1 79 | resp.Output = "Instance with hash " + args.Hash + " was not found" 80 | return nil 81 | } else { 82 | ip := inst.PTP.Interface.GetIP().String() 83 | resp.Output = "Shutting down " + args.Hash 84 | inst.PTP.Close() 85 | p.Instances.delete(args.Hash) 86 | if p.Restore.isActive() { 87 | err := p.Restore.removeEntry(args.Hash) 88 | if err != nil { 89 | ptp.Log(ptp.Error, err.Error()) 90 | } else { 91 | err := p.Restore.save() 92 | if err != nil { 93 | ptp.Log(ptp.Error, "Failed to save dump: %s", err.Error()) 94 | } 95 | } 96 | } 97 | k := 0 98 | for k, i := range usedIPs { 99 | if i != ip { 100 | usedIPs[k] = i 101 | k++ 102 | } 103 | } 104 | usedIPs = usedIPs[:k] 105 | bootstrap.unregisterInstance(args.Hash) 106 | return nil 107 | } 108 | } else if args.Dev != "" { 109 | resp.Output = "Removing " + args.Dev 110 | instances := p.Instances.get() 111 | for i, inf := range InterfaceNames { 112 | if inf == args.Dev { 113 | for _, instance := range instances { 114 | if instance.PTP.Interface.GetName() == args.Dev { 115 | resp.ExitCode = 12 116 | resp.Output += "Can't remove interface: In use" 117 | return nil 118 | } 119 | } 120 | InterfaceNames = append(InterfaceNames[:i], InterfaceNames[i+1:]...) 121 | resp.ExitCode = 0 122 | resp.Output += "Removed " + args.Dev 123 | return nil 124 | } 125 | } 126 | resp.ExitCode = 1 127 | resp.Output += "Interface was not found" 128 | return nil 129 | } 130 | resp.ExitCode = 2 131 | resp.Output = "Not enough parameters for stop" 132 | return nil 133 | } 134 | -------------------------------------------------------------------------------- /upload-ipfs.sh: -------------------------------------------------------------------------------- 1 | BRANCH=$1 2 | OS=$2 3 | 4 | upload_ipfs (){ 5 | filename=$1 6 | user="jenkins@optimal-dynamics.com" 7 | fingerprint="877B586E74F170BC4CF6ECABB971E2AC63D23DC9" 8 | cdnHost=$2 9 | extract_id() 10 | { 11 | id_src=$(echo $json | grep "id") 12 | id=${id_src:10:46} 13 | } 14 | 15 | json=`curl -k -s -X GET ${cdnHost}/rest/v1/cdn/raw?name=$filename` 16 | echo "Received: $json" 17 | extract_id 18 | echo "Previous file ID is $id" 19 | 20 | authId="$(curl -s ${cdnHost}/rest/v1/cdn/token?fingerprint=${fingerprint})" 21 | echo "Auth id obtained and signed $authId" 22 | 23 | sign="$(echo ${authId} | gpg --clearsign -u ${user})" 24 | token="$(curl -s --data-urlencode "request=${sign}" ${cdnHost}/rest/v1/cdn/token)" 25 | echo "Token obtained $token" 26 | 27 | echo "Uploading file..." 28 | upl_msg="$(curl -sk -H "token: ${token}" -Ffile=@$filename -Ftoken=${token} -X POST "${cdnHost}/rest/v1/cdn/uploadRaw")" 29 | echo "$upl_msg" 30 | 31 | echo "Removing previous" 32 | echo $Upload 33 | if [[ -n "$id" ]] && [[ $upl_msg != "An object with id: $id is exist in Bazaar. Increase the file version." ]] 34 | then 35 | curl -k -s -X DELETE "$cdnHost/rest/v1/cdn/raw?token=${token}&id=$id" 36 | fi 37 | echo -e "\\nCompleted" 38 | } 39 | 40 | case $OS in 41 | Linux) 42 | BASENAME="p2p" 43 | BIN_EXT="" 44 | BIN_DIR="p2p/debian/subutai-p2p/usr/bin" 45 | ;; 46 | MSYS_NT-10.0) 47 | BASENAME="p2p.exe" 48 | BIN_EXT=".exe" 49 | BIN_DIR="bin" 50 | ;; 51 | Darwin) 52 | BASENAME="p2p_osx" 53 | BIN_EXT="_osx" 54 | BIN_DIR="bin" 55 | ;; 56 | esac 57 | 58 | case $BRANCH in 59 | dev) 60 | BINNAME="p2p-dev$BIN_EXT" 61 | cd $BIN_DIR 62 | cp $BASENAME $BINNAME 63 | IPFSURL=https://devbazaar.subutai.io 64 | upload_ipfs $BINNAME $IPFSURL 65 | ;; 66 | master) 67 | BINNAME="p2p-master$BIN_EXT" 68 | cd $BIN_DIR 69 | cp $BASENAME $BINNAME 70 | IPFSURL=https://masterbazaar.subutai.io 71 | upload_ipfs $BINNAME $IPFSURL 72 | ;; 73 | head) 74 | BINNAME="p2p$BIN_EXT" 75 | if [ $OS = Linux ] || [$OS = MSYS_NT-10.0 ] 76 | then 77 | cd $BIN_DIR 78 | cp $BASENAME $BINNAME 79 | IPFSURL=https://bazaar.subutai.io 80 | upload_ipfs $BINNAME $IPFSURL 81 | fi 82 | ;; 83 | HEAD) 84 | BINNAME="subutai-p2p$PKG_EXT" 85 | if [ $OS = Linux ] || [$OS = MSYS_NT-10.0 ] 86 | then 87 | cd $BIN_DIR 88 | cp $BASENAME $BINNAME 89 | IPFSURL=https://bazaar.subutai.io 90 | upload_ipfs $BINNAME $IPFSURL 91 | fi 92 | ;; 93 | esac 94 | 95 | echo "---------" 96 | echo $BINNAME 97 | echo $OS 98 | echo $BRANCH 99 | echo $VERSION 100 | echo "---------" --------------------------------------------------------------------------------