├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── android
├── .gitignore
├── README.md
├── app
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── decoyrouting
│ │ │ └── tapdanceclient
│ │ │ └── ExampleInstrumentedTest.java
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── assets
│ │ │ ├── pubkey.dev
│ │ │ └── root.pem
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── decoyrouting
│ │ │ │ └── tapdanceclient
│ │ │ │ └── MainActivity.java
│ │ └── res
│ │ │ ├── drawable
│ │ │ ├── border_stdout.xml
│ │ │ ├── button.xml
│ │ │ └── button_normal.xml
│ │ │ ├── layout
│ │ │ ├── activity_main.xml
│ │ │ └── content_main.xml
│ │ │ ├── menu
│ │ │ └── menu_main.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── values-v21
│ │ │ └── styles.xml
│ │ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ │ └── values
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── decoyrouting
│ │ └── tapdanceclient
│ │ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── proxybind
│ └── build.gradle
└── settings.gradle
├── assets
├── ClientConf
├── ClientConf.dev
├── ClientConf.old
├── roots
└── station_pubkey_test
├── build-apk.sh
├── build
├── EmbeddedValues.java.enc
├── PsiphonCoreGradle.patch
├── TunnelManager.conjure.java.patch
├── TunnelManager.tapdance.java.patch
├── conjure.golang.patch
└── tapdance
│ ├── ClientConf
│ └── roots
├── cli
├── README.md
├── assets
├── cli.dockerfile
└── main.go
├── getifaddr
├── getifaddr.go
└── getifaddr_test.go
├── go.mod
├── go.sum
├── gobind
├── README.md
└── gobind.go
├── protobuf
├── Makefile
├── README.md
├── proto_test.go
├── signalling.pb.go
└── signalling.proto
├── tapdance
├── assets
├── assets-phantoms_test.go
├── assets.go
├── assets_test.go
├── common.go
├── conjure.go
├── conjure_overrides_test.go
├── conjure_test.go
├── conn_flow.go
├── conn_raw.go
├── counter.go
├── dialer.go
├── dialer_test.go
├── interfaces.go
├── logger.go
├── tapdance.go
├── utils.go
└── utils_test.go
├── tdproxy
├── README.md
├── flow.go
├── gengodoc.sh
├── tapdance.go
└── tapdance_test.go
├── test_scripts
├── README.md
├── go-1.7.4_wget.sh
├── ip.sh
├── nc_send.sh
├── psiphon_digest_cc.sh
├── seq.py
├── ssh-td.sh
└── twitter_wget.sh
└── tools
├── README.md
├── cjprobe
├── README.md
├── cjprobe.go
└── cjprobe_test.go
├── clientconf
├── clientconf.go
└── clientconf_test.go
├── elligator-test
└── elligator-test.go
├── gen-bias
├── README.md
└── main.go
├── makefile
├── ripe-atlas-probes
├── probes.json
├── setup.go
└── targets.json
├── utls-test
└── main.go
└── v6lookup
└── main.go
/.gitignore:
--------------------------------------------------------------------------------
1 | cli/cli
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Refraction Networking is a free-to-use anti-censorship technology, that places proxies at Internet Service Providers, so they are harder to block. This client includes support for both the TapDance and Conjure protocols
6 |
7 |
8 |
9 |
10 |
11 |
12 | # Build
13 | ## Download Golang and TapDance and dependencies
14 | 0. Install [Golang](https://golang.org/dl/) (currently tested against version 1.10 and latest).
15 |
16 | 1. Get source code for Go TapDance and all dependencies:
17 |
18 | ```bash
19 | go get -d -u -t github.com/refraction-networking/gotapdance/...
20 | ```
21 | Ignore the "no buildable Go source files" warning.
22 |
23 | If you have outdated versions of libraries used, you might want to do `go get -u all`.
24 |
25 | ## Usage
26 |
27 | There are 3 supported ways to use TapDance:
28 |
29 | * [Command Line Interface client](cli)
30 |
31 | * [Psiphon](https://psiphon.ca/) Android app integrated TapDance as one of their transports.
32 |
33 | * Use tapdance directly from other Golang program:
34 |
35 | ```Golang
36 | package main
37 |
38 | import (
39 | "github.com/refraction-networking/gotapdance/tapdance"
40 | "fmt"
41 | )
42 |
43 | func main() {
44 | // first, copy ClientConf and roots files into assets directory
45 | // make sure assets directory is writable (only) by the td process
46 | tapdance.AssetsSetDir("./path/to/assets/dir/")
47 |
48 | tdConn, err := tapdance.Dial("tcp", "censoredsite.com:80")
49 | if err != nil {
50 | fmt.Printf("tapdance.Dial() failed: %+v\n", err)
51 | return
52 | }
53 | // tdConn implements standard net.Conn, allowing to use it like any other Golang conn with
54 | // Write(), Read(), Close() etc. It also allows to pass tdConn to functions that expect
55 | // net.Conn, such as tls.Client() making it easy to do tls handshake over TapDance conn.
56 | _, err = tdConn.Write([]byte("GET / HTTP/1.1\nHost: censoredsite.com\n\n"))
57 | if err != nil {
58 | fmt.Printf("tdConn.Write() failed: %+v\n", err)
59 | return
60 | }
61 | buf := make([]byte, 16384)
62 | _, err = tdConn.Read(buf)
63 | // ...
64 | }
65 | ```
66 |
67 | * [CURRENTLY NOT MAINTAINED] Standalone TapDance mobile applications that use [Golang Bindings](gobind) as a shared library.
68 |
69 | * [Android application in Java](android)
70 |
71 |
72 | # Links
73 |
74 | [Refraction Networking](https://refraction.network) is an umbrella term for the family of similarly working technnologies.
75 |
76 | TapDance station code released for FOCI'17 on github: [refraction-networking/tapdance](https://github.com/refraction-networking/tapdance)
77 |
78 | Original 2014 paper: ["TapDance: End-to-Middle Anticensorship without Flow Blocking"](https://ericw.us/trow/tapdance-sec14.pdf)
79 |
80 | Newer(2017) paper that shows TapDance working at high-scale: ["An ISP-Scale Deployment of TapDance"](https://www.usenix.org/system/files/conference/foci17/foci17-paper-frolov_0.pdf)
81 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 | .idea
--------------------------------------------------------------------------------
/android/README.md:
--------------------------------------------------------------------------------
1 | # Build
2 | The project is built with Gradle.
3 |
4 | ### Command line
5 | Assemble app.
6 | ````
7 | bash$ cd gotapdance/android/app
8 | bash$ ../gradlew assemble
9 | ````
10 | Install app on device or emulator.
11 | ````
12 | bash$ cd gotapdance/android/app
13 | bash$ ../gradlew install
14 | ````
15 | Developer mode have to be enabled on device.
16 |
17 | ### IDE
18 | Tapdance also can be built from Android Studio or Intellij Idea via standard interface.
19 |
20 | # Screenshot
21 | One of the latest versions looks like this:
22 |
23 |
24 |
--------------------------------------------------------------------------------
/android/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | buildDir = 'build'
4 |
5 | android {
6 | compileSdkVersion 25
7 | buildToolsVersion "25.0.0"
8 | defaultConfig {
9 | applicationId "com.decoyrouting.tapdanceclient"
10 | minSdkVersion 15
11 | targetSdkVersion 25
12 | versionCode 1
13 | versionName "1.0"
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 | ndk {
16 | abiFilters "armeabi-v7a", "x86"
17 | }
18 | }
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | }
26 |
27 |
28 | dependencies {
29 | compile fileTree(include: ['*.jar'], dir: 'libs')
30 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
31 | exclude group: 'com.android.support', module: 'support-annotations'
32 | })
33 | compile 'com.android.support:appcompat-v7:25.0.0'
34 | compile 'com.android.support:design:25.0.0'
35 | testCompile 'junit:junit:4.12'
36 | compile project(':proxybind')
37 | }
38 |
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /home/sfrolov/Android/Sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/android/app/src/androidTest/java/com/decoyrouting/tapdanceclient/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.decoyrouting.tapdanceclient;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.decoyrouting.tapdanceclient", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/android/app/src/main/assets/pubkey.dev:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refraction-networking/gotapdance/0e1cc0a3cb1aabc8f4012012ef7f938d92e9f94d/android/app/src/main/assets/pubkey.dev
--------------------------------------------------------------------------------
/android/app/src/main/java/com/decoyrouting/tapdanceclient/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.decoyrouting.tapdanceclient;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.support.v7.widget.Toolbar;
6 | import android.text.method.ScrollingMovementMethod;
7 | import android.view.View;
8 | import android.view.Menu;
9 | import android.view.MenuItem;
10 | import android.widget.Button;
11 | import android.widget.TextView;
12 |
13 | import java.io.IOException;
14 | import java.io.InputStream;
15 |
16 | import go.gobind.*;
17 |
18 | public class MainActivity extends AppCompatActivity {
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 |
24 | setContentView(R.layout.activity_main);
25 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
26 | setSupportActionBar(toolbar);
27 |
28 | final TextView stdout_tv = (TextView) findViewById(R.id.et_stdout);
29 | stdout_tv.setMovementMethod(ScrollingMovementMethod.getInstance());
30 |
31 | final TextView tvState = (TextView) findViewById(R.id.state);
32 | final Button launchButton = (Button) findViewById(R.id.launchButton);
33 |
34 | new Thread() {
35 | @Override
36 | public void run() {
37 | try {
38 | while (!isInterrupted()) {
39 | Thread.sleep(100);
40 | runOnUiThread(new Runnable() {
41 | @Override
42 | public void run() {
43 | String stats = gobind.Gobind.getStats();
44 | Boolean isListening = gobind.Gobind.isListening();
45 | tvState.setText(stats);
46 | if (isListening) {
47 | launchButton.setText("Stop");
48 | } else {
49 | launchButton.setText("Launch");
50 | }
51 | stdout_tv.append(gobind.Gobind.getLog().toString());
52 | }
53 | });
54 | }
55 | } catch (InterruptedException e) {
56 | }
57 | }
58 | }.start();
59 |
60 | launchButton.setOnClickListener(new View.OnClickListener() {
61 | @Override
62 | public void onClick(View v) {
63 | new Thread(new Runnable() {
64 | public void run() {
65 | try {
66 | if (gobind.Gobind.isListening()) {
67 | gobind.Gobind.stop();
68 | } else {
69 | gobind.Gobind.listen();
70 | }
71 | } catch (final Exception ex) {
72 | runOnUiThread(new Runnable() {
73 | @Override
74 | public void run() {
75 | try {
76 | stdout_tv.append(ex.toString() + "\n");
77 | } catch (Exception e) {
78 | }
79 | }
80 | });
81 | }
82 | }
83 | }).start();
84 | }
85 | });
86 |
87 | try {
88 | gobind.Gobind.newDecoyProxy(10500);
89 | } catch (Exception ex) {
90 | System.out.println(ex);
91 | }
92 | }
93 |
94 | @Override
95 | public boolean onCreateOptionsMenu(Menu menu) {
96 | // Inflate the menu; this adds items to the action bar if it is present.
97 | getMenuInflater().inflate(R.menu.menu_main, menu);
98 | return true;
99 | }
100 |
101 | @Override
102 | public boolean onOptionsItemSelected(MenuItem item) {
103 | // Handle action bar item clicks here. The action bar will
104 | // automatically handle clicks on the Home/Up button, so long
105 | // as you specify a parent activity in AndroidManifest.xml.
106 | int id = item.getItemId();
107 |
108 | //noinspection SimplifiableIfStatement
109 | if (id == R.id.action_settings) {
110 | return true;
111 | }
112 |
113 | return super.onOptionsItemSelected(item);
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/border_stdout.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
19 |
20 |
21 |
26 |
27 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/button.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/button_normal.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
14 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
29 |
41 |
58 |
66 |
67 |
--------------------------------------------------------------------------------
/android/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refraction-networking/gotapdance/0e1cc0a3cb1aabc8f4012012ef7f938d92e9f94d/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refraction-networking/gotapdance/0e1cc0a3cb1aabc8f4012012ef7f938d92e9f94d/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refraction-networking/gotapdance/0e1cc0a3cb1aabc8f4012012ef7f938d92e9f94d/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refraction-networking/gotapdance/0e1cc0a3cb1aabc8f4012012ef7f938d92e9f94d/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refraction-networking/gotapdance/0e1cc0a3cb1aabc8f4012012ef7f938d92e9f94d/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
18 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #7e57c2
4 | #5e35b1
5 | #ede7f6
6 | #311b92
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Tapdance Client
3 | Settings
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
18 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/android/app/src/test/java/com/decoyrouting/tapdanceclient/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.decoyrouting.tapdanceclient;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildDir = 'build'
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.2'
9 | // NOTE: Do not place your application dependencies here; they belong
10 | // in the individual module build.gradle files
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | jcenter()
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
19 | android.useDeprecatedNdk=true
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refraction-networking/gotapdance/0e1cc0a3cb1aabc8f4012012ef7f938d92e9f94d/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Dec 04 01:24:29 MSK 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-bin.zip
7 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn ( ) {
37 | echo "$*"
38 | }
39 |
40 | die ( ) {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save ( ) {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/android/proxybind/build.gradle:
--------------------------------------------------------------------------------
1 | //configurations.maybeCreate("default")
2 | //artifacts.add("default", file('proxybind.aar'))
3 |
4 | plugins {
5 | id "org.golang.mobile.bind" version "0.2.7"
6 | }
7 |
8 | buildDir = 'build'
9 |
10 | def goPath = System.env.GOPATH
11 | if (!goPath) {
12 | // If GOPATH is unset, try default one
13 | goPath = System.env.HOME + "/go/"
14 | }
15 |
16 | gobind {
17 | pkg = "github.com/refraction-networking/gotapdance/gobind"
18 | GOPATH = goPath
19 | GOMOBILE = "$goPath/bin/gomobile"
20 | }
21 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'Tapdance'
2 |
3 | include ':app', ':proxybind'
4 |
--------------------------------------------------------------------------------
/assets/ClientConf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refraction-networking/gotapdance/0e1cc0a3cb1aabc8f4012012ef7f938d92e9f94d/assets/ClientConf
--------------------------------------------------------------------------------
/assets/ClientConf.dev:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refraction-networking/gotapdance/0e1cc0a3cb1aabc8f4012012ef7f938d92e9f94d/assets/ClientConf.dev
--------------------------------------------------------------------------------
/assets/ClientConf.old:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refraction-networking/gotapdance/0e1cc0a3cb1aabc8f4012012ef7f938d92e9f94d/assets/ClientConf.old
--------------------------------------------------------------------------------
/assets/station_pubkey_test:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refraction-networking/gotapdance/0e1cc0a3cb1aabc8f4012012ef7f938d92e9f94d/assets/station_pubkey_test
--------------------------------------------------------------------------------
/build-apk.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 |
4 | REPO_DIR=$GOPATH/src/github.com/refraction-networking/gotapdance/
5 |
6 | #sed -i.bak "s/buildInfo = \"\"/buildInfo = \"$TRAVIS_BRANCH-$TRAVIS_COMMIT\"/" tapdance/logger.go
7 | git clone https://github.com/Psiphon-Labs/psiphon-tunnel-core.git $GOPATH/src/github.com/Psiphon-Labs/psiphon-tunnel-core
8 | cd $GOPATH/src/github.com/Psiphon-Labs/psiphon-tunnel-core && git checkout -b build-refraction-networking
9 |
10 |
11 | go get github.com/kardianos/govendor
12 | cd $GOPATH/src/github.com/Psiphon-Labs/psiphon-tunnel-core && $GOPATH/bin/govendor remove github.com/refraction-networking/gotapdance/...
13 |
14 | sed -i.bak 's/refraction_networking_tapdance.Logger().Out = ioutil.Discard//' $GOPATH/src/github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/tapdance/tapdance.go
15 |
16 |
17 | # Conjure APK
18 | echo "Conjure APK before_Script"
19 |
20 | cd $GOPATH/src/github.com/refraction-networking/gotapdance
21 |
22 | docker pull refraction/psiandroid:latest
23 |
24 | mkdir -p $GOPATH/src/bitbucket.org/psiphon
25 | hg clone https://bitbucket.org/psiphon/psiphon-circumvention-system $GOPATH/src/bitbucket.org/psiphon/psiphon-circumvention-system
26 |
27 | cd $GOPATH/src/bitbucket.org/psiphon/psiphon-circumvention-system #&& hg checkout
28 |
29 | # Use modified EmbeddedValues.java for TapDance
30 | cd $GOPATH/src/github.com/refraction-networking/gotapdance
31 | /usr/local/ssl/bin/openssl enc -nosalt -aes-256-cbc -md sha512 -pbkdf2 -iter 1000 -pass pass:$aes_cbc_passwd -d -in build/EmbeddedValues.java.enc -out $GOPATH/src/bitbucket.org/psiphon/psiphon-circumvention-system/Android/app/src/main/java/com/psiphon3/psiphonlibrary/EmbeddedValues.java
32 |
33 | echo "patching..."
34 | # Patched tunneling protocol for TapDance
35 | patch $GOPATH/src/bitbucket.org/psiphon/psiphon-circumvention-system/Android/app/src/main/java/com/psiphon3/psiphonlibrary/TunnelManager.java build/TunnelManager.java.patch
36 |
37 | # Patch the Psiphon app's gradle build for java 1.8 compatibility [TODO]{priority:later} remove this when psiphon merges it themselves
38 | patch $GOPATH/src/bitbucket.org/psiphon/psiphon-circumvention-system/Android/app/build.gradle build/PsiphonCoreGradle.patch
39 |
40 | # Add dialer options to enable Conjure
41 | patch $GOPATH/src/github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/tapdance/tapdance.go build/conjure.golang.patch
42 |
43 | echo "digesting..."
44 | # Digest this branch's ClientConf into Psiphon's embedded_config
45 | ./test_scripts/psiphon_digest_cc.sh ./assets/ClientConf $GOPATH/src/github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/tapdance/embedded_config.go
46 |
47 | echo "Conjure APK script"
48 | cd $REPO_DIR
49 |
50 | # Build Psiphon Android Library ca.psiphon.aar
51 | #docker run -v $DOCKER_DIR:$GOPATH/go/src/github.com/refraction-networking/gotapdance -v $GOPATH/src/github.com/Psiphon-Labs/psiphon-tunnel-core:$GOPATH/go/src/github.com/Psiphon-Labs/psiphon-tunnel-core refraction/psiandroid /bin/bash -c 'cd $GOPATH/src/github.com/Psiphon-Labs/psiphon-tunnel-core/MobileLibrary/Android && ./make.bash "TAPDANCE"'
52 | docker run -v $REPO_DIR:/go/src/github.com/refraction-networking/gotapdance -v $GOPATH/src/github.com/Psiphon-Labs/psiphon-tunnel-core:/go/src/github.com/Psiphon-Labs/psiphon-tunnel-core refraction/psiandroid /bin/bash -c 'cd /go/src/github.com/Psiphon-Labs/psiphon-tunnel-core/MobileLibrary/Android && ./make.bash "TAPDANCE"'
53 |
54 | echo "moving .aar"
55 | cd $GOPATH/src/github.com/refraction-networking/gotapdance
56 | mv $GOPATH/src/github.com/Psiphon-Labs/psiphon-tunnel-core/MobileLibrary/Android/ca.psiphon.aar build/
57 |
58 | # Build Psiphon Android App PsiphonAndroid-debug.apk
59 | cp -f build/ca.psiphon.aar $GOPATH/src/bitbucket.org/psiphon/psiphon-circumvention-system/Android/app/libs/
60 |
61 | cd $GOPATH/src/github.com/refraction-networking/gotapdance
62 | echo "docker run gradlew assembleDebug.."
63 | #docker run -v $DOCKER_DIR:/go/src/github.com/refraction-networking/gotapdance -v $GOPATH/src/bitbucket.org/psiphon/psiphon-circumvention-system/Android:/go/src/bitbucket.org/psiphon/psiphon-circumvention-system/Android refraction/psiandroid /bin/bash -c 'yes | /android-sdk-linux/tools/bin/sdkmanager --update && cd /go/src/bitbucket.org/psiphon/psiphon-circumvention-system/Android && ./gradlew assembleDebug'
64 | docker run -v $REPO_DIR:/go/src/github.com/refraction-networking/gotapdance -v $GOPATH/src/bitbucket.org/psiphon/psiphon-circumvention-system/Android:/go/src/bitbucket.org/psiphon/psiphon-circumvention-system/Android refraction/psiandroid /bin/bash -c 'yes | /android-sdk-linux/tools/bin/sdkmanager --update && cd /go/src/bitbucket.org/psiphon/psiphon-circumvention-system/Android && ./gradlew assembleDebug'
65 |
66 | cp $GOPATH/src/bitbucket.org/psiphon/psiphon-circumvention-system/Android/app/build/outputs/apk/debug/PsiphonAndroid-debug.apk build/PsiphonAndroid-CJ-debug.apk
67 |
68 | pwd
69 | echo "build/PsiphonAndroid-CJ-debug.apk"
70 |
--------------------------------------------------------------------------------
/build/EmbeddedValues.java.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refraction-networking/gotapdance/0e1cc0a3cb1aabc8f4012012ef7f938d92e9f94d/build/EmbeddedValues.java.enc
--------------------------------------------------------------------------------
/build/PsiphonCoreGradle.patch:
--------------------------------------------------------------------------------
1 | diff -r 00488348a5f0 Android/app/build.gradle
2 | --- a/Android/app/build.gradle Tue Oct 22 15:38:52 2019 -0400
3 | +++ b/Android/app/build.gradle Wed Oct 30 14:28:38 2019 -0600
4 | @@ -40,6 +40,11 @@
5 | release
6 | }
7 |
8 | + compileOptions {
9 | + sourceCompatibility JavaVersion.VERSION_1_8
10 | + targetCompatibility JavaVersion.VERSION_1_8
11 | + }
12 | +
13 | buildTypes {
14 | debug {
15 | pseudoLocalesEnabled true
16 |
--------------------------------------------------------------------------------
/build/TunnelManager.conjure.java.patch:
--------------------------------------------------------------------------------
1 | @@ -732,6 +732,9 @@
2 | json.put("EgressRegion", egressRegion);
3 | }
4 |
5 | + json.put("TunnelProtocol", "CONJURE-OSSH");
6 | + json.put("DisableTactics", true);
7 | +
8 | if (tunnelConfig.disableTimeouts) {
9 | //disable timeouts
10 | MyLog.g("DisableTimeouts", "disableTimeouts", true);
11 |
--------------------------------------------------------------------------------
/build/TunnelManager.tapdance.java.patch:
--------------------------------------------------------------------------------
1 | @@ -732,6 +732,9 @@
2 | json.put("EgressRegion", egressRegion);
3 | }
4 |
5 | + json.put("TunnelProtocol", "TAPDANCE-OSSH");
6 | + json.put("DisableTactics", true);
7 | +
8 | if (tunnelConfig.disableTimeouts) {
9 | //disable timeouts
10 | MyLog.g("DisableTimeouts", "disableTimeouts", true);
11 |
--------------------------------------------------------------------------------
/build/conjure.golang.patch:
--------------------------------------------------------------------------------
1 | diff --git a/psiphon/common/tapdance/tapdance.go b/psiphon/common/tapdance/tapdance.go
2 | index de08e792..85ccb8ba 100644
3 | --- a/psiphon/common/tapdance/tapdance.go
4 | +++ b/psiphon/common/tapdance/tapdance.go
5 | @@ -40,6 +40,7 @@ import (
6 | "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
7 | "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
8 | "github.com/armon/go-proxyproto"
9 | + refraction_networking_proto "github.com/refraction-networking/gotapdance/protobuf"
10 | refraction_networking_tapdance "github.com/refraction-networking/gotapdance/tapdance"
11 | )
12 |
13 | @@ -327,8 +328,15 @@ func Dial(
14 |
15 | manager := newDialManager(netDialer.DialContext)
16 |
17 | + registrar := refraction_networking_tapdance.DecoyRegistrar{}
18 | +
19 | tapdanceDialer := &refraction_networking_tapdance.Dialer{
20 | - TcpDialer: manager.dial,
21 | + TcpDialer: manager.dial,
22 | + DarkDecoy: true,
23 | + UseProxyHeader: true,
24 | + Width: 5,
25 | + DarkDecoyRegistrar: registrar,
26 | + Transport: refraction_networking_proto.TransportType_Min,
27 | }
28 |
29 | // If the dial context is cancelled, use dialManager to interrupt
30 |
--------------------------------------------------------------------------------
/build/tapdance/ClientConf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refraction-networking/gotapdance/0e1cc0a3cb1aabc8f4012012ef7f938d92e9f94d/build/tapdance/ClientConf
--------------------------------------------------------------------------------
/cli/README.md:
--------------------------------------------------------------------------------
1 | # Gotapdance CLI version
2 |
3 | # Build
4 | After [downloading Golang, TD and dependencies:](../README.md)
5 |
6 | ```sh
7 | cd ${GOPATH:-~/go}/src/github.com/refraction-networking/gotapdance/cli # works even if GOPATH is not set
8 | go build -a .
9 | ```
10 |
11 | # Usage
12 |
13 | Simply run
14 |
15 | ```sh
16 | ./cli
17 | ```
18 |
19 | to listen to local connections on default 10500 port.
20 |
21 | Then, you'll have a few options:
22 |
23 | ## Configure HTTP proxy
24 |
25 | You will need to ask your particular application(e.g. browser) to use 127.0.0.1:10500 as HTTP proxy.
26 | In Firefox (both mobile and desktop) I prefer to type ```about:config``` into address line and set the following:
27 |
28 | ```conf
29 | network.proxy.http_port = 10500
30 | network.proxy.http = 127.0.0.1
31 | network.proxy.ssl_port = 10500
32 | network.proxy.ssl = 127.0.0.1
33 | network.proxy.type = 1
34 | ```
35 |
36 | To disable proxying you may simply set ```network.proxy.type``` back to ```5``` or ```0```.
37 |
38 | The same settings are available in Firefox GUI: Preferences->Advanced->Network->Settings
39 |
40 | ## Configure ssh SOCKS proxy
41 |
42 | If you have access to some ssh server, say `socksserver`, you can set up ssh SOCKS tunnel.
43 | First, modify and add the following to `.ssh/config`:
44 |
45 | ```ssh
46 | Host socksserver-td
47 | Hostname 123.456.789.012
48 | User cookiemonster
49 | ProxyCommand nc -X connect -x 127.0.0.1:10500 %h %p
50 | ```
51 |
52 | then run `ssh -D1234 socksserver-td -4`
53 |
54 | Now in Firefox you could just go to Preferences->Advanced->Network->Settings and set SOCKSv5 host to localhost:1234.
55 |
56 | ## Some utilities use following enivoronment variables:
57 |
58 | ```bash
59 | export https_proxy=127.0.0.1:10500
60 | export http_proxy=127.0.0.1:10500
61 | wget https://twitter.com
62 | ```
63 |
64 | Most of the popular utilities also have a flag to specify a proxy.
65 |
66 | ## Docker
67 |
68 | A simple dockerfile is provided that instantiates a golang environment in which to
69 | run the cli. This is primarily meant to be used with [the GNS3 simulation
70 | environment](https://github.com/refraction-networking/conjure/wiki/GNS3-Simulation).
71 |
72 | To build the docker environemnt use:
73 |
74 | ```sh
75 | # run from repo root
76 | docker build -t gotapdance/cli -f cli/cli.dockerfile .
77 | ```
78 |
79 | The environemnt can then be attached to using a `docker exec` or using telnet
80 | in the case of gns3. See the [wiki page](https://docs.gns3.com/docs/emulators/create-a-docker-container-for-gns3)
81 | for local docker image builds in gns3 for more details on setting up local
82 | docker appliances in gns3.
83 |
--------------------------------------------------------------------------------
/cli/assets:
--------------------------------------------------------------------------------
1 | ../assets/
--------------------------------------------------------------------------------
/cli/cli.dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.20
2 |
3 | RUN apt-get update
4 | RUN apt-get install -y -f libzmq3-dev
5 |
6 | WORKDIR /go/src/github/refracction-networking/gotapdance
7 | COPY . .
8 |
9 | # RUN go get -d -v ./...
10 | RUN go mod download
11 | RUN go mod tidy
12 | RUN go install ./cli
13 |
14 | # no run / entrypoint specified. this containter is meant to be run w/
15 | # gns3 and connected to using terminal or telnet.
16 |
--------------------------------------------------------------------------------
/getifaddr/getifaddr.go:
--------------------------------------------------------------------------------
1 | package getifaddr
2 |
3 | /*
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | char ADDR[INET6_ADDRSTRLEN];
10 | */
11 | import "C"
12 | import (
13 | "unsafe"
14 | "net"
15 | )
16 |
17 | // Get all interfaces on the device and check if they support by identifying
18 | // a non-local interface with an assigned ipv6 address
19 | func SupportsIpv6() (bool) {
20 | var ifaces *C.struct_ifaddrs
21 |
22 | if getrc, _ := C.getifaddrs(&ifaces); getrc != 0 {
23 | return false
24 | }
25 | defer C.freeifaddrs(ifaces)
26 |
27 | for fi := ifaces; fi != nil; fi = fi.ifa_next {
28 |
29 | if fi.ifa_addr == nil || fi.ifa_addr.sa_family != C.AF_INET6 {
30 | continue
31 | }
32 |
33 | sa_in := (*C.struct_sockaddr_in6)(unsafe.Pointer(fi.ifa_addr))
34 | if C.inet_ntop(
35 | C.int(fi.ifa_addr.sa_family),
36 | unsafe.Pointer(&sa_in.sin6_addr),
37 | &C.ADDR[0],
38 | C.socklen_t(unsafe.Sizeof(C.ADDR))) != nil {
39 |
40 | IpStr := C.GoString((*C.char)(unsafe.Pointer(&C.ADDR[0])))
41 | if realInterfaceAddr( IpStr ) {
42 | return true
43 | }
44 | } else {
45 | continue
46 | }
47 |
48 | }
49 | return false
50 | }
51 |
52 | var netBlacklistv6 = map[string]string{
53 | "multicast": "ff00::/8",
54 | "private": "fc00::/7",
55 | "link": "fe80::/10",
56 | "lo": "::1/128",
57 | }
58 |
59 | func realInterfaceAddr(IPStr string) bool {
60 | addr := net.ParseIP( IPStr )
61 | if addr == nil {
62 | return false
63 | }
64 |
65 | for _, netStr := range netBlacklistv6{
66 | _, blacklistNet, err := net.ParseCIDR(netStr)
67 | if err != nil {
68 | return false
69 | }
70 | if blacklistNet.Contains(addr) {
71 | return false
72 | }
73 | }
74 | return true
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/getifaddr/getifaddr_test.go:
--------------------------------------------------------------------------------
1 | package getifaddr
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func DisabledTestIpV6Support(t *testing.T) {
9 | fmt.Printf("Supports IPV6: %t\n", SupportsIpv6())
10 | }
11 |
12 | func TestInterfaceExclusion(t *testing.T) {
13 | var badAddrs = []string{
14 | "ff00::1:22:8",
15 | "fcff::22:1234",
16 | "fe80::a684:828:3aea:c02",
17 | "fdd0:d118:70ed:0:a17e:ba2:ac97:b72d",
18 | "fe80::ebdd:eef3:fc1a:ffb",
19 | }
20 | var goodAddrs = []string{
21 | "11:22:33:44::1",
22 | "abcd::ef01",
23 | "2a03:2880:f11c:8083:face:b00c:0:25de",
24 | "2a03:2880:f11b:83:face:b00c:0:25",
25 | }
26 |
27 | for _, addr := range badAddrs {
28 | if realInterfaceAddr(addr) {
29 | t.Fatalf("Bad address not filtered: %s", addr)
30 | }
31 | }
32 | for _, addr := range goodAddrs {
33 | if !realInterfaceAddr(addr) {
34 | t.Fatalf("Good address wrongly filtered: %s", addr)
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/refraction-networking/gotapdance
2 |
3 | go 1.22.0
4 |
5 | toolchain go1.24.1
6 |
7 | require (
8 | github.com/jinzhu/copier v0.4.0
9 | github.com/keltia/ripe-atlas v0.0.0-20211221125000-f6eb808d5dc6
10 | github.com/pelletier/go-toml v1.9.5
11 | github.com/pkg/errors v0.9.1
12 | github.com/pkg/profile v1.7.0
13 | github.com/refraction-networking/conjure v0.7.12-0.20250520170513-22f6cf9e6e66
14 | github.com/refraction-networking/ed25519 v0.1.2
15 | github.com/refraction-networking/utls v1.6.7
16 | github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507
17 | github.com/sirupsen/logrus v1.9.3
18 | github.com/stretchr/testify v1.10.0
19 | gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/conjure v0.0.0-20250401212049-c593391b702a
20 | golang.org/x/crypto v0.33.0
21 | golang.org/x/net v0.35.0
22 | google.golang.org/protobuf v1.36.5
23 | )
24 |
25 | require (
26 | filippo.io/bigmod v0.0.3 // indirect
27 | filippo.io/keygen v0.0.0-20240718133620-7f162efbbd87 // indirect
28 | github.com/andybalholm/brotli v1.1.1 // indirect
29 | github.com/cloudflare/circl v1.5.0 // indirect
30 | github.com/davecgh/go-spew v1.1.1 // indirect
31 | github.com/dchest/siphash v1.2.3 // indirect
32 | github.com/felixge/fgprof v0.9.4 // indirect
33 | github.com/flynn/noise v1.1.0 // indirect
34 | github.com/google/go-cmp v0.5.9 // indirect
35 | github.com/google/pprof v0.0.0-20240618054019-d3b898a103f8 // indirect
36 | github.com/keltia/proxy v0.9.5 // indirect
37 | github.com/klauspost/compress v1.17.11 // indirect
38 | github.com/libp2p/go-reuseport v0.4.0 // indirect
39 | github.com/mroth/weightedrand v1.0.0 // indirect
40 | github.com/pion/dtls/v2 v2.2.12 // indirect
41 | github.com/pion/logging v0.2.3 // indirect
42 | github.com/pion/randutil v0.1.0 // indirect
43 | github.com/pion/sctp v1.8.37 // indirect
44 | github.com/pion/stun v0.6.1 // indirect
45 | github.com/pion/transport/v2 v2.2.10 // indirect
46 | github.com/pion/transport/v3 v3.0.7 // indirect
47 | github.com/pmezard/go-difflib v1.0.0 // indirect
48 | github.com/refraction-networking/obfs4 v0.1.2 // indirect
49 | gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.6.0 // indirect
50 | gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/ptutil v0.0.0-20250130151315-efaf4e0ec0d3 // indirect
51 | gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2 v2.11.0 // indirect
52 | golang.org/x/sys v0.30.0 // indirect
53 | golang.org/x/text v0.22.0 // indirect
54 | gopkg.in/yaml.v3 v3.0.1 // indirect
55 | )
56 |
57 | replace github.com/pion/dtls/v2 => github.com/mingyech/dtls/v2 v2.0.0
58 |
59 | replace github.com/pion/transport/v2 => github.com/mingyech/transport/v2 v2.0.0
60 |
61 | replace gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/conjure => gitlab.torproject.org/onyinyang/conjure v0.0.0-20250403173837-5b5bb3154613
62 |
--------------------------------------------------------------------------------
/gobind/README.md:
--------------------------------------------------------------------------------
1 | ## Note
2 | This code is for previous version of TapDance Android app, which doesn't create VPN for the whole phone, but merely binds proxy to a certain port. It still could be used in this mode by configuring apps(e.g. Firefox) to use local proxy on said port, however usability is quite limited and we consider this app to be Proof of Concept.
3 |
4 | There is a plan to develop a new version of app, that would actually proxy all traffic.
5 |
6 | ## Get gomobile
7 | You'd need [gomobile](https://godoc.org/golang.org/x/mobile/cmd/gomobile) to compile GUI version:
8 | ```bash
9 | go get golang.org/x/mobile/cmd/gomobile
10 | gomobile init
11 | ```
12 |
13 | ## Wrapper
14 | ### To simply build proxybind.aar library for Android:
15 | ```
16 | cd ${GOPATH}/src/github.com/refraction-networking/gotapdance/gobind
17 | gomobile bind -target=android
18 | ```
19 | ### Gradle Plugin
20 | For convinience it is recommended to use [Gobind gradle plugin](https://godoc.org/golang.org/x/mobile/cmd/gomobile#hdr-Gobind_gradle_plugin), compatible with Android Studio.
21 |
--------------------------------------------------------------------------------
/gobind/gobind.go:
--------------------------------------------------------------------------------
1 | package gobind
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "github.com/sirupsen/logrus"
7 | "io"
8 |
9 | "github.com/refraction-networking/gotapdance/tapdance"
10 | "github.com/refraction-networking/gotapdance/tdproxy"
11 | )
12 |
13 | var td_proxy *tdproxy.TapDanceProxy
14 | var buffer bytes.Buffer
15 | var b = make([]byte, 1048576)
16 |
17 | func NewDecoyProxy(listenPort int) (err error) {
18 |
19 | tapdance.Logger().Out = &buffer
20 | tapdance.Logger().Level = logrus.InfoLevel
21 | tapdance.Logger().Formatter = new(logrus.JSONFormatter)
22 | td_proxy = tdproxy.NewTapDanceProxy(listenPort)
23 | if td_proxy == nil {
24 | err = errors.New("Unable to initialize Proxy")
25 | }
26 | return
27 | }
28 |
29 | func GetLog() (out string) {
30 | n, err := buffer.Read(b)
31 | if err == io.EOF {
32 | out = ""
33 | } else if err != nil {
34 | out = err.Error()
35 | } else {
36 | out = string(b[:n])
37 | }
38 | return
39 | }
40 |
41 | func Listen() (err error) {
42 | if td_proxy == nil {
43 | err = errors.New("Proxy is not initialized")
44 | } else {
45 | err = td_proxy.ListenAndServe()
46 | }
47 | return
48 | }
49 |
50 | func Stop() (err error) {
51 | if td_proxy == nil {
52 | err = errors.New("Proxy is not initialized")
53 | } else {
54 | err = td_proxy.Stop()
55 | }
56 | return
57 | }
58 |
59 | func GetStats() (stats string) {
60 | if td_proxy == nil {
61 | stats = "State: Not initialized."
62 | } else {
63 | stats = "State: " + td_proxy.GetStats()
64 | }
65 | return
66 | }
67 |
68 | func IsListening() (listening bool) {
69 | if td_proxy == nil {
70 | listening = false
71 | } else {
72 | if td_proxy.State == tdproxy.ProxyStateListening {
73 | listening = true
74 | } else {
75 | listening = false
76 | }
77 | }
78 | return
79 | }
80 |
--------------------------------------------------------------------------------
/protobuf/Makefile:
--------------------------------------------------------------------------------
1 | #
2 | # github.com/protobuf/protoc-gen-go was recently deprecated in favor of
3 | # google.golang.org/protobuf/protoc-gen-go and there have been some changes to the interface.
4 | # We are using protoc to generate golang as a package in a somewhat non-standard way and as such
5 | # we have to specify some options. For more details see
6 | # [here](https://developers.google.com/protocol-buffers/docs/reference/go-generated#package).
7 | #
8 | # protoc --go_out=./ --go_opt=M"./signalling.proto=./;tdproto" signalling.proto \
9 | #
10 | # --go_out=./
11 | # --go_out == generate go files into the current directory
12 | #
13 | # --go_opt=M"signalling.proto=./;tdproto"
14 | # --go_opt= == use options for protoc-gen-go
15 | # Msignalling.proto == the structures defined by the protobuf at this path are used by the module defined here
16 | # =./ == the module for importing related files is at ./
17 | # ;tdproto == use a custom package name (other than module name defined by package path, which we aren't using)
18 | #
19 | # signalling.proto == the file to compile using protoc
20 | #
21 | # --------------------------------------------------------------------------------------------------
22 | #
23 | # old command to compile the protobuf into a local file with package name tdproto
24 | #
25 | # protoc --go_out=import_path=tdproto:. signalling.proto \
26 |
27 | all:
28 | protoc --go_out=./ --go_opt=M"signalling.proto=./;tdproto" signalling.proto
29 |
--------------------------------------------------------------------------------
/protobuf/README.md:
--------------------------------------------------------------------------------
1 | # Protobuf messages for TapDance
2 | [](https://godoc.org/github.com/refraction-networking/gotapdance/protobuf)
3 | ---
4 |
5 | ### Rebuild
6 | protoc --go_out=import_path=tdproto:. signalling.proto
--------------------------------------------------------------------------------
/protobuf/proto_test.go:
--------------------------------------------------------------------------------
1 | package tdproto
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | "google.golang.org/protobuf/proto"
8 | )
9 |
10 | // Write a small go test using your APIMessage (serialize/deserialize)
11 | func TestBidirectionalAPIResponse(t *testing.T) {
12 | c2s := RegistrationResponse{}
13 | addr := uint32(12345)
14 | c2s.Ipv4Addr = &addr
15 | port := uint32(10)
16 | c2s.DstPort = &port
17 |
18 | // Serialize
19 | marsh, err := proto.Marshal(&c2s)
20 | require.Nil(t, err)
21 |
22 | // Deserialize
23 | deser := RegistrationResponse{}
24 | err = proto.Unmarshal(marsh, &deser)
25 | require.Nil(t, err)
26 | require.Equal(t, addr, deser.GetIpv4Addr())
27 | require.Equal(t, port, deser.GetDstPort())
28 | }
29 |
30 | // TestProtoLibVer validates that the accessor method returns a default value for
31 | // fields that are unset in a protobuf and that our initial incremented
32 | // ClientLibraryVersion should be 1.
33 | func TestProtoLibVer(t *testing.T) {
34 | c2s := ClientToStation{}
35 |
36 | defaultLibVer := c2s.GetClientLibVersion()
37 | require.Equal(t, uint32(0), defaultLibVer)
38 | }
39 |
--------------------------------------------------------------------------------
/tapdance/assets:
--------------------------------------------------------------------------------
1 | ../assets/
--------------------------------------------------------------------------------
/tapdance/assets-phantoms_test.go:
--------------------------------------------------------------------------------
1 | package tapdance
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "encoding/hex"
7 | "testing"
8 |
9 | ps "github.com/refraction-networking/conjure/pkg/phantoms"
10 | ca "github.com/refraction-networking/conjure/pkg/client/assets"
11 | "github.com/stretchr/testify/assert"
12 | "github.com/stretchr/testify/require"
13 | )
14 |
15 | func TestAssetsPhantomsBasics(t *testing.T) {
16 | phantomSet := ca.Assets().GetPhantomSubnets()
17 | assert.NotNil(t, phantomSet)
18 | }
19 |
20 | func TestAssetsPhantoms(t *testing.T) {
21 | var b bytes.Buffer
22 | logHolder := bufio.NewWriter(&b)
23 | oldLoggerOut := Logger().Out
24 | Logger().Out = logHolder
25 | defer func() {
26 | Logger().Out = oldLoggerOut
27 | if t.Failed() {
28 | // logHolder.Flush()
29 | // fmt.Printf("TapDance log was:\n%s\n", b.String())
30 | }
31 | }()
32 | oldpath := ca.Assets().GetAssetsDir()
33 |
34 | dir1 := t.TempDir()
35 |
36 | var testPhantoms = ps.GetDefaultPhantomSubnets()
37 |
38 | ca.AssetsSetDir(dir1)
39 | err := ca.Assets().SetPhantomSubnets(testPhantoms)
40 | if err != nil {
41 | t.Fatal(err)
42 | }
43 |
44 | seed, err := hex.DecodeString("5a87133b68da3468988a21659a12ed2ece07345c8c1a5b08459ffdea4218d12f")
45 | require.Nil(t, err)
46 |
47 | addr4, addr6, _, err := SelectPhantom(seed, both)
48 | require.Nil(t, err)
49 | require.Equal(t, "192.122.190.178", addr4.String())
50 | require.Equal(t, "2001:48a8:687f:1:b292:3bab:bade:351f", addr6.String())
51 |
52 | addr4, addr6, _, err = SelectPhantom(seed, v6)
53 | require.Nil(t, err)
54 | require.Nil(t, addr4)
55 | require.Equal(t, "2001:48a8:687f:1:b292:3bab:bade:351f", addr6.String())
56 |
57 | addr4, addr6, _, err = SelectPhantom(seed, v4)
58 | require.Nil(t, err)
59 | require.Equal(t, "192.122.190.178", addr4.String())
60 | require.Nil(t, addr6)
61 |
62 | ca.AssetsSetDir(oldpath)
63 | }
64 |
--------------------------------------------------------------------------------
/tapdance/assets.go:
--------------------------------------------------------------------------------
1 | package tapdance
2 |
3 | import (
4 | "crypto/x509"
5 | "encoding/binary"
6 | "encoding/hex"
7 | "errors"
8 | "net"
9 | "os"
10 | "path"
11 | "strings"
12 | "sync"
13 |
14 | ps "github.com/refraction-networking/conjure/pkg/phantoms"
15 | pb "github.com/refraction-networking/conjure/proto"
16 |
17 | "google.golang.org/protobuf/proto"
18 | )
19 |
20 | type assets struct {
21 | sync.RWMutex
22 | path string
23 |
24 | config *pb.ClientConf
25 |
26 | roots *x509.CertPool
27 |
28 | filenameRoots string
29 | filenameClientConf string
30 |
31 | socksAddr string
32 | }
33 |
34 | // could reset this internally to refresh assets and avoid woes of singleton testing
35 | var assetsInstance *assets
36 | var assetsOnce sync.Once
37 |
38 | // Assets is an access point to asset managing singleton.
39 | // First access to singleton sets path. Assets(), if called
40 | // before SetAssetsDir() sets path to "./assets/"
41 | func Assets() *assets {
42 | // We leave this warning here, because only Tapdance should
43 | // use this instance. Conjure uses assets provided by
44 | // github.com/refraction-networking/conjure/pkg/client/assets
45 | // and this Assets (and Tapdance as a whole) is deprecated
46 | Logger().Warnf("Loading TapDance Assets...(deprecated; use conjure assets)")
47 | var err error
48 | _initAssets := func() { err = initAssets("./assets/") }
49 | assetsOnce.Do(_initAssets)
50 | if err != nil {
51 | Logger().Warnf("error getting assets: %v", err)
52 | }
53 | return assetsInstance
54 | }
55 |
56 | // AssetsSetDir sets the directory to read assets from.
57 | // Functionally equivalent to Assets() after initialization, unless dir changes.
58 | func AssetsSetDir(dir string) (*assets, error) {
59 | var err error
60 | _initAssets := func() { err = initAssets(dir) }
61 | if assetsInstance != nil {
62 | assetsInstance.Lock()
63 | defer assetsInstance.Unlock()
64 | if dir != assetsInstance.path {
65 |
66 | if _, err := os.Stat(dir); err != nil {
67 | Logger().Warnf("Assets path unchanged %v.\n", err)
68 | return assetsInstance, err
69 | }
70 | Logger().Warnf("Assets path changed %s->%s. (Re)initializing", assetsInstance.path, dir)
71 | assetsInstance.path = dir
72 | err = assetsInstance.readConfigs()
73 | return assetsInstance, err
74 | }
75 | }
76 | assetsOnce.Do(_initAssets)
77 | return assetsInstance, err
78 | }
79 |
80 | func getDefaultKey() []byte {
81 | keyStr := "a1cb97be697c5ed5aefd78ffa4db7e68101024603511e40a89951bc158807177"
82 | key := make([]byte, hex.DecodedLen(len(keyStr)))
83 | hex.Decode(key, []byte(keyStr))
84 | return key
85 | }
86 |
87 | func getDefaultTapdanceKey() []byte {
88 | keyStr := "515868be7f45ab6f310afed4b229b7a479fc9fde553dea4ccdb369ab1899e70c"
89 | key := make([]byte, hex.DecodedLen(len(keyStr)))
90 | hex.Decode(key, []byte(keyStr))
91 | return key
92 | }
93 |
94 | func initAssets(path string) error {
95 | var defaultDecoys = []*pb.TLSDecoySpec{
96 | pb.InitTLSDecoySpec("192.122.190.104", "tapdance1.freeaeskey.xyz"),
97 | pb.InitTLSDecoySpec("192.122.190.105", "tapdance2.freeaeskey.xyz"),
98 | pb.InitTLSDecoySpec("192.122.190.106", "tapdance3.freeaeskey.xyz"),
99 | }
100 |
101 | defaultKey := getDefaultTapdanceKey()
102 | defaultConjureKey := getDefaultKey()
103 |
104 | defualtKeyType := pb.KeyType_AES_GCM_128
105 | defaultPubKey := pb.PubKey{Key: defaultKey, Type: &defualtKeyType}
106 | defaultConjurePubKey := pb.PubKey{Key: defaultConjureKey, Type: &defualtKeyType}
107 |
108 | defaultGeneration := uint32(1)
109 | defaultDecoyList := pb.DecoyList{TlsDecoys: defaultDecoys}
110 | defaultDnsRegDomain := "r.refraction.network"
111 | defaultDnsRegDohUrl := "https://1.1.1.1/dns-query"
112 | defaultStunServer := "stun.voip.blackberry.com:3478"
113 | defaultDnsRegPubkey := getDefaultKey()
114 | defaultDnsRegUtlsDistribution := "3*Firefox_65,1*Firefox_63,1*iOS_12_1"
115 | defaultDnsRegMethod := pb.DnsRegMethod_DOH
116 |
117 | defaultDnsRegConf := pb.DnsRegConf{
118 | DnsRegMethod: &defaultDnsRegMethod,
119 | Target: &defaultDnsRegDohUrl,
120 | Domain: &defaultDnsRegDomain,
121 | Pubkey: defaultDnsRegPubkey,
122 | UtlsDistribution: &defaultDnsRegUtlsDistribution,
123 | StunServer: &defaultStunServer,
124 | }
125 |
126 | defaultClientConf := pb.ClientConf{
127 | DecoyList: &defaultDecoyList,
128 | DefaultPubkey: &defaultPubKey,
129 | ConjurePubkey: &defaultConjurePubKey,
130 | Generation: &defaultGeneration,
131 | DnsRegConf: &defaultDnsRegConf,
132 | }
133 |
134 | assetsInstance = &assets{
135 | path: path,
136 | config: &defaultClientConf,
137 | filenameRoots: "roots",
138 | filenameClientConf: "ClientConf",
139 | socksAddr: "",
140 | }
141 | err := assetsInstance.readConfigs()
142 | return err
143 | }
144 |
145 | func (a *assets) GetAssetsDir() string {
146 | a.RLock()
147 | defer a.RUnlock()
148 | return a.path
149 | }
150 |
151 | func (a *assets) GetDNSRegConf() *pb.DnsRegConf {
152 | a.RLock()
153 | defer a.RUnlock()
154 | return a.config.DnsRegConf
155 | }
156 |
157 | func (a *assets) readConfigs() error {
158 | readRoots := func(filename string) error {
159 | rootCerts, err := os.ReadFile(filename)
160 | if err != nil {
161 | return err
162 | }
163 | roots := x509.NewCertPool()
164 | ok := roots.AppendCertsFromPEM(rootCerts)
165 | if !ok {
166 | return errors.New("failed to parse root certificates")
167 | }
168 | a.roots = roots
169 | return nil
170 | }
171 |
172 | readClientConf := func(filename string) error {
173 | buf, err := os.ReadFile(filename)
174 | if err != nil {
175 | return err
176 | }
177 | clientConf := &pb.ClientConf{}
178 | err = proto.Unmarshal(buf, clientConf)
179 | if err != nil {
180 | return err
181 | }
182 | a.config = clientConf
183 | return nil
184 | }
185 |
186 | var err error
187 | Logger().Infoln("Assets: reading from folder " + a.path)
188 |
189 | rootsFilename := path.Join(a.path, a.filenameRoots)
190 | err = readRoots(rootsFilename)
191 | if err != nil {
192 | Logger().Warn("Assets: failed to read root ca file: " + err.Error())
193 | } else {
194 | Logger().Infoln("X.509 root CAs successfully read from " + rootsFilename)
195 | }
196 |
197 | // Parse ClientConf for Decoys and Phantoms List
198 | clientConfFilename := path.Join(a.path, a.filenameClientConf)
199 | err = readClientConf(clientConfFilename)
200 | if err != nil {
201 | Logger().Warn("Assets: failed to read ClientConf file: " + err.Error())
202 | } else {
203 | Logger().Infoln("Client config successfully read from " + clientConfFilename)
204 | }
205 |
206 | return err
207 | }
208 |
209 | // Picks random decoy, returns Server Name Indication and addr in format ipv4:port
210 | func (a *assets) GetDecoyAddress() (sni string, addr string) {
211 | a.RLock()
212 | defer a.RUnlock()
213 |
214 | decoys := a.config.GetDecoyList().GetTlsDecoys()
215 | if len(decoys) == 0 {
216 | return "", ""
217 | }
218 | decoyIndex := getRandInt(0, len(decoys)-1)
219 | ip := make(net.IP, 4)
220 | binary.BigEndian.PutUint32(ip, decoys[decoyIndex].GetIpv4Addr())
221 | //[TODO]{priority:winter-break}: what checks need to be done, and what's guaranteed?
222 | addr = ip.To4().String() + ":443"
223 | sni = decoys[decoyIndex].GetHostname()
224 | return
225 | }
226 |
227 | // Get all Decoys from ClientConf
228 | func (a *assets) GetAllDecoys() []*pb.TLSDecoySpec {
229 | return a.config.GetDecoyList().GetTlsDecoys()
230 | }
231 |
232 | // Get all Decoys from ClientConf that have an IPv6 address
233 | func (a *assets) GetV6Decoys() []*pb.TLSDecoySpec {
234 | v6Decoys := make([]*pb.TLSDecoySpec, 0)
235 | allDecoys := a.config.GetDecoyList().GetTlsDecoys()
236 |
237 | for _, decoy := range allDecoys {
238 | if decoy.GetIpv6Addr() != nil {
239 | v6Decoys = append(v6Decoys, decoy)
240 | }
241 | }
242 |
243 | return v6Decoys
244 | }
245 |
246 | // Get all Decoys from ClientConf that have an IPv6 address
247 | func (a *assets) GetV4Decoys() []*pb.TLSDecoySpec {
248 | v6Decoys := make([]*pb.TLSDecoySpec, 0)
249 | allDecoys := a.config.GetDecoyList().GetTlsDecoys()
250 |
251 | for _, decoy := range allDecoys {
252 | if decoy.GetIpv4Addr() != 0 {
253 | v6Decoys = append(v6Decoys, decoy)
254 | }
255 | }
256 |
257 | return v6Decoys
258 | }
259 |
260 | // GetDecoy - Gets random DecoySpec
261 | func (a *assets) GetDecoy() *pb.TLSDecoySpec {
262 | a.RLock()
263 | defer a.RUnlock()
264 |
265 | decoys := a.config.GetDecoyList().GetTlsDecoys()
266 | chosenDecoy := &pb.TLSDecoySpec{}
267 | if len(decoys) == 0 {
268 | return chosenDecoy
269 | }
270 | decoyIndex := getRandInt(0, len(decoys)-1)
271 | chosenDecoy = decoys[decoyIndex]
272 |
273 | //[TODO]{priority:soon} stop enforcing values >= defaults.
274 | // Fix ackhole instead
275 | // No value checks when using
276 | if chosenDecoy.GetTimeout() < timeoutMin {
277 | timeout := uint32(timeoutMax)
278 | chosenDecoy.Timeout = &timeout
279 | }
280 | if chosenDecoy.GetTcpwin() < sendLimitMin {
281 | tcpWin := uint32(sendLimitMax)
282 | chosenDecoy.Tcpwin = &tcpWin
283 | }
284 | return chosenDecoy
285 | }
286 |
287 | // GetDecoy - Gets random IPv6 DecoySpec
288 | func (a *assets) GetV6Decoy() *pb.TLSDecoySpec {
289 | a.RLock()
290 | defer a.RUnlock()
291 |
292 | decoys := a.GetV6Decoys()
293 | chosenDecoy := &pb.TLSDecoySpec{}
294 | if len(decoys) == 0 {
295 | return chosenDecoy
296 | }
297 | decoyIndex := getRandInt(0, len(decoys)-1)
298 | chosenDecoy = decoys[decoyIndex]
299 |
300 | // No enforcing TCPWIN etc. values because this is conjure only
301 | return chosenDecoy
302 | }
303 |
304 | func (a *assets) GetRoots() *x509.CertPool {
305 | a.RLock()
306 | defer a.RUnlock()
307 |
308 | return a.roots
309 | }
310 |
311 | func (a *assets) GetPubkey() *[32]byte {
312 | a.RLock()
313 | defer a.RUnlock()
314 |
315 | var pKey [32]byte
316 | copy(pKey[:], a.config.GetDefaultPubkey().GetKey()[:])
317 | return &pKey
318 | }
319 |
320 | func (a *assets) GetConjurePubkey() *[32]byte {
321 | a.RLock()
322 | defer a.RUnlock()
323 |
324 | var pKey [32]byte
325 | copy(pKey[:], a.config.GetConjurePubkey().GetKey()[:])
326 | return &pKey
327 | }
328 |
329 | func (a *assets) GetGeneration() uint32 {
330 | a.RLock()
331 | defer a.RUnlock()
332 |
333 | return a.config.GetGeneration()
334 | }
335 |
336 | // Set ClientConf generation and store config to disk
337 | func (a *assets) SetGeneration(gen uint32) (err error) {
338 | a.Lock()
339 | defer a.Unlock()
340 |
341 | copyGen := gen
342 | a.config.Generation = ©Gen
343 | err = a.saveClientConf()
344 | return
345 | }
346 |
347 | // Set Public key and store config to disk
348 | func (a *assets) SetPubkey(pubkey *pb.PubKey) (err error) {
349 | a.Lock()
350 | defer a.Unlock()
351 |
352 | a.config.DefaultPubkey = pubkey
353 | err = a.saveClientConf()
354 | return
355 | }
356 |
357 | // Set ClientConf and store config to disk - if an error occurs (parse error or
358 | // write to file error) the error will be logged and the update will be aborted.
359 | func (a *assets) SetClientConf(conf *pb.ClientConf) (err error) {
360 | a.Lock()
361 | defer a.Unlock()
362 |
363 | origConf := a.config
364 | a.config = conf
365 | err = a.saveClientConf()
366 | if err != nil {
367 | a.config = origConf
368 | }
369 | return
370 | }
371 |
372 | // Not goroutine-safe, use at your own risk
373 | func (a *assets) GetClientConfPtr() *pb.ClientConf {
374 | return a.config
375 | }
376 |
377 | // Overwrite currently used decoys and store config to disk
378 | func (a *assets) SetDecoys(decoys []*pb.TLSDecoySpec) (err error) {
379 | a.Lock()
380 | defer a.Unlock()
381 |
382 | if a.config.DecoyList == nil {
383 | a.config.DecoyList = &pb.DecoyList{}
384 | }
385 | a.config.DecoyList.TlsDecoys = decoys
386 | err = a.saveClientConf()
387 | return
388 | }
389 |
390 | // Checks if decoy is in currently used ClientConf decoys list
391 | func (a *assets) IsDecoyInList(decoy *pb.TLSDecoySpec) bool {
392 | ipv4str := decoy.GetIpAddrStr()
393 | hostname := decoy.GetHostname()
394 | a.RLock()
395 | defer a.RUnlock()
396 | for _, d := range a.config.GetDecoyList().GetTlsDecoys() {
397 | if strings.Compare(d.GetHostname(), hostname) == 0 &&
398 | strings.Compare(d.GetIpAddrStr(), ipv4str) == 0 {
399 | return true
400 | }
401 | }
402 | return false
403 | }
404 |
405 | func (a *assets) saveClientConf() error {
406 | buf, err := proto.Marshal(a.config)
407 | if err != nil {
408 | return err
409 | }
410 | filename := path.Join(a.path, a.filenameClientConf)
411 | tmpFilename := path.Join(a.path, "."+a.filenameClientConf+"."+getRandString(5)+".tmp")
412 | err = os.WriteFile(tmpFilename, buf[:], 0644)
413 | if err != nil {
414 | return err
415 | }
416 |
417 | return os.Rename(tmpFilename, filename)
418 | }
419 |
420 | // SetStatsSocksAddr - Provide a socks address for reporting stats from the client in the form "addr:port"
421 | func (a *assets) SetStatsSocksAddr(addr string) {
422 | a.socksAddr = addr
423 | }
424 |
425 | // GetPhantomSubnets -
426 | func (a *assets) GetPhantomSubnets() *pb.PhantomSubnetsList {
427 | a.RLock()
428 | defer a.RUnlock()
429 |
430 | if a.config == nil {
431 | return ps.GetDefaultPhantomSubnets()
432 | }
433 |
434 | if phantomSubnetsList := a.config.GetPhantomSubnetsList(); phantomSubnetsList != nil {
435 | return phantomSubnetsList
436 | }
437 |
438 | return ps.GetDefaultPhantomSubnets()
439 | }
440 |
441 | // SetPhantomSubnets -
442 | func (a *assets) SetPhantomSubnets(subnetConf *pb.PhantomSubnetsList) error {
443 | a.Lock()
444 | defer a.Unlock()
445 |
446 | if a.config == nil {
447 | a.config = &pb.ClientConf{}
448 | }
449 |
450 | a.config.PhantomSubnetsList = subnetConf
451 |
452 | err := a.saveClientConf()
453 | return err
454 | }
455 |
--------------------------------------------------------------------------------
/tapdance/assets_test.go:
--------------------------------------------------------------------------------
1 | package tapdance
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "fmt"
7 | "io/ioutil"
8 | "net"
9 | "os"
10 | "path"
11 | "testing"
12 |
13 | pb "github.com/refraction-networking/conjure/proto"
14 | "google.golang.org/protobuf/proto"
15 | )
16 |
17 | func TestAssets_Decoys(t *testing.T) {
18 | var b bytes.Buffer
19 | logHolder := bufio.NewWriter(&b)
20 | oldLoggerOut := Logger().Out
21 | Logger().Out = logHolder
22 | defer func() {
23 | Logger().Out = oldLoggerOut
24 | if t.Failed() {
25 | logHolder.Flush()
26 | fmt.Printf("TapDance log was:\n%s\n", b.String())
27 | }
28 | }()
29 | oldpath := Assets().path
30 | Assets().saveClientConf()
31 | dir1, err := ioutil.TempDir("/tmp/", "decoy1")
32 | if err != nil {
33 | fmt.Println(err.Error())
34 | t.Fail()
35 | }
36 | dir2, err := ioutil.TempDir("/tmp/", "decoy2")
37 | if err != nil {
38 | t.Fatal(err)
39 | }
40 |
41 | var testDecoys1 = []*pb.TLSDecoySpec{
42 | pb.InitTLSDecoySpec("4.8.15.16", "ericw.us"),
43 | pb.InitTLSDecoySpec("19.21.23.42", "blahblahbl.ah"),
44 | }
45 |
46 | var testDecoys2 = []*pb.TLSDecoySpec{
47 | pb.InitTLSDecoySpec("0.1.2.3", "whatever.cn"),
48 | pb.InitTLSDecoySpec("255.254.253.252", "particular.ir"),
49 | pb.InitTLSDecoySpec("11.22.33.44", "what.is.up"),
50 | pb.InitTLSDecoySpec("8.255.255.8", "heh.meh"),
51 | }
52 |
53 | AssetsSetDir(dir1)
54 | err = Assets().SetDecoys(testDecoys1)
55 | if err != nil {
56 | t.Fatal(err)
57 | }
58 | if !Assets().IsDecoyInList(pb.InitTLSDecoySpec("19.21.23.42", "blahblahbl.ah")) {
59 | t.Fatal("Decoy 19.21.23.42(blahblahbl.ah) is NOT in Decoy List!")
60 | }
61 | AssetsSetDir(dir2)
62 | err = Assets().SetDecoys(testDecoys2)
63 | if err != nil {
64 | t.Fatal(err)
65 | }
66 | if Assets().IsDecoyInList(pb.InitTLSDecoySpec("19.21.23.42", "blahblahbl.ah")) {
67 | t.Fatal("Decoy 19.21.23.42(blahblahbl.ah) is in Decoy List!")
68 | }
69 | if !Assets().IsDecoyInList(pb.InitTLSDecoySpec("11.22.33.44", "what.is.up")) {
70 | t.Fatal("Decoy 11.22.33.44(what.is.up) is NOT in Decoy List!")
71 | }
72 |
73 | decoyInList := func(d *pb.TLSDecoySpec, decoyList []*pb.TLSDecoySpec) bool {
74 | for _, elem := range decoyList {
75 | if proto.Equal(elem, d) {
76 | return true
77 | }
78 | }
79 | return false
80 | }
81 |
82 | for i := 0; i < 10; i++ {
83 | _sni, addr := Assets().GetDecoyAddress()
84 | hostAddr, _, err := net.SplitHostPort(addr)
85 | if err != nil {
86 | t.Fatal("Corrupted addr:", addr, ". Error:", err.Error())
87 | }
88 | decoyServ := pb.InitTLSDecoySpec(hostAddr, _sni)
89 | if !decoyInList(decoyServ, Assets().config.DecoyList.TlsDecoys) {
90 | fmt.Println("decoyServ not in List!")
91 | fmt.Println("decoyServ:", decoyServ)
92 | fmt.Println("Assets().decoys:", Assets().config.DecoyList.TlsDecoys)
93 | t.Fail()
94 | }
95 | }
96 | AssetsSetDir(dir1)
97 |
98 | if !Assets().IsDecoyInList(pb.InitTLSDecoySpec("19.21.23.42", "blahblahbl.ah")) {
99 | t.Fatal("Decoy 19.21.23.42(blahblahbl.ah) is NOT in Decoy List!")
100 | }
101 | if Assets().IsDecoyInList(pb.InitTLSDecoySpec("11.22.33.44", "what.is.up")) {
102 | t.Fatal("Decoy 11.22.33.44(what.is.up) is in Decoy List!")
103 | }
104 | for i := 0; i < 10; i++ {
105 | _sni, addr := Assets().GetDecoyAddress()
106 | hostAddr, _, err := net.SplitHostPort(addr)
107 | if err != nil {
108 | t.Fatal("Corrupted addr:", addr, ". Error:", err.Error())
109 | }
110 | decoyServ := pb.InitTLSDecoySpec(hostAddr, _sni)
111 | if !decoyInList(decoyServ, Assets().config.DecoyList.TlsDecoys) {
112 | fmt.Println("decoyServ not in List!")
113 | fmt.Println("decoyServ:", decoyServ)
114 | fmt.Println("Assets().decoys:", Assets().config.DecoyList.TlsDecoys)
115 | t.Fail()
116 | }
117 | }
118 | os.Remove(path.Join(dir1, Assets().filenameClientConf))
119 | os.Remove(path.Join(dir2, Assets().filenameClientConf))
120 | os.Remove(dir1)
121 | os.Remove(dir2)
122 | AssetsSetDir(oldpath)
123 | }
124 |
125 | func TestAssets_Pubkey(t *testing.T) {
126 | var b bytes.Buffer
127 | logHolder := bufio.NewWriter(&b)
128 | oldLoggerOut := Logger().Out
129 | Logger().Out = logHolder
130 | defer func() {
131 | Logger().Out = oldLoggerOut
132 | if t.Failed() {
133 | logHolder.Flush()
134 | fmt.Printf("TapDance log was:\n%s\n", b.String())
135 | }
136 | }()
137 | initPubKey := func(defaultKey []byte) *pb.PubKey {
138 | defualtKeyType := pb.KeyType_AES_GCM_128
139 | return &pb.PubKey{Key: defaultKey, Type: &defualtKeyType}
140 | }
141 |
142 | oldpath := Assets().path
143 | Assets().saveClientConf()
144 | dir1, err := ioutil.TempDir("/tmp/", "pubkey1")
145 | if err != nil {
146 | t.Fatal(err)
147 | }
148 | dir2, err := ioutil.TempDir("/tmp/", "pubkey2")
149 | if err != nil {
150 | t.Fatal(err)
151 | }
152 |
153 | var pubkey1 = initPubKey([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
154 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
155 | 27, 28, 29, 30, 31})
156 | var pubkey2 = initPubKey([]byte{200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211,
157 | 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226,
158 | 227, 228, 229, 230, 231})
159 |
160 | AssetsSetDir(dir1)
161 | err = Assets().SetPubkey(pubkey1)
162 | if err != nil {
163 | t.Fatal(err)
164 | }
165 | AssetsSetDir(dir2)
166 | err = Assets().SetPubkey(pubkey2)
167 | if err != nil {
168 | t.Fatal(err)
169 | }
170 | if !bytes.Equal(Assets().config.DefaultPubkey.Key[:], pubkey2.Key[:]) {
171 | fmt.Println("Pubkeys are not equal!")
172 | fmt.Println("Assets().stationPubkey:", Assets().config.DefaultPubkey.Key[:])
173 | fmt.Println("pubkey2:", pubkey2)
174 | t.Fail()
175 | }
176 |
177 | AssetsSetDir(dir1)
178 | if !bytes.Equal(Assets().config.DefaultPubkey.Key[:], pubkey1.Key[:]) {
179 | fmt.Println("Pubkeys are not equal!")
180 | fmt.Println("Assets().stationPubkey:", Assets().config.DefaultPubkey.Key[:])
181 | fmt.Println("pubkey1:", pubkey1)
182 | t.Fail()
183 | }
184 | // os.Remove(path.Join(dir1, Assets().filenameStationPubkey))
185 | // os.Remove(path.Join(dir2, Assets().filenameStationPubkey))
186 | os.Remove(dir1)
187 | os.Remove(dir2)
188 | AssetsSetDir(oldpath)
189 | }
190 |
--------------------------------------------------------------------------------
/tapdance/common.go:
--------------------------------------------------------------------------------
1 | package tapdance
2 |
3 | import (
4 | "crypto/rand"
5 | "encoding/hex"
6 | "errors"
7 | "fmt"
8 | "math"
9 | "os"
10 | "strconv"
11 | "time"
12 |
13 | "github.com/refraction-networking/ed25519/extra25519"
14 | "golang.org/x/crypto/curve25519"
15 | )
16 |
17 | const AES_GCM_TAG_SIZE = 16
18 |
19 | const timeoutMax = 30000
20 | const timeoutMin = 20000
21 |
22 | const sendLimitMax = 15614
23 | const sendLimitMin = 14400
24 |
25 | // timeout for sending TD request and getting a response
26 | const deadlineConnectTDStationMin = 11175
27 | const deadlineConnectTDStationMax = 14231
28 |
29 | // deadline to establish TCP connection to decoy
30 | const deadlineTCPtoDecoyMin = deadlineConnectTDStationMin
31 | const deadlineTCPtoDecoyMax = deadlineConnectTDStationMax
32 |
33 | // during reconnects we send FIN to server and wait until we get FIN back
34 | const waitForFINDieMin = 2 * deadlineConnectTDStationMin
35 | const waitForFINDieMax = 2 * deadlineConnectTDStationMax
36 |
37 | const maxInt16 = int16(^uint16(0) >> 1) // max msg size -> might have to chunk
38 | //const minInt16 = int16(-maxInt16 - 1)
39 |
40 | type flowType int8
41 |
42 | /*______________________TapdanceFlowConn Mode Chart _________________________________\
43 | |FlowType |Default Tag|Diff from old-school bidirectional | Engines spawned|
44 | |-------------|-----------|-----------------------------------------|----------------|
45 | |Bidirectional| HTTP GET | | Writer, Reader |
46 | |Upload | HTTP POST | acquires upload | Writer, Reader |
47 | |ReadOnly | HTTP GET | yields upload, writer sync ignored | Reader |
48 | |Rendezvous | HTTP GET | passes data in handshake and shuts down | |
49 | \_____________|___________|_________________________________________|_______________*/
50 |
51 | const (
52 | flowUpload flowType = 0x1
53 | flowReadOnly flowType = 0x2
54 | flowBidirectional flowType = 0x4
55 | flowRendezvous flowType = 0x0 // rendezvous flows shutdown after handshake
56 | )
57 |
58 | func (m *flowType) Str() string {
59 | switch *m {
60 | case flowUpload:
61 | return "FlowUpload"
62 | case flowReadOnly:
63 | return "FlowReadOnly"
64 | case flowBidirectional:
65 | return "FlowBidirectional"
66 | default:
67 | return strconv.Itoa(int(*m))
68 | }
69 | }
70 |
71 | type msgType int8
72 |
73 | const (
74 | msgRawData msgType = 1
75 | msgProtobuf msgType = 2
76 | )
77 |
78 | func (m *msgType) Str() string {
79 | switch *m {
80 | case msgRawData:
81 | return "msg raw_data"
82 | case msgProtobuf:
83 | return "msg protobuf"
84 | default:
85 | return strconv.Itoa(int(*m))
86 | }
87 | }
88 |
89 | var errMsgClose = errors.New("MSG CLOSE")
90 | var errNotImplemented = errors.New("Not implemented")
91 |
92 | type tdTagType int8
93 |
94 | const (
95 | tagHttpGetIncomplete tdTagType = 0
96 | tagHttpGetComplete tdTagType = 1
97 | tagHttpPostIncomplete tdTagType = 2
98 | )
99 |
100 | func (m *tdTagType) Str() string {
101 | switch *m {
102 | case tagHttpGetIncomplete:
103 | return "HTTP GET Incomplete"
104 | case tagHttpGetComplete:
105 | return "HTTP GET Complete"
106 | case tagHttpPostIncomplete:
107 | return "HTTP POST Incomplete"
108 | default:
109 | return strconv.Itoa(int(*m))
110 | }
111 | }
112 |
113 | // Fixed-Size-Payload has a 1 byte flags field.
114 | // bit 0 (1 << 7) determines if flow is bidirectional(0) or upload-only(1)
115 | // bit 1 (1 << 6) enables dark-decoys
116 | // bits 2-5 are unassigned
117 | // bit 6 determines whether PROXY-protocol-formatted string will be sent
118 | // bit 7 (1 << 0) signals to use TypeLen outer proto
119 | var (
120 | tdFlagUploadOnly = uint8(1 << 7)
121 | // tdFlagDarkDecoy = uint8(1 << 6)
122 | tdFlagProxyHeader = uint8(1 << 1)
123 | tdFlagUseTIL = uint8(1 << 0)
124 | )
125 |
126 | var default_flags = tdFlagUseTIL
127 |
128 | // Global EnableProxyProtocol() is deprecated,
129 | // use tapdance.Dialer with UseProxyHeader flag instead
130 | //
131 | // Requests station to send client's IP to covert in following form:
132 | // PROXY TCP4 x.x.x.x 127.0.0.1 1111 1234\r\n
133 | //
134 | // ^__^ ^_____^ ^_________________^
135 | // proto clientIP garbage
136 | func EnableProxyProtocol() {
137 | Logger().Println("tapdance.EnableProxyProtocol() is deprecated, " +
138 | "use tapdance.Dialer with UseProxyHeader flag instead.")
139 | default_flags |= tdFlagProxyHeader
140 | return
141 | }
142 |
143 | var tlsSecretLog string
144 |
145 | func SetTlsLogFilename(filename string) error {
146 | tlsSecretLog = filename
147 | // Truncate file
148 | f, err := os.Create(filename)
149 | if err != nil {
150 | return err
151 | }
152 | return f.Close()
153 | }
154 |
155 | func WriteTlsLog(clientRandom, masterSecret []byte) error {
156 | if tlsSecretLog != "" {
157 | f, err := os.OpenFile(tlsSecretLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
158 | if err != nil {
159 | return err
160 | }
161 |
162 | _, err = fmt.Fprintf(f, "CLIENT_RANDOM %s %s\n",
163 | hex.EncodeToString(clientRandom),
164 | hex.EncodeToString(masterSecret))
165 | if err != nil {
166 | return err
167 | }
168 |
169 | return f.Close()
170 | }
171 | return nil
172 | }
173 |
174 | // How much time to sleep on trying to connect to decoys to prevent overwhelming them
175 | func sleepBeforeConnect(attempt int) (waitTime <-chan time.Time) {
176 | if attempt >= 1 {
177 | ms := math.Min(25*math.Pow(2, float64(attempt)), 15000)
178 | waitTime = time.After(time.Duration(int(ms)) * time.Millisecond)
179 | }
180 | return
181 | }
182 |
183 | // takes Station's Public Key
184 | // returns Shared Secret, and Eligator Representative
185 | func generateEligatorTransformedKey(stationPubkey []byte) ([]byte, []byte, error) {
186 | if len(stationPubkey) != 32 {
187 | return nil, nil, errors.New("Unexpected station pubkey length. Expected: 32." +
188 | " Received: " + strconv.Itoa(len(stationPubkey)) + ".")
189 | }
190 | var sharedSecret, clientPrivate, clientPublic, representative [32]byte
191 | for ok := false; ok != true; {
192 | var sliceKeyPrivate []byte = clientPrivate[:]
193 | _, err := rand.Read(sliceKeyPrivate)
194 | if err != nil {
195 | return nil, nil, err
196 | }
197 |
198 | ok = extra25519.ScalarBaseMult(&clientPublic, &representative, &clientPrivate)
199 | }
200 | var stationPubkeyByte32 [32]byte
201 | copy(stationPubkeyByte32[:], stationPubkey)
202 | curve25519.ScalarMult(&sharedSecret, &clientPrivate, &stationPubkeyByte32)
203 |
204 | // extra25519.ScalarBaseMult does not randomize most significant bit(sign of y_coord?)
205 | // Other implementations of elligator may have up to 2 non-random bits.
206 | // Here we randomize the bit, expecting it to be flipped back to 0 on station
207 | randByte := make([]byte, 1)
208 | _, err := rand.Read(randByte)
209 | if err != nil {
210 | return nil, nil, err
211 | }
212 | representative[31] |= (0xC0 & randByte[0])
213 | return sharedSecret[:], representative[:], nil
214 | }
215 |
--------------------------------------------------------------------------------
/tapdance/conjure_overrides_test.go:
--------------------------------------------------------------------------------
1 | package tapdance
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/refraction-networking/conjure/pkg/transports/wrapping/prefix"
7 | pb "github.com/refraction-networking/conjure/proto"
8 | "github.com/stretchr/testify/require"
9 | "google.golang.org/protobuf/types/known/anypb"
10 | )
11 |
12 | func TestConjureTransportOverride(t *testing.T) {
13 | reg := ConjureReg{}
14 | reg.ConjureSession = &ConjureSession{}
15 | reg.ConjureSession.DisableRegistrarOverrides = false
16 | reg.Transport = &prefix.ClientTransport{}
17 |
18 | err := reg.UnpackRegResp(nil)
19 | require.Nil(t, err)
20 |
21 | regResp := &pb.RegistrationResponse{}
22 |
23 | err = reg.UnpackRegResp(regResp)
24 | require.Nil(t, err)
25 |
26 | var id int32 = -2
27 | truePtr := true
28 | tp := &pb.PrefixTransportParams{
29 | PrefixId: &id,
30 | Prefix: []byte("aaaa"),
31 | RandomizeDstPort: &truePtr,
32 | }
33 | apb, _ := anypb.New(tp)
34 | regResp = &pb.RegistrationResponse{
35 | TransportParams: apb,
36 | }
37 |
38 | // Make sure that when overrides are allowed, they are applied even if it is not a prefix that
39 | // is included in the default prefixes that the client knows about.
40 | err = reg.UnpackRegResp(regResp)
41 | require.Nil(t, err)
42 | require.Equal(t, []byte("aaaa"), reg.Transport.(*prefix.ClientTransport).Prefix.Bytes())
43 | require.Equal(t, prefix.PrefixID(id), reg.Transport.(*prefix.ClientTransport).Prefix.ID())
44 | }
45 |
--------------------------------------------------------------------------------
/tapdance/conjure_test.go:
--------------------------------------------------------------------------------
1 | package tapdance
2 |
3 | import (
4 | "context"
5 | "crypto/hmac"
6 | "encoding/hex"
7 | "fmt"
8 | "net"
9 | "testing"
10 |
11 | "github.com/refraction-networking/conjure/pkg/core"
12 | pb "github.com/refraction-networking/conjure/proto"
13 | tls "github.com/refraction-networking/utls"
14 | ps "github.com/refraction-networking/conjure/pkg/phantoms"
15 | ca "github.com/refraction-networking/conjure/pkg/client/assets"
16 | "github.com/stretchr/testify/require"
17 | )
18 |
19 | func TestTLSFailure(t *testing.T) {
20 |
21 | testUrls := map[string]string{
22 | "expiredTlsUrl": "expired.badssl.com", // x509: certificate has expired or is not yet valid
23 | "wrongHostTlsUrl": "wrong.host.badssl.com",
24 | "untrustedRootTlsUrl": "untrusted-root.badssl.com",
25 | "revokedTlsUrl": "revoked.badssl.com",
26 | "pinningTlsUrl": "pinning-test.badssl.com",
27 | }
28 |
29 | simpleRequest := "GET / HTTP/1.1\r\nHOST:%s\r\n\r\n"
30 |
31 | for issue, url := range testUrls {
32 |
33 | dialConn, err := net.Dial("tcp", url+":443")
34 | if err != nil {
35 | t.Fatalf("Failed when we shouldn't have: %v", err)
36 | }
37 | defer dialConn.Close()
38 |
39 | config := tls.Config{ServerName: url}
40 | tlsConn := tls.UClient(dialConn, &config, tls.HelloChrome_62)
41 | defer tlsConn.Close()
42 |
43 | request := fmt.Sprintf(simpleRequest, url)
44 |
45 | _, err = tlsConn.Write([]byte(request))
46 | if err != nil {
47 | t.Logf("%v - %v: [%v]", issue, url, err)
48 | } else {
49 | t.Logf("%v - %v: ", issue, url)
50 | }
51 | }
52 |
53 | }
54 |
55 | func TestSelectBoth(t *testing.T) {
56 | seed := []byte{
57 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
58 | 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
59 | }
60 |
61 | // Set up temp directory for stub ClientConf (so we don't overwrite our current one)
62 | oldpath := ca.Assets().GetAssetsDir()
63 | ca.AssetsSetDir(t.TempDir())
64 | defer ca.AssetsSetDir(oldpath)
65 |
66 | // Default Phantoms (can't assume ClientConf is particular dev version)
67 | var testPhantoms = ps.GetDefaultPhantomSubnets()
68 |
69 | // Set phantoms
70 | err := ca.Assets().SetPhantomSubnets(testPhantoms)
71 | if err != nil {
72 | t.Fatal(err)
73 | }
74 |
75 | phantomIPAddr4, phantomIPAddr6, _, err := SelectPhantom(seed, both)
76 | require.Nil(t, err, "encountered err while selecting IPs")
77 | require.NotNil(t, phantomIPAddr4, "Failed to select IPv4 address (support: both")
78 | require.Equal(t, "192.122.190.252", phantomIPAddr4.String(), "Incorrect Address chosen")
79 | require.NotNil(t, phantomIPAddr6, "Failed to select IPv6 address (support: both")
80 | require.Equal(t, "2001:48a8:687f:1:fc9d:ee40:b05d:6656", phantomIPAddr6.String(), "Incorrect Address chosen")
81 | }
82 |
83 | func TestConjureHMAC(t *testing.T) {
84 | // generated using
85 | // echo "customString" | hmac256 "1abcd2efgh3ijkl4"
86 | // soln1Str := "d209c99ea22606e5b990a770247b0cd005c157208cb7194fef407fe3fa7e9266"
87 | soln1Str := "d10b84f9e2cc57bb4294b8929a3fca25cce7f95eb226fa5bcddc5417e1d2eac2"
88 |
89 | soln1 := make([]byte, hex.DecodedLen(len(soln1Str)))
90 | hex.Decode(soln1, []byte(soln1Str))
91 |
92 | test1 := core.ConjureHMAC([]byte("1abcd2efgh3ijkl4"), "customString")
93 | test1Str := make([]byte, hex.EncodedLen(len(test1)))
94 | hex.Encode(test1Str, test1)
95 |
96 | if len(test1) != len(soln1) {
97 | t.Fatalf("Wrong hash Length:\n%s\n%s", soln1Str, test1Str)
98 | }
99 |
100 | if !hmac.Equal(test1, soln1) {
101 | t.Fatalf("Wrong hash returned:\n%s\n%s", soln1Str, test1Str)
102 | }
103 | }
104 |
105 | func TestGenerateKeys(t *testing.T) {
106 | var fakePubkey [32]byte
107 | k, _ := hex.DecodeString("00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF")
108 | copy(fakePubkey[:], k)
109 | keys, err := core.GenerateClientSharedKeys(fakePubkey)
110 | if err != nil {
111 | t.Fatalf("Failed to generate Conjure Keys: %v", err)
112 | }
113 | if keys == nil {
114 | t.Fatalf("Incorrect Keys generated: %v", keys.SharedSecret)
115 | }
116 | }
117 |
118 | func TestRegDigest(t *testing.T) {
119 | reg := ConjureReg{}
120 | soln1 := "{result:\"no stats tracked\"}"
121 |
122 | if reg.digestStats() != soln1 {
123 | t.Fatalf("Incorrect stats digest returned")
124 | }
125 |
126 | testRTT := uint32(1000)
127 | reg.stats = &pb.SessionStats{
128 | TotalTimeToConnect: &testRTT,
129 | TcpToDecoy: &testRTT}
130 |
131 | soln2 := "{result:\"success\", tcp_to_decoy:1000, tls_to_decoy:0, total_time_to_connect:1000}"
132 | if reg.digestStats() != soln2 {
133 | t.Fatalf("Incorrect stats digest returned")
134 | }
135 |
136 | reg.stats.TlsToDecoy = &testRTT
137 |
138 | soln3 := "{result:\"success\", tcp_to_decoy:1000, tls_to_decoy:1000, total_time_to_connect:1000}"
139 | if reg.digestStats() != soln3 {
140 | t.Fatalf("Incorrect stats digest returned")
141 | }
142 | }
143 |
144 | func TestCheckV6Decoys(t *testing.T) {
145 | ca.AssetsSetDir("./assets")
146 | decoysV6 := ca.Assets().GetV6Decoys()
147 | numDecoys := len(decoysV6)
148 |
149 | for _, decoy := range decoysV6 {
150 | if decoy.Ipv4Addr != nil {
151 | // If a decoys Ipv4 address is defined it will ignore the IPv6 address
152 | numDecoys--
153 | }
154 | }
155 |
156 | // t.Logf("V6 Decoys: %v", numDecoys)
157 | // if numDecoys < 5 {
158 | // t.Fatalf("Not enough V6 decoys in ClientConf (has: %v, need at least: %v)", numDecoys, 5)
159 | // }
160 | }
161 |
162 | func TestGetFirstConnection(t *testing.T) {
163 | type params struct {
164 | ips []*net.IP
165 | dialErr error
166 | retErr error
167 | }
168 |
169 | ip1 := net.IPv4(1, 1, 1, 1)
170 | ip2 := net.IPv6loopback
171 |
172 | testCases := []params{
173 | {nil, nil, ErrNoOpenConns},
174 | {[]*net.IP{}, nil, ErrNoOpenConns},
175 | {[]*net.IP{&ip1}, nil, nil},
176 | {[]*net.IP{nil}, nil, ErrNoOpenConns},
177 | {[]*net.IP{&ip1, &ip1}, nil, nil},
178 | {[]*net.IP{&ip1, &ip2}, nil, nil},
179 | {[]*net.IP{&ip2, &ip1}, nil, nil},
180 | {[]*net.IP{&ip2, &ip2}, nil, nil},
181 | {[]*net.IP{&ip1, nil}, nil, nil},
182 | {[]*net.IP{nil, &ip1}, nil, nil},
183 | {[]*net.IP{&ip2, nil}, nil, nil},
184 | }
185 |
186 | for i, c := range testCases {
187 | testGetFirstConn(t, c.ips, c.dialErr, c.retErr, i)
188 | }
189 | }
190 |
191 | func testGetFirstConn(t *testing.T, addrList []*net.IP, dialErr error, retErr error, i int) {
192 | reg := ConjureReg{
193 | sessionIDStr: "test",
194 | phantomDstPort: 443,
195 | }
196 |
197 | cl, _ := net.Pipe()
198 | defer cl.Close()
199 |
200 | dialFn := func(ctx context.Context, network, laddr, raddr string) (net.Conn, error) {
201 | return cl, dialErr
202 | }
203 |
204 | c, err := reg.getFirstConnection(context.Background(), dialFn, addrList)
205 | if retErr != nil {
206 | require.ErrorIs(t, err, retErr, i)
207 | } else {
208 | require.Nil(t, err, i)
209 | require.NotNil(t, c, i)
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/tapdance/counter.go:
--------------------------------------------------------------------------------
1 | package tapdance
2 |
3 | import "sync"
4 |
5 | // CounterUint64 is a goroutine-safe uint64 counter.
6 | // Wraps, if underflows/overflows.
7 | type CounterUint64 struct {
8 | sync.RWMutex
9 | value uint64
10 | }
11 |
12 | // Inc increases the counter and returns resulting value
13 | func (c *CounterUint64) Inc() uint64 {
14 | c.Lock()
15 | defer c.Unlock()
16 | if c.value == ^uint64(0) {
17 | // if max
18 | c.value = 0
19 | } else {
20 | c.value++
21 | }
22 | return c.value
23 | }
24 |
25 | // GetAndInc returns current value and then increases the counter
26 | func (c *CounterUint64) GetAndInc() uint64 {
27 | c.Lock()
28 | retVal := c.value
29 | if c.value == ^uint64(0) {
30 | // if max
31 | c.value = 0
32 | } else {
33 | c.value++
34 | }
35 | c.Unlock()
36 | return retVal
37 | }
38 |
39 | // Dec decrements the counter and returns resulting value
40 | func (c *CounterUint64) Dec() uint64 {
41 | c.Lock()
42 | defer c.Unlock()
43 | if c.value == 0 {
44 | c.value = ^uint64(0)
45 | } else {
46 | c.value--
47 | }
48 | return c.value
49 | }
50 |
51 | // Get returns current counter value
52 | func (c *CounterUint64) Get() (value uint64) {
53 | c.RLock()
54 | value = c.value
55 | c.RUnlock()
56 | return
57 | }
58 |
59 | // Set assigns current counter value
60 | func (c *CounterUint64) Set(value uint64) {
61 | c.Lock()
62 | c.value = value
63 | c.Unlock()
64 | return
65 | }
66 |
--------------------------------------------------------------------------------
/tapdance/dialer.go:
--------------------------------------------------------------------------------
1 | package tapdance
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "net"
8 | "strings"
9 | "time"
10 |
11 | transports "github.com/refraction-networking/conjure/pkg/transports/client"
12 | pb "github.com/refraction-networking/conjure/proto"
13 | )
14 |
15 | var sessionsTotal CounterUint64
16 | var randomizePortDefault = false
17 |
18 | // Dialer contains options and implements advanced functions for establishing TapDance connection.
19 | type Dialer struct {
20 | SplitFlows bool
21 |
22 | // THIS IS REQUIRED TO INTERFACE WITH PSIPHON ANDROID
23 | // we use their dialer to prevent connection loopback into our own proxy
24 | // connection when tunneling the whole device.
25 | //
26 | // Deprecated: Dialer does not allow specifying the local address used for NAT traversal in
27 | // some transports. Use DialerWithLaddr instead.
28 | Dialer func(context.Context, string, string) (net.Conn, error)
29 |
30 | // DialerWithLaddr allows a custom dialer to be used for the underlying TCP/UDP connection.
31 | //
32 | // THIS IS REQUIRED TO INTERFACE WITH PSIPHON ANDROID
33 | // we use their dialer to prevent connection loopback into our own proxy
34 | // connection when tunneling the whole device.
35 | DialerWithLaddr dialFunc
36 |
37 | DarkDecoy bool
38 |
39 | // The type of registrar to use when performing Conjure registrations.
40 | DarkDecoyRegistrar Registrar
41 |
42 | // DisableRegistrarOverrides Indicates whether the client will allow the registrar to provide
43 | // alternative parameters that may work better in substitute for the deterministically selected
44 | // parameters. This only works for bidirectional registration methods where the client receives
45 | // a RegistrationResponse.
46 | DisableRegistrarOverrides bool
47 |
48 | // The type of transport to use for Conjure connections.
49 | Transport pb.TransportType
50 | TransportConfig Transport
51 |
52 | // RegDelay is the delay duration to wait for registration ingest.
53 | RegDelay time.Duration
54 |
55 | UseProxyHeader bool
56 | V6Support bool
57 |
58 | // Width indicates the number of independent decoy registrations to send in parallel as success
59 | // rates for individual decoy registrations are relatively low. (Default 5)
60 | //
61 | // Deprecated: Use the Width parameter in the Decoy Registrar.
62 | Width int
63 |
64 | // Subnet that we want to limit to (or empty if they're all fine)
65 | PhantomNet string
66 |
67 | // Whether we want to register and connect to a phantom, or register only
68 | RegisterOnly bool
69 | }
70 |
71 | // Dial connects to the address on the named network.
72 | //
73 | // The only supported network at this time: "tcp".
74 | // The address has the form "host:port".
75 | // The host must be a literal IP address, or a host name that can be
76 | // resolved to IP addresses.
77 | // To avoid abuse, only certain whitelisted ports are allowed.
78 | //
79 | // Example: Dial("tcp", "golang.org:80")
80 | func Dial(network, address string) (net.Conn, error) {
81 | var d Dialer
82 | return d.Dial(network, address)
83 | }
84 |
85 | // Dial connects to the address on the named network.
86 | func (d *Dialer) Dial(network, address string) (net.Conn, error) {
87 | return d.DialContext(context.Background(), network, address)
88 | }
89 |
90 | // DialContext connects to the address on the named network using the provided context.
91 | // Long deadline is strongly advised, since tapdance will try multiple decoys.
92 | //
93 | // The only supported network at this time: "tcp".
94 | // The address has the form "host:port".
95 | // The host must be a literal IP address, or a host name that can be
96 | // resolved to IP addresses.
97 | // To avoid abuse, only certain whitelisted ports are allowed.
98 | //
99 | // Example: Dial("tcp", "golang.org:80")
100 | func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
101 | if network != "tcp" {
102 | return nil, &net.OpError{Op: "dial", Net: network, Err: net.UnknownNetworkError(network)}
103 | }
104 | if len(address) > 0 {
105 | _, _, err := net.SplitHostPort(address)
106 | if err != nil {
107 | return nil, err
108 | }
109 | }
110 |
111 | if d.DialerWithLaddr != nil && d.Dialer != nil {
112 | return nil, fmt.Errorf("both DialerWithLaddr and Dialer are defined, only one dialer can be used")
113 | }
114 |
115 | if d.Dialer != nil {
116 | d.DialerWithLaddr = func(ctx context.Context, network, laddr, raddr string) (net.Conn, error) {
117 | if laddr != "" {
118 | return nil, errUnsupportedLaddr
119 | }
120 | return d.Dialer(ctx, network, raddr)
121 | }
122 | } else if d.DialerWithLaddr == nil {
123 | // custom dialer is not set, use default
124 | d.DialerWithLaddr = func(ctx context.Context, network, laddr, raddr string) (net.Conn, error) {
125 | defaultDialer := net.Dialer{}
126 | localAddr, err := resolveAddr(network, laddr)
127 | if err != nil {
128 | return nil, fmt.Errorf("error resolving laddr: %v", err)
129 | }
130 |
131 | defaultDialer.LocalAddr = localAddr
132 | return defaultDialer.DialContext(ctx, network, raddr)
133 | }
134 | }
135 |
136 | if !d.SplitFlows {
137 | if !d.DarkDecoy {
138 | flow, err := makeTdFlow(flowBidirectional, nil, address)
139 | if err != nil {
140 | return nil, err
141 | }
142 | flow.tdRaw.Dialer = d.Dialer
143 | flow.tdRaw.useProxyHeader = d.UseProxyHeader
144 | return flow, flow.DialContext(ctx)
145 | }
146 | // Conjure
147 | var cjSession *ConjureSession
148 |
149 | transport := d.TransportConfig
150 | var err error
151 | if d.TransportConfig == nil {
152 | transport, err = transports.ConfigFromTransportType(d.Transport, randomizePortDefault)
153 | }
154 | if err != nil {
155 | return nil, err
156 | }
157 |
158 | // If specified, only select a phantom from a given range
159 | if d.PhantomNet != "" {
160 | _, phantomRange, err := net.ParseCIDR(d.PhantomNet)
161 | if err != nil {
162 | return nil, errors.New("Invalid Phantom network goal")
163 | }
164 | cjSession = FindConjureSessionInRange(address, d.TransportConfig, phantomRange)
165 | if cjSession == nil {
166 | return nil, errors.New("Failed to find Phantom in target subnet")
167 | }
168 | } else {
169 | cjSession = MakeConjureSession(address, transport)
170 | }
171 |
172 | cjSession.Dialer = d.DialerWithLaddr
173 | cjSession.UseProxyHeader = d.UseProxyHeader
174 | cjSession.DisableRegistrarOverrides = d.DisableRegistrarOverrides
175 | cjSession.RegDelay = d.RegDelay
176 |
177 | if d.V6Support {
178 | cjSession.V6Support = &V6{include: both, support: true}
179 | } else {
180 | cjSession.V6Support = &V6{include: v4, support: false}
181 | }
182 | if len(address) == 0 {
183 | return nil, errors.New("Dark Decoys require target address to be set")
184 | }
185 | return DialConjure(ctx, cjSession, d.DarkDecoyRegistrar, d.RegisterOnly)
186 | }
187 |
188 | return nil, errors.New("SplitFlows are not supported")
189 | }
190 |
191 | // DialProxy establishes direct connection to TapDance station proxy.
192 | // Users are expected to send HTTP CONNECT request next.
193 | func (d *Dialer) DialProxy() (net.Conn, error) {
194 | return d.DialProxyContext(context.Background())
195 | }
196 |
197 | // DialProxyContext establishes direct connection to TapDance station proxy using the provided context.
198 | // Users are expected to send HTTP CONNECT request next.
199 | func (d *Dialer) DialProxyContext(ctx context.Context) (net.Conn, error) {
200 | return d.DialContext(ctx, "tcp", "")
201 | }
202 |
203 | func resolveAddr(network, addrStr string) (net.Addr, error) {
204 | if addrStr == "" {
205 | return nil, nil
206 | }
207 |
208 | if strings.Contains(network, "tcp") {
209 | return net.ResolveTCPAddr(network, addrStr)
210 | }
211 |
212 | return net.ResolveUDPAddr(network, addrStr)
213 | }
214 |
215 | var errUnsupportedLaddr = fmt.Errorf("dialer does not support laddr")
216 |
--------------------------------------------------------------------------------
/tapdance/dialer_test.go:
--------------------------------------------------------------------------------
1 | package tapdance
2 |
3 | import (
4 | "bufio"
5 | "crypto/tls"
6 | "fmt"
7 | "io/ioutil"
8 | "net"
9 | "net/http"
10 | "net/url"
11 | "os"
12 | "testing"
13 |
14 | pb "github.com/refraction-networking/conjure/proto"
15 | )
16 |
17 | func setupTestAssets() error {
18 | tmpDir, err := ioutil.TempDir("/tmp/", "td-test-")
19 | if err != nil {
20 | return err
21 | }
22 | AssetsSetDir(tmpDir)
23 | // make sure station won't send new ClientConf
24 | err = Assets().SetGeneration(100500)
25 | if err != nil {
26 | return err
27 | }
28 |
29 | // use testing public key
30 | keyType := pb.KeyType_AES_GCM_128
31 | stationTestPubkey, err := ioutil.ReadFile("../assets/station_pubkey_test")
32 | if err != nil {
33 | return err
34 | }
35 |
36 | pubKey := &pb.PubKey{
37 | Key: stationTestPubkey,
38 | Type: &keyType,
39 | }
40 | if err != nil {
41 | return err
42 | }
43 | Assets().SetPubkey(pubKey)
44 |
45 | // use correct decoy
46 | tapdance1Decoy := pb.InitTLSDecoySpec("192.122.190.104", "tapdance1.freeaeskey.xyz")
47 | err = Assets().SetDecoys([]*pb.TLSDecoySpec{tapdance1Decoy})
48 | if err != nil {
49 | return err
50 | }
51 | return nil
52 | }
53 |
54 | func TestMain(m *testing.M) {
55 | err := setupTestAssets()
56 | if err != nil {
57 | panic(err)
58 | }
59 | retCode := m.Run()
60 | os.Exit(retCode)
61 | }
62 |
63 | func tapDanceDialTest(t *testing.T, darkDecoys bool) {
64 | urlParse := func(urlStr string) url.URL {
65 | _url, err := url.Parse(urlStr)
66 | if err != nil {
67 | panic(err)
68 | }
69 | return *_url
70 | }
71 | testUrls := []url.URL{
72 | // TODO: uncomment when/if :80 is allowed on all stations
73 | // urlParse("http://detectportal.firefox.com:80/success.txt"),
74 | urlParse("https://tapdance1.freeaeskey.xyz:443/"),
75 | }
76 |
77 | tdDialer := Dialer{DarkDecoy: darkDecoys}
78 | for _, testUrl := range testUrls {
79 | referenceResponse, err := getResponseString(testUrl, net.Dial)
80 | if err != nil {
81 | t.Fatalf("Failed to get reference response from %v : %v. Check your connection",
82 | testUrl, err)
83 | }
84 |
85 | tdResponse, err := getResponseString(testUrl, tdDialer.Dial)
86 | if err != nil {
87 | t.Fatalf("Failed to get response from %v via TapDance: %v.", testUrl.String(), err)
88 | }
89 | if string(referenceResponse) != string(tdResponse) {
90 | t.Fatalf("Unexpected response from %s\nExpected: %s\nGot: %s",
91 | testUrl.String(), string(referenceResponse), string(tdResponse))
92 | }
93 | }
94 | }
95 |
96 | // These tests run forever
97 | func DisabledTestTapdanceDial(t *testing.T) {
98 | tapDanceDialTest(t, false)
99 | }
100 |
101 | func DisabledTestDarkDecoyDial(t *testing.T) {
102 | tapDanceDialTest(t, true)
103 | }
104 |
105 | func getResponseString(url url.URL, dial func(network, address string) (net.Conn, error)) (string, error) {
106 | conn, err := dial("tcp", url.Hostname()+":"+url.Port())
107 | if err != nil {
108 | return "", fmt.Errorf("dial failed: %v", err)
109 | }
110 | if url.Scheme == "https" {
111 | conn = tls.Client(conn, &tls.Config{ServerName: url.Hostname()})
112 | }
113 | defer conn.Close()
114 |
115 | req, err := http.NewRequest("GET", url.String(), nil)
116 | req.Host = url.Hostname()
117 | if err != nil {
118 | return "", fmt.Errorf("http.NewRequest failed: %v", err)
119 | }
120 |
121 | err = req.Write(conn)
122 | if err != nil {
123 | return "", fmt.Errorf("write failed: %v", err)
124 | }
125 |
126 | resp, err := http.ReadResponse(bufio.NewReader(conn), req)
127 | if err != nil {
128 | return "", fmt.Errorf("http.ReadResponse failed: %v", err)
129 | }
130 |
131 | responseBody, err := ioutil.ReadAll(resp.Body)
132 | if err != nil {
133 | return "", fmt.Errorf("ioutil.ReadAll failed: %v", err)
134 | }
135 | return string(responseBody), nil
136 | }
137 |
--------------------------------------------------------------------------------
/tapdance/interfaces.go:
--------------------------------------------------------------------------------
1 | package tapdance
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/refraction-networking/conjure/pkg/core/interfaces"
7 | )
8 |
9 | // Transport provides a generic interface for utilities that allow the client to dial and connect to
10 | // a phantom address when creating a Conjure connection.
11 | type Transport interfaces.Transport
12 |
13 | // Registrar defines the interface for a module completing the initial portion of the conjure
14 | // protocol which registers the clients intent to connect, along with the specifics of the session
15 | // they wish to establish.
16 | type Registrar interface {
17 | Register(*ConjureSession, context.Context) (*ConjureReg, error)
18 |
19 | // PrepareRegKeys prepares key materials specific to the registrar
20 | PrepareRegKeys(stationPubkey [32]byte, sessionSecret []byte) error
21 | }
22 |
--------------------------------------------------------------------------------
/tapdance/logger.go:
--------------------------------------------------------------------------------
1 | package tapdance
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "sync"
7 |
8 | "github.com/sirupsen/logrus"
9 | )
10 |
11 | // implements interface logrus.Formatter
12 | type formatter struct {
13 | }
14 |
15 | func (f *formatter) Format(entry *logrus.Entry) ([]byte, error) {
16 | return []byte(fmt.Sprintf("[%s] %s\n", entry.Time.Format("15:04:05"), entry.Message)), nil
17 | }
18 |
19 | var logrusLogger *logrus.Logger
20 | var initLoggerOnce sync.Once
21 |
22 | // Logger is an access point for TapDance-wide logger
23 | func Logger() *logrus.Logger {
24 | initLoggerOnce.Do(func() {
25 | logrusLogger = logrus.New()
26 | logrusLogger.Formatter = new(formatter)
27 | // logrusLogger.Level = logrus.InfoLevel
28 | logrusLogger.Level = logrus.DebugLevel
29 |
30 | // buildInfo const will be overwritten by CI with `sed` for test builds
31 | // if not overwritten -- this is a NO-OP
32 | const buildInfo = ""
33 | if len(buildInfo) > 0 {
34 | logrusLogger.Infof("Running gotapdance build %s", buildInfo)
35 | }
36 | })
37 | return logrusLogger
38 | }
39 |
40 | // SetLoggerOutput will allow a caller to change the Logger output from the
41 | // default of os.Stderr
42 | func SetLoggerOutput(out io.Writer) {
43 | Logger().SetOutput(out)
44 | }
45 |
--------------------------------------------------------------------------------
/tapdance/tapdance.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package tapdance implements a refraction networking client in golang.
3 | */
4 | package tapdance
5 |
--------------------------------------------------------------------------------
/tapdance/utils.go:
--------------------------------------------------------------------------------
1 | package tapdance
2 |
3 | import (
4 | "bytes"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | "crypto/rand"
8 | "crypto/sha256"
9 | "encoding/binary"
10 | "errors"
11 | "fmt"
12 | mrand "math/rand"
13 | "net"
14 | "strconv"
15 | "strings"
16 | "time"
17 |
18 | "github.com/refraction-networking/ed25519/extra25519"
19 | "golang.org/x/crypto/curve25519"
20 | )
21 |
22 | // The key argument should be the AES key, either 16 or 32 bytes
23 | // to select AES-128 or AES-256.
24 | func aesGcmEncrypt(plaintext []byte, key []byte, iv []byte) ([]byte, error) {
25 | block, err := aes.NewCipher(key)
26 | if err != nil {
27 | return nil, err
28 | }
29 |
30 | aesGcmCipher, err := cipher.NewGCM(block)
31 | if err != nil {
32 | return nil, err
33 | }
34 | return aesGcmCipher.Seal(nil, iv, plaintext, nil), nil
35 | }
36 |
37 | // Tries to get crypto random int in range [min, max]
38 | // In case of crypto failure -- return insecure pseudorandom
39 | func getRandInt(min int, max int) int {
40 | // I can't believe Golang is making me do that
41 | // Flashback to awful C/C++ libraries
42 | diff := max - min
43 | if diff < 0 {
44 | Logger().Warningf("getRandInt(): max is less than min")
45 | min = max
46 | diff *= -1
47 | } else if diff == 0 {
48 | return min
49 | }
50 | var v int64
51 | err := binary.Read(rand.Reader, binary.LittleEndian, &v)
52 | if v < 0 {
53 | v *= -1
54 | }
55 | if err != nil {
56 | Logger().Warningf("Unable to securely get getRandInt(): " + err.Error())
57 | v = mrand.Int63()
58 | }
59 | return min + int(v%int64(diff+1))
60 | }
61 |
62 | // returns random duration between min and max in milliseconds
63 | func getRandomDuration(min int, max int) time.Duration {
64 | return time.Millisecond * time.Duration(getRandInt(min, max))
65 | }
66 |
67 | // Get padding of length [minLen, maxLen).
68 | // Distributed in pseudogaussian style.
69 | // Padded using symbol '#'. Known plaintext attacks, anyone?
70 | func getRandPadding(minLen int, maxLen int, smoothness int) string {
71 | paddingLen := 0
72 | for j := 0; j < smoothness; j++ {
73 | paddingLen += getRandInt(minLen, maxLen)
74 | }
75 | paddingLen = paddingLen / smoothness
76 |
77 | return strings.Repeat("#", paddingLen)
78 | }
79 |
80 | func getRandString(length int) string {
81 | const alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
82 | randString := make([]byte, length)
83 | for i := range randString {
84 | randString[i] = alphabet[getRandInt(0, len(alphabet)-1)]
85 | }
86 | return string(randString)
87 | }
88 |
89 | type tapdanceSharedKeys struct {
90 | FspKey, FspIv, VspKey, VspIv, NewMasterSecret, ConjureSeed []byte
91 | }
92 |
93 | func getMsgWithHeader(msgType msgType, msgBytes []byte) []byte {
94 | if len(msgBytes) == 0 {
95 | return nil
96 | }
97 | bufSend := new(bytes.Buffer)
98 | var err error
99 | switch msgType {
100 | case msgProtobuf:
101 | if len(msgBytes) <= int(maxInt16) {
102 | bufSend.Grow(2 + len(msgBytes)) // to avoid double allocation
103 | err = binary.Write(bufSend, binary.BigEndian, int16(len(msgBytes)))
104 |
105 | } else {
106 | bufSend.Grow(2 + 4 + len(msgBytes)) // to avoid double allocation
107 | bufSend.Write([]byte{0, 0})
108 | err = binary.Write(bufSend, binary.BigEndian, int32(len(msgBytes)))
109 | }
110 | case msgRawData:
111 | err = binary.Write(bufSend, binary.BigEndian, int16(-len(msgBytes)))
112 | default:
113 | panic("getMsgWithHeader() called with msgType: " + strconv.Itoa(int(msgType)))
114 | }
115 | if err != nil {
116 | // shouldn't ever happen
117 | Logger().Errorln("getMsgWithHeader() failed with error: ", err)
118 | Logger().Errorln("msgType ", msgType)
119 | Logger().Errorln("msgBytes ", msgBytes)
120 | }
121 | bufSend.Write(msgBytes)
122 | return bufSend.Bytes()
123 | }
124 |
125 | func uint16toInt16(i uint16) int16 {
126 | pos := int16(i & 32767)
127 | neg := int16(0)
128 | if i&32768 != 0 {
129 | neg = int16(-32768)
130 | }
131 | return pos + neg
132 | }
133 |
134 | // generates HTTP request, that is ready to have tag prepended to it
135 | func generateHTTPRequestBeginning(decoyHostname string) []byte {
136 | sharedHeaders := `Host: ` + decoyHostname +
137 | "\nUser-Agent: TapDance/1.2 (+https://refraction.network/info)"
138 | httpTag := fmt.Sprintf(`GET / HTTP/1.1
139 | %s
140 | X-Ignore: %s`, sharedHeaders, getRandPadding(7, maxInt(612-len(sharedHeaders), 7), 10))
141 | return []byte(strings.Replace(httpTag, "\n", "\r\n", -1))
142 | }
143 |
144 | func reverseEncrypt(ciphertext []byte, keyStream []byte) []byte {
145 | var plaintext string
146 | // our plaintext can be antyhing where x & 0xc0 == 0x40
147 | // i.e. 64-127 in ascii (@, A-Z, [\]^_`, a-z, {|}~ DEL)
148 | // This means that we are allowed to choose the last 6 bits
149 | // of each byte in the ciphertext arbitrarily; the upper 2
150 | // bits will have to be 01, so that our plaintext ends up
151 | // in the desired range.
152 | var ka, kb, kc, kd byte // key stream bytes
153 | var ca, cb, cc, cd byte // ciphertext bytes
154 | var pa, pb, pc, pd byte // plaintext bytes
155 | var sa, sb, sc byte // secret bytes
156 |
157 | var tagIdx, keystreamIdx int
158 |
159 | for tagIdx < len(ciphertext) {
160 | ka = keyStream[keystreamIdx]
161 | kb = keyStream[keystreamIdx+1]
162 | kc = keyStream[keystreamIdx+2]
163 | kd = keyStream[keystreamIdx+3]
164 | keystreamIdx += 4
165 |
166 | // read 3 bytes
167 | sa = ciphertext[tagIdx]
168 | sb = ciphertext[tagIdx+1]
169 | sc = ciphertext[tagIdx+2]
170 | tagIdx += 3
171 |
172 | // figure out what plaintext needs to be in base64 encode
173 | ca = (ka & 0xc0) | ((sa & 0xfc) >> 2) // 6 bits sa
174 | cb = (kb & 0xc0) | (((sa & 0x03) << 4) | ((sb & 0xf0) >> 4)) // 2 bits sa, 4 bits sb
175 | cc = (kc & 0xc0) | (((sb & 0x0f) << 2) | ((sc & 0xc0) >> 6)) // 4 bits sb, 2 bits sc
176 | cd = (kd & 0xc0) | (sc & 0x3f) // 6 bits sc
177 |
178 | // Xor with key_stream, and add on 0x40 so it's in range of allowed
179 | pa = (ca ^ ka) + 0x40
180 | pb = (cb ^ kb) + 0x40
181 | pc = (cc ^ kc) + 0x40
182 | pd = (cd ^ kd) + 0x40
183 |
184 | plaintext += string(pa)
185 | plaintext += string(pb)
186 | plaintext += string(pc)
187 | plaintext += string(pd)
188 | }
189 | return []byte(plaintext)
190 | }
191 |
192 | func minInt(a, b int) int {
193 | if a > b {
194 | return b
195 | }
196 | return a
197 | }
198 |
199 | func maxInt(a, b int) int {
200 | if a > b {
201 | return a
202 | }
203 | return b
204 | }
205 |
206 | // Converts provided duration to raw milliseconds.
207 | // Returns a pointer to u32, because protobuf wants pointers.
208 | // Max valid input duration (that fits into uint32): 49.71 days.
209 | func durationToU32ptrMs(d time.Duration) *uint32 {
210 | i := uint32(d.Nanoseconds() / int64(time.Millisecond))
211 | return &i
212 | }
213 |
214 | func readAndClose(c net.Conn, readDeadline time.Duration) {
215 | tinyBuf := []byte{0}
216 | c.SetReadDeadline(time.Now().Add(readDeadline))
217 | c.Read(tinyBuf)
218 | c.Close()
219 | }
220 |
221 | func errIsTimeout(err error) bool {
222 | if err != nil {
223 | if strings.Contains(err.Error(), ": i/o timeout") || // client timed out
224 | err.Error() == "EOF" { // decoy timed out
225 | return true
226 | }
227 | }
228 | return false
229 | }
230 |
231 | // obfuscateTagAndProtobuf() generates key-pair and combines it /w stationPubkey to generate
232 | // sharedSecret. Client will use Eligator to find and send uniformly random representative for its
233 | // public key (and avoid sending it directly over the wire, as points on ellyptic curve are
234 | // distinguishable)
235 | // Then the sharedSecret will be used to encrypt stegoPayload and protobuf slices:
236 | // - stegoPayload is encrypted with AES-GCM KEY=sharedSecret[0:16], IV=sharedSecret[16:28]
237 | // - protobuf is encrypted with AES-GCM KEY=sharedSecret[0:16], IV={new random IV}, that will be
238 | // prepended to encryptedProtobuf and eventually sent out together
239 | //
240 | // Returns
241 | // - tag(concatenated representative and encrypted stegoPayload),
242 | // - encryptedProtobuf(concatenated 12 byte IV + encrypted protobuf)
243 | // - error
244 | func obfuscateTagAndProtobuf(stegoPayload []byte, protobuf []byte, stationPubkey []byte) ([]byte, []byte, error) {
245 | if len(stationPubkey) != 32 {
246 | return nil, nil, errors.New("Unexpected station pubkey length. Expected: 32." +
247 | " Received: " + strconv.Itoa(len(stationPubkey)) + ".")
248 | }
249 | var sharedSecret, clientPrivate, clientPublic, representative [32]byte
250 | for ok := false; ok != true; {
251 | var sliceKeyPrivate []byte = clientPrivate[:]
252 | _, err := rand.Read(sliceKeyPrivate)
253 | if err != nil {
254 | return nil, nil, err
255 | }
256 |
257 | ok = extra25519.ScalarBaseMult(&clientPublic, &representative, &clientPrivate)
258 | }
259 | var stationPubkeyByte32 [32]byte
260 | copy(stationPubkeyByte32[:], stationPubkey)
261 | curve25519.ScalarMult(&sharedSecret, &clientPrivate, &stationPubkeyByte32)
262 |
263 | // extra25519.ScalarBaseMult does not randomize most significant bit(sign of y_coord?)
264 | // Other implementations of elligator may have up to 2 non-random bits.
265 | // Here we randomize the bit, expecting it to be flipped back to 0 on station
266 | randByte := make([]byte, 1)
267 | _, err := rand.Read(randByte)
268 | if err != nil {
269 | return nil, nil, err
270 | }
271 | representative[31] |= (0xC0 & randByte[0])
272 |
273 | tagBuf := new(bytes.Buffer) // What we have to encrypt with the shared secret using AES
274 | tagBuf.Write(representative[:])
275 |
276 | stationPubkeyHash := sha256.Sum256(sharedSecret[:])
277 | aesKey := stationPubkeyHash[:16]
278 | aesIvTag := stationPubkeyHash[16:28] // 12 bytes for stegoPayload nonce
279 |
280 | encryptedStegoPayload, err := aesGcmEncrypt(stegoPayload, aesKey, aesIvTag)
281 | if err != nil {
282 | return nil, nil, err
283 | }
284 |
285 | tagBuf.Write(encryptedStegoPayload)
286 | tag := tagBuf.Bytes()
287 |
288 | if len(protobuf) == 0 {
289 | return tag, nil, err
290 | }
291 |
292 | // probably could have used all zeros as IV here, but better to err on safe side
293 | aesIvProtobuf := make([]byte, 12)
294 | _, err = rand.Read(aesIvProtobuf)
295 | if err != nil {
296 | return nil, nil, err
297 | }
298 |
299 | encryptedProtobuf, err := aesGcmEncrypt(protobuf, aesKey, aesIvProtobuf)
300 | return tag, append(aesIvProtobuf, encryptedProtobuf...), err
301 | }
302 |
--------------------------------------------------------------------------------
/tapdance/utils_test.go:
--------------------------------------------------------------------------------
1 | package tapdance
2 |
3 | import (
4 | "crypto/aes"
5 | "crypto/cipher"
6 | "crypto/rand"
7 | "encoding/hex"
8 | "fmt"
9 | "strings"
10 | "testing"
11 |
12 | "github.com/pkg/errors"
13 | )
14 |
15 | type TestRandReader struct{}
16 |
17 | func (z TestRandReader) Read(b []byte) (n int, err error) {
18 | for i := range b {
19 | b[i] = 4 // chosen by fair dice roll
20 | }
21 |
22 | return len(b), nil
23 | }
24 |
25 | var testRandReader TestRandReader
26 |
27 | // this function is only currently used in testing
28 | func aesGcmDecrypt(ciphertext []byte, key []byte, iv []byte) (plaintext []byte, err error) {
29 | block, err := aes.NewCipher(key)
30 | if err != nil {
31 | return
32 | }
33 |
34 | aesGcmCipher, err := cipher.NewGCM(block)
35 | if err != nil {
36 | return
37 | }
38 |
39 | plaintext, err = aesGcmCipher.Open(nil, iv, ciphertext, nil)
40 | if err != nil {
41 | return
42 | }
43 | return
44 | }
45 |
46 | func TestAES_GCM_EncryptDecrypt(t *testing.T) {
47 | iv, _ := hex.DecodeString("156738805e207a6f2c50413a")
48 | key, _ := hex.DecodeString("13c8ff335b01aaf970cbc7b7e3072249")
49 | plaintext, _ := hex.DecodeString("560b81b86b1f30da6d66a982310be6af471f2e9de248a58aa2731bd9b746532c98666d9e9963b1d02ec1d759c228f599411229cd98f2bfd71ad6007f71e4d6bc20a3e2a00322df06159536534480ec97288929dc87cbd658c49894d40b1997292bfa720625e18661fa66999cf4e7030c8bf4cfbe15d77d47c13d5236a8c797e95e80df9d7af6730d35f9a7aa9f5e478b739516bd6e0e5e64dcbc6cda669fdc5f0efbac5e23b25a3ad91e005e276d39438285bfe00c3b53b33f7127becc49ff9825d78f3cab06d315e22aea83a12cb69547d40a5d36c1d5cd288efc678a627cab2583c80f1d81bc3e3d27a4bd")
50 | expectedCryptotext, _ := hex.DecodeString("8ba27bd00b04ec8c8448d517d444b732e8e7179153eaaa9ffdddc8733d88e97dd86fee5eedf96395ba6bfbf98e0e9c74e72baa90ad0271fb621500eb9e15a0c984aa9c886db3f4cb1aab16b42aad4be78b477e9b57ada945fe7e3eb063bf0aff1800d9ec5a9c3be895ef0b785165a592f18fdf3184d167db2be93cd4b6e5e8dd533ee3bca05e19abea75d50aa68fa1ffd2da37090f6e73e94b1372ea2585eabd8f9c388d1cb4e058bbc72e2cd2d286135665944d0bd99bfea1ec06213ffd451252cf16b13828eee7e5688ad78d959d6447c841d4f52e58eac03baab1f2ba86f6fd0a9e68ac0e4d375a764507229a075d0e87661d84887a6d74d71297")
51 |
52 | cryptotext, err := aesGcmEncrypt(plaintext, key, iv)
53 | if err != nil {
54 | t.Fatalf(err.Error())
55 | }
56 | rePlaintext, err := aesGcmDecrypt(cryptotext, key, iv)
57 |
58 | if err != nil {
59 | t.Fatalf(err.Error())
60 | }
61 |
62 | if strings.Compare(string(plaintext), string(rePlaintext)) != 0 {
63 | t.Fatalf("Decrypted text differs from original!\nDecrypt(Encrypt(text)): %s\ntext: %s\n",
64 | hex.Dump(rePlaintext), hex.Dump(plaintext))
65 | }
66 |
67 | if strings.Compare(string(cryptotext), string(expectedCryptotext)) != 0 {
68 | t.Fatalf("Encrypted text differs from expected!\nExpected: %s\nGot: %s\n",
69 | hex.Dump(expectedCryptotext), hex.Dump(cryptotext))
70 | }
71 |
72 | }
73 |
74 | func TestReverseEncrypt(t *testing.T) {
75 | tag := []byte{192, 165, 165, 138, 112, 105, 67, 167, 10, 78, 204, 32, 77, 236, 146, 173, 91, 175, 146, 53, 43, 15, 69, 55, 133, 158, 89, 221, 140, 12, 117, 34, 155, 231, 154, 103, 195, 18, 139, 225, 245, 92, 240, 135, 121, 95, 51, 38, 110, 231, 27, 218, 38, 127, 128, 35, 170, 52, 162, 219, 27, 24, 249, 191, 194, 251, 188, 93, 85, 211, 229, 150, 151, 189, 34, 252, 105, 173, 227, 169, 97, 191, 137, 37, 110, 235, 72, 170, 99, 143, 98, 201, 2, 80, 226, 224, 2, 143, 7, 116, 26, 29, 199, 232, 112, 105, 209, 37, 55, 108, 161, 205, 10, 43, 172, 78, 169, 94, 44, 130, 201, 232, 192, 37, 1, 127, 33, 89, 183, 114, 83, 210, 122, 132, 135, 242, 96, 115, 61, 147, 41, 179, 237, 34, 72, 153, 81, 47, 11, 117, 95, 224, 60, 198, 211, 181, 221, 185, 117, 3, 172, 6, 189, 90, 237, 81, 147, 118, 8, 31, 165, 59, 143, 60, 120, 39, 228, 156, 199, 166, 140, 165, 241, 150, 242, 198}
76 | keystream := []byte{246, 204, 136, 183, 208, 201, 249, 218, 131, 117, 96, 249, 155, 7, 222, 35, 221, 95, 82, 237, 27, 90, 158, 165, 132, 44, 1, 229, 127, 116, 20, 135, 203, 220, 175, 224, 16, 136, 75, 172, 14, 20, 128, 238, 168, 192, 231, 133, 209, 154, 71, 205, 161, 135, 195, 135, 9, 66, 207, 28, 238, 90, 252, 4, 121, 229, 79, 84, 246, 167, 123, 187, 73, 65, 97, 219, 229, 93, 188, 135, 236, 84, 230, 5, 207, 105, 254, 181, 177, 68, 222, 192, 190, 182, 177, 33, 252, 118, 161, 101, 60, 35, 233, 36, 22, 242, 198, 8, 20, 151, 249, 172, 207, 58, 95, 110, 19, 84, 169, 17, 185, 3, 120, 102, 48, 13, 40, 238, 150, 10, 174, 204, 0, 144, 21, 250, 4, 39, 211, 85, 164, 90, 12, 104, 43, 130, 224, 77, 113, 79, 142, 97, 205, 71, 156, 211, 73, 42, 51, 169, 30, 81, 132, 85, 217, 18, 151, 184, 166, 32, 188, 0, 170, 67, 80, 80, 253, 165, 42, 5, 6, 27, 72, 62, 57, 7, 174, 156, 198, 72, 224, 132, 199, 175, 28, 175, 193, 17, 242, 143, 4, 152, 83, 205, 50, 26, 171, 28, 27, 190, 226, 5, 214, 152, 232, 131, 212, 104, 186, 219, 178, 172, 234, 35, 1, 177, 25, 79, 79, 166, 185, 85, 167, 110, 88, 114, 49, 201, 163, 201, 20, 139, 106, 125, 151, 191, 47, 160, 254, 34, 173, 229}
77 | result := string(reverseEncrypt(tag, keystream))
78 | expectedResult := "FF^RrnxsSO|sHknCNA`pOpJ`OUN|@@pjEVygP{`SFJuQyNbakMFYXV[uJReyipbbKSO@EbDiCoqhWw\\jeE|`UuN^AkUJHgwYMUGCeOInHci{o]ITTrfyrg^ao\\ddCcNVbRK]Q}guYre~GHMftRlB_fJfCfz^HAklOgUPBRGnuZwvf_BcMxBz}IMv^bujvTfUfy~Ja_zSfSqCweileGpVbXE{}QvfugUCpgjA^EiylGVVE}owA}LrPdf"
79 | if strings.Compare(expectedResult, result) != 0 {
80 | t.Fatalf("Expected encryption differs from result!\nExpected: %s\nGot: %s\n",
81 | expectedResult, result)
82 | }
83 | }
84 |
85 | func TestObfuscationRandomness(t *testing.T) {
86 | testKey, _ := hex.DecodeString("b47066bc390d2605cc13581c496ea995cb8cfadf00a649052509ef4ac8a51a07")
87 |
88 | tag := make([]byte, 177)
89 |
90 | rc := randomnessChecker{}
91 | for i := 0; i < 10000; i++ {
92 | _, err := rand.Read(tag)
93 | if err != nil {
94 | t.Fatalf("Error: %v\n", err)
95 | }
96 | _, representative, err := generateEligatorTransformedKey(testKey)
97 | if err != nil {
98 | t.Fatalf("Error: %v\n", err)
99 | }
100 | rc.addSample(representative)
101 | }
102 |
103 | err := rc.testInRange(4700, 5300)
104 | if err != nil {
105 | t.Fatal(err)
106 | }
107 | }
108 |
109 | // only supports samples of same size
110 | type randomnessChecker struct {
111 | bitCounts []int // how many bits are 1
112 | sampleCounts []int // how many samples for bit
113 | }
114 |
115 | func (rt *randomnessChecker) addSample(sample []byte) {
116 | for len(rt.bitCounts) < 8*len(sample) {
117 | // allocate bigger arrays (they are all of same size)
118 | rt.bitCounts = append(rt.bitCounts, make([]int, 8*len(sample))...)
119 | rt.sampleCounts = append(rt.sampleCounts, make([]int, 8*len(sample))...)
120 | }
121 |
122 | for sampleIdx := 0; sampleIdx < len(sample); sampleIdx++ {
123 | for bitIdx := 0; bitIdx < 8; bitIdx++ {
124 | mask := byte(1 << uint(bitIdx))
125 | bitCountIdx := sampleIdx*8 + bitIdx
126 | rt.sampleCounts[bitCountIdx] += 1
127 | if sample[sampleIdx]&mask >= 1 {
128 | rt.bitCounts[bitCountIdx] += 1
129 | }
130 | }
131 | }
132 |
133 | }
134 |
135 | func (rt *randomnessChecker) getNumSamples() int {
136 | numSamples := 0
137 | for i := 0; i < len(rt.sampleCounts); i++ {
138 | if rt.sampleCounts[0] != 0 {
139 | numSamples += 1
140 | }
141 | }
142 | return numSamples
143 | }
144 |
145 | // returns error if there are clear issues with randomness
146 | func (rt *randomnessChecker) testSimple() error {
147 | numSamples := rt.getNumSamples()
148 | for i := 0; i < numSamples; i++ {
149 | if rt.bitCounts[i] == 0 {
150 | return errors.New(fmt.Sprintf("Bit #%v is always zero. Sampled %v times.",
151 | i, rt.sampleCounts[i]))
152 | }
153 | if rt.bitCounts[i] == rt.sampleCounts[i] {
154 | return errors.New(fmt.Sprintf("Bit #%v is always one. Sampled %v times.",
155 | i, rt.sampleCounts[i]))
156 | }
157 | }
158 | return nil
159 | }
160 |
161 | // returns error if amount of times a bit is set is not in [min, max]
162 | func (rt *randomnessChecker) testInRange(min, max int) error {
163 | numSamples := rt.getNumSamples()
164 | for i := 0; i < numSamples; i++ {
165 |
166 | // Leak One bit for now as adding new randomness will break things.
167 | // see issue #38
168 | if i == 254 {
169 | continue
170 | }
171 |
172 | if rt.bitCounts[i] < min || rt.bitCounts[i] > max {
173 | return errors.New(fmt.Sprintf("Expected: bit #%v is set %v - %v times"+
174 | " out of %v samples. Got: bit is set %v times.",
175 | i, min, max, rt.sampleCounts[i], rt.bitCounts[i]))
176 | }
177 | }
178 | return nil
179 | }
180 |
--------------------------------------------------------------------------------
/tdproxy/README.md:
--------------------------------------------------------------------------------
1 | # tdproxy
2 | `import "github.com/refraction-networking/gotapdance/tdproxy"`
3 |
4 | * [Overview](#pkg-overview)
5 | * [Imported Packages](#pkg-imports)
6 | * [Index](#pkg-index)
7 |
8 | ## Overview
9 | Package tdproxy implements TapdanceProxy, which can ListenAndServe() on a given port,
10 | so you can use it as a SOCKS or HTTP proxy elsewhere.
11 |
12 | ## Imported Packages
13 |
14 | - [github.com/refraction-networking/gotapdance/tapdance](./../tapdance)
15 |
16 | ## Index
17 | * [Constants](#pkg-constants)
18 | * [Variables](#pkg-variables)
19 | * [type TapDanceProxy](#TapDanceProxy)
20 | * [func NewTapDanceProxy(listenPort int) \*TapDanceProxy](#NewTapDanceProxy)
21 | * [func (proxy \*TapDanceProxy) GetStatistics() (statistics string)](#TapDanceProxy.GetStatistics)
22 | * [func (proxy \*TapDanceProxy) GetStats() (stats string)](#TapDanceProxy.GetStats)
23 | * [func (proxy \*TapDanceProxy) ListenAndServe() error](#TapDanceProxy.ListenAndServe)
24 | * [func (proxy \*TapDanceProxy) Stop() error](#TapDanceProxy.Stop)
25 |
26 | #### Package files
27 | [flow.go](./flow.go) [tapdance.go](./tapdance.go)
28 |
29 | ## Constants
30 | ``` go
31 | const (
32 | ProxyStateInitialized = "Initialized"
33 | ProxyStateListening = "Listening"
34 | ProxyStateStopped = "Stopped"
35 | ProxyStateError = "Error"
36 | )
37 | ```
38 |
39 | ## Variables
40 | ``` go
41 | var Logger = tapdance.Logger()
42 | ```
43 |
44 | ## type [TapDanceProxy](./tapdance.go#L23-L46)
45 | ``` go
46 | type TapDanceProxy struct {
47 | State string
48 | // contains filtered or unexported fields
49 | }
50 | ```
51 | TODO: consider implementing https://golang.org/pkg/net/#Listener or other default interface
52 |
53 | ### func [NewTapDanceProxy](./tapdance.go#L48)
54 | ``` go
55 | func NewTapDanceProxy(listenPort int) *TapDanceProxy
56 | ```
57 |
58 | ### func (\*TapDanceProxy) [GetStatistics](./tapdance.go#L132)
59 | ``` go
60 | func (proxy *TapDanceProxy) GetStatistics() (statistics string)
61 | ```
62 |
63 | ### func (\*TapDanceProxy) [GetStats](./tapdance.go#L146)
64 | ``` go
65 | func (proxy *TapDanceProxy) GetStats() (stats string)
66 | ```
67 |
68 | ### func (\*TapDanceProxy) [ListenAndServe](./tapdance.go#L70)
69 | ``` go
70 | func (proxy *TapDanceProxy) ListenAndServe() error
71 | ```
72 |
73 | ### func (\*TapDanceProxy) [Stop](./tapdance.go#L100)
74 | ``` go
75 | func (proxy *TapDanceProxy) Stop() error
76 | ```
77 |
78 | - - -
79 | Generated by [godoc2ghmd](https://github.com/GandalfUK/godoc2ghmd)
--------------------------------------------------------------------------------
/tdproxy/flow.go:
--------------------------------------------------------------------------------
1 | package tdproxy
2 |
3 | import (
4 | "errors"
5 | "github.com/refraction-networking/gotapdance/tapdance"
6 | "io"
7 | "net"
8 | "strconv"
9 | "strings"
10 | "time"
11 | )
12 |
13 | // Connection-oriented state
14 | type tapDanceFlow struct {
15 | // tunnel index and start time
16 | id uint64
17 | startMs time.Time
18 |
19 | // reference to global proxy
20 | proxy *TapDanceProxy
21 |
22 | servConn net.Conn // can cast to tapdance.Conn but don't need to
23 | userConn net.Conn
24 | splitFlows bool
25 | }
26 |
27 | // TODO: use dial() functor
28 | func makeTapDanceFlow(proxy *TapDanceProxy, id uint64, splitFlows bool) *tapDanceFlow {
29 | tdFlow := new(tapDanceFlow)
30 |
31 | tdFlow.proxy = proxy
32 | tdFlow.id = id
33 |
34 | tdFlow.startMs = time.Now()
35 | tdFlow.splitFlows = splitFlows
36 |
37 | Logger.Debugf("Created new TD Flow: %#v\n", tdFlow)
38 | return tdFlow
39 | }
40 |
41 | func (TDstate *tapDanceFlow) redirect() error {
42 | dialer := tapdance.Dialer{SplitFlows: TDstate.splitFlows, DarkDecoy: true}
43 | var err error
44 | TDstate.servConn, err = dialer.DialProxy()
45 | if err != nil {
46 | TDstate.userConn.Close()
47 | return err
48 | }
49 | errChan := make(chan error)
50 | defer func() {
51 | TDstate.userConn.Close()
52 | TDstate.servConn.Close()
53 | _ = <-errChan // wait for second goroutine to close
54 | }()
55 |
56 | forwardFromServerToClient := func() {
57 | buf := make([]byte, 65536)
58 | n, _err := io.CopyBuffer(TDstate.userConn, TDstate.servConn, buf)
59 | Logger.Debugf("{tapDanceFlow} forwardFromServerToClient returns, bytes sent: " +
60 | strconv.FormatUint(uint64(n), 10))
61 | if _err == nil {
62 | _err = errors.New("server returned without error")
63 | }
64 | errChan <- _err
65 | return
66 | }
67 |
68 | forwardFromClientToServer := func() {
69 | buf := make([]byte, 65536)
70 | n, _err := io.CopyBuffer(TDstate.servConn, TDstate.userConn, buf)
71 | Logger.Debugf("{tapDanceFlow} forwardFromClientToServer returns, bytes sent: " +
72 | strconv.FormatUint(uint64(n), 10))
73 | if _err == nil {
74 | _err = errors.New("closed by application layer")
75 | }
76 | errChan <- _err
77 | return
78 | }
79 |
80 | go forwardFromServerToClient()
81 | go forwardFromClientToServer()
82 |
83 | if err = <-errChan; err != nil {
84 | if err.Error() == "MSG_CLOSE" || err.Error() == "closed by application layer" {
85 | Logger.Debugln("[Session " + strconv.FormatUint(uint64(TDstate.id), 10) +
86 | " Redirect function returns gracefully: " + err.Error())
87 | TDstate.proxy.closedGracefully.Inc()
88 | err = nil
89 | } else {
90 | str_err := err.Error()
91 |
92 | // statistics
93 | if strings.Contains(str_err, "TapDance station didn't pick up the request") {
94 | TDstate.proxy.notPickedUp.Inc()
95 | } else if strings.Contains(str_err, ": i/o timeout") {
96 | TDstate.proxy.timedOut.Inc()
97 | } else {
98 | TDstate.proxy.unexpectedError.Inc()
99 | }
100 | }
101 | }
102 | return err
103 | }
104 |
--------------------------------------------------------------------------------
/tdproxy/gengodoc.sh:
--------------------------------------------------------------------------------
1 | PACKAGE="github.com/refraction-networking/gotapdance/tdproxy"
2 | RFILE="$GOPATH/src/$PACKAGE/README.md"
3 | godoc2ghmd $PACKAGE > $RFILE
4 |
5 |
--------------------------------------------------------------------------------
/tdproxy/tapdance.go:
--------------------------------------------------------------------------------
1 | // Package tdproxy implements TapdanceProxy, which can ListenAndServe() on a given port,
2 | // so you can use it as a SOCKS or HTTP proxy elsewhere.
3 | package tdproxy
4 |
5 | import (
6 | "github.com/refraction-networking/gotapdance/tapdance"
7 | "net"
8 | "strconv"
9 | "sync"
10 | "time"
11 | )
12 |
13 | var Logger = tapdance.Logger()
14 |
15 | const (
16 | ProxyStateInitialized = "Initialized"
17 | ProxyStateListening = "Listening"
18 | ProxyStateStopped = "Stopped"
19 | ProxyStateError = "Error"
20 | )
21 |
22 | // TODO: consider implementing https://golang.org/pkg/net/#Listener or other default interface
23 | type TapDanceProxy struct {
24 | State string
25 |
26 | listener net.Listener
27 |
28 | listenPort int
29 |
30 | countTunnels tapdance.CounterUint64
31 |
32 | // statistics
33 | notPickedUp tapdance.CounterUint64
34 | timedOut tapdance.CounterUint64
35 | closedGracefully tapdance.CounterUint64
36 | unexpectedError tapdance.CounterUint64
37 |
38 | connections struct {
39 | sync.RWMutex
40 | m map[uint64]*tapDanceFlow
41 | }
42 |
43 | statsTicker *time.Ticker
44 |
45 | stop bool
46 | }
47 |
48 | func NewTapDanceProxy(listenPort int) *TapDanceProxy {
49 | //Logger.Level = logrus.DebugLevel
50 | proxy := new(TapDanceProxy)
51 | proxy.listenPort = listenPort
52 |
53 | proxy.connections.m = make(map[uint64]*tapDanceFlow)
54 | proxy.State = ProxyStateInitialized
55 |
56 | Logger.Infof("Successfully initialized new Tapdance Proxy")
57 | Logger.Debugf("%#v\n", proxy)
58 |
59 | return proxy
60 | }
61 |
62 | func (proxy *TapDanceProxy) statsHelper() error {
63 | proxy.statsTicker = time.NewTicker(time.Second * time.Duration(60))
64 | for range proxy.statsTicker.C {
65 | Logger.Infof(proxy.GetStatistics())
66 | }
67 | return nil
68 | }
69 |
70 | func (proxy *TapDanceProxy) ListenAndServe() error {
71 | var err error
72 | listenAddress := "127.0.0.1:" + strconv.Itoa(proxy.listenPort)
73 |
74 | proxy.State = ProxyStateListening
75 | proxy.stop = false
76 | if proxy.listener, err = net.Listen("tcp", listenAddress); err != nil {
77 | proxy.State = ProxyStateError
78 | return err
79 | }
80 | Logger.Infof("Accepting connections at port " + strconv.Itoa(proxy.listenPort))
81 | go proxy.statsHelper()
82 |
83 | for !proxy.stop {
84 | if conn, err := proxy.listener.Accept(); err == nil {
85 | go proxy.handleUserConn(conn)
86 | } else {
87 | if proxy.stop {
88 | proxy.State = ProxyStateStopped
89 | err = nil
90 | } else {
91 | proxy.State = ProxyStateError
92 | }
93 | return err
94 | }
95 | }
96 | proxy.State = ProxyStateStopped
97 | return nil
98 | }
99 |
100 | func (proxy *TapDanceProxy) Stop() error {
101 | proxy.stop = true
102 | proxy.listener.Close()
103 | proxy.connections.Lock()
104 | for _, tdState := range proxy.connections.m {
105 | tdState.servConn.Close()
106 | }
107 | proxy.connections.Unlock()
108 | proxy.statsTicker.Stop()
109 | return nil
110 | }
111 |
112 | func (proxy *TapDanceProxy) handleUserConn(userConn net.Conn) {
113 | tdState := proxy.addFlow(&userConn)
114 | defer func() {
115 | proxy.connections.Lock()
116 | delete(proxy.connections.m, tdState.id)
117 | proxy.connections.Unlock()
118 | }()
119 |
120 | // Initial request is not lost, because we still haven't read anything from client socket
121 | // So we just start Redirecting (client socket) <-> (server socket)
122 | if err := tdState.redirect(); err != nil {
123 | Logger.Errorf("[Session " + strconv.FormatUint(uint64(tdState.id), 10) +
124 | "] Shut down with error: " + err.Error())
125 | } else {
126 | Logger.Infof("[Session " + strconv.FormatUint(uint64(tdState.id), 10) +
127 | "] Closed gracefully.")
128 | }
129 | return
130 | }
131 |
132 | func (proxy *TapDanceProxy) GetStatistics() (statistics string) {
133 | statistics = "Sessions total: " +
134 | strconv.FormatUint(uint64(proxy.countTunnels.Get()), 10)
135 | statistics += ". Not picked up: " +
136 | strconv.FormatUint(uint64(proxy.notPickedUp.Get()), 10)
137 | statistics += ". Timed out: " +
138 | strconv.FormatUint(uint64(proxy.timedOut.Get()), 10)
139 | statistics += ". Unexpected error: " +
140 | strconv.FormatUint(uint64(proxy.unexpectedError.Get()), 10)
141 | statistics += ". Graceful close: " +
142 | strconv.FormatUint(uint64(proxy.closedGracefully.Get()), 10)
143 | return
144 | }
145 |
146 | func (proxy *TapDanceProxy) GetStats() (stats string) {
147 | stats = proxy.State + "\nPort: " + strconv.Itoa(proxy.listenPort) +
148 | "\nActive connections: " + strconv.Itoa(len(proxy.connections.m))
149 | return
150 | }
151 |
152 | func (proxy *TapDanceProxy) addFlow(userConn *net.Conn) (pTapdanceState *tapDanceFlow) {
153 | // Init connection state
154 | id := proxy.countTunnels.GetAndInc()
155 |
156 | pTapdanceState = makeTapDanceFlow(proxy, id, false)
157 | pTapdanceState.userConn = *userConn
158 |
159 | proxy.connections.Lock()
160 | proxy.connections.m[id] = pTapdanceState
161 | proxy.connections.Unlock()
162 |
163 | return
164 | }
165 |
--------------------------------------------------------------------------------
/tdproxy/tapdance_test.go:
--------------------------------------------------------------------------------
1 | package tdproxy
2 |
3 | import (
4 | "crypto/tls"
5 | "fmt"
6 | "io"
7 | "io/ioutil"
8 | "math/rand"
9 | "os"
10 | "testing"
11 | "time"
12 |
13 | pb "github.com/refraction-networking/conjure/proto"
14 | "github.com/refraction-networking/gotapdance/tapdance"
15 | "golang.org/x/net/websocket"
16 | )
17 |
18 | func setupTestAssets() error {
19 | tmpDir, err := ioutil.TempDir("/tmp/", "td-test-")
20 | if err != nil {
21 | return err
22 | }
23 | tapdance.AssetsSetDir(tmpDir)
24 | // make sure station won't send new ClientConf
25 | err = tapdance.Assets().SetGeneration(100500)
26 | if err != nil {
27 | return err
28 | }
29 |
30 | // use testing public key
31 | keyType := pb.KeyType_AES_GCM_128
32 | stationTestPubkey, err := ioutil.ReadFile("../assets/station_pubkey_test")
33 | if err != nil {
34 | return err
35 | }
36 |
37 | pubKey := &pb.PubKey{
38 | Key: stationTestPubkey,
39 | Type: &keyType,
40 | }
41 | if err != nil {
42 | return err
43 | }
44 | tapdance.Assets().SetPubkey(pubKey)
45 |
46 | // use correct decoy
47 | tapdance1Decoy := pb.InitTLSDecoySpec("192.122.190.104", "tapdance1.freeaeskey.xyz")
48 | err = tapdance.Assets().SetDecoys([]*pb.TLSDecoySpec{tapdance1Decoy})
49 | if err != nil {
50 | return err
51 | }
52 | return nil
53 | }
54 |
55 | func TestMain(m *testing.M) {
56 | err := setupTestAssets()
57 | if err != nil {
58 | panic(err)
59 | }
60 | retCode := m.Run()
61 | os.Exit(retCode)
62 | }
63 |
64 | // Fails in Conjure
65 | func DisabledTestSendSeq(t *testing.T) {
66 | conn, err := tapdance.Dial("tcp", "sfrolov.io:443")
67 | if err != nil {
68 | t.Error(err)
69 | return
70 | }
71 | conf, err := websocket.NewConfig("wss://sfrolov.io/echo", "http://localhost/")
72 | if err != nil {
73 | t.Error(err)
74 | return
75 | }
76 | wsConn, err := websocket.NewClient(conf,
77 | tls.Client(conn, &tls.Config{ServerName: "sfrolov.io"}))
78 | //err = sendseq.SendSeq(,
79 | // tls.Client(conn, &tls.Config{ServerName: "sfrolov.io"}))
80 | if err != nil {
81 | t.Error(err)
82 | return
83 | }
84 |
85 | rand.Seed(time.Now().UTC().Unix())
86 |
87 | randString := func(n int) []byte {
88 | const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
89 | b := make([]byte, n)
90 | for i := range b {
91 | b[i] = alphabet[rand.Intn(len(alphabet))]
92 | }
93 | return b
94 | }
95 |
96 | const repetitions = 5
97 | for ii := 0; ii < repetitions; ii++ {
98 | bytesOut := randString(20000 + rand.Intn(40000))
99 | bytesIn := make([]byte, len(bytesOut))
100 | _, err = wsConn.Write(bytesOut)
101 | if err != nil {
102 | t.Error(err)
103 | return
104 | }
105 |
106 | conn.SetDeadline(time.Now().Add(time.Second * time.Duration(10)))
107 | wsConn.SetDeadline(time.Now().Add(time.Second * time.Duration(10)))
108 | _, err = io.ReadFull(wsConn, bytesIn)
109 | if err != nil {
110 | t.Error(err)
111 | }
112 |
113 | for i := range bytesOut {
114 | if bytesIn[i] != bytesOut[i] {
115 | fmt.Println("bytesIn: ", bytesIn)
116 | fmt.Println("bytesOut: ", bytesOut)
117 | t.Errorf("received buffer differs from sent at position %v", i)
118 | }
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/test_scripts/README.md:
--------------------------------------------------------------------------------
1 | # Test Scripts
2 |
3 | Collection of quickly written small scripts designed to work with TapDance.
4 |
5 | We move fast and break things, so I don't guarantee your computer
6 | won't catch on fire, if you try to use that.
7 |
8 | * twitter_wget.sh - simply wget's twitter
9 |
10 | * go-1.7.4_wget.sh - downloads Golang 1.7.4 acrhive (~81MB)
11 |
12 | * ip.sh - queries http://ipinfo.io with curl for current ip
13 |
14 | * ssh-td.sh - ssh via TapDance. Usage: `./ssh-td.sh $hostname`
15 |
16 | * nc_send.sh - sends random data to poor innocent server
17 | (specify how much data, e.g. 21k or 42m)
18 |
19 | * seq.py - sends and receives enumerated bytes of data and checks if they are
20 | received successfully and in order. Blatantly stolen from
21 | [ewust's repo](https://github.com/ewust/sendseq).
22 | To use, point TapDance server into seq.py receiver
23 | and proxy seq.py sender through TapDance client.
24 |
25 |
--------------------------------------------------------------------------------
/test_scripts/go-1.7.4_wget.sh:
--------------------------------------------------------------------------------
1 | export https_proxy=127.0.0.1:10500
2 | export http_proxy=127.0.0.1:10500
3 | #wget https://www.twitter.com
4 | rm go1.7.4.linux-amd64.tar.gz
5 | wget storage.googleapis.com/golang/go1.7.4.linux-amd64.tar.gz
6 | sha256sum go1.7.4.linux-amd64.tar.gz
7 | echo "Expected: 47fda42e46b4c3ec93fa5d4d4cc6a748aa3f9411a2a2b7e08e3a6d80d753ec8b"
8 |
--------------------------------------------------------------------------------
/test_scripts/ip.sh:
--------------------------------------------------------------------------------
1 | curl -vi --proxy http://127.0.0.1:10500 "http://ipinfo.io"
2 | echo
3 |
--------------------------------------------------------------------------------
/test_scripts/nc_send.sh:
--------------------------------------------------------------------------------
1 | print_usage() {
2 | echo 'Usage: ./nc_send.sh ${SIZE}'
3 | echo ' ${SIZE} format examples: 67k, 22m '
4 | }
5 |
6 | gen_file() {
7 | mkdir -p random_files
8 | if [ ! -f $rand_filename ]; then
9 | head -c $size $rand_filename
10 | if [ $? -ne 0 ]
11 | then
12 | echo "Generation of file with size ${size} failed!"
13 | print_usage
14 | rm $rand_filename
15 | exit 2
16 | fi
17 | fi
18 | }
19 |
20 | if [[ $# -eq 0 ]] ; then
21 | echo 'Error: send message size is not specified!'
22 | print_usage
23 | exit 1
24 | fi
25 |
26 |
27 | size="$1"
28 | rand_filename="random_files/$size"
29 |
30 | website="twitter.com"
31 | port="443"
32 |
33 | gen_file
34 | nc -X connect -x 127.0.0.1:10500 $website $port -v < $rand_filename
35 |
--------------------------------------------------------------------------------
/test_scripts/psiphon_digest_cc.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # This Script is a psiphon specific tool to
4 | # 1. digest the clientConf of the current branch into the
5 | # format used in psiphons embedded_config.go
6 | # 2. Perform search and replace on the embedded_config.go file to
7 | # remove the old clientconfig and replace with new digested value.
8 | #
9 | # Use:
10 | # $ psiphon_digest_cc.sh $PATH_TO_ASSETS/ClientConf $PATH_TO_PSIPHON_CORE/psiphon/common/tapdance/embedded_config.go
11 | #
12 | # Note: This will replace the value inline in the Psiphon embedded config.
13 |
14 | if [ "$#" -ne 2 ]; then
15 | echo "Not enough input arguments:"
16 | echo '$ psiphon_digest_cc.sh $PATH_TO_ASSETS/ClientConf $PATH_TO_PSIPHON_TD_CORE/embedded_config.go'
17 | exit 1
18 | fi
19 |
20 | TMP_SEDFILE="build/embedded_config.sed"
21 |
22 | # Digest the clienConf to hex
23 | EMBEDDED_CC=$(hexdump -ve '"\\\\\\\\\x" 1/1 "%.2x"' $1);
24 |
25 | # Create search and replace rule and store to sed scriptfile (can be too long for sed inline)
26 | printf "s/embeddedClientConf = \"[x0-9a-fA-F\\]+\"/embeddedClientConf = \"$EMBEDDED_CC\"/g" > $TMP_SEDFILE
27 |
28 | # Replace the old ClientConf from the psiphon config
29 | #sed -r -i '' -f $TMP_SEDFILE $2
30 | sed -r -f $TMP_SEDFILE -i $2
31 |
32 | #rm $TMP_SEDFILE
33 |
--------------------------------------------------------------------------------
/test_scripts/seq.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import socket
4 | import time
5 | import struct
6 | import sys
7 | from optparse import OptionParser
8 |
9 |
10 |
11 |
12 | parser = OptionParser()
13 | parser.add_option("-p", "--port", dest="port", default=8888,
14 | help="port to listen on or connect to")
15 | parser.add_option("-c", "--connect", dest="connect",
16 | action="store_true", default=False, help="connect to remote host")
17 | parser.add_option("-H", "--host", dest="host",
18 | default="127.0.0.1", help="remote host to connect to or bind on")
19 | parser.add_option("-s", "--send", dest="send",
20 | action="store_true", default=False, help="send data")
21 |
22 | (options, args) = parser.parse_args()
23 |
24 | connect = options.connect
25 | send = options.send
26 | port = int(options.port)
27 | host = options.host
28 |
29 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
30 |
31 | conn = s
32 |
33 | if connect:
34 | s.connect((host, port))
35 | print 'connected'
36 | else:
37 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
38 | s.bind((host, port))
39 | s.listen(1)
40 | conn, addr = s.accept()
41 | print 'Connection from', addr
42 |
43 |
44 |
45 | last = time.time()
46 | last_n = 0
47 | last_buf = ''
48 | extra = ''
49 | #s.send('HELLO')
50 |
51 | last_time = time.time()
52 |
53 | def occasional_print(n):
54 | global last_time
55 | if (n % (256*1024)) == 0:
56 | now = time.time()
57 | bw = (256*1024*4)/(now - last_time)
58 | print '%.06f ---> %08x %.3f MB/s' % (now, n, bw/1000000)
59 | last_time = now
60 |
61 |
62 | BUF_SIZE = 1024
63 |
64 | if send:
65 | ##### SEND
66 | n = 0
67 | for i in xrange(1024*20*1024/(BUF_SIZE/4)):
68 | buf = ''
69 | for j in xrange(BUF_SIZE/4):
70 | n += 1
71 | buf += struct.pack('!I', n)
72 |
73 | occasional_print(n)
74 |
75 | conn.sendall(buf)
76 |
77 | else:
78 | ##### RECEIVE
79 | extra = ''
80 | last_buf = ''
81 | last_n = 0
82 | while True:
83 | buf = conn.recv(BUF_SIZE)
84 | buf = extra + buf
85 | for i in xrange(len(buf)/4):
86 | n, = struct.unpack('!I', buf[4*i:4*i+4])
87 | if n != (last_n + 1) and n != 0:
88 | print '=========ERROR: expected %08x got %08x at offset %d (len %d)' % (last_n+1, n, 4*i, len(buf))
89 | print ''
90 | print '----last buf:'
91 | print last_buf.encode('hex')
92 | print '----this buf:'
93 | print buf.encode('hex')
94 | sys.exit(1)
95 | last_n = n
96 |
97 | occasional_print(n)
98 | last_buf = buf
99 | extra = ''
100 | if (len(buf)%4) != 0:
101 | extra = buf[-(len(buf) % 4):]
102 |
--------------------------------------------------------------------------------
/test_scripts/ssh-td.sh:
--------------------------------------------------------------------------------
1 | print_usage() {
2 | echo 'Usage: ./ip-td.sh ${HOSTNAME}'
3 | }
4 |
5 | if [[ $# -eq 0 ]] ; then
6 | echo 'Error: hostname is not specified!'
7 | print_usage
8 | exit 1
9 | fi
10 |
11 | HOSTNAME="$1"
12 |
13 | ssh ${HOSTNAME} -o "ProxyCommand=nc -X connect -x localhost:10500 %h %p"
14 |
--------------------------------------------------------------------------------
/test_scripts/twitter_wget.sh:
--------------------------------------------------------------------------------
1 | export https_proxy=127.0.0.1:10500
2 | export http_proxy=127.0.0.1:10500
3 | #wget https://www.twitter.com
4 | wget https://twitter.com
5 |
--------------------------------------------------------------------------------
/tools/README.md:
--------------------------------------------------------------------------------
1 | # Tools
2 |
3 | Tools for Refraction Networking development.
4 |
5 | To build use:
6 |
7 | ```sh
8 | # To build all binaries
9 | $ make
10 |
11 | # To build one specific one
12 | $ make clientconf
13 |
14 | ```
15 |
16 | ## Client Conf
17 |
18 | `clientconf`
19 |
20 | Client Config management tools. Used for adding
21 |
22 | ## IPv6 Lookup
23 |
24 | `v6lookup`
25 |
26 | Perform a AAAA lookup for each domain name as part of clientConfig supplement for Conjure.
27 |
28 | ## CJProbe
29 |
30 | `cjprobe`
31 |
32 | Run UDP probe tool to test station reachability. Sends raw UDP packets or DNS requests containing
33 | string tag the conjure stations look for and log, validating that a station lies on path for a
34 | client, server pair.
35 |
36 | ## Elligator Test
37 |
38 | `elligator-test`
39 |
40 | Tests to ensure that the elligator functionality implemented here in golang will match that of the station implementation.
41 |
42 | ## uTLS test
43 |
44 | `utls-test`
45 |
46 | Test various utls fingerprints against decoys in the ClientConf.
47 |
--------------------------------------------------------------------------------
/tools/cjprobe/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Conjure Prober
3 |
4 | This tool is a testing system for measuring conjure station reachability. Each station watches for
5 | UDP packets containing a special string, and logs source and destination IP addresses when it is
6 | detected. This allows a station operator to run this tool from disparate locations and validate that
7 | traffic routes past a designated station. [Station string checking](https://github.com/refraction-networking/conjure/blob/da349002ae89bf05b3fa2a3197ae2d3b8eefa3f9/src/process_packet.rs#L353)
8 | can be seen here.
9 |
10 | For compatibility with RIPE atlas this was designed be sent as a DNS lookup
11 | request with the special string as the domain. In this tool we send the DNS encoded version of the
12 | tag as a raw UDP packet. This tool does provide the option `-dns` which causes this tool to use
13 | the golang DNS lookup system but this is not the default behavior.
14 |
15 | In general UDP was chosen because it allows us to send the packet without participation on the side of the
16 | target address. This allows us to validate Phantom addresses even if no host resides at the address
17 | (which would be impossible for any TCP based probe).
18 |
19 | ## Usage
20 |
21 | ```sh
22 | cjprobe [options] [TARGETS...]:
23 | -d Only scan decoy addresses, ignore subnets from clientconf or command line args
24 | -dns
25 | Send the tag as a DNS request (uses golang DNS lookup sending 8 probes)
26 | -f ClientConf
27 | ClientConf file to parse
28 | -no6
29 | Ignore IPv6 decoys and subnets when probing
30 | -p int
31 | Destination port of all probes sent (default 53)
32 | -q Quiet mode - prevents probe result logging
33 | -s Only scan subnet blocks, ignore decoys from clientconf or command line args
34 | -sa int
35 | Number of addresses to choose from each subnet (default 1)
36 | -ss int
37 | Seed for random selection of address from subnet blocks (default -1)
38 | -tag string
39 | Set a custom tag to be sent over the probe. Only works with raw UDP packet mode
40 | -url string
41 | Set a custom domain string for DNS lookup. Only works with DNS request mode
42 | -w int
43 | Number of parallel workers for the connect_to_all test (default 20)
44 | ```
45 |
46 | For example to scan all decoys from a given clientconf you could run the following. Currently
47 | phantom subnets CAN be stored in the Clientconf struct, but no distributed ClientConf contains
48 | phantom subnets (this will come soon).
49 |
50 | ```sh
51 | cjprobe -f ../assets/ClientConf
52 | ```
53 |
54 | Subnets and decoy address targets can also be specified in the tailing args. To perform a scan that
55 | uses 5 addresses from each phantom subnet provided chosen in a reproducible way we can use the `-sa`
56 | option to set the `addresses-per-subnet` and the `-ss` option to set the `subnet-seed`. This selects
57 | from two subnets, supporting ipv4 and ipv6.
58 |
59 | ```sh
60 | cjprobe -sa 5 -ss 100 10.0.0.1/8 2106:abcd::1/32
61 | ```
62 |
63 | To send a DNS query we can provide the `-dns` flag. Note that when
64 | using the `-dns` option that there is no control over retries, so addresses not running public
65 | DNS resolvers will force golang to send 8 DNS requests per address. As shown below we can also mix
66 | our tailing targets between subnets and decoy addresses.
67 |
68 | ```sh
69 | cjprobe -dns 8.8.8.8 1.1.1.1 128.138.0.1/16
70 | ```
71 |
72 | ## Notes
73 |
74 | Many clientconfigs have duplicate decoy addresses as there are multiple domain names that reference
75 | the same decoy IP address. This tool automatically de-duplicates before sending probes, so the probe
76 | function is only called once for each address.
77 |
--------------------------------------------------------------------------------
/tools/cjprobe/cjprobe.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "flag"
6 | "fmt"
7 | "io/ioutil"
8 | "math/rand"
9 | "net"
10 | "os"
11 | "strings"
12 | "sync"
13 | "time"
14 |
15 | pb "github.com/refraction-networking/conjure/proto"
16 | log "github.com/sirupsen/logrus"
17 | "google.golang.org/protobuf/proto"
18 | )
19 |
20 | var specialUDPEncoded = "\x38xCKe9ECO5lNwXgd5Q25w0C2qUR7whltkA8BbyNokGIp5rzzm0hc7yqbR\x38FAP3S9w7oLrvvei7IphdwZEKUvF5iZeSdtDFEDc6cIDiv11aTNkOp08k\x38mRISHvoeSWSgMOjkbR2un5XKpJEZIK31Bc2obUGRIoY2tpxm6RUV5nOU\x07SuifuqZ"
21 | var specialUDPPayload = "xCKe9ECO5lNwXgd5Q25w0C2qUR7whltkA8BbyNokGIp5rzzm0hc7yqbR.FAP3S9w7oLrvvei7IphdwZEKUvF5iZeSdtDFEDc6cIDiv11aTNkOp08k.mRISHvoeSWSgMOjkbR2un5XKpJEZIK31Bc2obUGRIoY2tpxm6RUV5nOU.SuifuqZ"
22 | var specialUDPPort = 53
23 |
24 | func main() {
25 |
26 | decoyOnly := flag.Bool("d", false, "Only scan decoy addresses, ignore subnets from clientconf or command line args")
27 | subnetOnly := flag.Bool("s", false, "Only scan subnet blocks, ignore decoys from clientconf or command line args")
28 | addressesPerSubnet := flag.Int("sa", 1, "Number of addresses to choose from each subnet")
29 | subnetSeed := flag.Int64("ss", -1, "Seed for random selection of address from subnet blocks")
30 |
31 | excludeV6 := flag.Bool("no6", false, "Ignore IPv6 decoys and subnets when probing")
32 |
33 | workers := flag.Int("w", 20, "Number of parallel workers for the connect_to_all test")
34 |
35 | fname := flag.String("f", "", "`ClientConf` file to parse")
36 | port := flag.Int("p", 53, "Destination port of all probes sent")
37 |
38 | quiet := flag.Bool("q", false, "Quiet mode - prevents probe result logging")
39 |
40 | customTag := flag.String("tag", "", "Set a custom tag to be sent over the probe. Only works with raw UDP packet mode")
41 | customURL := flag.String("url", "", "Set a custom domain string for DNS lookup. Only works with DNS request mode")
42 |
43 | useDNS := flag.Bool("dns", false, "Send the tag as a DNS request (uses golang DNS lookup sending 8 probes)")
44 |
45 | flag.Parse()
46 |
47 | // Quick compatibility check
48 | if *decoyOnly && *subnetOnly {
49 | log.Warn("Decoy only (-d) and Subnet only (-s) conflict, use only one.")
50 | flag.Usage()
51 | os.Exit(1)
52 | }
53 |
54 | // Set the payload and port if they were set by command line arg
55 | if *customURL != "" {
56 | specialUDPPayload = *customURL
57 | }
58 | if *customTag != "" {
59 | specialUDPEncoded = *customTag
60 | }
61 | if *port != 53 {
62 | specialUDPPort = *port
63 | }
64 |
65 | var targets = []string{}
66 | var subnets = []*net.IPNet{}
67 |
68 | // parse args as decoy addresses or subnets
69 | for _, arg := range flag.Args() {
70 | _, subnet, err := net.ParseCIDR(arg)
71 | if err == nil {
72 | if !*decoyOnly {
73 | subnets = append(subnets, subnet)
74 | }
75 | } else if addr := net.ParseIP(arg); addr != nil {
76 | if !*subnetOnly {
77 | targets = append(targets, addr.String())
78 | }
79 | } else {
80 | if !*quiet {
81 | log.Warnf("failed to parse target \"%s\"", arg)
82 | }
83 | }
84 | }
85 |
86 | // parse decoys from clientconf into string array
87 | var clientConf *pb.ClientConf
88 | var err error
89 | if *fname != "" {
90 | clientConf, err = parseClientConf(*fname)
91 | if err != nil {
92 | log.Fatal(err)
93 | }
94 |
95 | if !*subnetOnly {
96 | for _, decoy := range clientConf.GetDecoyList().TlsDecoys {
97 | // Decoy string includes port which we do not want
98 | addr := strings.Split(decoy.GetIpAddrStr(), ":")[0]
99 | targets = append(targets, addr)
100 | }
101 | }
102 | if !*decoyOnly {
103 | log.Warnf("Not currently implemented")
104 | // if blocks := clientConf.GetPhantomSubnetsList(); blocks != nil {
105 | // for _, subnetStr := range blocks.Blocks {
106 | // _, subnet, err := net.ParseCIDR(subnetStr)
107 | // if err != nil {
108 | // continue
109 | // }
110 | // subnets = append(subnets, subnet)
111 | // }
112 | // }
113 | }
114 | }
115 | // select random addresses from subnets
116 | targets = append(targets, selectFromSubnets(subnets, *addressesPerSubnet, *subnetSeed)...)
117 |
118 | // de-duplicate addresses in list
119 | targets = removeDuplicateValues(targets)
120 |
121 | // Exclude v6 addresses if option is specified.
122 | if *excludeV6 {
123 | log.Info("excluding IPv6 targets")
124 | targets = removeIPv6Addrs(targets)
125 | }
126 |
127 | // fmt.Printf("so: %v, sa: %v, ss:%v, do:%v, no6:%v, wrkrs:%d, cc:%s, p: %v, q: %v, args: %+v\n",
128 | // *subnetOnly, *addressesPerSubnet, *subnetSeed, *decoyOnly, *excludeV6, *workers, *fname, *port, *quiet, flag.Args())
129 |
130 | // fmt.Println(targets, subnets)
131 | ConnectToAll(targets, *workers, *quiet, *useDNS)
132 | }
133 |
134 | func parseClientConf(fname string) (*pb.ClientConf, error) {
135 |
136 | clientConf := pb.ClientConf{}
137 | buf, err := ioutil.ReadFile(fname)
138 | if err != nil {
139 | return nil, fmt.Errorf("Error reading file - %v", err)
140 | }
141 | err = proto.Unmarshal(buf, &clientConf)
142 | if err != nil {
143 | return nil, fmt.Errorf("Error parsing ClientConf - %v", err)
144 | }
145 | return &clientConf, nil
146 | }
147 |
148 | type jobTuple struct {
149 | Target string
150 | Total uint
151 | JobID uint
152 | }
153 |
154 | // ConnectToAll parallelizes the process pg sending tagged packets to hosts.
155 | func ConnectToAll(targets []string, workers int, quiet bool, useDNS bool) {
156 |
157 | if !quiet {
158 | log.Info("Connection to all registration decoys in client conf.")
159 | }
160 | var wg sync.WaitGroup
161 |
162 | jobChan := make(chan jobTuple, len(targets))
163 |
164 | for id := 0; id < workers; id++ {
165 | wg.Add(1)
166 | go func(id int) {
167 | defer wg.Done()
168 |
169 | jobsCompleted := 0
170 | var err error
171 |
172 | for jobTuple := range jobChan {
173 |
174 | target := jobTuple.Target
175 | output := fmt.Sprintf("[%d/%d/%d] (%d) Connecting to decoy %s ... ",
176 | jobsCompleted, jobTuple.JobID, jobTuple.Total, id, target)
177 |
178 | start := time.Now()
179 | if useDNS {
180 | err = ConnectSpecialDNS(target, specialUDPPayload)
181 | } else {
182 | err = ConnectSpecial(target, specialUDPEncoded)
183 | }
184 | duration := time.Since(start)
185 | if !quiet {
186 | if err != nil {
187 | output += fmt.Sprintf("%d Failure: %s\n", duration.Milliseconds(), err.Error())
188 | log.Warn(output)
189 | } else {
190 | output += fmt.Sprintf("%d Success\n", duration.Milliseconds())
191 | log.Info(output)
192 | }
193 | }
194 | jobsCompleted++
195 | }
196 |
197 | }(id) // end go worker func
198 | }
199 |
200 | go func() {
201 | total := uint(len(targets))
202 | for idx, target := range targets {
203 | jobChan <- jobTuple{Target: target, Total: total, JobID: uint(idx)}
204 | }
205 | close(jobChan)
206 | }()
207 |
208 | wg.Wait()
209 | }
210 |
211 | // ConnectSpecial sends a raw UDP packet with the tag specified. `target` must be an IP address.
212 | func ConnectSpecial(target, tag string) error {
213 | conn, err := net.Dial("udp", net.JoinHostPort(target, fmt.Sprint(specialUDPPort)))
214 | if err != nil {
215 | return err
216 | }
217 | defer conn.Close()
218 |
219 | fmt.Fprintf(conn, tag)
220 |
221 | return nil
222 | }
223 |
224 | func removeDuplicateValues(strSlice []string) []string {
225 | keys := make(map[string]bool)
226 | list := []string{}
227 |
228 | // If the key(values of the slice) is not equal
229 | // to the already present value in new slice (list)
230 | // then we append it. else we jump on another element.
231 | for _, entry := range strSlice {
232 | if _, value := keys[entry]; !value {
233 | keys[entry] = true
234 | list = append(list, entry)
235 | }
236 | }
237 | return list
238 | }
239 |
240 | func removeIPv6Addrs(addrSlice []string) []string {
241 | return []string{}
242 | }
243 |
244 | // ConnectSpecialDNS sends the special tag as a DNS request and ignores any response. This uses
245 | // The golang DNS resolution, so it may result in retries or longer timeouts as we do not expect
246 | // targets to actually function as DNS resolvers.
247 | func ConnectSpecialDNS(target, tag string) error {
248 | r := &net.Resolver{
249 | PreferGo: true,
250 | Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
251 | d := net.Dialer{
252 | Timeout: time.Millisecond * time.Duration(10000),
253 | }
254 | return d.DialContext(ctx, "udp", net.JoinHostPort(target, fmt.Sprint(specialUDPPort)))
255 | },
256 | }
257 | _, err := r.LookupHost(context.Background(), tag)
258 | return err
259 | }
260 |
261 | func selectFromSubnets(subnets []*net.IPNet, aps int, seed int64) []string {
262 | list := []string{}
263 |
264 | if seed != -1 {
265 | rand.Seed(seed)
266 | }
267 | for _, subnet := range subnets {
268 | for i := 0; i < aps; i++ {
269 |
270 | randomBytes := make([]byte, len(subnet.IP))
271 | _, err := rand.Read(randomBytes)
272 | if err != nil {
273 | continue
274 | }
275 |
276 | addressBytes := subnet.IP.Mask(subnet.Mask)
277 | newAddr := []byte{}
278 | for idx, b := range subnet.Mask {
279 | randMask := randomBytes[idx] & (^b)
280 | newAddr = append(newAddr, randMask|addressBytes[idx])
281 | }
282 |
283 | addr := net.IP(newAddr).String()
284 | list = append(list, addr)
285 | }
286 | }
287 |
288 | return list
289 | }
290 |
--------------------------------------------------------------------------------
/tools/cjprobe/cjprobe_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net"
5 | "testing"
6 | )
7 |
8 | func TestCJProbeSelectFromSubnet(t *testing.T) {
9 | subnetStrings := []string{"10.0.0.1/8", "128.138.0.0/16"}
10 | var seed int64 = 1000
11 |
12 | strToNet := func(s []string) []*net.IPNet {
13 | list := []*net.IPNet{}
14 | for _, str := range s {
15 | _, subnet, err := net.ParseCIDR(str)
16 | if err != nil {
17 | t.Fatal(err)
18 | }
19 | list = append(list, subnet)
20 | }
21 | return list
22 | }
23 |
24 | selected := selectFromSubnets(strToNet(subnetStrings), 1, seed)
25 | if len(selected) != 2 {
26 | t.FailNow()
27 | } else if selected[0] != "10.138.86.216" || selected[1] != "128.138.31.3" {
28 | t.FailNow()
29 | }
30 |
31 | subnetStrings = []string{"fe80::/32"}
32 | selected = selectFromSubnets(strToNet(subnetStrings), 1, seed)
33 | if len(selected) != 1 {
34 | t.FailNow()
35 | } else if selected[0] != "fe80:0:b5fb:1f03:8ffb:fce7:9f18:5f4a" {
36 | t.FailNow()
37 | }
38 |
39 | subnetStrings = []string{"fe80::/32", "10.0.0.1/8"}
40 | selected = selectFromSubnets(strToNet(subnetStrings), 2, seed)
41 | if len(selected) != 4 {
42 | t.FailNow()
43 | } else if selected[0] != "fe80:0:b5fb:1f03:8ffb:fce7:9f18:5f4a" ||
44 | selected[1] != "fe80:0:94ed:b854:57d6:c84d:6bc0:2a82" ||
45 | selected[2] != "10.78.228.45" ||
46 | selected[3] != "10.30.156.59" {
47 | t.FailNow()
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tools/clientconf/clientconf_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 |
7 | pb "github.com/refraction-networking/conjure/proto"
8 | )
9 |
10 | func TestUpdateDNSReg(t *testing.T) {
11 | originalTarget := "original target"
12 | originalDomain := "original domain"
13 | originalMethod := pb.DnsRegMethod_DOH
14 | originalPubkey := []byte{1, 2}
15 | original := &pb.DnsRegConf{
16 | DnsRegMethod: &originalMethod,
17 | Target: &originalTarget,
18 | Domain: &originalDomain,
19 | Pubkey: originalPubkey,
20 | }
21 | cc := &pb.ClientConf{
22 | DnsRegConf: original,
23 | }
24 |
25 | newTarget := "target"
26 | newDomain := ""
27 | newMethod := pb.DnsRegMethod_UDP
28 | newPubkey := []byte{3, 4}
29 | new := &pb.DnsRegConf{
30 | Target: &newTarget,
31 | Domain: &newDomain,
32 | DnsRegMethod: &newMethod,
33 | Pubkey: newPubkey,
34 | }
35 |
36 | updateDNSReg(cc, new)
37 |
38 | if *original.Domain != originalDomain {
39 | t.Fatalf("domain should not be updated")
40 | }
41 |
42 | if *original.Target == originalTarget {
43 | t.Fatalf("failed to update target")
44 | }
45 |
46 | if *original.DnsRegMethod == originalMethod {
47 | t.Fatalf("failed to update method")
48 | }
49 |
50 | if reflect.DeepEqual(original.Pubkey, originalPubkey) {
51 | t.Fatalf("failed to update pubkey")
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/tools/elligator-test/elligator-test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2013 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // package extra25519
6 | package main
7 |
8 | import (
9 | "crypto/rand"
10 | "encoding/hex"
11 | "fmt"
12 |
13 | "github.com/refraction-networking/ed25519/extra25519"
14 | )
15 |
16 | func main() {
17 | var publicKey, representative, privateKey [32]byte
18 |
19 | for {
20 | rand.Reader.Read(privateKey[:])
21 |
22 | if !extra25519.ScalarBaseMult(&publicKey, &representative, &privateKey) {
23 | continue
24 | }
25 | break
26 |
27 | }
28 |
29 | fmt.Printf("Public key: %s\n", hex.EncodeToString(publicKey[:]))
30 | fmt.Printf("Representative: %s\n", hex.EncodeToString(representative[:]))
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/tools/gen-bias/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Conjure ClientConf Bias Generator
3 |
4 | This tool takes a conjure client configuration and generates (based on sample selections) the
5 | popularity of addresses in the phantom subnets.
6 |
--------------------------------------------------------------------------------
/tools/gen-bias/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/sha256"
5 | "encoding/binary"
6 | "encoding/hex"
7 | "flag"
8 | "fmt"
9 | "io/ioutil"
10 | "os"
11 |
12 | "github.com/refraction-networking/conjure/pkg/phantoms"
13 | pb "github.com/refraction-networking/conjure/proto"
14 | "golang.org/x/crypto/hkdf"
15 | "google.golang.org/protobuf/proto"
16 | )
17 |
18 | func main() {
19 | defaultSeed := "5a87133b68ea3468988a21659a12ed2ece07345c8c1a5b08459ffdea4218d12f"
20 | defaultSalt := "phantom-bias-test"
21 | defaultConfigPath := "../assets/ClientConf"
22 | defaultOutFile := "./bias.out"
23 |
24 | var seedStr, saltStr, inFile, outFile string
25 | var v6, both bool
26 | var cliVersion uint
27 |
28 | flag.StringVar(&seedStr, "seed", defaultSeed, "Overrides the default seed for phantom selection")
29 | flag.StringVar(&saltStr, "salt", defaultSalt, "Overrides the default salt for phantom selection")
30 | flag.StringVar(&inFile, "f", defaultConfigPath, "Filepath to look for the parse-able conjure ClientConfig")
31 | flag.StringVar(&outFile, "o", defaultOutFile, "Filepath to write output bias into")
32 | flag.BoolVar(&v6, "v6", false, "Generate bias output for IPv6 subnets only")
33 | flag.BoolVar(&both, "both", false, "Generate bias output for BOTH IPv4 and IPv6 subnets")
34 | flag.UintVar(&cliVersion, "version", 1, "Client library version to generate bias for")
35 |
36 | flag.Parse()
37 |
38 | seed, err := hex.DecodeString(seedStr)
39 | if err != nil {
40 | panic(err)
41 | }
42 |
43 | salt := []byte(saltStr)
44 |
45 | clientConf, err := parseClientConf(inFile)
46 | if err != nil {
47 | panic(err)
48 | }
49 |
50 | ps := clientConf.GetPhantomSubnetsList()
51 | ipCount := map[string]int{}
52 |
53 | os.Setenv("PHANTOM_SUBNET_LOCATION", "/dev/null")
54 | phantomSelector, err := phantoms.NewPhantomIPSelector()
55 | if err != nil {
56 | panic(err)
57 | }
58 |
59 | subnetConfig := phantoms.SubnetConfig{WeightedSubnets: ps.WeightedSubnets}
60 | newGen := phantomSelector.AddGeneration(-1, &subnetConfig)
61 |
62 | totTrials := 1_000_000
63 | for i := 0; i < totTrials; i++ {
64 | curSeed := expandSeed(seed, salt, i)
65 | addr, err := phantomSelector.Select(curSeed, newGen, cliVersion, v6)
66 | if err != nil {
67 | continue
68 | }
69 | ipCount[addr.String()]++
70 | }
71 |
72 | // Write file
73 | f, err := os.Create(outFile)
74 | if err != nil {
75 | panic(err)
76 | }
77 | defer f.Close()
78 |
79 | for ip, count := range ipCount {
80 | f.WriteString(fmt.Sprintf("%s %d\n", ip, count))
81 | }
82 | }
83 |
84 | func expandSeed(seed, salt []byte, i int) []byte {
85 | bi := make([]byte, 8)
86 | binary.LittleEndian.PutUint64(bi, uint64(i))
87 | return hkdf.Extract(sha256.New, seed, append(salt, bi...))
88 | }
89 |
90 | func parseClientConf(fname string) (*pb.ClientConf, error) {
91 |
92 | clientConf := &pb.ClientConf{}
93 | buf, err := ioutil.ReadFile(fname)
94 | if err != nil {
95 | return nil, err
96 | }
97 | err = proto.Unmarshal(buf, clientConf)
98 | if err != nil {
99 | return nil, err
100 | }
101 | return clientConf, nil
102 | }
103 |
--------------------------------------------------------------------------------
/tools/makefile:
--------------------------------------------------------------------------------
1 |
2 | GOCMD=go
3 | GOBUILD=$(GOCMD) build
4 | GOCLEAN=$(GOCMD) clean
5 | GOTEST=$(GOCMD) test
6 | BINARY_NAMES=clientconf v6lookup elligator-test utls-test gen-bias # cjprobe
7 |
8 |
9 | all: build
10 | build:
11 | mkdir -p bin;
12 | for binary in $(BINARY_NAMES) ; do \
13 | echo "building $$binary"; \
14 | cd $$binary && $(GOBUILD) -o ../bin/$$binary ; \
15 | cd -; \
16 | done
17 |
18 | clientconf:
19 | echo "building clientconf"; \
20 | cd clientconf && $(GOBUILD) -o ../bin/clientconf ; \
21 | cd ../; \
22 |
23 |
24 | gen-bias:
25 | echo "building gen-bias"; \
26 | cd gen-bias && $(GOBUILD) -o ../bin/gen-bias ; \
27 | cd ../; \
28 |
29 | v6lookup:
30 | echo "building v6lookup"; \
31 | cd v6lookup && $(GOBUILD) -o ../bin/v6lookup ; \
32 | cd ../; \
33 |
34 | cjprobe:
35 | echo "building conjure probe tool"; \
36 | cd cjprobe && $(GOBUILD) -o ../bin/cjprobe ; \
37 | cd ../; \
38 |
39 | elligator-test:
40 | echo "building elligator-test"; \
41 | cd elligator-test && $(GOBUILD) -o ../bin/elligator-test ; \
42 | cd ../; \
43 |
44 | utls-test:
45 | echo "building utls-test"; \
46 | cd utls-test && $(GOBUILD) -o ../bin/utls ; \
47 | cd -; \
48 |
49 | test:
50 | $(GOTEST) ./...
51 | clean:
52 | $(GOCLEAN)
53 | rm -rf bin
54 |
--------------------------------------------------------------------------------
/tools/ripe-atlas-probes/probes.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "requested": 3,
4 | "type": "probes",
5 | "value": "33418,53719,10814"
6 | },
7 | {
8 | "requested": 40,
9 | "type": "country",
10 | "value": "SA"
11 | },
12 | {
13 | "requested": 40,
14 | "type": "country",
15 | "value": "AR"
16 | },
17 | {
18 | "requested": 100,
19 | "type": "country",
20 | "value": "CN"
21 | },
22 | {
23 | "requested": 175,
24 | "type": "country",
25 | "value": "IR"
26 | },
27 | {
28 | "requested": 125,
29 | "type": "country",
30 | "value": "IN"
31 | }
32 | ]
33 |
--------------------------------------------------------------------------------
/tools/ripe-atlas-probes/setup.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "flag"
6 | "fmt"
7 | "net/http"
8 | "os"
9 | "strconv"
10 | "strings"
11 | "time"
12 |
13 | atlas "github.com/keltia/ripe-atlas"
14 | )
15 |
16 | const (
17 | TracerouteCost = 30
18 | DNSCost = 10
19 | OneOffMultiplier = 2
20 | )
21 |
22 | func main() {
23 | targetsFilename := flag.String("t", "targets.json", "The JSON file from which probe targets are loaded. They can be specified by IPv4 addresses or hostnames.")
24 | key := flag.String("key", "", "The RIPE Atlas API key used to run the measurements.")
25 | probesFilename := flag.String("p", "probes.json", "The JSON file from which probes are loaded. See https://atlas.ripe.net/docs/api/v2/manual/measurements/types/in_detail.html for information on the format.")
26 | arg := flag.String("arg", "", "The DNS lookup argument to use in measurements.")
27 | verbose := flag.Bool("v", false, "Print out lots of information about RIPE Atlas API requests.")
28 |
29 | flag.Parse()
30 |
31 | if *key == "" {
32 | fmt.Fprintln(os.Stderr, "You must specify an API key.")
33 | os.Exit(1)
34 | }
35 |
36 | if *arg == "" {
37 | fmt.Fprintln(os.Stderr, "You must specify a query argument.")
38 | os.Exit(1)
39 | }
40 |
41 | targetsFile, err := os.Open(*targetsFilename)
42 | if err != nil {
43 | fmt.Fprintf(os.Stderr, `Couldn't open targets file "%s": %s\n`, *targetsFilename, err)
44 | os.Exit(1)
45 | }
46 | defer targetsFile.Close()
47 |
48 | var targets []string
49 | err = json.NewDecoder(targetsFile).Decode(&targets)
50 | if err != nil {
51 | fmt.Fprintln(os.Stderr, "Couldn't decode targets file:", err)
52 | os.Exit(1)
53 | }
54 |
55 | probesFile, err := os.Open(*probesFilename)
56 | if err != nil {
57 | fmt.Fprintf(os.Stderr, `Couldn't open probes file "%s": %s\n`, *targetsFilename, err)
58 | os.Exit(1)
59 | }
60 | defer probesFile.Close()
61 |
62 | var probes []atlas.ProbeSet
63 | err = json.NewDecoder(probesFile).Decode(&probes)
64 | if err != nil {
65 | fmt.Fprintln(os.Stderr, "Couldn't decode probes file:", err)
66 | os.Exit(1)
67 | }
68 |
69 | var totalProbes int
70 | for _, p := range probes {
71 | totalProbes += p.Requested
72 | }
73 |
74 | config := atlas.Config{
75 | APIKey: *key,
76 | Verbose: *verbose,
77 | }
78 | if *verbose {
79 | config.Level = 2
80 | }
81 |
82 | client, err := atlas.NewClient(config)
83 | if err != nil {
84 | fmt.Fprintln(os.Stderr, "Failed to create RIPE Atlas client:", err)
85 | os.Exit(1)
86 | }
87 |
88 | credits, err := client.GetCredits()
89 | if err != nil {
90 | fmt.Fprintln(os.Stderr, "Failed to get credits information for RIPE Atlas account:", err)
91 | os.Exit(1)
92 | }
93 |
94 | creditEstimate := OneOffMultiplier * (TracerouteCost + DNSCost) * len(targets) * totalProbes
95 | fmt.Printf("You have %d credits. By my estimation, these measurements could take up to %d credits (depending on how many probes are available). Do you want to continue? [y/N] ", credits.CurrentBalance, creditEstimate)
96 |
97 | inputLoop:
98 | for {
99 | var input string
100 | _, err = fmt.Scanf("%s", &input)
101 | if err != nil {
102 | fmt.Fprintln(os.Stderr, "Failed to read input:", err)
103 | os.Exit(1)
104 | }
105 |
106 | switch strings.ToLower(input) {
107 | case "y", "yes":
108 | break inputLoop
109 | case "n", "no":
110 | os.Exit(0)
111 | default:
112 | fmt.Printf(`I don't know what "%s" means. Please answer y or N. `, input)
113 | }
114 | }
115 |
116 | fmt.Println("Creating measurements...")
117 |
118 | dnsDefinitions := make([]atlas.Definition, 0, len(targets))
119 | tracerouteDefinitions := make([]atlas.Definition, 0, len(targets))
120 |
121 | tag := "refraction-routing-probe-" + time.Now().Format("2006-01-02-15-04-05")
122 |
123 | for _, target := range targets {
124 | dns := atlas.Definition{
125 | Description: "DNS routing probe for " + target,
126 | Tags: []string{tag},
127 | Type: "dns",
128 | AF: 4,
129 | IsOneoff: true,
130 | IsPublic: false,
131 | QueryClass: "IN",
132 | QueryType: "A",
133 | Target: target,
134 | QueryArgument: *arg,
135 | }
136 | dnsDefinitions = append(dnsDefinitions, dns)
137 |
138 | traceroute := atlas.Definition{
139 | Description: "Traceroute routing probe for " + target,
140 | Tags: []string{tag},
141 | Type: "traceroute",
142 | AF: 4,
143 | Protocol: "UDP",
144 | IsOneoff: true,
145 | IsPublic: false,
146 | Target: target,
147 | }
148 | tracerouteDefinitions = append(tracerouteDefinitions, traceroute)
149 | }
150 |
151 | tracerouteRequest := client.NewMeasurement()
152 | tracerouteRequest.Definitions = tracerouteDefinitions
153 | tracerouteRequest.Probes = probes
154 |
155 | tracerouteResp, err := client.Traceroute(tracerouteRequest)
156 | if err != nil {
157 | fmt.Fprintln(os.Stderr, "Failed to create traceroute measurements:", err)
158 | os.Exit(1)
159 | }
160 |
161 | fmt.Println("Successfully created traceroute measurements. Waiting for information on probes to proceed to DNS measurements.")
162 |
163 | // The RIPE Atlas API allows us to specify another measurement ID to use the
164 | // same probes in theory, however it seems that if the first measurement
165 | // reports itself as failed, the second will never happen. Thus, we should manually
166 | // get the probes to add onto the second measurement.
167 | body := struct {
168 | Probes []struct {
169 | ID int `json:"id"`
170 | } `json:"probes"`
171 | }{}
172 |
173 | // TODO: we can't check if body.Probes is the length we want, as some probes
174 | // probably will not pick up the measurement. Checking for a non-zero length,
175 | // however, isn't optimal---the probes might not be finished filling in. There
176 | // isn't a clear way to tell whether probes are done being populated, but a
177 | // model that waits until multiple calls end up in the same (non-zero) length
178 | // consecutively might make more sense.
179 | for len(body.Probes) == 0 {
180 | // It seems like RIPE's API takes a bit of an eventual consistency model,
181 | // so the probes aren't immediately available after creating the request.
182 | // Keep trying until we get a non-empty probes array.
183 | time.Sleep(10 * time.Second)
184 |
185 | // The client doesn't allow us to get the probes of a measurement. :(
186 | req, err := http.NewRequest("GET", fmt.Sprintf("https://atlas.ripe.net/api/v2/measurements/%d?fields=probes", tracerouteResp.Measurements[0]), nil)
187 | if err != nil {
188 | fmt.Fprintln(os.Stderr, "Failed to create probes request:", err)
189 | os.Exit(1)
190 | }
191 |
192 | req.Header.Add("Authorization", "Key "+*key)
193 | resp, err := http.DefaultClient.Do(req)
194 | if err != nil {
195 | fmt.Fprintln(os.Stderr, "Failed to get measurement:", err)
196 | os.Exit(1)
197 | }
198 | defer resp.Body.Close()
199 |
200 | err = json.NewDecoder(resp.Body).Decode(&body)
201 | if err != nil {
202 | fmt.Fprintln(os.Stderr, "Failed to parse probes from measurement:", err)
203 | os.Exit(1)
204 | }
205 | }
206 |
207 | fmt.Println("Using probes for DNS requests:", body.Probes)
208 |
209 | probeIDs := make([]string, 0, totalProbes)
210 | for _, probe := range body.Probes {
211 | probeIDs = append(probeIDs, strconv.Itoa(probe.ID))
212 | }
213 |
214 | probesString := strings.Join(probeIDs, ",")
215 |
216 | dnsRequest := client.NewMeasurement()
217 | dnsRequest.Definitions = dnsDefinitions
218 | dnsRequest.Probes = []atlas.ProbeSet{{Requested: len(probeIDs), Type: "probes", Value: probesString}}
219 |
220 | _, err = client.DNS(dnsRequest)
221 | if err != nil {
222 | fmt.Fprintln(os.Stderr, "Failed to create DNS measurements:", err)
223 | os.Exit(1)
224 | }
225 |
226 | fmt.Printf("Successfully created measurements! To fetch the results, run the following command in a few minutes:\n\n")
227 | fmt.Printf(" curl -H \"Authorization: Key %s\" https://atlas.ripe.net/api/v2/measurements/tags/%s/results/ > results.json\n", *key, tag)
228 | }
229 |
--------------------------------------------------------------------------------
/tools/ripe-atlas-probes/targets.json:
--------------------------------------------------------------------------------
1 | [
2 | "23.135.208.7",
3 | "35.0.7.7",
4 | "66.219.112.7",
5 | "74.115.236.7",
6 | "141.210.7.7",
7 | "141.217.7.7",
8 | "141.218.7.7",
9 | "148.61.7.7",
10 | "158.80.7.7",
11 | "163.253.36.7",
12 | "164.76.7.7",
13 | "192.35.168.7",
14 | "192.41.229.7",
15 | "192.41.232.7",
16 | "192.41.238.7",
17 | "192.88.242.7",
18 | "192.101.250.7",
19 | "192.122.182.7",
20 | "192.122.184.7",
21 | "192.122.200.7",
22 | "192.138.137.7",
23 | "192.160.192.7",
24 | "192.160.204.7",
25 | "192.188.100.7",
26 | "192.203.195.7",
27 | "192.245.252.7",
28 | "192.245.254.7",
29 | "198.17.130.7",
30 | "198.17.132.7",
31 | "198.17.134.7",
32 | "198.108.7.7",
33 | "204.38.7.7",
34 | "204.106.17.7",
35 | "204.106.28.7",
36 | "204.106.29.7",
37 | "204.106.31.7",
38 | "206.201.157.7",
39 | "207.72.7.7",
40 | "208.68.24.7",
41 | "153.106.169.11",
42 | "207.74.79.195",
43 | "141.213.3.26",
44 | "64.113.59.229",
45 | "173.225.190.9",
46 | "141.219.64.203",
47 | "64.235.151.177",
48 | "141.211.4.26",
49 | "50.227.122.254",
50 | "199.102.68.205",
51 | "207.190.178.55",
52 | "207.190.177.219",
53 | "64.90.129.192",
54 | "69.58.46.3",
55 | "69.58.32.250",
56 | "69.58.42.73",
57 | "141.211.217.254",
58 | "141.211.95.9",
59 | "206.57.232.232",
60 | "204.38.39.132",
61 | "64.49.123.208",
62 | "69.58.39.4",
63 | "64.235.150.114",
64 | "153.106.195.182",
65 | "206.57.165.182",
66 | "153.106.195.22",
67 | "155.139.3.34",
68 | "74.3.176.105",
69 | "74.3.177.84",
70 | "206.57.177.213",
71 | "208.92.221.89",
72 | "173.225.183.114",
73 | "64.49.112.157",
74 | "142.54.11.195",
75 | "35.8.7.168",
76 | "35.8.27.235",
77 | "69.58.50.71",
78 | "204.38.32.121",
79 | "161.57.12.6",
80 | "141.217.172.45",
81 | "64.90.131.114",
82 | "141.213.14.217",
83 | "64.113.57.148",
84 | "207.75.234.81",
85 | "199.16.185.140",
86 | "173.225.184.140",
87 | "153.106.200.141",
88 | "206.57.252.215",
89 | "198.111.167.171",
90 | "67.194.254.186",
91 | "64.49.113.128"
92 | ]
93 |
--------------------------------------------------------------------------------
/tools/utls-test/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "crypto/tls"
6 | "flag"
7 | "fmt"
8 | "io"
9 | "io/ioutil"
10 | "log"
11 | "net"
12 | "net/http"
13 | "net/http/httputil"
14 | "net/url"
15 | "os"
16 | "sync"
17 | "time"
18 |
19 | pb "github.com/refraction-networking/conjure/proto"
20 | utls "github.com/refraction-networking/utls"
21 | "golang.org/x/net/http2"
22 | "google.golang.org/protobuf/proto"
23 | )
24 |
25 | var (
26 | fingerprints = []utls.ClientHelloID{utls.HelloChrome_62, utls.HelloChrome_72, utls.HelloChrome_83}
27 | dialTimeout = time.Duration(5) * time.Second
28 | )
29 |
30 | func HttpGetByHelloID(hostname string, addr string, helloID utls.ClientHelloID) (*http.Response, error) {
31 | config := utls.Config{ServerName: hostname}
32 | dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
33 | if err != nil {
34 | return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
35 | }
36 | uTlsConn := utls.UClient(dialConn, &config, helloID)
37 |
38 | err = uTlsConn.SetReadDeadline(time.Now().Add(10 * time.Second))
39 | if err != nil {
40 | return nil, err
41 | }
42 |
43 | defer uTlsConn.Close()
44 |
45 | err = uTlsConn.Handshake()
46 | if err != nil {
47 | return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
48 | }
49 |
50 | response, err := httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol, hostname)
51 | if err == nil {
52 | response.TLS = &tls.ConnectionState{
53 | Version: uTlsConn.ConnectionState().Version,
54 | CipherSuite: uTlsConn.ConnectionState().CipherSuite}
55 | }
56 | return response, err
57 | }
58 |
59 | func httpGetOverConn(conn net.Conn, alpn string, requestHostname string) (*http.Response, error) {
60 | req := &http.Request{
61 | Method: "GET",
62 | URL: &url.URL{Host: requestHostname + "/"},
63 | Header: make(http.Header),
64 | Host: requestHostname,
65 | }
66 |
67 | switch alpn {
68 | case "h2":
69 | req.Proto = "HTTP/2.0"
70 | req.ProtoMajor = 2
71 | req.ProtoMinor = 0
72 |
73 | tr := http2.Transport{}
74 | cConn, err := tr.NewClientConn(conn)
75 | if err != nil {
76 | return nil, err
77 | }
78 | return cConn.RoundTrip(req)
79 | case "http/1.1", "":
80 | req.Proto = "HTTP/1.1"
81 | req.ProtoMajor = 1
82 | req.ProtoMinor = 1
83 |
84 | err := req.Write(conn)
85 | if err != nil {
86 | return nil, err
87 | }
88 | return http.ReadResponse(bufio.NewReader(conn), req)
89 | default:
90 | return nil, fmt.Errorf("unsupported ALPN: %v", alpn)
91 | }
92 | }
93 |
94 | func worker(id int, decoys <-chan *pb.TLSDecoySpec, results chan<- string, wg *sync.WaitGroup) {
95 | var rsp *http.Response
96 | var err error
97 | var workerTotal int = 0
98 |
99 | defer wg.Done()
100 |
101 | for d := range decoys {
102 | workerTotal++
103 | for _, fp := range fingerprints {
104 |
105 | requestHostname := d.GetHostname()
106 | requestAddr := d.GetIpAddrStr()
107 |
108 | rsp, err = HttpGetByHelloID(requestHostname, requestAddr, fp)
109 | if err != nil {
110 | results <- fmt.Sprintf("#%v [%v]> %s - %s -%s failed: %+v\n",
111 | id, workerTotal, fp.Str(), requestAddr, requestHostname, err)
112 | } else {
113 | results <- fmt.Sprintf("#%v [%v]> %s - %s - %s response: %v - %v - %v - %s\n",
114 | id, workerTotal, fp.Str(), requestAddr, requestHostname, rsp.StatusCode,
115 | rsp.TLS.Version, rsp.TLS.CipherSuite, rsp.Header)
116 | // results <- fmt.Sprintf(out, "#> %s - %s - %s response: %+s\n", fp.Str(), requestAddr, requestHostname, dumpResponseNoBody(response))
117 | }
118 | }
119 | }
120 | fmt.Printf("worker %v shutting down\n", id)
121 | }
122 |
123 | func dumpResponseNoBody(response *http.Response) string {
124 | resp, err := httputil.DumpResponse(response, false)
125 | if err != nil {
126 | return fmt.Sprintf("failed to dump response: %v", err)
127 | }
128 |
129 | return string(resp)
130 | }
131 |
132 | func parseClientConf(fname string) pb.ClientConf {
133 |
134 | clientConf := pb.ClientConf{}
135 | buf, err := ioutil.ReadFile(fname)
136 | if err != nil {
137 | log.Fatal("Error reading file:", err)
138 | }
139 | err = proto.Unmarshal(buf, &clientConf)
140 | if err != nil {
141 | log.Fatal("Error parsing ClientConf", err)
142 | }
143 | return clientConf
144 | }
145 |
146 | func main() {
147 |
148 | var assetsFile = flag.String("f", "ClientConf", "The ClientConf")
149 | var outFilename = flag.String("o", "", "`output` file name to write new/modified config")
150 | var workers = flag.Int("w", 10, "Number of worker threads to spawn")
151 | flag.Parse()
152 |
153 | fmt.Printf("Attempting connections for ClientConf (%s) \n", *assetsFile)
154 |
155 | if *assetsFile == "" {
156 | fmt.Println("Please provide a clientConf file.")
157 | os.Exit(1)
158 | }
159 |
160 | // Parse ClientConf
161 | clientConf := pb.ClientConf{}
162 | clientConf = parseClientConf(*assetsFile)
163 |
164 | var allDecoys = clientConf.DecoyList.GetTlsDecoys()
165 | var decoys = make(chan *pb.TLSDecoySpec, len(allDecoys))
166 | var results = make(chan string, len(allDecoys)*len(fingerprints))
167 | var wg sync.WaitGroup
168 | var out io.Writer = os.Stdout
169 |
170 | if *outFilename != "" {
171 | f, err := os.OpenFile(*outFilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
172 | if err != nil {
173 | fmt.Println(err)
174 | os.Exit(1)
175 | }
176 | defer f.Close()
177 | out = f
178 | }
179 |
180 | for i := 0; i < *workers; i++ {
181 | wg.Add(1)
182 | go worker(i, decoys, results, &wg)
183 | }
184 |
185 | for _, decoy := range allDecoys {
186 | decoys <- decoy
187 | }
188 | close(decoys)
189 |
190 | var printTotal int = 0
191 | for j := 0; j < len(allDecoys)*len(fingerprints); j++ {
192 | r := <-results
193 | printTotal++
194 | fmt.Fprintf(out, "(%v) %s", printTotal, r)
195 | }
196 |
197 | wg.Wait()
198 | }
199 |
--------------------------------------------------------------------------------
/tools/v6lookup/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/binary"
5 | "encoding/hex"
6 | "flag"
7 | "fmt"
8 | "io/ioutil"
9 | "log"
10 | "net"
11 | "os"
12 | "sync"
13 | "time"
14 |
15 | "github.com/jinzhu/copier"
16 | pb "github.com/refraction-networking/conjure/proto"
17 | "google.golang.org/protobuf/proto"
18 | )
19 |
20 | func main() {
21 |
22 | var assets_file = flag.String("f", "ClientConf", "The ClientConf")
23 | var out_fname = flag.String("o", "", "`output` file name to write new/modified config")
24 | var noout = flag.Bool("noout", false, "Don't print ClientConf")
25 | var lookupV6 = flag.Bool("l6", false, "Perform AAAA DNS lookup for all hostnames and add to TlsDecoySpec")
26 | flag.Parse()
27 |
28 | fmt.Printf("Looking up IPv6 addresses for Conf (%s) \n", *assets_file)
29 |
30 | if *assets_file == "" {
31 | fmt.Println("Please provide a clientConf file.")
32 | os.Exit(1)
33 | }
34 |
35 | clientConf := &pb.ClientConf{}
36 |
37 | // Parse ClientConf
38 | clientConf = parseClientConf(*assets_file)
39 |
40 | // v6Decoys := lookupHosts(clientConf.DecoyList.GetTlsDecoys())
41 | // fmt.Printf("Found %d Ipv6 Decoys\n", len(v6Decoys))
42 | if *lookupV6 {
43 | v6Decoys := make([]*pb.TLSDecoySpec, 0)
44 | var ipv6Arr [16]byte
45 | uniqueIPv6Addrs := make(map[[16]byte]bool)
46 | ipv6Decoys := make(chan *pb.TLSDecoySpec, 1)
47 | i := 0
48 |
49 | go func() {
50 | for decoy := range ipv6Decoys {
51 | fmt.Printf("%v", decoy.GetHostname())
52 | copy(ipv6Arr[:], decoy.GetIpv6Addr()[:16])
53 | if uniqueIPv6Addrs[ipv6Arr] == false {
54 | uniqueIPv6Addrs[ipv6Arr] = true
55 | v6Decoys = append(v6Decoys, decoy)
56 | fmt.Printf("%v, %v\n", decoy.GetHostname(), decoy.GetIpAddrStr())
57 | i++
58 | }
59 | }
60 | }()
61 |
62 | for _, decoy := range clientConf.DecoyList.GetTlsDecoys() {
63 | lookupHost(decoy, ipv6Decoys)
64 | }
65 |
66 | fmt.Printf("Unique Addresses: %v\n", i)
67 |
68 | for _, decoy := range v6Decoys {
69 | clientConf.DecoyList.TlsDecoys = append(clientConf.DecoyList.TlsDecoys, decoy)
70 | }
71 | }
72 |
73 | if !*noout {
74 | printClientConf(clientConf)
75 | }
76 |
77 | if *out_fname != "" {
78 | buf, err := proto.Marshal(clientConf)
79 | if err != nil {
80 | log.Fatal("Error writing output:", err)
81 | }
82 | err = ioutil.WriteFile(*out_fname, buf[:], 0644)
83 | if err != nil {
84 | log.Fatal("Error writing output:", err)
85 | }
86 | }
87 | }
88 |
89 | func lookupHost(decoy *pb.TLSDecoySpec, ip6Chan chan *pb.TLSDecoySpec) {
90 |
91 | decoyHostname := decoy.GetHostname()
92 | decoyIPs, err := net.LookupIP(decoyHostname)
93 | if err == nil && decoyIPs != nil {
94 | for _, decoyIP := range decoyIPs {
95 | if decoyIP.To4() != nil {
96 | continue
97 | }
98 | newDecoy := DeepCopy(decoy)
99 | newDecoy.Ipv6Addr = decoyIP
100 | newDecoy.Ipv4Addr = nil
101 | fmt.Printf("%v, %v, (%v), [%v]\n", len(decoyIPs), decoyHostname, decoy.GetIpAddrStr(), decoyIP)
102 | ip6Chan <- newDecoy
103 | }
104 | }
105 | return
106 | }
107 |
108 | func lookupHosts(decoyList []*pb.TLSDecoySpec) map[string][]byte {
109 | v6Decoys := make(map[string][]byte)
110 |
111 | for _, decoy := range decoyList {
112 | decoyHostname := decoy.GetHostname()
113 | decoyIP, err := net.ResolveIPAddr("ip6", decoyHostname)
114 | if err == nil && decoyIP != nil {
115 | ipBytes := []byte(decoyIP.IP.To16())
116 | if ipBytes != nil {
117 | v6Decoys[decoyHostname] = ipBytes
118 | }
119 | fmt.Printf("%v -(%v)- %v\n", decoyHostname, decoy.GetIpAddrStr(), decoyIP)
120 | }
121 | }
122 | return v6Decoys
123 | }
124 |
125 | func parseClientConf(fname string) *pb.ClientConf {
126 |
127 | clientConf := &pb.ClientConf{}
128 | buf, err := ioutil.ReadFile(fname)
129 | if err != nil {
130 | log.Fatal("Error reading file:", err)
131 | }
132 | err = proto.Unmarshal(buf, clientConf)
133 | if err != nil {
134 | log.Fatal("Error parsing ClientConf", err)
135 | }
136 | return clientConf
137 | }
138 |
139 | type jobTuple struct {
140 | Decoy *pb.TLSDecoySpec
141 | Total uint
142 | JobId uint
143 | }
144 |
145 | // DeepCopy - Create a Deep Copy of a given TLSDecoySpec Object
146 | func DeepCopy(ds *pb.TLSDecoySpec) *pb.TLSDecoySpec {
147 | newDs := pb.TLSDecoySpec{}
148 | copier.Copy(&newDs, ds)
149 | return &newDs
150 | }
151 |
152 | func ConnectToAll(decoyList []*pb.TLSDecoySpec, workers int) {
153 |
154 | fmt.Println("Connection to all registration decoys in client conf.")
155 | var wg sync.WaitGroup
156 |
157 | decoyChan := make(chan jobTuple, len(decoyList))
158 |
159 | for id := 0; id < workers; id++ {
160 | wg.Add(1)
161 | go func(id int) {
162 | defer wg.Done()
163 |
164 | jobsCompleted := 0
165 | var decoy *pb.TLSDecoySpec
166 | var err error
167 |
168 | for decoyTuple := range decoyChan {
169 |
170 | decoy = decoyTuple.Decoy
171 | fmt.Printf("[%d/%d/%d] (%d) Connecting to decoy %s -- %s ... \n",
172 | jobsCompleted, decoyTuple.JobId, decoyTuple.Total, id, decoy.GetHostname(), decoy.GetIpAddrStr())
173 |
174 | err = ConnectSpecial(decoy)
175 | if err != nil {
176 | fmt.Printf("[%d/%d/%d] (%d) Failure: %s\n",
177 | jobsCompleted, decoyTuple.JobId, decoyTuple.Total, id, err.Error())
178 | } else {
179 | fmt.Printf("[%d/%d/%d] (%d) Success\n",
180 | jobsCompleted, decoyTuple.JobId, decoyTuple.Total, id)
181 | }
182 | jobsCompleted++
183 | }
184 |
185 | }(id) // end go worker func
186 | }
187 |
188 | go func() {
189 | for idx := 0; idx < len(decoyList); idx++ {
190 | decoyChan <- jobTuple{Decoy: decoyList[idx], Total: uint(len(decoyList)), JobId: uint(idx)}
191 | }
192 | close(decoyChan)
193 | }()
194 |
195 | wg.Wait()
196 | }
197 |
198 | func ConnectSpecial(decoy *pb.TLSDecoySpec) error {
199 |
200 | timeout := time.Duration(5 * time.Second)
201 |
202 | conn, err := net.DialTimeout("tcp", decoy.GetIpAddrStr(), timeout)
203 | if err != nil {
204 | return err
205 | }
206 | defer closeConn(conn, decoy.GetIpAddrStr())
207 |
208 | fmt.Fprintf(conn, "'This must be Thursday,' said Arthur to himself, sinking low over his beer. 'I never could get the hang of Thursdays.'")
209 |
210 | err = conn.SetReadDeadline(time.Now().Add(5 * time.Second))
211 | if err != nil {
212 | // fmt.Printf("SetReadDeadline failed: %s", err.Error())
213 | return nil
214 | }
215 |
216 | recvBuf := make([]byte, 1024)
217 |
218 | _, err = conn.Read(recvBuf[:]) // recv data
219 | if err != nil {
220 | if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
221 | // fmt.Printf("read timeout: %s", err.Error())
222 | return nil
223 | // time out
224 | } else {
225 | // fmt.Printf("read error: %s", err.Error())
226 | return nil
227 | }
228 | }
229 |
230 | return nil
231 | }
232 |
233 | func closeConn(conn net.Conn, addr string) {
234 | // fmt.Printf("Closing connection to: %s\n", addr)
235 | conn.Close()
236 | }
237 |
238 | func printClientConf(clientConf *pb.ClientConf) {
239 | fmt.Printf("Generation: %d\n", clientConf.GetGeneration())
240 | if clientConf.GetDefaultPubkey() != nil {
241 | fmt.Printf("\nDefault Pubkey: %s\n", hex.EncodeToString(clientConf.GetDefaultPubkey().Key[:]))
242 | }
243 | if clientConf.DecoyList == nil {
244 | return
245 | }
246 | decoys := clientConf.DecoyList.TlsDecoys
247 | fmt.Printf("\nDecoy List: %d decoys\n", len(decoys))
248 | for i, decoy := range decoys {
249 | ip := make(net.IP, 4)
250 | binary.BigEndian.PutUint32(ip, decoy.GetIpv4Addr())
251 | // ip6 := net.IP{}
252 | // ip6.UnmarshalText(decoy.GetIpv6Addr())
253 | ip6 := net.IP(decoy.GetIpv6Addr())
254 | fmt.Printf("%d:\n %s (%s / [%s])\n", i, decoy.GetHostname(), ip.To4().String(), ip6.To16().String())
255 | if decoy.GetPubkey() != nil {
256 | fmt.Printf(" pubkey: %s\n", hex.EncodeToString(decoy.GetPubkey().Key[:]))
257 | }
258 | if decoy.GetTimeout() != 0 {
259 | fmt.Printf(" timeout: %d ms\n", decoy.GetTimeout())
260 | }
261 | if decoy.GetTcpwin() != 0 {
262 | fmt.Printf(" tcpwin: %d bytes\n", decoy.GetTcpwin())
263 | }
264 | }
265 | // if clientConf.GetDarkDecoyBlocks() != nil {
266 | // darkDecoyBlocks := clientConf.DarkDecoyBlocks.Blocks
267 | // fmt.Printf("\nDark Decoy Blocks: %d blocks\n", len(darkDecoyBlocks))
268 | // for i, block := range darkDecoyBlocks {
269 | // fmt.Printf("%d:\n %s\n", i, block)
270 | // }
271 | // }
272 | }
273 |
--------------------------------------------------------------------------------