├── .github └── workflows │ └── test.yaml ├── .gitignore ├── .travis.yml ├── BUILD.bazel ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── LICENSE-APACHE2 ├── LICENSE-MPL-RabbitMQ ├── MODULE.bazel ├── Makefile ├── README.md ├── WORKSPACE ├── erlang.mk ├── rebar.config ├── rebar.lock ├── src ├── inet_tcp_proxy_dist.app.src ├── inet_tcp_proxy_dist.erl ├── inet_tcp_proxy_dist_app.erl ├── inet_tcp_proxy_dist_conn_sup.erl ├── inet_tcp_proxy_dist_controller.erl └── inet_tcp_proxy_dist_sup.erl └── test ├── proxy_dist_SUITE.erl ├── proxy_dist_test_lib.erl └── proxy_dist_test_lib.hrl /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | otp: 14 | - "24.3" 15 | - "25.1" 16 | extra_flags: 17 | - --experimental_enable_bzlmod 18 | - "" 19 | steps: 20 | - name: CHECKOUT 21 | uses: actions/checkout@v2 22 | - name: CONFIGURE ERLANG 23 | uses: erlef/setup-beam@v1 24 | with: 25 | otp-version: ${{ matrix.otp }} 26 | - name: TEST 27 | run: | 28 | bazelisk test //... \ 29 | --color=yes ${{ matrix.extra_flags }} 30 | - name: RESOVLE TEST LOGS PATH 31 | run: | 32 | echo "::set-output name=LOGS_PATH::$(readlink -f bazel-testlogs)" 33 | id: resolve-test-logs-path 34 | - name: CAPTURE TEST LOGS 35 | uses: actions/upload-artifact@v2 36 | with: 37 | name: bazel-testlogs-${{ matrix.otp }}${{ matrix.extra_flags }} 38 | path: ${{ steps.resolve-test-logs-path.outputs.LOGS_PATH }}/* 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | /.erlang.mk/ 3 | _build/* 4 | /.inet_tcp_proxy_dist.plt 5 | /deps/ 6 | /ebin/ 7 | /inet_tcp_proxy_dist.d 8 | /logs/ 9 | /xrefr 10 | 11 | /bazel-* 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # vim:sw=2:et: 2 | 3 | dist: xenial 4 | sudo: false 5 | language: erlang 6 | notifications: 7 | email: 8 | recipients: 9 | - alerts@rabbitmq.com 10 | on_success: never 11 | on_failure: always 12 | cache: 13 | apt: true 14 | 15 | otp_release: 16 | - "21.3" 17 | - "22.3" 18 | - "23.0" 19 | 20 | script: 21 | - make 22 | - make xref 23 | - make check 24 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_erlang//:erlang_app.bzl", "erlang_app", "test_erlang_app") 2 | load("@rules_erlang//:xref.bzl", "xref") 3 | load("@rules_erlang//:dialyze.bzl", "dialyze", "plt") 4 | load("@rules_erlang//:ct.bzl", "assert_suites", "ct_suite") 5 | 6 | NAME = "inet_tcp_proxy_dist" 7 | 8 | DESCRIPTION = "Erlang distribution proxy to simulate network failures" 9 | 10 | VERSION = "0.1.0" 11 | 12 | erlang_app( 13 | app_description = DESCRIPTION, 14 | app_name = NAME, 15 | app_version = VERSION, 16 | ) 17 | 18 | test_erlang_app( 19 | app_description = DESCRIPTION, 20 | app_name = NAME, 21 | app_version = VERSION, 22 | ) 23 | 24 | xref() 25 | 26 | plt(name = "base_plt") 27 | 28 | dialyze( 29 | plt = ":base_plt", 30 | ) 31 | 32 | suites = [ 33 | ct_suite( 34 | name = "proxy_dist_SUITE", 35 | additional_hdrs = [ 36 | "test/proxy_dist_test_lib.hrl", 37 | ], 38 | additional_srcs = [ 39 | "test/proxy_dist_test_lib.erl", 40 | ], 41 | ), 42 | ] 43 | 44 | assert_suites( 45 | suites, 46 | glob(["test/**/*_SUITE.erl"]), 47 | ) 48 | 49 | alias( 50 | name = NAME, 51 | actual = ":erlang_app", 52 | visibility = ["//visibility:public"], 53 | ) 54 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open 4 | and welcoming community, we pledge to respect all people who contribute through reporting 5 | issues, posting feature requests, updating documentation, submitting pull requests or 6 | patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free experience for 9 | everyone, regardless of level of experience, gender, gender identity and expression, 10 | sexual orientation, disability, personal appearance, body size, race, ethnicity, age, 11 | religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic addresses, 20 | without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or reject comments, 24 | commits, code, wiki edits, issues, and other contributions that are not aligned to this 25 | Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors 26 | that they deem inappropriate, threatening, offensive, or harmful. 27 | 28 | By adopting this Code of Conduct, project maintainers commit themselves to fairly and 29 | consistently applying these principles to every aspect of managing this project. Project 30 | maintainers who do not follow or enforce the Code of Conduct may be permanently removed 31 | from the project team. 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an 34 | individual is representing the project or its community. 35 | 36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by 37 | contacting a project maintainer at [info@rabbitmq.com](mailto:info@rabbitmq.com). All complaints will 38 | be reviewed and investigated and will result in a response that is deemed necessary and 39 | appropriate to the circumstances. Maintainers are obligated to maintain confidentiality 40 | with regard to the reporter of an incident. 41 | 42 | This Code of Conduct is adapted from the 43 | [Contributor Covenant](http://contributor-covenant.org), version 1.3.0, available at 44 | [contributor-covenant.org/version/1/3/0/](http://contributor-covenant.org/version/1/3/0/) 45 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | RabbitMQ projects use pull requests to discuss, collaborate on and accept code contributions. 4 | Pull requests is the primary place of discussing code changes. 5 | 6 | ## How to Contribute 7 | 8 | The process is fairly standard: 9 | 10 | * Fork the repository or repositories you plan on contributing to 11 | * Clone [RabbitMQ umbrella repository](https://github.com/rabbitmq/rabbitmq-public-umbrella) 12 | * `cd umbrella`, `make co` 13 | * Create a branch with a descriptive name in the relevant repositories 14 | * Make your changes, run tests, commit with a [descriptive message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), push to your fork 15 | * Submit pull requests with an explanation what has been changed and **why** 16 | * Submit a filled out and signed [Contributor Agreement](https://github.com/rabbitmq/ca#how-to-submit) if needed (see below) 17 | * Be patient. We will get to your pull request eventually 18 | 19 | If what you are going to work on is a substantial change, please first ask the core team 20 | of their opinion on [RabbitMQ mailing list](https://groups.google.com/forum/#!forum/rabbitmq-users). 21 | 22 | 23 | ## Code of Conduct 24 | 25 | See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md). 26 | 27 | 28 | ## Contributor Agreement 29 | 30 | If you want to contribute a non-trivial change, please submit a signed copy of our 31 | [Contributor Agreement](https://github.com/rabbitmq/ca#how-to-submit) around the time 32 | you submit your pull request. This will make it much easier (in some cases, possible) 33 | for the RabbitMQ team at Pivotal to merge your contribution. 34 | 35 | 36 | ## Where to Ask Questions 37 | 38 | If something isn't clear, feel free to ask on our [mailing list](https://groups.google.com/forum/#!forum/rabbitmq-users). 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This package, Ra, is dual-licensed under 2 | the Apache License v2 and the Mozilla Public License v2.0. 3 | 4 | For the Apache License, please see the file LICENSE-APACHE2. 5 | 6 | For the Mozilla Public License, please see the file LICENSE-MPL-RabbitMQ. 7 | 8 | For attribution of copyright and other details of provenance, please 9 | refer to the source code. 10 | 11 | If you have any questions regarding licensing, please contact us at 12 | info@rabbitmq.com. 13 | -------------------------------------------------------------------------------- /LICENSE-APACHE2: -------------------------------------------------------------------------------- 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 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2017 Pivotal Software Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MPL-RabbitMQ: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "inet_tcp_proxy_dist", 3 | version = "0.1.0", 4 | compatibility_level = 1, 5 | ) 6 | 7 | bazel_dep( 8 | name = "rules_erlang", 9 | version = "3.8.0", 10 | ) 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT = inet_tcp_proxy_dist 2 | PROJECT_DESCRIPTION = Erlang distribution proxy to simulate network failures 3 | PROJECT_VERSION = 0.1.0 4 | 5 | include $(if $(ERLANG_MK_FILENAME),$(ERLANG_MK_FILENAME),erlang.mk) 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # inet_tcp_proxy 2 | 3 | ## What is This? 4 | 5 | This is a set of utilities that can be used to simulate [some types of] network partitions 6 | in an distributed Erlang cluster, originally used in RabbitMQ integration tests. 7 | 8 | This proxy is not as comprehensive as [Toxiproxy](https://github.com/Shopify/toxiproxy) or similar tools; 9 | it is, however, very easy to embed into Erlang integration tests, and it is sufficient for some 10 | test suites. 11 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 2 | 3 | http_archive( 4 | name = "bazel_skylib", 5 | sha256 = "af87959afe497dc8dfd4c6cb66e1279cb98ccc84284619ebfec27d9c09a903de", 6 | urls = [ 7 | "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.2.0/bazel-skylib-1.2.0.tar.gz", 8 | "https://github.com/bazelbuild/bazel-skylib/releases/download/1.2.0/bazel-skylib-1.2.0.tar.gz", 9 | ], 10 | ) 11 | 12 | load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") 13 | 14 | bazel_skylib_workspace() 15 | 16 | load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") 17 | 18 | git_repository( 19 | name = "rules_erlang", 20 | remote = "https://github.com/rabbitmq/rules_erlang.git", 21 | tag = "3.8.0", 22 | ) 23 | 24 | load( 25 | "@rules_erlang//:rules_erlang.bzl", 26 | "erlang_config", 27 | "rules_erlang_dependencies", 28 | ) 29 | 30 | erlang_config() 31 | 32 | rules_erlang_dependencies() 33 | 34 | load("@erlang_config//:defaults.bzl", "register_defaults") 35 | 36 | register_defaults() 37 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {minimum_otp_vsn, "22.3"}. 2 | {erl_opts, [debug_info]}. 3 | {deps, []}. 4 | {project_plugins, [rebar3_hex]}. 5 | {profiles, 6 | [{test, [{deps, [meck, proper]}]}] 7 | }. 8 | {dist_node, [ 9 | {sname, 'inet_tcp_proxy'} 10 | ]}. 11 | {dialyzer, [{warnings, 12 | [error_handling, 13 | race_conditions, 14 | unmatched_returns]}]}. 15 | {xref_extra_paths, ["test"]}. 16 | {xref_checks,[undefined_function_calls, 17 | undefined_functions, 18 | locals_not_used, 19 | % exports_not_used, 20 | deprecated_function_calls, 21 | deprecated_functions]}. 22 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /src/inet_tcp_proxy_dist.app.src: -------------------------------------------------------------------------------- 1 | {application, inet_tcp_proxy_dist, 2 | [{description, "Erlang distribution proxy to simulate network failures"}, 3 | {vsn, "0.1.0"}, 4 | {registered, [inet_tcp_proxy_dist_sup]}, 5 | {applications, [kernel, stdlib]}, 6 | {env, []}, 7 | {mod, {inet_tcp_proxy_dist_app, []}}, 8 | {modules, []}, 9 | {licenses, ["Apache-2.0", "MPL-2.0"]}]}. 10 | -------------------------------------------------------------------------------- /src/inet_tcp_proxy_dist.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 1997-2018. All Rights Reserved. 5 | %% 6 | %% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %% you may not use this file except in compliance with the License. 8 | %% You may obtain a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, software 13 | %% distributed under the License is distributed on an "AS IS" BASIS, 14 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %% See the License for the specific language governing permissions and 16 | %% limitations under the License. 17 | %% 18 | %% %CopyrightEnd% 19 | %% 20 | %% Originally based on inet_tcp_dist and inet_tls_dist. 21 | -module(inet_tcp_proxy_dist). 22 | 23 | -export([enable/0]). 24 | 25 | %% Handles the connection setup phase with other Erlang nodes. 26 | 27 | -export([listen/1, accept/1, accept_connection/5, 28 | setup/5, close/1, select/1, is_node_name/1]). 29 | 30 | %% Optional 31 | -export([setopts/2, getopts/2]). 32 | 33 | %% Generalized dist API 34 | -export([gen_listen/2, gen_accept/2, gen_accept_connection/6, 35 | gen_setup/6, gen_select/2]). 36 | 37 | %% internal exports 38 | 39 | -export([accept_loop/3,do_accept/7,do_setup/7,getstat/1,tick/3]). 40 | 41 | -export([dist_proc_start_link/0, dist_proc_init/1, dist_proc_loop/3]). 42 | -export([system_continue/3, 43 | system_terminate/4, 44 | system_get_state/1, 45 | system_replace_state/2]). 46 | 47 | -export([allow/1, block/1, is_blocked/1, info/0]). 48 | -export([notify_new_state/2, dbg/1]). 49 | 50 | -import(error_logger,[error_msg/2]). 51 | 52 | -include_lib("kernel/include/net_address.hrl"). 53 | -include_lib("kernel/include/dist.hrl"). 54 | -include_lib("kernel/include/dist_util.hrl"). 55 | 56 | -record(proxy_socket, { 57 | driver :: atom(), 58 | socket :: term(), 59 | pid :: pid(), 60 | initiated = false :: boolean(), 61 | 62 | dhandle = undefined :: any(), 63 | node = undefined :: atom() | undefined 64 | }). 65 | 66 | %% ------------------------------------------------------------ 67 | %% Enable dist compression (if proto dist is configured). 68 | %% ------------------------------------------------------------ 69 | 70 | enable() -> 71 | case inet_tcp_proxy_dist_controller:is_dist_proto_mod_configured() of 72 | true -> 73 | {ok, _} = application:ensure_all_started(inet_tcp_proxy_dist), 74 | true; 75 | false -> 76 | false 77 | end. 78 | 79 | %% ------------------------------------------------------------ 80 | %% Select this protocol based on node name 81 | %% select(Node) => Bool 82 | %% ------------------------------------------------------------ 83 | 84 | select(Node) -> 85 | gen_select(inet_tcp, Node). 86 | 87 | gen_select(Driver, Node) -> 88 | case split_node(atom_to_list(Node), $@, []) of 89 | [_, Host] -> 90 | case inet:getaddr(Host, Driver:family()) of 91 | {ok,_} -> true; 92 | _ -> false 93 | end; 94 | _ -> false 95 | end. 96 | 97 | %% ------------------------------------------------------------ 98 | %% Create the listen socket, i.e. the port that this erlang 99 | %% node is accessible through. 100 | %% ------------------------------------------------------------ 101 | 102 | listen(Name) -> 103 | gen_listen(inet_tcp, Name). 104 | 105 | gen_listen(Driver, Name) -> 106 | case do_listen(Driver, [{active, false}, {packet,2}, {reuseaddr, true}]) of 107 | {ok, Socket} -> 108 | TcpAddress = get_tcp_address(Driver, Socket), 109 | {_,Port} = TcpAddress#net_address.address, 110 | ErlEpmd = net_kernel:epmd_module(), 111 | case ErlEpmd:register_node(Name, Port, Driver) of 112 | {ok, Creation} -> 113 | {ok, {Socket, TcpAddress, Creation}}; 114 | Error -> 115 | Error 116 | end; 117 | Error -> 118 | Error 119 | end. 120 | 121 | do_listen(Driver, Options) -> 122 | {First,Last} = case application:get_env(kernel,inet_dist_listen_min) of 123 | {ok,N} when is_integer(N) -> 124 | case application:get_env(kernel, 125 | inet_dist_listen_max) of 126 | {ok,M} when is_integer(M) -> 127 | {N,M}; 128 | _ -> 129 | {N,N} 130 | end; 131 | _ -> 132 | {0,0} 133 | end, 134 | do_listen(Driver, First, Last, listen_options([{backlog,128}|Options])). 135 | 136 | do_listen(_Driver, First,Last,_) when First > Last -> 137 | {error,eaddrinuse}; 138 | do_listen(Driver, First,Last,Options) -> 139 | case Driver:listen(First, Options) of 140 | {error, eaddrinuse} -> 141 | do_listen(Driver, First+1,Last,Options); 142 | Other -> 143 | Other 144 | end. 145 | 146 | listen_options(Opts0) -> 147 | Opts1 = 148 | case application:get_env(kernel, inet_dist_use_interface) of 149 | {ok, Ip} -> 150 | [{ip, Ip} | Opts0]; 151 | _ -> 152 | Opts0 153 | end, 154 | case application:get_env(kernel, inet_dist_listen_options) of 155 | {ok,ListenOpts} -> 156 | ListenOpts ++ Opts1; 157 | _ -> 158 | Opts1 159 | end. 160 | 161 | 162 | %% ------------------------------------------------------------ 163 | %% Accepts new connection attempts from other Erlang nodes. 164 | %% ------------------------------------------------------------ 165 | 166 | accept(Listen) -> 167 | gen_accept(inet_tcp, Listen). 168 | 169 | gen_accept(Driver, Listen) -> 170 | spawn_opt(?MODULE, accept_loop, [Driver, self(), Listen], [link, {priority, max}]). 171 | 172 | accept_loop(Driver, Kernel, Listen) -> 173 | case Driver:accept(Listen) of 174 | {ok, Socket} -> 175 | Kernel ! {accept,self(),Socket,Driver:family(),tcp_proxy}, 176 | _ = controller(Driver, Kernel, Socket), 177 | accept_loop(Driver, Kernel, Listen); 178 | Error -> 179 | exit(Error) 180 | end. 181 | 182 | controller(Driver, Kernel, Socket) -> 183 | receive 184 | {Kernel, controller, Pid} -> 185 | flush_controller(Pid, Socket), 186 | Driver:controlling_process(Socket, Pid), 187 | flush_controller(Pid, Socket), 188 | Pid ! {self(), controller}; 189 | {Kernel, unsupported_protocol} -> 190 | exit(unsupported_protocol) 191 | end. 192 | 193 | flush_controller(Pid, Socket) -> 194 | receive 195 | {tcp, Socket, Data} -> 196 | Pid ! {tcp, Socket, Data}, 197 | flush_controller(Pid, Socket); 198 | {tcp_closed, Socket} -> 199 | Pid ! {tcp_closed, Socket}, 200 | flush_controller(Pid, Socket) 201 | after 0 -> 202 | ok 203 | end. 204 | 205 | %% ------------------------------------------------------------ 206 | %% Accepts a new connection attempt from another Erlang node. 207 | %% Performs the handshake with the other side. 208 | %% ------------------------------------------------------------ 209 | 210 | accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) -> 211 | gen_accept_connection(inet_tcp, AcceptPid, Socket, MyNode, Allowed, SetupTime). 212 | 213 | gen_accept_connection(Driver, AcceptPid, Socket, MyNode, Allowed, SetupTime) -> 214 | spawn_opt(?MODULE, do_accept, 215 | [Driver, self(), AcceptPid, Socket, MyNode, Allowed, SetupTime], 216 | [link, {priority, max}]). 217 | 218 | do_accept(Driver, Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) -> 219 | receive 220 | {AcceptPid, controller} -> 221 | Timer = dist_util:start_timer(SetupTime), 222 | case check_ip(Driver, Socket) of 223 | true -> 224 | ProxySocket = #proxy_socket{pid = DistCtrl} = 225 | proxy_socket(Driver, Socket, undefined, false), 226 | HSData = #hs_data{ 227 | kernel_pid = Kernel, 228 | this_node = MyNode, 229 | socket = DistCtrl, 230 | timer = Timer, 231 | this_flags = 0, 232 | allowed = Allowed, 233 | f_send = fun(Ctrl, Data) when Ctrl =:= DistCtrl -> 234 | f_send(ProxySocket, Data) 235 | end, 236 | f_recv = fun(Ctrl, Len, Timeout) when Ctrl =:= DistCtrl -> 237 | f_recv(ProxySocket, Len, Timeout) 238 | end, 239 | f_setopts_pre_nodeup = 240 | fun(Ctrl) when Ctrl =:= DistCtrl -> 241 | inet:setopts(Socket, 242 | [{active, false}, 243 | {packet, 4}, 244 | nodelay()]) 245 | end, 246 | f_setopts_post_nodeup = 247 | fun(Ctrl) when Ctrl =:= DistCtrl -> 248 | inet:setopts(Socket, 249 | [{active, true}, 250 | % {deliver, port}, 251 | {packet, 4}, 252 | binary, 253 | nodelay()]) 254 | end, 255 | f_getll = fun(Ctrl) when Ctrl =:= DistCtrl -> 256 | {ok, DistCtrl} 257 | end, 258 | f_address = fun(Ctrl, Node) when Ctrl =:= DistCtrl -> get_remote_id(Driver, Socket, Node) end, 259 | mf_tick = fun(Ctrl) when Ctrl =:= DistCtrl -> ?MODULE:tick(Ctrl, Driver, Socket) end, 260 | mf_getstat = fun(Ctrl) when Ctrl =:= DistCtrl -> ?MODULE:getstat(Socket) end, 261 | mf_setopts = fun(Ctrl, Opts) when Ctrl =:= DistCtrl -> ?MODULE:setopts(Socket, Opts) end, 262 | mf_getopts = fun(Ctrl, Opts) when Ctrl =:= DistCtrl -> ?MODULE:getopts(Socket, Opts) end, 263 | f_handshake_complete = fun(Ctrl, Node, DHandle) when Ctrl =:= DistCtrl -> 264 | handshake_complete(Ctrl, Node, DHandle, ProxySocket) 265 | end 266 | }, 267 | dist_util:handshake_other_started(HSData); 268 | {false,IP} -> 269 | error_msg("** Connection attempt from " 270 | "disallowed IP ~w ** ~n", [IP]), 271 | ?shutdown(no_node) 272 | end 273 | end. 274 | 275 | proxy_socket(Driver, Socket, Node, Initiated) -> 276 | {ok, _} = application:ensure_all_started(inet_tcp_proxy_dist), 277 | Id = {Node, Initiated, make_ref()}, 278 | {ok, Pid} = supervisor:start_child(inet_tcp_proxy_dist_conn_sup, #{ 279 | id => Id, 280 | start => {?MODULE, dist_proc_start_link, []}, 281 | restart => temporary 282 | }), 283 | %% We link this process to the connection handler we just spawned. 284 | %% One usecase where this is useful is when both ends try to connect 285 | %% to each other. In this case, one connection will survive and 286 | %% the other will exit. The link will take care of exiting the 287 | %% connection handler process at the same time. 288 | erlang:link(Pid), 289 | #proxy_socket{driver = Driver, 290 | socket = Socket, 291 | pid = Pid, 292 | initiated = Initiated}. 293 | 294 | handshake_complete(DistCtrl, Node, DHandle, ProxySocket) -> 295 | #proxy_socket{socket = Socket} = ProxySocket, 296 | ok = gen_tcp:controlling_process(Socket, DistCtrl), 297 | DistCtrl ! ProxySocket#proxy_socket{node = Node, 298 | dhandle = DHandle}, 299 | ok. 300 | 301 | dist_proc_start_link() -> 302 | proc_lib:start_link(?MODULE, dist_proc_init, [self()]). 303 | 304 | dist_proc_init(Parent) -> 305 | Debug = sys:debug_options([]), 306 | Self = self(), 307 | proc_lib:init_ack(Parent, {ok, Self}), 308 | receive 309 | ProxySocket = #proxy_socket{pid = Self, node = Node} 310 | when Node =/= undefined -> 311 | Blocked = is_blocked__internal(Node), 312 | case Blocked of 313 | false -> 314 | logger:debug( 315 | ?MODULE_STRING ": connection handler to ~s ready " 316 | "(~p)", 317 | [Node, Self]), 318 | ProxySocket1 = output_dist_data(ProxySocket), 319 | dist_proc_loop(ProxySocket1, Parent, Debug); 320 | true -> 321 | logger:debug( 322 | ?MODULE_STRING ": connection to ~s blocked; " 323 | "handler terminating (~p)", 324 | [Node, Self]), 325 | exit({shutdown, blocked}) 326 | end 327 | after 10000 -> 328 | exit({shutdown, init_timeout}) 329 | end. 330 | 331 | dist_proc_loop(#proxy_socket{socket = Socket} = ProxySocket, 332 | Parent, 333 | Debug) -> 334 | ProxySocket1 = 335 | receive 336 | dist_data -> 337 | output_dist_data(ProxySocket); 338 | {tcp, Socket, Data} -> 339 | input_dist_data(ProxySocket, Data); 340 | {tcp_closed, Socket} -> 341 | exit(normal); 342 | {notify_new_state, allowed} -> 343 | %% Flush Erlang's output buffer if data is available. 344 | output_dist_data(ProxySocket); 345 | {notify_new_state, _} -> 346 | ProxySocket; 347 | {info, From} -> 348 | send_info(ProxySocket, From), 349 | ProxySocket; 350 | {system, From, Request} -> 351 | sys:handle_system_msg( 352 | Request, From, Parent, ?MODULE, Debug, 353 | [ProxySocket]); 354 | Msg -> 355 | logger:debug( 356 | ?MODULE_STRING ": Unhandled message (ignored): ~p", [Msg]), 357 | ProxySocket 358 | end, 359 | dist_proc_loop(ProxySocket1, Parent, Debug). 360 | 361 | output_dist_data(#proxy_socket{ 362 | node = Node, 363 | driver = Driver, 364 | socket = Socket, 365 | dhandle = DHandle} = ProxySocket) -> 366 | Blocked = is_blocked__internal(Node), 367 | case Blocked of 368 | false -> 369 | case erlang:dist_ctrl_get_data(DHandle) of 370 | none -> 371 | erlang:dist_ctrl_get_data_notification(DHandle), 372 | ProxySocket; 373 | Data -> 374 | Driver:send(Socket, Data), 375 | output_dist_data(ProxySocket) 376 | end; 377 | true -> 378 | ProxySocket 379 | end. 380 | 381 | input_dist_data(#proxy_socket{dhandle = DHandle} = ProxySocket, 382 | Data) -> 383 | erlang:dist_ctrl_put_data(DHandle, Data), 384 | ProxySocket. 385 | 386 | is_blocked__internal(Node) -> 387 | Blocked = is_blocked__internal1(Node), 388 | DictKey = {?MODULE, last_block_warning}, 389 | LastWarning = case get(DictKey) of 390 | undefined -> allowed; 391 | Value -> Value 392 | end, 393 | case LastWarning of 394 | allowed when not Blocked -> 395 | ok; 396 | blocked when Blocked -> 397 | ok; 398 | allowed when Blocked -> 399 | put(DictKey, blocked), 400 | logger:debug( 401 | ?MODULE_STRING ": Communication from ~s to ~s BLOCKED (~p)~n", 402 | [node(), Node, self()]); 403 | blocked when not Blocked -> 404 | put(DictKey, allowed), 405 | logger:debug( 406 | ?MODULE_STRING ": Communication from ~s to ~s allowed (~p)~n", 407 | [node(), Node, self()]) 408 | end, 409 | Blocked. 410 | 411 | is_blocked__internal1(Node) when Node =/= undefined andalso Node =/= node() -> 412 | is_blocked(Node). 413 | 414 | send_info(#proxy_socket{ 415 | node = Node, 416 | initiated = Initiated}, 417 | Requester) -> 418 | Info = #{peer => Node, 419 | blocked => is_blocked__internal1(Node), 420 | initiated => Initiated}, 421 | Requester ! {info, self(), Info}, 422 | ok. 423 | 424 | system_continue(Parent, Debug, [ProxySocket]) -> 425 | dist_proc_loop(ProxySocket, Parent, Debug). 426 | 427 | -spec system_terminate(term(), pid(), [sys:dbg_opt()], #proxy_socket{}) -> 428 | no_return(). 429 | 430 | system_terminate(Reason, _Parent, _Debug, _ProxySocket) -> 431 | %% FIXME: This process is part of a supervision tree. Don't we have 432 | %% a problem if this process is taken down as part of a supervision 433 | %% tree termination? Because, depending on the order of process 434 | %% shutdown, other processes/applications may loose their connection 435 | %% to a remote node. 436 | %% 437 | %% Another chicken and egg issue is: during shutdown, another 438 | %% process might reopen the connection to a remote node, populating 439 | %% this supervision tree again. 440 | %% 441 | %% Ideas to explore: 442 | %% * Swith back to no compression on shutdown, possibly using 443 | %% `inet_tcp_dist` if possible; what to do on restart? 444 | %% * Store this process PID in a persistent_term and detach it 445 | %% from the supervision tree and store. It can reattached on 446 | %% restart using that persistent_term. 447 | exit(Reason). 448 | 449 | system_get_state(ProxySocket) -> 450 | {ok, ProxySocket}. 451 | 452 | system_replace_state(StateFun, ProxySocket) -> 453 | NewProxySocket = StateFun(ProxySocket), 454 | {ok, ProxySocket, NewProxySocket}. 455 | 456 | %% we may not always want the nodelay behaviour 457 | %% for performance reasons 458 | 459 | nodelay() -> 460 | case application:get_env(kernel, dist_nodelay) of 461 | undefined -> 462 | {nodelay, true}; 463 | {ok, true} -> 464 | {nodelay, true}; 465 | {ok, false} -> 466 | {nodelay, false}; 467 | _ -> 468 | {nodelay, true} 469 | end. 470 | 471 | 472 | %% ------------------------------------------------------------ 473 | %% Get remote information about a Socket. 474 | %% ------------------------------------------------------------ 475 | get_remote_id(Driver, Socket, Node) -> 476 | case inet:peername(Socket) of 477 | {ok,Address} -> 478 | case split_node(atom_to_list(Node), $@, []) of 479 | [_,Host] -> 480 | #net_address{address=Address,host=Host, 481 | protocol=tcp_proxy,family=Driver:family()}; 482 | _ -> 483 | %% No '@' or more than one '@' in node name. 484 | ?shutdown(no_node) 485 | end; 486 | {error, _Reason} -> 487 | ?shutdown(no_node) 488 | end. 489 | 490 | %% ------------------------------------------------------------ 491 | %% Setup a new connection to another Erlang node. 492 | %% Performs the handshake with the other side. 493 | %% ------------------------------------------------------------ 494 | 495 | setup(Node, Type, MyNode, LongOrShortNames,SetupTime) -> 496 | gen_setup(inet_tcp, Node, Type, MyNode, LongOrShortNames, SetupTime). 497 | 498 | gen_setup(Driver, Node, Type, MyNode, LongOrShortNames, SetupTime) -> 499 | spawn_opt(?MODULE, do_setup, 500 | [Driver, self(), Node, Type, MyNode, LongOrShortNames, SetupTime], 501 | [link, {priority, max}]). 502 | 503 | do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> 504 | ?trace("~p~n",[{inet_tcp_dist,self(),setup,Node}]), 505 | Blocked = is_blocked__internal(Node), 506 | case Blocked of 507 | false -> ok; 508 | true -> ?shutdown(Node) 509 | end, 510 | [Name, Address] = splitnode(Driver, Node, LongOrShortNames), 511 | AddressFamily = Driver:family(), 512 | ErlEpmd = net_kernel:epmd_module(), 513 | {ARMod, ARFun} = get_address_resolver(ErlEpmd), 514 | Timer = dist_util:start_timer(SetupTime), 515 | case ARMod:ARFun(Name, Address, AddressFamily) of 516 | {ok, Ip, TcpPort, Version} -> 517 | ?trace("address_please(~p) -> version ~p~n", 518 | [Node,Version]), 519 | do_setup_connect(Driver, Kernel, Node, Address, AddressFamily, 520 | Ip, TcpPort, Version, Type, MyNode, Timer); 521 | {ok, Ip} -> 522 | case ErlEpmd:port_please(Name, Ip) of 523 | {port, TcpPort, Version} -> 524 | ?trace("port_please(~p) -> version ~p~n", 525 | [Node,Version]), 526 | do_setup_connect(Driver, Kernel, Node, Address, AddressFamily, 527 | Ip, TcpPort, Version, Type, MyNode, Timer); 528 | _ -> 529 | ?trace("port_please (~p) " 530 | "failed.~n", [Node]), 531 | ?shutdown(Node) 532 | end; 533 | _Other -> 534 | ?trace("inet_getaddr(~p) " 535 | "failed (~p).~n", [Node,_Other]), 536 | ?shutdown(Node) 537 | end. 538 | 539 | %% 540 | %% Actual setup of connection 541 | %% 542 | do_setup_connect(Driver, Kernel, Node, Address, AddressFamily, 543 | Ip, TcpPort, Version, Type, MyNode, Timer) -> 544 | dist_util:reset_timer(Timer), 545 | case 546 | Driver:connect( 547 | Ip, TcpPort, 548 | connect_options([{active, false}, {packet, 2}])) 549 | of 550 | {ok, Socket} -> 551 | ProxySocket = #proxy_socket{pid = DistCtrl} = 552 | proxy_socket(Driver, Socket, Node, true), 553 | HSData = #hs_data{ 554 | kernel_pid = Kernel, 555 | other_node = Node, 556 | this_node = MyNode, 557 | socket = DistCtrl, 558 | timer = Timer, 559 | this_flags = 0, 560 | other_version = Version, 561 | f_send = fun(Ctrl, Data) when Ctrl =:= DistCtrl -> 562 | f_send(ProxySocket, Data) 563 | end, 564 | f_recv = fun(Ctrl, Len, Timeout) when Ctrl =:= DistCtrl -> 565 | f_recv(ProxySocket, Len, Timeout) 566 | end, 567 | f_setopts_pre_nodeup = 568 | fun(Ctrl) when Ctrl =:= DistCtrl -> 569 | inet:setopts 570 | (Socket, 571 | [{active, false}, 572 | {packet, 4}, 573 | nodelay()]) 574 | end, 575 | f_setopts_post_nodeup = 576 | fun(Ctrl) when Ctrl =:= DistCtrl -> 577 | inet:setopts 578 | (Socket, 579 | [{active, true}, 580 | % {deliver, port}, 581 | {packet, 4}, 582 | binary, 583 | nodelay()]) 584 | end, 585 | f_getll = fun(Ctrl) when Ctrl =:= DistCtrl -> 586 | {ok, DistCtrl} 587 | end, 588 | f_address = 589 | fun(Ctrl, _RemoteNode) when Ctrl =:= DistCtrl -> 590 | #net_address{ 591 | address = {Ip,TcpPort}, 592 | host = Address, 593 | protocol = tcp_proxy, 594 | family = AddressFamily} 595 | end, 596 | mf_tick = fun(Ctrl) when Ctrl =:= DistCtrl -> ?MODULE:tick(Ctrl, Driver, Socket) end, 597 | mf_getstat = fun(Ctrl) when Ctrl =:= DistCtrl -> ?MODULE:getstat(Socket) end, 598 | request_type = Type, 599 | mf_setopts = fun(Ctrl, Opts) when Ctrl =:= DistCtrl -> ?MODULE:setopts(Socket, Opts) end, 600 | mf_getopts = fun(Ctrl, Opts) when Ctrl =:= DistCtrl -> ?MODULE:getopts(Socket, Opts) end, 601 | f_handshake_complete = fun(Ctrl, RemoteNode, DHandle) when Ctrl =:= DistCtrl -> 602 | handshake_complete(Ctrl, RemoteNode, DHandle, ProxySocket) 603 | end 604 | }, 605 | dist_util:handshake_we_started(HSData); 606 | _ -> 607 | %% Other Node may have closed since 608 | %% discovery ! 609 | ?trace("other node (~p) " 610 | "closed since discovery (port_please).~n", 611 | [Node]), 612 | ?shutdown(Node) 613 | end. 614 | 615 | connect_options(Opts) -> 616 | case application:get_env(kernel, inet_dist_connect_options) of 617 | {ok,ConnectOpts} -> 618 | ConnectOpts ++ Opts; 619 | _ -> 620 | Opts 621 | end. 622 | 623 | %% 624 | %% Close a socket. 625 | %% 626 | close(Socket) -> 627 | inet_tcp:close(Socket). 628 | 629 | 630 | %% If Node is illegal terminate the connection setup!! 631 | splitnode(Driver, Node, LongOrShortNames) -> 632 | case split_node(atom_to_list(Node), $@, []) of 633 | [Name|Tail] when Tail =/= [] -> 634 | Host = lists:append(Tail), 635 | case split_node(Host, $., []) of 636 | [_] when LongOrShortNames =:= longnames -> 637 | case Driver:parse_address(Host) of 638 | {ok, _} -> 639 | [Name, Host]; 640 | _ -> 641 | error_msg("** System running to use " 642 | "fully qualified " 643 | "hostnames **~n" 644 | "** Hostname ~ts is illegal **~n", 645 | [Host]), 646 | ?shutdown(Node) 647 | end; 648 | L when length(L) > 1, LongOrShortNames =:= shortnames -> 649 | error_msg("** System NOT running to use fully qualified " 650 | "hostnames **~n" 651 | "** Hostname ~ts is illegal **~n", 652 | [Host]), 653 | ?shutdown(Node); 654 | _ -> 655 | [Name, Host] 656 | end; 657 | [_] -> 658 | error_msg("** Nodename ~p illegal, no '@' character **~n", 659 | [Node]), 660 | ?shutdown(Node); 661 | _ -> 662 | error_msg("** Nodename ~p illegal **~n", [Node]), 663 | ?shutdown(Node) 664 | end. 665 | 666 | split_node([Chr|T], Chr, Ack) -> [lists:reverse(Ack)|split_node(T, Chr, [])]; 667 | split_node([H|T], Chr, Ack) -> split_node(T, Chr, [H|Ack]); 668 | split_node([], _, Ack) -> [lists:reverse(Ack)]. 669 | 670 | %% ------------------------------------------------------------ 671 | %% Fetch local information about a Socket. 672 | %% ------------------------------------------------------------ 673 | get_tcp_address(Driver, Socket) -> 674 | {ok, Address} = inet:sockname(Socket), 675 | {ok, Host} = inet:gethostname(), 676 | #net_address { 677 | address = Address, 678 | host = Host, 679 | protocol = tcp_proxy, 680 | family = Driver:family() 681 | }. 682 | 683 | %% ------------------------------------------------------------ 684 | %% Determine if EPMD module supports address resolving. Default 685 | %% is to use inet:getaddr/2. 686 | %% ------------------------------------------------------------ 687 | get_address_resolver(EpmdModule) -> 688 | case erlang:function_exported(EpmdModule, address_please, 3) of 689 | true -> {EpmdModule, address_please}; 690 | _ -> {erl_epmd, address_please} 691 | end. 692 | 693 | %% ------------------------------------------------------------ 694 | %% Do only accept new connection attempts from nodes at our 695 | %% own LAN, if the check_ip environment parameter is true. 696 | %% ------------------------------------------------------------ 697 | check_ip(Driver, Socket) -> 698 | case application:get_env(check_ip) of 699 | {ok, true} -> 700 | case get_ifs(Socket) of 701 | {ok, IFs, IP} -> 702 | check_ip(Driver, IFs, IP); 703 | _ -> 704 | ?shutdown(no_node) 705 | end; 706 | _ -> 707 | true 708 | end. 709 | 710 | get_ifs(Socket) -> 711 | case inet:peername(Socket) of 712 | {ok, {IP, _}} -> 713 | case inet:getif(Socket) of 714 | {ok, IFs} -> {ok, IFs, IP}; 715 | Error -> Error 716 | end; 717 | Error -> 718 | Error 719 | end. 720 | 721 | check_ip(Driver, [{OwnIP, _, Netmask}|IFs], PeerIP) -> 722 | case {Driver:mask(Netmask, PeerIP), Driver:mask(Netmask, OwnIP)} of 723 | {M, M} -> true; 724 | _ -> check_ip(Driver, IFs, PeerIP) 725 | end; 726 | check_ip(_Driver, [], PeerIP) -> 727 | {false, PeerIP}. 728 | 729 | is_node_name(Node) when is_atom(Node) -> 730 | case split_node(atom_to_list(Node), $@, []) of 731 | [_, _Host] -> true; 732 | _ -> false 733 | end; 734 | is_node_name(_Node) -> 735 | false. 736 | 737 | tick(DistCtrl, Driver, Socket) -> 738 | DictKey = {?MODULE, peer}, 739 | Peer = case get(DictKey) of 740 | undefined -> 741 | DistCtrl ! {info, self()}, 742 | Info = receive {info, DistCtrl, I} -> I end, 743 | case Info of 744 | #{peer := P} -> 745 | put(DictKey, P), 746 | P; 747 | _ -> 748 | undefined 749 | end; 750 | P -> 751 | P 752 | end, 753 | Blocked = case Peer of 754 | undefined -> false; 755 | _ -> is_blocked__internal(Peer) 756 | end, 757 | case Blocked of 758 | false -> 759 | case Driver:send(Socket, [], [force]) of 760 | {error, closed} -> 761 | self() ! {tcp_closed, Socket}, 762 | {error, closed}; 763 | R -> 764 | R 765 | end; 766 | true -> 767 | ok 768 | end. 769 | 770 | getstat(Socket) -> 771 | case inet:getstat(Socket, [recv_cnt, send_cnt, send_pend]) of 772 | {ok, Stat} -> 773 | split_stat(Stat,0,0,0); 774 | Error -> 775 | Error 776 | end. 777 | 778 | split_stat([{recv_cnt, R}|Stat], _, W, P) -> 779 | split_stat(Stat, R, W, P); 780 | split_stat([{send_cnt, W}|Stat], R, _, P) -> 781 | split_stat(Stat, R, W, P); 782 | split_stat([{send_pend, P}|Stat], R, W, _) -> 783 | split_stat(Stat, R, W, P); 784 | split_stat([], R, W, P) -> 785 | {ok, R, W, P}. 786 | 787 | 788 | setopts(S, Opts) -> 789 | case [Opt || {K,_}=Opt <- Opts, 790 | K =:= active orelse K =:= deliver orelse K =:= packet] of 791 | [] -> inet:setopts(S,Opts); 792 | Opts1 -> {error, {badopts,Opts1}} 793 | end. 794 | 795 | getopts(S, Opts) -> 796 | inet:getopts(S, Opts). 797 | 798 | f_send(#proxy_socket{ 799 | driver = Driver, 800 | socket = S}, 801 | Data) -> 802 | Driver:send(S, Data). 803 | 804 | f_recv(#proxy_socket{ 805 | driver = Driver, 806 | socket = S}, 807 | Length, 808 | Timeout) -> 809 | Driver:recv(S, Length, Timeout). 810 | 811 | %% ------------------------------------------------------------ 812 | %% Public API to manage allowed/blocked peers. 813 | %% ------------------------------------------------------------ 814 | 815 | allow(Peer) -> inet_tcp_proxy_dist_controller:allow(Peer). 816 | block(Peer) -> inet_tcp_proxy_dist_controller:block(Peer). 817 | is_blocked(Peer) -> inet_tcp_proxy_dist_controller:is_blocked(Peer). 818 | info() -> inet_tcp_proxy_dist_controller:info(). 819 | 820 | notify_new_state(DistPid, Blocked) -> 821 | DistPid ! {notify_new_state, Blocked}, 822 | ok. 823 | 824 | dbg(ProxySocket) -> 825 | dbg:tracer(), 826 | dbg:p(self(), [c, m]), 827 | dbg:tpl(?MODULE, cx), 828 | dbg:tpl(erlang, dist_ctrl_get_data, cx), 829 | dbg:tpl(erlang, dist_ctrl_put_data, cx), 830 | dbg:tpl(erlang, dist_ctrl_get_data_notification, cx), 831 | dbg:tpl(ProxySocket#proxy_socket.driver, send, cx). 832 | -------------------------------------------------------------------------------- /src/inet_tcp_proxy_dist_app.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2018-2021 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | -module(inet_tcp_proxy_dist_app). 8 | -behaviour(application). 9 | 10 | -export([start/2]). 11 | -export([stop/1]). 12 | 13 | start(_Type, _Args) -> 14 | inet_tcp_proxy_dist_sup:start_link(). 15 | 16 | stop(_State) -> 17 | ok. 18 | -------------------------------------------------------------------------------- /src/inet_tcp_proxy_dist_conn_sup.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2018-2021 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | -module(inet_tcp_proxy_dist_conn_sup). 8 | -behaviour(supervisor). 9 | 10 | -export([start_link/0]). 11 | -export([init/1]). 12 | 13 | start_link() -> 14 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 15 | 16 | init([]) -> 17 | Procs = [], 18 | {ok, {{one_for_one, 1, 5}, Procs}}. 19 | -------------------------------------------------------------------------------- /src/inet_tcp_proxy_dist_controller.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2018-2021 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | -module(inet_tcp_proxy_dist_controller). 8 | 9 | -export([allow/1, 10 | block/1, 11 | is_blocked/1, 12 | is_dist_proto_mod_configured/0, 13 | is_inet_tcp_dist_proxy_conn_sup_ready/0, 14 | is_proxy_dist_fully_configured/0, 15 | connection_states/0, 16 | info/0]). 17 | 18 | -define(PT_BLOCKED_PAIR(NodeA, NodeB), {?MODULE, blocked_pair, NodeA, NodeB}). 19 | 20 | %% ------------------------------------------------------------------- 21 | %% Public API. 22 | %% ------------------------------------------------------------------- 23 | 24 | allow(Peer) when Peer =:= node() -> 25 | ok; 26 | allow(Peer) when Peer =/= undefined -> 27 | case is_dist_proto_mod_configured() of 28 | true -> 29 | logger:debug( 30 | ?MODULE_STRING ": Allowing connection from ~s to ~s", 31 | [node(), Peer]), 32 | Erased = persistent_term:erase(pt_blocked_pair(node(), Peer)), 33 | case Erased of 34 | true -> notify_new_state(Peer, allowed); 35 | false -> ok 36 | end; 37 | false -> 38 | ok 39 | end, 40 | ok. 41 | 42 | block(Peer) when Peer =:= node() -> 43 | ok; 44 | block(Peer) when Peer =/= undefined -> 45 | case is_dist_proto_mod_configured() of 46 | true -> 47 | logger:debug( 48 | ?MODULE_STRING ": BLOCKING connection from ~s to ~s", 49 | [node(), Peer]), 50 | persistent_term:put(pt_blocked_pair(node(), Peer), true), 51 | notify_new_state(Peer, blocked); 52 | false -> 53 | ok 54 | end, 55 | ok. 56 | 57 | is_blocked(Peer) when Peer =:= node() -> 58 | false; 59 | is_blocked(Peer) when Peer =/= undefined -> 60 | persistent_term:get(pt_blocked_pair(node(), Peer), false). 61 | 62 | pt_blocked_pair(NodeA, NodeB) when NodeB < NodeA -> 63 | ?PT_BLOCKED_PAIR(NodeB, NodeA); 64 | pt_blocked_pair(NodeA, NodeB) -> 65 | ?PT_BLOCKED_PAIR(NodeA, NodeB). 66 | 67 | notify_new_state(Node, State) -> 68 | case get_dist_proc_for_node(Node) of 69 | undefined -> 70 | ok; 71 | DistPid -> 72 | logger:debug( 73 | ?MODULE_STRING ": Notify dist process ~p about new state ~p", 74 | [DistPid, State]), 75 | inet_tcp_proxy_dist:notify_new_state(DistPid, State) 76 | end. 77 | 78 | get_proto_dist_module() -> 79 | case init:get_argument(proto_dist) of 80 | {ok, [[ModStr]]} -> ModStr; 81 | _ -> "inet_tcp" 82 | end. 83 | 84 | is_dist_proto_mod_configured() -> 85 | "inet_tcp_proxy" =:= get_proto_dist_module(). 86 | 87 | is_inet_tcp_dist_proxy_conn_sup_ready() -> 88 | is_pid(erlang:whereis(inet_tcp_proxy_dist_conn_sup)). 89 | 90 | is_proxy_dist_fully_configured() -> 91 | is_dist_proto_mod_configured() 92 | andalso 93 | is_inet_tcp_dist_proxy_conn_sup_ready(). 94 | 95 | connection_states() -> 96 | DistProcs = get_dist_procs(), 97 | [get_dist_proc_info(Pid) || Pid <- DistProcs]. 98 | 99 | info() -> 100 | Ready = {is_dist_proto_mod_configured(), 101 | is_inet_tcp_dist_proxy_conn_sup_ready()}, 102 | case Ready of 103 | {true, true} -> 104 | States = connection_states(), 105 | UseColors = case os:getenv("TERM") of 106 | false -> false; 107 | "" -> false; 108 | _ -> true 109 | end, 110 | display_info(States, UseColors); 111 | {false, _} -> 112 | io:format( 113 | "Dist proxy unavailable: dist proto module set to `~s` " 114 | "(instead of `inet_tcp_proxy`)~n", 115 | [get_proto_dist_module()]); 116 | {_, false} -> 117 | io:format( 118 | "Dist proxy unavailable: `inet_tcp_proxy_dist` " 119 | "application not started~n") 120 | end, 121 | ok. 122 | 123 | display_info(States, UseColors) -> 124 | {TitleColor, 125 | BlockedColor, 126 | ColorReset, 127 | LineStart, 128 | LineChar, 129 | LineReset} = case UseColors of 130 | true -> 131 | {"\033[1m", 132 | "\033[31m", 133 | "\033[0m", 134 | "\033(0", 135 | "q", 136 | "\033(B"}; 137 | false -> 138 | {"", 139 | "", 140 | "", 141 | "", 142 | "-", 143 | ""} 144 | end, 145 | io:format( 146 | "~n" 147 | "~sErlang distribution connections between ~s and peer nodes:~s~n" 148 | "[*] node which initiated the connection~n", 149 | [TitleColor, node(), ColorReset]), 150 | This = node(), 151 | %% Displays something like: 152 | %% *rabbit@rmq1* <---[ none ]---> rabbit@rmq0 153 | %% rabbit@rmq1 <---[ zstd ]---> *rabbit@rmq2* 154 | %% rabbit@rmq1 <---[ lz4 ]---> *rabbit@rmq3* 155 | lists:foreach( 156 | fun 157 | (#{peer := Remote, blocked := false, initiated := true}) -> 158 | io:format( 159 | " *~s* " 160 | "~s<~s~9.." ++ LineChar ++ "s~s>~s" 161 | " ~s~n", 162 | [This, "", LineStart, "", LineReset, 163 | ColorReset, Remote]); 164 | (#{peer := Remote, blocked := true, initiated := true}) -> 165 | io:format( 166 | " *~s* " 167 | "~s<~s~3.." ++ LineChar ++ "s~s X ~s~3.." ++ LineChar ++ "s~s>~s" 168 | " ~s~n", 169 | [This, BlockedColor, LineStart, "", LineReset, 170 | LineStart, "", LineReset, 171 | ColorReset, Remote]); 172 | (#{peer := Remote, blocked := false, initiated := false}) -> 173 | io:format( 174 | " ~s " 175 | "~s<~s~9.." ++ LineChar ++ "s~s>~s" 176 | " *~s*~n", 177 | [This, "", LineStart, "", LineReset, 178 | ColorReset, Remote]); 179 | (#{peer := Remote, blocked := true, initiated := false}) -> 180 | io:format( 181 | " ~s " 182 | "~s<~s~3.." ++ LineChar ++ "s~s X ~s~3.." ++ LineChar ++ "s~s>~s" 183 | " *~s*~n", 184 | [This, BlockedColor, LineStart, "", LineReset, 185 | LineStart, "", LineReset, 186 | ColorReset, Remote]) 187 | end, States), 188 | 189 | io:format("~n", []). 190 | 191 | %% ------------------------------------------------------------------- 192 | %% Internal helpers. 193 | %% ------------------------------------------------------------------- 194 | 195 | conn_sup_children() -> 196 | supervisor:which_children(inet_tcp_proxy_dist_conn_sup). 197 | 198 | get_dist_procs() -> 199 | [Pid || {_, Pid, _, _} <- conn_sup_children()]. 200 | 201 | get_dist_proc_for_node(Node) -> 202 | get_dist_proc_for_node(conn_sup_children(), Node). 203 | 204 | get_dist_proc_for_node([{{Node, _, _}, Child, _Type, _Modules} | _], 205 | Node) -> 206 | Child; 207 | get_dist_proc_for_node([{{undefined, _, _}, Child, _Type, _Modules} | Rest], 208 | Node) -> 209 | case get_dist_proc_info(Child, infinity) of 210 | #{peer := Node} -> Child; 211 | _ -> get_dist_proc_for_node(Rest, Node) 212 | end; 213 | get_dist_proc_for_node([{_Id, _Child, _Type, _Modules} | Rest], 214 | Node) -> 215 | get_dist_proc_for_node(Rest, Node); 216 | get_dist_proc_for_node([], _) -> 217 | undefined. 218 | 219 | get_dist_proc_info(Pid) -> 220 | get_dist_proc_info(Pid, 5000). 221 | 222 | get_dist_proc_info(Pid, Timeout) -> 223 | Pid ! {info, self()}, 224 | receive 225 | {info, Pid, Info} -> Info 226 | after Timeout -> undefined 227 | end. 228 | -------------------------------------------------------------------------------- /src/inet_tcp_proxy_dist_sup.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2018-2021 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | -module(inet_tcp_proxy_dist_sup). 8 | -behaviour(supervisor). 9 | 10 | -export([start_link/0]). 11 | -export([init/1]). 12 | 13 | start_link() -> 14 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 15 | 16 | init([]) -> 17 | Procs = [#{id => inet_tcp_proxy_dist_conn_sup, 18 | start => {inet_tcp_proxy_dist_conn_sup, start_link, []}, 19 | type => supervisor} 20 | ], 21 | {ok, {{one_for_one, 1, 5}, Procs}}. 22 | -------------------------------------------------------------------------------- /test/proxy_dist_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(proxy_dist_SUITE). 2 | 3 | -export([suite/0, 4 | all/0, 5 | groups/0, 6 | init_per_suite/1, 7 | end_per_suite/1, 8 | init_per_group/2, 9 | end_per_group/2, 10 | init_per_testcase/2, 11 | end_per_testcase/2, 12 | 13 | two_nodes_with_default_dist/0, 14 | two_nodes_with_default_dist/1, 15 | two_nodes_with_proxy_dist/0, 16 | two_nodes_with_proxy_dist/1, 17 | proxy_node_connects_to_default_node/0, 18 | proxy_node_connects_to_default_node/1, 19 | default_node_connects_to_proxy_node/0, 20 | default_node_connects_to_proxy_node/1, 21 | app_is_started_but_proto_dist_is_unconfigured/0, 22 | app_is_started_but_proto_dist_is_unconfigured/1, 23 | proto_dist_is_configured_but_app_is_stopped/0, 24 | proto_dist_is_configured_but_app_is_stopped/1, 25 | send_large_message/0, 26 | send_large_message/1, 27 | three_nodes_with_proxy_on_two_only/0, 28 | three_nodes_with_proxy_on_two_only/1, 29 | asymmetrical_block_works/0, 30 | asymmetrical_block_works/1, 31 | 32 | test_basic_communication/2, 33 | asymmetrical_block_works/2 34 | ]). 35 | 36 | -import(proxy_dist_test_lib, 37 | [send_to_tstcntrl/1, 38 | apply_on_test_node/2, 39 | stop_test_node/1]). 40 | 41 | -include_lib("common_test/include/ct.hrl"). 42 | -include_lib("eunit/include/eunit.hrl"). 43 | -include("proxy_dist_test_lib.hrl"). 44 | 45 | %% ------------------------------------------------------------------- 46 | %% common_test callbacks. 47 | %% ------------------------------------------------------------------- 48 | 49 | suite() -> 50 | [{timetrap, {minutes, 10}}]. 51 | 52 | all() -> 53 | [{group, non_parallel_tests}]. 54 | 55 | groups() -> 56 | [ 57 | {non_parallel_tests, [], 58 | [two_nodes_with_default_dist, 59 | two_nodes_with_proxy_dist, 60 | proxy_node_connects_to_default_node, 61 | default_node_connects_to_proxy_node, 62 | app_is_started_but_proto_dist_is_unconfigured, 63 | proto_dist_is_configured_but_app_is_stopped, 64 | send_large_message, 65 | three_nodes_with_proxy_on_two_only, 66 | asymmetrical_block_works]} 67 | ]. 68 | 69 | init_per_suite(Config) -> 70 | Config. 71 | 72 | end_per_suite(_Config) -> 73 | ok. 74 | 75 | init_per_group(_Group, Config) -> 76 | Config. 77 | 78 | end_per_group(_Group, _Config) -> 79 | ok. 80 | 81 | init_per_testcase(Testcase, Config) -> 82 | NodeConfigs = node_configs_for_testcase(Testcase), 83 | Config1 = case Testcase of 84 | send_large_message -> 85 | [{message_size, 20 * 1024 * 1024} | Config]; 86 | _ -> 87 | Config 88 | end, 89 | [{testcase, Testcase}, {node_configs, NodeConfigs} | Config1]. 90 | 91 | node_configs_for_testcase(two_nodes_with_default_dist) -> 92 | [#{proto_dist => default, 93 | expect_proxy_enabled => false}, 94 | #{proto_dist => default, 95 | expect_proxy_enabled => false}]; 96 | node_configs_for_testcase(proxy_node_connects_to_default_node) -> 97 | [#{proto_dist => "inet_tcp_proxy", 98 | start_apps => [inet_tcp_proxy_dist], 99 | expect_proxy_enabled => true}, 100 | #{proto_dist => default}]; 101 | node_configs_for_testcase(default_node_connects_to_proxy_node) -> 102 | [#{proto_dist => default, 103 | expect_proxy_enabled => false}, 104 | #{proto_dist => "inet_tcp_proxy", 105 | start_apps => [inet_tcp_proxy_dist], 106 | expect_proxy_enabled => true}]; 107 | node_configs_for_testcase(app_is_started_but_proto_dist_is_unconfigured) -> 108 | [#{proto_dist => default, 109 | start_apps => [inet_tcp_proxy_dist], 110 | expect_proxy_enabled => false}, 111 | #{proto_dist => "inet_tcp_proxy", 112 | start_apps => [inet_tcp_proxy_dist], 113 | expect_proxy_enabled => true}]; 114 | node_configs_for_testcase(proto_dist_is_configured_but_app_is_stopped) -> 115 | [#{proto_dist => "inet_tcp_proxy", 116 | start_apps => [], 117 | expect_proxy_enabled => false}, 118 | #{proto_dist => "inet_tcp_proxy", 119 | start_apps => [inet_tcp_proxy_dist], 120 | expect_proxy_enabled => true}]; 121 | node_configs_for_testcase(three_nodes_with_proxy_on_two_only) -> 122 | [#{proto_dist => "inet_tcp_proxy", 123 | start_apps => [inet_tcp_proxy_dist], 124 | expect_proxy_enabled => true}, 125 | #{proto_dist => "inet_tcp_proxy", 126 | start_apps => [inet_tcp_proxy_dist], 127 | expect_proxy_enabled => true}, 128 | #{proto_dist => default, 129 | expect_proxy_enabled => false}]; 130 | node_configs_for_testcase(_) -> 131 | [#{proto_dist => "inet_tcp_proxy", 132 | start_apps => [inet_tcp_proxy_dist], 133 | expect_proxy_enabled => true}, 134 | #{proto_dist => "inet_tcp_proxy", 135 | start_apps => [inet_tcp_proxy_dist], 136 | expect_proxy_enabled => true}]. 137 | 138 | end_per_testcase(_Testcase, _Config) -> 139 | ok. 140 | 141 | %% ------------------------------------------------------------------- 142 | %% Testcases. 143 | %% ------------------------------------------------------------------- 144 | 145 | two_nodes_with_default_dist() -> 146 | [{doc, 147 | "Verify that default dist works before we test " 148 | "inet_tcp_proxy_dist"}]. 149 | two_nodes_with_default_dist(Config) -> 150 | gen_dist_test(test_basic_communication, Config). 151 | 152 | two_nodes_with_proxy_dist() -> 153 | [{doc, 154 | "Verify that default dist works before we test " 155 | "inet_tcp_proxy_dist"}]. 156 | two_nodes_with_proxy_dist(Config) -> 157 | gen_dist_test(test_basic_communication, Config). 158 | 159 | proxy_node_connects_to_default_node() -> 160 | [{doc, 161 | "Verify a proxy-dist node can connect to a default-dist node"}]. 162 | proxy_node_connects_to_default_node(Config) -> 163 | gen_dist_test(test_basic_communication, Config). 164 | 165 | default_node_connects_to_proxy_node() -> 166 | [{doc, 167 | "Verify a default-dist node can connect to a proxy-dist node"}]. 168 | default_node_connects_to_proxy_node(Config) -> 169 | gen_dist_test(test_basic_communication, Config), 170 | Config1 = rotate_nodes_in_config(Config), 171 | gen_dist_test(test_basic_communication, Config1). 172 | 173 | app_is_started_but_proto_dist_is_unconfigured() -> 174 | [{doc, 175 | "Verify that a node without the proto dist behaves as a default node"}]. 176 | app_is_started_but_proto_dist_is_unconfigured(Config) -> 177 | gen_dist_test(test_basic_communication, Config), 178 | Config1 = rotate_nodes_in_config(Config), 179 | gen_dist_test(test_basic_communication, Config1). 180 | 181 | proto_dist_is_configured_but_app_is_stopped() -> 182 | [{doc, 183 | "Verify that a node without the proto dist behaves as a default node"}]. 184 | proto_dist_is_configured_but_app_is_stopped(Config) -> 185 | gen_dist_test(test_basic_communication, Config), 186 | Config1 = rotate_nodes_in_config(Config), 187 | gen_dist_test(test_basic_communication, Config1), 188 | Config2 = rotate_nodes_in_config(Config1), 189 | gen_dist_test(test_basic_communication, Config2). 190 | 191 | send_large_message() -> 192 | [{doc, 193 | "Verify that we can send larger amount of data"}]. 194 | send_large_message(Config) -> 195 | gen_dist_test(test_basic_communication, Config). 196 | 197 | three_nodes_with_proxy_on_two_only() -> 198 | [{doc, 199 | "Verify that a cluster of three nodes with inconsistent node " 200 | "configurations (i.e. a mix of default and proxy)"}]. 201 | three_nodes_with_proxy_on_two_only(Config) -> 202 | gen_dist_test(test_basic_communication, Config). 203 | 204 | test_basic_communication(Config, NHs) -> 205 | Nodes = lists:sort([NH#node_handle.nodename || NH <- NHs]), 206 | 207 | %% Start applications (e.g. inet_tcp_proxy_dist) & check 208 | %% proxy status if requested. 209 | [begin 210 | apply_on_test_node( 211 | NH, 212 | fun() -> 213 | case NodeConfig of 214 | #{start_apps := Apps} 215 | when Apps =/= [] -> 216 | [{ok, _} = application:ensure_all_started(App) 217 | || App <- Apps]; 218 | _ -> 219 | ok 220 | end, 221 | case NodeConfig of 222 | #{expect_proxy_enabled := State} -> 223 | ?assertEqual( 224 | State, 225 | inet_tcp_proxy_dist_neg: 226 | is_proxy_dist_fully_configured()); 227 | _ -> 228 | ok 229 | end 230 | end) 231 | end 232 | || #node_handle{priv = NodeConfig} = NH <- NHs], 233 | 234 | %% Block communication, then from each node, ping all other nodes to 235 | %% try establish the connection. 236 | block(NHs), 237 | [begin 238 | This = NH#node_handle.nodename, 239 | Others = Nodes -- [This], 240 | ct:pal( 241 | ?LOW_IMPORTANCE, 242 | "Ping other nodes from ~s --> ~p", 243 | [This, Others]), 244 | true = apply_on_test_node( 245 | NH, 246 | fun() -> 247 | lists:all( 248 | fun(Node) -> pang =:= net_adm:ping(Node) end, 249 | Others) 250 | end) 251 | end 252 | || #node_handle{priv = #{expect_proxy_enabled := true}} = NH <- NHs], 253 | 254 | %% From each node, verify that one node knows no other node. 255 | [begin 256 | This = NH#node_handle.nodename, 257 | ct:pal( 258 | ?LOW_IMPORTANCE, 259 | "Check nodes known to ~s", 260 | [This]), 261 | true = apply_on_test_node( 262 | NH, 263 | fun() -> 264 | [] =:= nodes() 265 | end) 266 | end 267 | || #node_handle{priv = #{expect_proxy_enabled := true}} = NH <- NHs], 268 | 269 | %% Allow communication, then from each node, ping all other nodes to 270 | %% establish the connection. 271 | allow(NHs), 272 | [begin 273 | This = NH#node_handle.nodename, 274 | Others = Nodes -- [This], 275 | ct:pal( 276 | ?LOW_IMPORTANCE, 277 | "Ping other nodes from ~s --> ~p", 278 | [This, Others]), 279 | true = apply_on_test_node( 280 | NH, 281 | fun() -> 282 | lists:all( 283 | fun(Node) -> pong =:= net_adm:ping(Node) end, 284 | Others) 285 | end) 286 | end 287 | || NH <- NHs], 288 | 289 | %% From each node, verify that one node knows about the others. 290 | [begin 291 | This = NH#node_handle.nodename, 292 | Others = Nodes -- [This], 293 | ct:pal( 294 | ?LOW_IMPORTANCE, 295 | "Check nodes known to ~s", 296 | [This]), 297 | true = apply_on_test_node( 298 | NH, 299 | fun() -> Others =:= lists:sort(nodes()) end) 300 | end 301 | || NH <- NHs], 302 | 303 | %% Block communication (after the connections were established), 304 | %% then from each node, try to send a message to all other nodes and 305 | %% verify it times out. 306 | block(NHs), 307 | Ref1 = make_ref(), 308 | Bytes = proplists:get_value(message_size, Config, 100000), 309 | [begin 310 | %% Spawn a process on all other nodes to wait for a message 311 | %% from this node. 312 | OtherNHs = NHs -- [NH], 313 | [spawn(fun() -> 314 | apply_on_test_node( 315 | OtherNH, 316 | fun() -> 317 | send_to_tstcntrl({Ref1, self()}), 318 | receive 319 | {From, Msg} -> From ! {self(), Msg} 320 | end 321 | end) 322 | end) 323 | || OtherNH <- OtherNHs], 324 | 325 | %% Get PIDs from other nodes and send them a message. 326 | This = NH#node_handle.nodename, 327 | Pids = [receive {Ref1, Pid} -> Pid end || _ <- OtherNHs], 328 | [begin 329 | ct:pal( 330 | ?LOW_IMPORTANCE, 331 | "Send message from ~s to ~s", 332 | [This, node(Pid)]), 333 | ok = apply_on_test_node( 334 | NH, 335 | fun() -> 336 | Msg = crypto:strong_rand_bytes(Bytes), 337 | Pid ! {self(), Msg}, 338 | receive 339 | {Pid, Msg} -> 340 | unexpected_success 341 | after 2000 -> 342 | ok 343 | end 344 | end) 345 | end 346 | || Pid <- Pids] 347 | end 348 | || #node_handle{priv = #{expect_proxy_enabled := true}} = NH <- NHs], 349 | 350 | %% Allow communication, then from each node, send a message to all 351 | %% other nodes. 352 | allow(NHs), 353 | Ref2 = make_ref(), 354 | Bytes = proplists:get_value(message_size, Config, 100000), 355 | [begin 356 | %% Spawn a process on all other nodes to wait for a message 357 | %% from this node. 358 | OtherNHs = NHs -- [NH], 359 | [spawn(fun() -> 360 | apply_on_test_node( 361 | OtherNH, 362 | fun() -> 363 | send_to_tstcntrl({Ref2, self()}), 364 | receive 365 | {From, Msg} -> From ! {self(), Msg} 366 | end 367 | end) 368 | end) 369 | || OtherNH <- OtherNHs], 370 | 371 | %% Get PIDs from other nodes and send them a message. 372 | This = NH#node_handle.nodename, 373 | Pids = [receive {Ref2, Pid} -> Pid end || _ <- OtherNHs], 374 | [begin 375 | ct:pal( 376 | ?LOW_IMPORTANCE, 377 | "Send message from ~s to ~s", 378 | [This, node(Pid)]), 379 | ok = apply_on_test_node( 380 | NH, 381 | fun() -> 382 | Msg = crypto:strong_rand_bytes(Bytes), 383 | Pid ! {self(), Msg}, 384 | receive {Pid, Msg} -> ok end 385 | end) 386 | end 387 | || Pid <- Pids] 388 | end 389 | || NH <- NHs]. 390 | 391 | asymmetrical_block_works() -> 392 | [{doc, 393 | "Verify that blocking one direction does not interfere with the " 394 | "communication in the opposite direction"}]. 395 | asymmetrical_block_works(Config) -> 396 | gen_dist_test(asymmetrical_block_works, Config). 397 | 398 | asymmetrical_block_works( 399 | _Config, 400 | [#node_handle{nodename = N1} = NH1, 401 | #node_handle{nodename = N2} = NH2]) -> 402 | PingN1 = fun() -> net_adm:ping(N1) end, 403 | PingN2 = fun() -> net_adm:ping(N2) end, 404 | 405 | %% Establishing the connection is not possible because it involves 406 | %% bidirectional communication. 407 | block(NH1, [N2]), 408 | ?assertEqual(pang, apply_on_test_node(NH1, PingN2)), 409 | ?assertEqual(pang, apply_on_test_node(NH2, PingN1)), 410 | 411 | %% Establishing the connection is now possible. 412 | allow(NH1, [N2]), 413 | ?assertEqual(pong, apply_on_test_node(NH2, PingN1)), 414 | ?assertEqual(pong, apply_on_test_node(NH1, PingN2)), 415 | 416 | Ref1 = make_ref(), 417 | Ref2 = make_ref(), 418 | ForwardMsg = fun(Ref) -> 419 | fun() -> 420 | send_to_tstcntrl({Ref, self()}), 421 | receive Msg1 -> send_to_tstcntrl(Msg1) end, 422 | receive Msg2 -> send_to_tstcntrl(Msg2) end 423 | end 424 | end, 425 | spawn(fun() -> apply_on_test_node(NH1, ForwardMsg(Ref1)) end), 426 | spawn(fun() -> apply_on_test_node(NH2, ForwardMsg(Ref2)) end), 427 | Pid1 = receive {Ref1, P1} -> P1 end, 428 | Pid2 = receive {Ref2, P2} -> P2 end, 429 | 430 | SendMsg = fun(Pid, Msg) -> 431 | fun() -> 432 | inet_tcp_proxy_dist_controller:info(), 433 | Pid ! Msg 434 | end 435 | end, 436 | Msg1 = {Ref1, take1}, 437 | Msg2 = {Ref2, take1}, 438 | apply_on_test_node(NH1, SendMsg(Pid2, Msg1)), 439 | apply_on_test_node(NH2, SendMsg(Pid1, Msg2)), 440 | 441 | receive Msg1 -> ok end, 442 | receive Msg2 -> ok end, 443 | 444 | block(NH1, [N2]), 445 | Msg3 = {Ref1, take2}, 446 | Msg4 = {Ref2, take2}, 447 | apply_on_test_node(NH1, SendMsg(Pid2, Msg3)), 448 | apply_on_test_node(NH2, SendMsg(Pid1, Msg4)), 449 | 450 | ?assertEqual( 451 | ok, 452 | receive Msg3 -> msg_received_unexpectedly after 5000 -> ok end), 453 | ?assertEqual( 454 | ok, 455 | receive Msg4 -> ok after 5000 -> msg_not_received_before_timeout end), 456 | 457 | ok. 458 | 459 | %% ------------------------------------------------------------------- 460 | %% Helpers. 461 | %% ------------------------------------------------------------------- 462 | 463 | gen_dist_test(Test, Config) -> 464 | NodeConfigs = proplists:get_value(node_configs, Config, []), 465 | 466 | NHs = [begin 467 | NH0 = start_test_node(Config, NodeConfig), 468 | NH0#node_handle{priv = NodeConfig} 469 | end 470 | || NodeConfig <- NodeConfigs], 471 | try 472 | ?MODULE:Test(Config, NHs) 473 | catch 474 | _:Reason:Stacktrace -> 475 | [stop_test_node(NH) || NH <- NHs], 476 | ct:fail({Reason, Stacktrace}) 477 | end, 478 | [stop_test_node(NH) || NH <- NHs], 479 | ok. 480 | 481 | start_test_node(Config, NodeConfig) -> 482 | Nodename = make_test_nodename(Config), 483 | DistProto = maps:get(proto_dist, NodeConfig, default), 484 | Args0 = "-kernel logger_level debug", 485 | Args1 = case DistProto of 486 | default -> Args0; 487 | _ -> Args0 ++ " -proto_dist " ++ DistProto 488 | end, 489 | proxy_dist_test_lib:start_test_node(Nodename, Args1). 490 | 491 | make_test_nodename(Config) -> 492 | N = erlang:unique_integer([positive]), 493 | Case = proplists:get_value(testcase, Config), 494 | atom_to_list(?MODULE) 495 | ++ "_" 496 | ++ atom_to_list(Case) 497 | ++ "_" 498 | ++ integer_to_list(N). 499 | 500 | rotate_nodes_in_config(Config) -> 501 | [Head | Tail] = proplists:get_value(node_configs, Config), 502 | NodeConfigs = Tail ++ [Head], 503 | lists:keystore(node_configs, 1, Config, {node_configs, NodeConfigs}). 504 | 505 | allow(NHs) -> maybe_allow(NHs, true). 506 | block(NHs) -> maybe_allow(NHs, false). 507 | 508 | maybe_allow(NHs, Allowed) -> 509 | Nodes = lists:sort([NH#node_handle.nodename || NH <- NHs]), 510 | 511 | [begin 512 | This = NH#node_handle.nodename, 513 | Others = Nodes -- [This], 514 | case Allowed of 515 | true -> allow(NH, Others); 516 | false -> block(NH, Others) 517 | end 518 | end 519 | || NH <- NHs]. 520 | 521 | allow(#node_handle{nodename = This} = NH, Peers) -> 522 | ct:pal( 523 | ?LOW_IMPORTANCE, 524 | "Allowing communication: ~s --> ~p", 525 | [This, Peers]), 526 | apply_on_test_node( 527 | NH, 528 | fun() -> 529 | lists:foreach( 530 | fun(Node) -> inet_tcp_proxy_dist:allow(Node) end, 531 | Peers) 532 | end). 533 | 534 | block(#node_handle{nodename = This} = NH, Peers) -> 535 | ct:pal( 536 | ?LOW_IMPORTANCE, 537 | "BLOCKING communication: ~s --> ~p", 538 | [This, Peers]), 539 | apply_on_test_node( 540 | NH, 541 | fun() -> 542 | lists:foreach( 543 | fun(Node) -> 544 | inet_tcp_proxy_dist:block(Node) 545 | end, 546 | Peers) 547 | end). 548 | -------------------------------------------------------------------------------- /test/proxy_dist_test_lib.erl: -------------------------------------------------------------------------------- 1 | %% Copied from Erlang/OTP master branch 2 | %% commit: 7ed659fd1144b455883eef18dab38cddc904a065 3 | 4 | %% 5 | %% %CopyrightBegin% 6 | %% 7 | %% Copyright Ericsson AB 2017. All Rights Reserved. 8 | %% 9 | %% Licensed under the Apache License, Version 2.0 (the "License"); 10 | %% you may not use this file except in compliance with the License. 11 | %% You may obtain a copy of the License at 12 | %% 13 | %% http://www.apache.org/licenses/LICENSE-2.0 14 | %% 15 | %% Unless required by applicable law or agreed to in writing, software 16 | %% distributed under the License is distributed on an "AS IS" BASIS, 17 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | %% See the License for the specific language governing permissions and 19 | %% limitations under the License. 20 | %% 21 | %% %CopyrightEnd% 22 | %% 23 | 24 | -module(proxy_dist_test_lib). 25 | 26 | -include_lib("common_test/include/ct.hrl"). 27 | -include_lib("public_key/include/public_key.hrl"). 28 | -include("proxy_dist_test_lib.hrl"). 29 | 30 | -export([tstsrvr_format/2, send_to_tstcntrl/1]). 31 | -export([apply_on_test_node/4, apply_on_test_node/2]). 32 | -export([stop_test_node/1, start_test_node/2]). 33 | %% 34 | -export([cnct2tstsrvr/1]). 35 | 36 | -define(AWAIT_SSL_NODE_UP_TIMEOUT, 30000). 37 | 38 | 39 | 40 | %% test_node side api 41 | %% 42 | 43 | tstsrvr_format(Fmt, ArgList) -> 44 | send_to_tstsrvr({format, Fmt, ArgList}). 45 | 46 | send_to_tstcntrl(Message) -> 47 | send_to_tstsrvr({message, Message}). 48 | 49 | 50 | %% 51 | %% test_server side api 52 | %% 53 | 54 | apply_on_test_node( 55 | #node_handle{connection_handler = Hndlr} = Node, 56 | M, F, A) when is_atom(M), is_atom(F), is_list(A) -> 57 | Ref = erlang:monitor(process, Hndlr), 58 | apply_on_test_node(Node, Ref, {apply, self(), Ref, M, F, A}). 59 | 60 | apply_on_test_node( 61 | #node_handle{connection_handler = Hndlr} = Node, 62 | Fun) when is_function(Fun, 0) -> 63 | Ref = erlang:monitor(process, Hndlr), 64 | apply_on_test_node(Node, Ref, {apply, self(), Ref, Fun}). 65 | 66 | apply_on_test_node(Node, Ref, Msg) -> 67 | send_to_test_node(Node, Msg), 68 | receive 69 | {'DOWN', Ref, process, Hndlr, Reason} -> 70 | exit({handler_died, Hndlr, Reason}); 71 | {Ref, Result} -> 72 | Result 73 | end. 74 | 75 | stop_test_node(#node_handle{connection_handler = Handler, 76 | socket = Socket, 77 | name = Name}) -> 78 | ct:pal("Trying to stop test node ~s.~n", [Name]), 79 | Mon = erlang:monitor(process, Handler), 80 | unlink(Handler), 81 | case gen_tcp:send(Socket, term_to_binary(stop)) of 82 | ok -> 83 | receive 84 | {'DOWN', Mon, process, Handler, Reason} -> 85 | case Reason of 86 | normal -> 87 | ok; 88 | _ -> 89 | ct:pal( 90 | "stop_test_node/1 ~s Down ~p ~n", 91 | [Name,Reason]) 92 | end 93 | end; 94 | Error -> 95 | erlang:demonitor(Mon, [flush]), 96 | ct:pal("stop_test_node/1 ~s Warning ~p ~n", [Name,Error]) 97 | end. 98 | 99 | start_test_node(Name, Args) -> 100 | {ok, LSock} = gen_tcp:listen(0, 101 | [binary, {packet, 4}, {active, false}]), 102 | {ok, ListenPort} = inet:port(LSock), 103 | CmdLine = mk_node_cmdline(ListenPort, Name, Args), 104 | ct:log("Attempting to start test node ~ts: ~ts~n", [Name, CmdLine]), 105 | case open_port({spawn, CmdLine}, []) of 106 | Port when is_port(Port) -> 107 | unlink(Port), 108 | erlang:port_close(Port), 109 | case await_test_node_up(Name, LSock) of 110 | #node_handle{} = NodeHandle -> 111 | ct:log("Test node ~s started.~n", [Name]), 112 | NodeName = list_to_atom(Name ++ "@" ++ host_name()), 113 | NodeHandle#node_handle{nodename = NodeName}; 114 | Error -> 115 | exit({failed_to_start_node, Name, Error}) 116 | end; 117 | Error -> 118 | exit({failed_to_start_node, Name, Error}) 119 | end. 120 | 121 | host_name() -> 122 | [_, Host] = string:split(atom_to_list(node()), "@"), 123 | %% [$@ | Host] = lists:dropwhile(fun ($@) -> false; (_) -> true end, 124 | %% atom_to_list(node())), 125 | Host. 126 | 127 | mk_node_cmdline(ListenPort, Name, Args) -> 128 | Static = "-detached -noinput", 129 | ThisTestPa = filename:dirname(code:which(?MODULE)), 130 | ThisProjectPa = filename:join([ThisTestPa, "..", "ebin"]), 131 | DepsPa = filelib:wildcard( 132 | filename:join( 133 | [ThisProjectPa, "..", "deps", "*", "ebin"])), 134 | Pa = string:join([ThisTestPa, ThisProjectPa | DepsPa], " "), 135 | Prog = case catch init:get_argument(progname) of 136 | {ok,[[P]]} -> P; 137 | _ -> exit(no_progname_argument_found) 138 | end, 139 | NameSw = case net_kernel:longnames() of 140 | false -> "-sname "; 141 | _ -> "-name " 142 | end, 143 | {ok, Pwd} = file:get_cwd(), 144 | "\"" ++ Prog ++ "\" " 145 | ++ Static ++ " " 146 | ++ NameSw ++ " " ++ Name ++ " " 147 | ++ "-pa " ++ Pa ++ " " 148 | % ++ "-run application start crypto -run application start public_key " 149 | ++ "-eval 'net_kernel:verbose(1)' " 150 | ++ "-run " ++ atom_to_list(?MODULE) ++ " cnct2tstsrvr " 151 | ++ host_name() ++ " " 152 | ++ integer_to_list(ListenPort) ++ " " 153 | ++ Args ++ " " 154 | ++ "-env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ Name ++ " " 155 | ++ "-kernel inet_dist_connect_options \"[{recbuf,12582912},{sndbuf,12582912},{high_watermark,8388608},{low_watermark,4194304}]\" " 156 | ++ "-kernel inet_dist_listen_options \"[{recbuf,12582912},{sndbuf,12582912},{high_watermark,8388608},{low_watermark,4194304}]\" " 157 | ++ "-kernel error_logger \"{file,\\\"" ++ Pwd ++ "/error_log." ++ Name ++ "\\\"}\" " 158 | ++ "-kernel logger \"[{handler,default,logger_std_h,#{config=>#{file=>\\\"" ++ Pwd ++ "/logger." ++ Name ++ "\\\"}}}]\" " 159 | ++ "-setcookie " ++ atom_to_list(erlang:get_cookie()). 160 | 161 | %% 162 | %% Connection handler test_server side 163 | %% 164 | 165 | await_test_node_up(Name, LSock) -> 166 | case gen_tcp:accept(LSock, ?AWAIT_SSL_NODE_UP_TIMEOUT) of 167 | {ok, Socket} -> 168 | gen_tcp:close(LSock), 169 | case gen_tcp:recv(Socket, 0) of 170 | {ok, Bin} -> 171 | check_test_node_up(Socket, Name, Bin); 172 | {error, closed} -> 173 | gen_tcp:close(Socket), 174 | exit({lost_connection_with_test_node_before_up, Name}) 175 | end; 176 | {error, Error} -> 177 | gen_tcp:close(LSock), 178 | ct:log("Accept failed for test node ~s: ~p~n", [Name,Error]), 179 | exit({accept_failed, Error}) 180 | end. 181 | 182 | check_test_node_up(Socket, Name, Bin) -> 183 | case catch binary_to_term(Bin) of 184 | {'EXIT', _} -> 185 | gen_tcp:close(Socket), 186 | exit({bad_data_received_from_test_node, Name, Bin}); 187 | {test_node_up, NodeName} -> 188 | case list_to_atom(Name++"@"++host_name()) of 189 | NodeName -> 190 | Parent = self(), 191 | Go = make_ref(), 192 | %% Spawn connection handler on test server side 193 | Pid = spawn( 194 | fun () -> 195 | link(group_leader()), 196 | receive Go -> ok end, 197 | process_flag(trap_exit, true), 198 | tstsrvr_con_loop(Name, Socket, Parent) 199 | end), 200 | ok = gen_tcp:controlling_process(Socket, Pid), 201 | Pid ! Go, 202 | #node_handle{connection_handler = Pid, 203 | socket = Socket, 204 | name = Name}; 205 | _ -> 206 | exit({unexpected_test_node_connected, NodeName}) 207 | end; 208 | Msg -> 209 | exit({unexpected_msg_instead_of_test_node_up, Name, Msg}) 210 | end. 211 | 212 | send_to_test_node(#node_handle{connection_handler = Hndlr}, Term) -> 213 | Hndlr ! {relay_to_test_node, term_to_binary(Term)}, 214 | ok. 215 | 216 | tstsrvr_con_loop(Name, Socket, Parent) -> 217 | ok = inet:setopts(Socket,[{active,once}]), 218 | receive 219 | {relay_to_test_node, Data} when is_binary(Data) -> 220 | case gen_tcp:send(Socket, Data) of 221 | ok -> 222 | ok; 223 | _Error -> 224 | gen_tcp:close(Socket), 225 | exit({failed_to_relay_data_to_test_node, Name, Data}) 226 | end; 227 | {tcp, Socket, Bin} -> 228 | try binary_to_term(Bin) of 229 | {format, FmtStr, ArgList} -> 230 | ct:log(FmtStr, ArgList); 231 | {message, Msg} -> 232 | ct:log("Got message ~p", [Msg]), 233 | Parent ! Msg; 234 | {apply_res, To, Ref, Res} -> 235 | To ! {Ref, Res}; 236 | bye -> 237 | {error, closed} = gen_tcp:recv(Socket, 0), 238 | ct:log("Test node ~s stopped.~n", [Name]), 239 | gen_tcp:close(Socket), 240 | exit(normal); 241 | Unknown -> 242 | exit({unexpected_message_from_test_node, Name, Unknown}) 243 | catch 244 | error : _ -> 245 | gen_tcp:close(Socket), 246 | exit({bad_data_received_from_test_node, Name, Bin}) 247 | end; 248 | {tcp_closed, Socket} -> 249 | gen_tcp:close(Socket), 250 | exit({lost_connection_with_test_node, Name}); 251 | {'EXIT', Parent, Reason} -> 252 | exit({'EXIT', parent, Reason}); 253 | Unknown -> 254 | exit({unknown, Unknown}) 255 | end, 256 | tstsrvr_con_loop(Name, Socket, Parent). 257 | 258 | %% 259 | %% Connection handler test_node side 260 | %% 261 | 262 | % cnct2tstsrvr() is called via command line arg -run ... 263 | cnct2tstsrvr([Host, Port]) when is_list(Host), is_list(Port) -> 264 | %% Spawn connection handler on test node side 265 | ConnHandler 266 | = spawn(fun () -> 267 | case catch gen_tcp:connect(Host, 268 | list_to_integer(Port), 269 | [binary, 270 | {packet, 4}, 271 | {active, false}]) of 272 | {ok, Socket} -> 273 | notify_test_node_up(Socket), 274 | ets:new(test_server_info, 275 | [set, 276 | public, 277 | named_table, 278 | {keypos, 1}]), 279 | ets:insert(test_server_info, 280 | {test_server_handler, self()}), 281 | test_node_con_loop(Socket); 282 | Error -> 283 | halt("Failed to connect to test server " ++ 284 | lists:flatten(io_lib:format("Host:~p ~n Port:~p~n Error:~p~n", 285 | [Host, Port, Error]))) 286 | end 287 | end), 288 | spawn(fun () -> 289 | Mon = erlang:monitor(process, ConnHandler), 290 | receive 291 | {'DOWN', Mon, process, ConnHandler, Reason} -> 292 | receive after 1000 -> ok end, 293 | halt("test server connection handler terminated: " ++ 294 | lists:flatten(io_lib:format("~p", [Reason]))) 295 | end 296 | end). 297 | 298 | notify_test_node_up(Socket) -> 299 | case catch gen_tcp:send(Socket, 300 | term_to_binary({test_node_up, node()})) of 301 | ok -> ok; 302 | _ -> halt("Failed to notify test server that I'm up") 303 | end. 304 | 305 | send_to_tstsrvr(Term) -> 306 | case catch ets:lookup_element(test_server_info, test_server_handler, 2) of 307 | Hndlr when is_pid(Hndlr) -> 308 | Hndlr ! {relay_to_test_server, term_to_binary(Term)}, ok; 309 | _ -> 310 | receive after 200 -> ok end, 311 | send_to_tstsrvr(Term) 312 | end. 313 | 314 | test_node_con_loop(Socket) -> 315 | inet:setopts(Socket,[{active,once}]), 316 | receive 317 | {relay_to_test_server, Data} when is_binary(Data) -> 318 | case gen_tcp:send(Socket, Data) of 319 | ok -> 320 | ok; 321 | _Error -> 322 | gen_tcp:close(Socket), 323 | halt("Failed to relay data to test server") 324 | end; 325 | {tcp, Socket, Bin} -> 326 | case catch binary_to_term(Bin) of 327 | {'EXIT', _} -> 328 | gen_tcp:close(Socket), 329 | halt("test server sent me bad data"); 330 | {apply, From, Ref, M, F, A} -> 331 | spawn_link( 332 | fun () -> 333 | send_to_tstsrvr({apply_res, 334 | From, 335 | Ref, 336 | (catch apply(M, F, A))}) 337 | end); 338 | {apply, From, Ref, Fun} -> 339 | spawn_link(fun () -> 340 | send_to_tstsrvr({apply_res, 341 | From, 342 | Ref, 343 | (catch Fun())}) 344 | end); 345 | stop -> 346 | gen_tcp:send(Socket, term_to_binary(bye)), 347 | init:stop(), 348 | receive after infinity -> ok end; 349 | _Unknown -> 350 | halt("test server sent me an unexpected message") 351 | end; 352 | {tcp_closed, Socket} -> 353 | halt("Lost connection to test server") 354 | end, 355 | test_node_con_loop(Socket). 356 | -------------------------------------------------------------------------------- /test/proxy_dist_test_lib.hrl: -------------------------------------------------------------------------------- 1 | %% Copied from Erlang/OTP master branch 2 | %% commit: 7ed659fd1144b455883eef18dab38cddc904a065 3 | 4 | %% 5 | %% %CopyrightBegin% 6 | %% 7 | %% Copyright Ericsson AB 2017. All Rights Reserved. 8 | %% 9 | %% Licensed under the Apache License, Version 2.0 (the "License"); 10 | %% you may not use this file except in compliance with the License. 11 | %% You may obtain a copy of the License at 12 | %% 13 | %% http://www.apache.org/licenses/LICENSE-2.0 14 | %% 15 | %% Unless required by applicable law or agreed to in writing, software 16 | %% distributed under the License is distributed on an "AS IS" BASIS, 17 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | %% See the License for the specific language governing permissions and 19 | %% limitations under the License. 20 | %% 21 | %% %CopyrightEnd% 22 | %% 23 | 24 | -record(node_handle, 25 | {connection_handler, 26 | socket, 27 | name, 28 | nodename, 29 | priv} 30 | ). 31 | --------------------------------------------------------------------------------