├── .gitattributes ├── .github ├── Dockerfile.al2023-test ├── Dockerfile.debian-sid-test ├── Dockerfile.debian12-test ├── container-tests-al2023.sh ├── container-tests-debian12.sh └── workflows │ ├── shellcheck.yml │ ├── test-al2023-in-docker.yml │ ├── test-debian-sid-in-docker.yml │ └── test-debian12-in-docker.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── GNUmakefile ├── LICENSE ├── NOTICE ├── README.md ├── amazon-ec2-net-utils.spec ├── bin └── setup-policy-routes.sh ├── doc └── setup-policy-routes.8 ├── lib └── lib.sh ├── sysctl └── 90-ipv6-dad.conf ├── systemd ├── network │ └── 80-ec2.network └── system │ ├── policy-routes@.service │ ├── refresh-policy-routes@.service │ └── refresh-policy-routes@.timer └── udev └── 99-vpc-policy-routes.rules /.gitattributes: -------------------------------------------------------------------------------- 1 | debian/ export-ignore 2 | -------------------------------------------------------------------------------- /.github/Dockerfile.al2023-test: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/amazonlinux/amazonlinux:2023 2 | 3 | RUN dnf -y install make rpm-build openssh-clients 4 | -------------------------------------------------------------------------------- /.github/Dockerfile.debian-sid-test: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/debian/debian:sid 2 | 3 | RUN apt-get update && \ 4 | DEBIAN_FRONTEND=noninteractive apt-get -y dist-upgrade && \ 5 | DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends build-essential debhelper git devscripts shellcheck 6 | -------------------------------------------------------------------------------- /.github/Dockerfile.debian12-test: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/debian/debian:12 2 | 3 | RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends build-essential debhelper git devscripts shellcheck 4 | -------------------------------------------------------------------------------- /.github/container-tests-al2023.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | v=$(rpmspec -q --qf "%{version}" amazon-ec2-net-utils.spec) 4 | make scratch-sources version=${v} 5 | mv ../amazon-ec2-net-utils-${v}.tar.gz . 6 | rpmbuild --define "_sourcedir $PWD" -bb amazon-ec2-net-utils.spec 7 | -------------------------------------------------------------------------------- /.github/container-tests-debian12.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | v=$(dpkg-parsechangelog -S Version -l debian/changelog | sed -E 's,^\S:,,; s,-\S+,,') 4 | make scratch-sources version=${v} 5 | DEBEMAIL=nobody@localhost DEBFULLNAME="test runner" dch -v "${v}-1" -b -D unstable "scratch build" 6 | mv ../amazon-ec2-net-utils-${v}.tar.gz ../amazon-ec2-net-utils_${v}.orig.tar.gz 7 | dpkg-buildpackage -uc -us 8 | -------------------------------------------------------------------------------- /.github/workflows/shellcheck.yml: -------------------------------------------------------------------------------- 1 | name: shellcheck 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Install dependencies 18 | run: sudo apt-get update && DEBIAN_FRONTEND=noninteractive sudo apt-get -y install shellcheck 19 | - name: Run shellcheck 20 | run: shellcheck -s bash -S warning bin/*.sh lib/*.sh 21 | -------------------------------------------------------------------------------- /.github/workflows/test-al2023-in-docker.yml: -------------------------------------------------------------------------------- 1 | name: AL2023 package build 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Build the test container image (AL2023) 18 | run: docker build . --file .github/Dockerfile.al2023-test --tag amazon-ec2-net-utils-tests:al2023 19 | - name: Run tests in container (AL2023) 20 | run: docker run -v $(readlink -f $PWD):/src -w /src --entrypoint ".github/container-tests-al2023.sh" amazon-ec2-net-utils-tests:al2023 21 | -------------------------------------------------------------------------------- /.github/workflows/test-debian-sid-in-docker.yml: -------------------------------------------------------------------------------- 1 | name: Debian sid package build 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Build the test container image (Debian sid/unstable) 18 | run: docker build . --file .github/Dockerfile.debian-sid-test --tag amazon-ec2-net-utils-tests:debian-sid 19 | - name: Run a containerized scratch build (Debian sid/unstable) 20 | run: docker run -v $(readlink -f $PWD):/src -w /src --entrypoint ".github/container-tests-debian12.sh" amazon-ec2-net-utils-tests:debian-sid 21 | -------------------------------------------------------------------------------- /.github/workflows/test-debian12-in-docker.yml: -------------------------------------------------------------------------------- 1 | name: Debian 12 package build 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Build the test container image (Debian 12) 18 | run: docker build . --file .github/Dockerfile.debian12-test --tag amazon-ec2-net-utils-tests:debian12 19 | - name: Run a containerized scratch build (Debian 12) 20 | run: docker run -v $(readlink -f $PWD):/src -w /src --entrypoint ".github/container-tests-debian12.sh" amazon-ec2-net-utils-tests:debian12 21 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the correct branch. 27 | Amazon Linux 2023 releases are made from the *main* branch, while 28 | Amazon Linux 2 releases are made from the *1.x* branch. Your pull 29 | requests should target the branch appropriate to the distribution 30 | you're targeting with your improvement. If you are targeting a 31 | distribution other than Amazon Linux, you should most likely be 32 | working on the *main* branch. 33 | 2. You check existing open, and recently merged, pull requests to make 34 | sure someone else hasn't addressed the problem already. 35 | 3. You open an issue to discuss any significant work - we would hate 36 | for your time to be wasted. 37 | 38 | To send us a pull request, please: 39 | 40 | 1. Fork the repository. 41 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 42 | 3. Ensure local tests pass. 43 | 4. Commit to your fork using clear commit messages. 44 | 5. Send us a pull request, answering any default questions in the pull request interface. 45 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 46 | 47 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 48 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 49 | 50 | 51 | ## Finding contributions to work on 52 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 53 | 54 | 55 | ## Code of Conduct 56 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 57 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 58 | opensource-codeofconduct@amazon.com with any additional questions or comments. 59 | 60 | 61 | ## Security issue notifications 62 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 63 | 64 | 65 | ## Licensing 66 | 67 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 68 | 69 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 70 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | pkgname=amazon-ec2-net-utils 2 | version=2.6.0 3 | 4 | # Used by 'install' 5 | PREFIX?=/usr/local 6 | BINDIR=${DESTDIR}${PREFIX}/bin 7 | UDEVDIR=${DESTDIR}/usr/lib/udev/rules.d 8 | SYSTEMDDIR=${DESTDIR}/usr/lib/systemd 9 | SYSTEMD_SYSTEM_DIR=${SYSTEMDDIR}/system 10 | SYSTEMD_NETWORK_DIR=${SYSTEMDDIR}/network 11 | SHARE_DIR=${DESTDIR}/${PREFIX}/share/${pkgname} 12 | 13 | SHELLSCRIPTS=$(wildcard bin/*.sh) 14 | SHELLLIBS=$(wildcard lib/*.sh) 15 | UDEVRULES=$(wildcard udev/*.rules) 16 | 17 | DIRS:=${BINDIR} ${UDEVDIR} ${SYSTEMDDIR} ${SYSTEMD_SYSTEM_DIR} ${SYSTEMD_NETWORK_DIR} ${SHARE_DIR} 18 | 19 | .PHONY: help 20 | help: ## show help 21 | @egrep -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' 22 | 23 | ${DIRS}: 24 | install -d $@ 25 | 26 | define varsubst 27 | sed -i "s,AMAZON_EC2_NET_UTILS_LIBDIR,${PREFIX}/share/${pkgname},g" $1 28 | endef 29 | 30 | .PHONY: install 31 | install: ${SHELLSCRIPTS} ${UDEVRULES} ${SHELLLIBS} | ${DIRS} ## Install the software. Respects DESTDIR 32 | $(foreach f,${SHELLSCRIPTS},tgt=${BINDIR}/$$(basename --suffix=.sh $f);\ 33 | install -m755 $f $$tgt;${call varsubst,$$tgt};) 34 | $(foreach f,${SHELLLIBS},install -m644 $f ${SHARE_DIR}) 35 | $(foreach f,${UDEVRULES},install -m644 $f ${UDEVDIR};) 36 | $(foreach f,$(wildcard systemd/network/*.network),install -m644 $f ${SYSTEMD_NETWORK_DIR};) 37 | $(foreach f,$(wildcard systemd/system/*.service systemd/system/*.timer),install -m644 $f ${SYSTEMD_SYSTEM_DIR};) 38 | 39 | .PHONY: check 40 | check: ## Run tests 41 | @set -x; for script in ${SHELLSCRIPTS} ${SHELLLIBS}; do \ 42 | shellcheck --severity warning $${script};\ 43 | done 44 | 45 | .PHONY: scratch-rpm 46 | scratch-rpm: source_version_suffix=$(shell git describe --dirty --tags | sed "s,^v${version},,") 47 | scratch-rpm: rpm_version_suffix=$(shell git describe --dirty --tags | sed "s,^v${version},,; s,-,.,g") 48 | scratch-rpm: scratch-sources 49 | scratch-rpm: ## build an RPM based on the current working copy 50 | rpmbuild -D "_sourcedir $(CURDIR)/.." -D "_source_version_suffix ${source_version_suffix}" \ 51 | -D "_rpmdir $(CURDIR)/RPMS" \ 52 | -D "_rpm_version_suffix ${rpm_version_suffix}" -bb amazon-ec2-net-utils.spec 53 | 54 | .PHONY: scratch-deb 55 | scratch-deb: v=$(shell dpkg-parsechangelog -S Version -l debian/changelog | sed -E 's,^\S:,,; s,-\S+,,') 56 | scratch-deb: scratch_v=$(shell git describe --dirty --tags | sed "s,^v${version}-,,") 57 | scratch-deb: ## Build a pre-release .deb based on the current working copy 58 | DEBEMAIL=nobody@localhost DEBFULLNAME="test runner" dch -v "${v}.${scratch_v}-1~1" -b -D unstable "scratch build" 59 | dpkg-buildpackage -uc -us --build=binary 60 | 61 | .PHONY: scratch-sources 62 | scratch-sources: version=$(shell git describe --dirty --tags | sed "s,^v,,") 63 | scratch-sources: ## generate a tarball based on the current working copy 64 | tar czf ../${pkgname}-${version}.tar.gz --exclude=.git --transform 's,^./,./${pkgname}-${version}/,' . 65 | 66 | .PHONY: release-sources 67 | release-sources: uncommitted-check head-check ## generate a release tarball 68 | git archive --format tar.gz --prefix ${pkgname}-${version}/ HEAD > ../${pkgname}-${version}.tar.gz 69 | 70 | .PHONY: uncommitted-check 71 | uncommitted-check: 72 | @if ! git update-index --refresh --unmerged || \ 73 | ! git diff-index --name-only --exit-code HEAD; then \ 74 | echo "*** ERROR: Uncommitted changes in above files"; exit 1; fi 75 | 76 | .PHONY: head-check 77 | head-check: 78 | @if ! git diff --name-only --exit-code v${version} HEAD > /dev/null; then \ 79 | echo "*** ERROR: Git checkout not at version ${version}"; exit 1; fi ; \ 80 | 81 | tag: uncommitted-check ## Tag a new release 82 | @if git rev-parse --verify v$(version) > /dev/null 2>&1; then \ 83 | echo "*** ERROR: Version $(version) is already tagged"; exit 1; fi 84 | git tag v${version} 85 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 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 [yyyy] [name of copyright owner] 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 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | amazon-ec2-net-utils 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # amazon-ec2-net-utils # 2 | 3 | ## Background ## 4 | 5 | The amazon-ec2-net-utils package provides functionality needed to 6 | configure a Linux instance for optimal performance in a VPC 7 | environment. It handles: 8 | 9 | * Per-interface policy routing rules to accommodate VPC source/dest 10 | restrictions 11 | * Configuration of secondary IPv4 addresses 12 | * Configuration of ENIs upon hotplug 13 | * Routing configuration for delegated prefixes 14 | 15 | The version 1.x branch of the amazon-ec2-net-utils package was used in 16 | Amazon Linux 2 and earlier releases. It has a long history and is 17 | tightly coupled to ISC dhclient and initscripts network 18 | configuration. Both of these components are deprecated and will not 19 | make up the primary network configuration framework in future releases 20 | of Amazon Linux or other distributions. The 2.x branch (released from 21 | the `main` branch in git) represents a complete rewrite targeting a 22 | more modern network management framework. The rest of this document 23 | describes the 2.x branch. 24 | 25 | ## Implementation ## 26 | 27 | amazon-ec2-net-utils leverages systemd-networkd for most of the actual 28 | interface configuration, and is primarily responsible for mapping 29 | configuration information available via IMDS to systemd-networkd input 30 | configuration. It provides event-based configuration via udev rules, 31 | with timer based actions in order to detect non event based changes 32 | (e.g. secondary IP address assignment). Generated configuration is 33 | stored in the /run/ ephemeral filesystem and is not persisted across 34 | instance reboots. The generated configuration is expected to be 35 | regenerated from scratch upon reboot. Customers can override the 36 | behavior of the package by creating configuration files in the local 37 | administration network directory /etc/systemd/network as described in 38 | systemd-networkd's documentation. 39 | 40 | By utilizing a common framework in the form of systemd, the 41 | amazon-ec2-net-utils package should be able to integrate with any 42 | systemd-based distribution. This allows us to provide customers with a 43 | common baseline behavior regardless of whether they choose Amazon 44 | Linux or a third-party distribution. Testing has been performed on 45 | Debian, Fedora, and Amazon Linux 2023. 46 | 47 | ## Usage ## 48 | 49 | amazon-ec2-net-utils is expected to be pre-installed on Amazon Linux 50 | 2023 and future releases. In the common case, customers should not 51 | need to be aware of its operation. Configuration of network interfaces 52 | should occur following the principle of least astonishment. That is, 53 | traffic should be routed via the ENI associated with the source 54 | address. Custom configuration should be respected. New ENI 55 | attachments should be used automatically, and associated resources 56 | should be cleaned up on detachment. Manipulation of an ENI attachment 57 | should not impact the functionality of any other ENIs. 58 | 59 | ## Build and install ## 60 | 61 | The recommended way to install amazon-ec2-net-utils is by building a 62 | package for your distribution. A spec file and debian subdirectory are 63 | provided and should be reasonably suitable for modern rpm or dpkg 64 | based distributions. Build dependencies are declared in debian/control 65 | and in amazon-ec2-net-utils.spec and can be installed using standard 66 | tools from the distributions (e.g. dpkg-checkbuilddeps and apt, or dnf 67 | builddep, etc) 68 | 69 | The post installation scripts in the spec file and or .deb package 70 | will stop NetworkManager or ifupdown, if running, and initialize 71 | systemd-networkd and systemd-resolved. The expectation is that 72 | amazon-ec2-net-utils will take over and initialize a running system, 73 | without rebooting, such that it is indistinguishable from a system 74 | that booted with amazon-ec2-net-utils. 75 | 76 | ### rpm build and installation ### 77 | 78 | $ mkdir -p rpmbuild/BUILD 79 | $ git -C amazon-ec2-net-utils/ archive main | (cd rpmbuild/BUILD/ && tar xvf -) 80 | $ rpmbuild -bb rpmbuild/BUILD/amazon-ec2-net-utils.spec 81 | $ sudo dnf install rpmbuild/RPMS/noarch/amazon-ec2-net-utils-2.0.0-1.al2022.noarch.rpm 82 | 83 | ### dpkg build and installation ### 84 | 85 | $ dpkg-buildpackage -uc -us -b 86 | $ sudo apt install ../amazon-ec2-net-utils_2.0.0-1_all.deb 87 | 88 | ### Installation verification ### 89 | 90 | $ # inspect the state of the system to verify that networkd is running: 91 | $ networkctl # should report all physical interfaces as "routable" and "configured" 92 | $ networkctl status eth0 # should report "/run/systemd/network/70-eth0.network" as the network conf file 93 | $ resolvectl # show status of systemd-resolved 94 | 95 | **Example:** 96 | 97 | [ec2-user@ip-10-0-0-114 ~]$ networkctl 98 | IDX LINK TYPE OPERATIONAL SETUP 99 | 1 lo loopback carrier unmanaged 100 | 2 eth0 ether routable configured 101 | 102 | 2 links listed. 103 | [ec2-user@ip-10-0-0-114 ~]$ networkctl status eth0 104 | ● 2: eth0 105 | Link File: /usr/lib/systemd/network/99-default.link 106 | Network File: /run/systemd/network/70-eth0.network 107 | Type: ether 108 | State: routable (configured) 109 | Alternative Names: enp0s5 110 | ens5 111 | Path: pci-0000:00:05.0 112 | Driver: ena 113 | Vendor: Amazon.com, Inc. 114 | Model: Elastic Network Adapter (ENA) 115 | HW Address: 02:c9:76:e3:18:0b 116 | MTU: 9001 (min: 128, max: 9216) 117 | QDisc: mq 118 | IPv6 Address Generation Mode: eui64 119 | Queue Length (Tx/Rx): 2/2 120 | Address: 10.0.0.114 (DHCP4 via 10.0.0.1) 121 | fe80::c9:76ff:fee3:180b 122 | Gateway: 10.0.0.1 123 | DNS: 10.0.0.2 124 | Activation Policy: up 125 | DHCP4 Client ID: IAID:0xed10bdb8/DUID 126 | DHCP6 Client DUID: DUID-EN/Vendor:0000ab11a9aa54876c81082a0000 127 | 128 | Sep 01 17:44:54 ip-10-0-0-114.us-west-2.compute.internal systemd-networkd[2042]: eth0: Link UP 129 | Sep 01 17:44:54 ip-10-0-0-114.us-west-2.compute.internal systemd-networkd[2042]: eth0: Gained carrier 130 | Sep 01 17:44:54 ip-10-0-0-114.us-west-2.compute.internal systemd-networkd[2042]: eth0: Gained IPv6LL 131 | Sep 01 17:44:54 ip-10-0-0-114.us-west-2.compute.internal systemd-networkd[2042]: eth0: DHCPv4 address 10.0.0.114/24 via 10.0.0.1 132 | Sep 01 17:44:54 ip-10-0-0-114.us-west-2.compute.internal systemd-networkd[2042]: eth0: Re-configuring with /run/systemd/network/70-eth0.net> 133 | Sep 01 17:44:54 ip-10-0-0-114.us-west-2.compute.internal systemd-networkd[2042]: eth0: DHCP lease lost 134 | Sep 01 17:44:54 ip-10-0-0-114.us-west-2.compute.internal systemd-networkd[2042]: eth0: DHCPv6 lease lost 135 | Sep 01 17:44:54 ip-10-0-0-114.us-west-2.compute.internal systemd-networkd[2042]: eth0: DHCPv4 address 10.0.0.114/24 via 10.0.0.1 136 | [ec2-user@ip-10-0-0-114 ~]$ resolvectl 137 | Global 138 | Protocols: LLMNR=resolve -mDNS -DNSOverTLS DNSSEC=no/unsupported 139 | resolv.conf mode: uplink 140 | 141 | Link 2 (eth0) 142 | Current Scopes: DNS 143 | Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported 144 | Current DNS Server: 10.0.0.2 145 | DNS Servers: 10.0.0.2 146 | 147 | ## Getting help ## 148 | 149 | If you're using amazon-ec2-net-utils as packaged by a Linux 150 | distribution, please consider using your distribution's support 151 | channels first. Your distribution may have modified the behavior of 152 | the package to facilitate better integration, and may have more 153 | specific guidance for you. 154 | 155 | Alternatively, if you don't believe your issue is distribution 156 | specific, please feel free to open an issue on GitHub. 157 | 158 | ## Contributing ## 159 | 160 | We are happy to review proposed changes. If you're considering 161 | introducing any major functionality or behavior changes, you may wish 162 | to consider opening an issue where we can discuss the details before 163 | you proceed with implementation. Please refer to 164 | [CONTRIBUTING.md](CONTRIBUTING.md) for additional expectations. 165 | -------------------------------------------------------------------------------- /amazon-ec2-net-utils.spec: -------------------------------------------------------------------------------- 1 | Name: amazon-ec2-net-utils 2 | %define base_version 2.6.0 3 | %define source_version %{base_version}%{?_source_version_suffix} 4 | Version: %{base_version}%{?_rpm_version_suffix} 5 | Release: 1%{?dist} 6 | Summary: utilities for managing network interfaces in Amazon EC2 7 | 8 | License: Apache 2.0 9 | URL: https://github.com/aws/amazon-ec2-net-utils/ 10 | Source0: amazon-ec2-net-utils-%{source_version}.tar.gz 11 | 12 | BuildArch: noarch 13 | 14 | BuildRequires: make 15 | Requires: systemd-networkd, udev, curl, iproute 16 | Requires: /usr/bin/md5sum 17 | Requires: (systemd-resolved or systemd < 250) 18 | 19 | %description 20 | amazon-ec2-net-utils-ng provides udev integration and helper utilities 21 | to manage network configuration in the Amazon EC2 cloud environment 22 | 23 | %prep 24 | 25 | %autosetup -n %{name}-%{source_version} 26 | 27 | %install 28 | make install DESTDIR=%{buildroot} PREFIX=/usr 29 | 30 | %files 31 | /usr/lib/systemd/network/80-ec2.network 32 | /usr/lib/systemd/system/policy-routes@.service 33 | /usr/lib/systemd/system/refresh-policy-routes@.service 34 | /usr/lib/systemd/system/refresh-policy-routes@.timer 35 | 36 | /usr/lib/udev/rules.d/99-vpc-policy-routes.rules 37 | %{_bindir}/setup-policy-routes 38 | %dir %{_datarootdir}/amazon-ec2-net-utils 39 | %{_datarootdir}/amazon-ec2-net-utils/lib.sh 40 | 41 | %post 42 | 43 | setup_policy_routes() { 44 | local iface node 45 | for node in /sys/class/net/*; do 46 | iface=$(basename $node) 47 | unset ID_NET_DRIVER 48 | eval $(udevadm info --export --query=property /sys/class/net/$iface) 49 | case $ID_NET_DRIVER in 50 | ena|ixgbevf|vif) 51 | systemctl restart policy-routes@${iface}.service 52 | systemctl start refresh-policy-routes@${iface}.timer 53 | ;; 54 | esac 55 | done 56 | } 57 | 58 | if [ $1 -eq 1 ]; then 59 | # This is a new install 60 | systemctl enable systemd-networkd.service 61 | systemctl enable systemd-resolved.service 62 | systemctl disable NetworkManager-wait-online.service 63 | systemctl disable NetworkManager.service 64 | [ -f /etc/resolv.conf ] && mv /etc/resolv.conf /etc/resolv.conf.old 65 | ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf 66 | if [ -d /run/systemd/system ]; then 67 | systemctl stop NetworkManager.service 68 | systemctl start systemd-networkd.service 69 | setup_policy_routes 70 | systemctl start systemd-resolved.service 71 | fi 72 | elif [ $1 -gt 1 ]; then 73 | # This is an upgrade, there's less setup to do, but we do want to 74 | # ensure we apply any configuration introduced by the new version 75 | systemctl daemon-reload 76 | setup_policy_routes 77 | fi 78 | 79 | %changelog 80 | 81 | -------------------------------------------------------------------------------- /bin/setup-policy-routes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 6 | # not use this file except in compliance with the License. A copy of the 7 | # License is located at 8 | # 9 | # http://aws.amazon.com/apache2.0/ 10 | # 11 | # or in the "license" file accompanying this file. This file is distributed 12 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 13 | # express or implied. See the License for the specific language governing 14 | # permissions and limitations under the License. 15 | 16 | set -eo pipefail -o noclobber -o nounset 17 | 18 | export unitdir lockdir runtimeroot reload_flag 19 | declare -r runtimeroot="/run/amazon-ec2-net-utils" 20 | declare -r lockdir="${runtimeroot}/setup-policy-routes" 21 | declare -r unitdir="/run/systemd/network" 22 | declare -r reload_flag="${runtimeroot}/.policy-routes-reload-networkd" 23 | 24 | libdir=${LIBDIR_OVERRIDE:-AMAZON_EC2_NET_UTILS_LIBDIR} 25 | # shellcheck source=../lib/lib.sh 26 | . "${libdir}/lib.sh" 27 | 28 | iface="$1" 29 | [ -n "$iface" ] || { error "Invocation error"; exit 1; } 30 | 31 | mkdir -p "$runtimeroot" 32 | 33 | do_setup() { 34 | ether=$(cat /sys/class/net/${iface}/address) 35 | 36 | declare -i changes=0 37 | changes+=$(setup_interface $iface $ether) 38 | if [ $changes -gt 0 ]; then 39 | touch "$reload_flag" 40 | fi 41 | } 42 | 43 | case "$2" in 44 | refresh) 45 | [ -e "/sys/class/net/${iface}" ] || exit 0 46 | info "Starting configuration refresh for $iface" 47 | do_setup 48 | ;; 49 | start) 50 | register_networkd_reloader 51 | while [ ! -e "/sys/class/net/${iface}" ]; do 52 | debug "Waiting for sysfs node to exist" 53 | sleep 0.1 54 | done 55 | info "Starting configuration for $iface" 56 | debug /lib/systemd/systemd-networkd-wait-online -i "$iface" 57 | /lib/systemd/systemd-networkd-wait-online -i "$iface" 58 | export EC2_IF_INITIAL_SETUP=1 59 | do_setup 60 | ;; 61 | remove) 62 | register_networkd_reloader 63 | info "Removing configuration for $iface." 64 | rm -rf "/run/network/$iface" \ 65 | "${unitdir}/70-${iface}.network" \ 66 | "${unitdir}/70-${iface}.network.d" || true 67 | touch "$reload_flag" 68 | ;; 69 | stop|cleanup) 70 | # this is a no-op, only supported for compatibility 71 | :;; 72 | *) 73 | echo "USAGE: $0: start|stop" 74 | echo " This tool is normally invoked via udev rules." 75 | echo " See https://github.com/amazonlinux/amazon-ec2-net-utils" 76 | ;; 77 | esac 78 | 79 | exit 0 80 | -------------------------------------------------------------------------------- /doc/setup-policy-routes.8: -------------------------------------------------------------------------------- 1 | \" Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | \" 3 | \" Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | \" not use this file except in compliance with the License. A copy of the 5 | \" License is located at 6 | \" 7 | \" http://aws.amazon.com/apache2.0/ 8 | \" 9 | \" or in the "license" file accompanying this file. This file is distributed 10 | \" on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | \" express or implied. See the License for the specific language governing 12 | \" permissions and limitations under the License. 13 | \" SPDX-License-Identifier: Apache-2.0 14 | .TH SETUP-POLICY-ROUTES 8 23-Dec-2022 amazon-ec2-net-utils 15 | .SH NAME 16 | setup-policy-routes \- configure systemd-networkd within an Amazon VPC network 17 | .SH SYNOPSIS 18 | .B setup-policy-routes 19 | .RI "INTERFACE" 20 | .RI "stop|start|cleanup" 21 | .SH DESCRIPTION 22 | .B setup-policy-routes 23 | installs systemd-networkd configuration to the /run/systemd/network 24 | directory to configure the given network interface for optimal use in 25 | an Amazon Virtual Private Cloud (VPC) network environment. 26 | 27 | The installed systemd-networkd configuration: 28 | 29 | .IP 30 | 31 | \(bu Sets up DHCPv4 and DHCPv6 clients 32 | 33 | \(bu Assigns or unassigns secondary IPv4 addresses as IP aliases as 34 | appropriate 35 | 36 | \(bu Installs per-interface route tables and policy routing rules to 37 | ensure that egress traffic with a given source address is routed via 38 | the ENI with which that address (or its corresponding prefix) is 39 | associated. This ensures compliance with the VPC anti-spoofing 40 | protections. 41 | 42 | \(bu Installs interface aliases corresponding to the ENI ID and 43 | attachment ID 44 | 45 | .PP 46 | Installed configuration can be overridden as needed by placing a file 47 | with the same name as the generated file in /etc/systemd/network. See 48 | .BR systemd.network(5) 49 | for more details. 50 | 51 | .PP 52 | .B setup-policy-routes 53 | is not normally executed by hand, but is instead normally invoked by 54 | udev rules either on boot or by a systemd timer. 55 | 56 | .SH OPTIONS 57 | .B setup-policy-routes 58 | takes a network interface name as the first parameter and one of 59 | start, stop, or cleanup as the second parameter: 60 | .IP 61 | \(bu 62 | .B start 63 | indicates that the given interface should be configured 64 | 65 | \(bu 66 | .B stop 67 | indicates that the given interface should be deconfigured and the 68 | installed configuration should be cleaned up 69 | 70 | \(bu 71 | .B cleanup 72 | performs internal housekeeping 73 | 74 | .SH ENVIRONMENT 75 | .PP 76 | .B setup-policy-routes 77 | recognizes the following environment variables 78 | 79 | .IP 80 | .B EC2_IF_INITIAL_SETUP 81 | This variable should be set when performing initial configuration of 82 | an interface, as opposed to refreshing the configuration of an 83 | interface that is already active. It enables additional retry logic 84 | for some internal operations that rely on the Amazon EC2 Instance 85 | Metadata Service (IMDS) in order to ensure that the interface 86 | setup accounts for any configuration data that may still be in flight 87 | while the interface is being configured on the instance. 88 | 89 | .SH FILES 90 | .PP 91 | .IR /run/systemd/network/*.network 92 | .br 93 | .IR /run/systemd/network/*.network.d/eni.conf 94 | .br 95 | .IR /usr/lib/systemd/network/80-ec2.network 96 | .br 97 | .IR /usr/lib/udev/rules.d/99-vpc-policy-routes.rules 98 | 99 | .SH SEE ALSO 100 | .BR systemd-networkd (8), 101 | .BR networkctl (1), 102 | .BR systemd.network(5), 103 | .BR systemd.timer(5), 104 | .UR https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html 105 | The Amazon Virtual Private Cloud User Guide 106 | .UE 107 | .UR https://github.com/amazonlinux/amazon-ec2-net-utils 108 | The amazon-ec2-net-utils GitHub repository. 109 | .UE 110 | .br 111 | -------------------------------------------------------------------------------- /lib/lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"). You may 6 | # not use this file except in compliance with the License. A copy of the 7 | # License is located at 8 | # 9 | # http://aws.amazon.com/apache2.0/ 10 | # 11 | # or in the "license" file accompanying this file. This file is distributed 12 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 13 | # express or implied. See the License for the specific language governing 14 | # permissions and limitations under the License. 15 | 16 | # These should be set by the calling program 17 | declare ether 18 | declare unitdir 19 | declare lockdir 20 | declare reload_flag 21 | 22 | declare -r imds_endpoints=("http://169.254.169.254/latest" "http://[fd00:ec2::254]/latest") 23 | declare -r imds_token_path="api/token" 24 | declare -r syslog_facility="user" 25 | declare -r syslog_tag="ec2net" 26 | declare -i -r rule_base=10000 27 | declare -r default_route="DEFAULT" 28 | 29 | # Systemd installs routes with a metric of 1024 by default. We 30 | # override to a lower metric to ensure that our fully configured 31 | # interfaces are preferred over those in the process of being 32 | # configured. 33 | declare -i -r metric_base=512 34 | declare imds_endpoint="" 35 | declare imds_token="" 36 | declare imds_interface="" 37 | declare self_iface_name="" 38 | 39 | make_token_request() { 40 | local ep=${1:-""} 41 | local interface=${2:-""} 42 | local -a curl_opts=(--max-time 5 --connect-timeout 0.15 -s --fail -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds: 60") 43 | if [ -n "$interface" ]; then 44 | curl_opts+=(--interface "$interface") 45 | fi 46 | curl "${curl_opts[@]}" "${ep}/${imds_token_path}" 47 | } 48 | 49 | get_lowest_secondary_interface() { 50 | # This is the best effort guess at what the lowest secondary interface would be. 51 | # We know that systemd will use either en or eth as default prefix. 52 | # It will return empty if there are no secondary interfaces. 53 | basename -a /sys/class/net/* | grep -E '^e(n|th)' | sort -V | sed -n '2p' 54 | } 55 | 56 | get_token() { 57 | # Try getting a token early, using each endpoint in 58 | # turn. Whichever interface and endpoint responds will be used for all the IMDS calls 59 | # used to setup the interface. For IMDS interface we will 60 | # try to call the IMDS from its self first, failing that the 61 | # primary eni, and failing that the best guess at the 62 | # lowest secondary eni if available. 63 | # On initial interface setup, we'll retry 64 | # this operation for up to 30 seconds, but on subsequent 65 | # invocations we avoid retrying 66 | local deadline 67 | local intf=$self_iface_name 68 | deadline=$(date -d "now+30 seconds" +%s) 69 | local old_opts=$- 70 | 71 | while [ "$(date +%s)" -lt $deadline ]; do 72 | for ep in "${imds_endpoints[@]}"; do 73 | set +e 74 | imds_token=$(make_token_request "$ep" "$intf") 75 | 76 | if [ -z "$imds_token" ]; then 77 | imds_token=$(make_token_request "$ep") 78 | intf="$default_route" 79 | fi 80 | 81 | if [ -z "$imds_token" ]; then 82 | lowest_secondary=$(get_lowest_secondary_interface) 83 | if [ -n "$lowest_secondary" ]; then 84 | imds_token=$(make_token_request "$ep" "$lowest_secondary") 85 | intf="$lowest_secondary" 86 | fi 87 | fi 88 | [[ $old_opts = *e* ]] && set -e 89 | 90 | if [ -n "$imds_token" ]; then 91 | debug "Got IMDSv2 token for interface ${self_iface_name} from ${ep} via ${intf}" 92 | imds_endpoint=$ep 93 | imds_interface=$intf 94 | return 95 | fi 96 | done 97 | 98 | if [ ! -v EC2_IF_INITIAL_SETUP ]; then 99 | break 100 | fi 101 | sleep 0.5 102 | done 103 | } 104 | 105 | log() { 106 | local priority 107 | priority=$1 ; shift 108 | logger --id=$$ --priority "${syslog_facility}.${priority}" --tag "$syslog_tag" "$@" 109 | } 110 | 111 | debug() { 112 | log debug "$@" 113 | } 114 | 115 | info() { 116 | log info "$@" 117 | } 118 | 119 | error() { 120 | log err "$@" 121 | } 122 | 123 | get_meta() { 124 | local key=$1 125 | local max_tries=${2:-10} 126 | declare -i attempts=0 127 | debug "[get_meta] Querying IMDS for ${key}" 128 | 129 | get_token 130 | if [[ -z $imds_endpoint || -z $imds_token || -z $imds_interface ]]; then 131 | error "[get_meta] Unable to obtain IMDS token, endpoint, or interface" 132 | return 1 133 | fi 134 | local url="${imds_endpoint}/meta-data/${key}" 135 | local meta rc 136 | local curl_opts=(-s --max-time 5 -H "X-aws-ec2-metadata-token:${imds_token}" -f) 137 | if [[ "$imds_interface" != "$default_route" ]]; then 138 | curl_opts+=(--interface "$imds_interface") 139 | fi 140 | 141 | while [ $attempts -lt $max_tries ]; do 142 | meta=$(curl "${curl_opts[@]}" "$url") 143 | rc=$? 144 | if [ $rc -eq 0 ]; then 145 | echo "$meta" 146 | return 0 147 | fi 148 | attempts+=1 149 | done 150 | return 1 151 | } 152 | 153 | get_imds() { 154 | local key=$1 155 | local max_tries=${2:-10} 156 | get_meta $key $max_tries 157 | } 158 | 159 | get_iface_imds() { 160 | local mac=$1 161 | local key=$2 162 | local max_tries=${3:-10} 163 | get_imds network/interfaces/macs/${mac}/${key} $max_tries 164 | } 165 | 166 | _install_and_reload() { 167 | local src=$1 168 | local dest=$2 169 | if [[ -e "$dest" && -s "$src" ]]; then 170 | if [ "$(md5sum < $dest)" = "$(md5sum < $src)" ]; then 171 | # The config is unchanged since last run. Nothing left to do: 172 | rm "$src" 173 | echo 0 174 | else 175 | # The file content has changed, we need to reload: 176 | mv "$src" "$dest" 177 | echo 1 178 | fi 179 | return 180 | fi 181 | 182 | # If we're here then we're creating a new config file 183 | if [ "$(stat --format=%s $src)" -gt 0 ]; then 184 | mv "$src" "$dest" 185 | echo 1 186 | return 187 | fi 188 | rm "$src" 189 | echo 0 190 | } 191 | 192 | create_ipv4_aliases() { 193 | local iface=$1 194 | local mac=$2 195 | local addresses 196 | subnet_supports_ipv4 "$iface" || return 0 197 | addresses=$(get_iface_imds $mac local-ipv4s | tail -n +2 | sort) 198 | if [[ -z "$addresses" ]]; then 199 | info "No addresses found for ${iface}" 200 | return 0 201 | fi 202 | local drop_in_dir="${unitdir}/70-${iface}.network.d" 203 | mkdir -p "$drop_in_dir" 204 | local file="$drop_in_dir/ec2net_alias.conf" 205 | local work="${file}.new" 206 | touch "$work" 207 | 208 | for a in $addresses; do 209 | cat <> "$work" 210 | [Address] 211 | Address=${a}/32 212 | AddPrefixRoute=false 213 | EOF 214 | done 215 | _install_and_reload "$work" "$file" 216 | } 217 | 218 | subnet_supports_ipv4() { 219 | local iface=$1 220 | if [ -z "$iface" ]; then 221 | error "${FUNCNAME[0]} called without an interface" 222 | return 1 223 | fi 224 | ! ip -4 addr show dev "$iface" scope global | \ 225 | sed -n -E 's,^.*inet (\S+).*,\1,p' | grep -E -q '^169\.254\.' 226 | } 227 | 228 | subnet_supports_ipv6() { 229 | local iface=$1 230 | if [ -z "$iface" ]; then 231 | error "${FUNCNAME[0]} called without an interface" 232 | return 1 233 | fi 234 | ip -6 addr show dev "$iface" scope global | grep -q inet6 235 | } 236 | 237 | subnet_prefixroutes() { 238 | local ether=$1 239 | local family=${2:-ipv4} 240 | if [ -z "$ether" ]; then 241 | err "${FUNCNAME[0]} called without an MAC address" 242 | return 1 243 | fi 244 | case "$family" in 245 | ipv4) 246 | get_iface_imds "$ether" "subnet-${family}-cidr-block" 247 | ;; 248 | ipv6) 249 | get_iface_imds "$ether" "subnet-${family}-cidr-blocks" 250 | ;; 251 | esac 252 | } 253 | 254 | create_rules() { 255 | local iface=$1 256 | local device_number=$2 257 | local network_card=$3 258 | local family=$4 259 | local addrs prefixes 260 | local local_addr_key subnet_pd_key 261 | local drop_in_dir="${unitdir}/70-${iface}.network.d" 262 | mkdir -p "$drop_in_dir" 263 | 264 | local -i ruleid=$((device_number+rule_base+100*network_card)) 265 | 266 | case $family in 267 | 4) 268 | if ! subnet_supports_ipv4 $iface; then 269 | return 0 270 | fi 271 | local_addr_key=local-ipv4s 272 | subnet_pd_key=ipv4-prefix 273 | ;; 274 | 6) 275 | if ! subnet_supports_ipv6 $iface; then 276 | return 0 277 | fi 278 | local_addr_key=ipv6s 279 | subnet_pd_key=ipv6-prefix 280 | ;; 281 | *) 282 | error "unable to determine protocol" 283 | return 1 284 | ;; 285 | esac 286 | 287 | # We'd like to retry here, but we can't distinguish between an 288 | # IMDS failure, a propagation delay, or a legitimately empty 289 | # response. 290 | addrs=$(get_iface_imds ${ether} ${local_addr_key} || true) 291 | if [[ -z "$addrs" ]]; then 292 | info "No addresses found for ${ether}" 293 | return 0 294 | fi 295 | 296 | # don't fail or retry prefix retrieval. IMDS currently returns an 297 | # error, rather than an empty response, if no prefixes are 298 | # assigned, so we are unable to distinguish between a service 299 | # error and a successful but empty response 300 | prefixes=$(get_iface_imds ${ether} ${subnet_pd_key} 1 || true) 301 | 302 | local source 303 | local file="$drop_in_dir/ec2net_policy_${family}.conf" 304 | local work="${file}.new" 305 | touch "$work" 306 | 307 | for source in $addrs $prefixes; do 308 | cat <> "$work" 309 | [RoutingPolicyRule] 310 | From=${source} 311 | Priority=${ruleid} 312 | Table=${ruleid} 313 | EOF 314 | done 315 | _install_and_reload "$work" "$file" 316 | } 317 | 318 | create_if_overrides() { 319 | local iface="$1"; test -n "$iface" || { echo "Invalid iface at $LINENO" >&2 ; exit 1; } 320 | local -i device_number="$2"; test -n "$device_number" || { echo "Invalid device_number at $LINENO" >&2 ; exit 1; } 321 | local -i network_card="$3"; test -n "$network_card" || { echo "Invalid network_card at $LINENO" >&2 ; exit 1; } 322 | local ether="$4"; test -n "$ether" || { echo "Invalid ether at $LINENO" >&2 ; exit 1; } 323 | local cfgfile="$5"; test -n "$cfgfile" || { echo "Invalid cfgfile at $LINENO" >&2 ; exit 1; } 324 | 325 | local cfgdir="${cfgfile}.d" 326 | local dropin="${cfgdir}/eni.conf" 327 | local -i metric=$((metric_base+100*network_card+device_number)) 328 | local -i tableid=$((rule_base+100*network_card+device_number)) 329 | 330 | mkdir -p "$cfgdir" 331 | cat < "${dropin}.tmp" 332 | # Configuration for ${iface} generated by policy-routes@${iface}.service 333 | [Match] 334 | MACAddress=${ether} 335 | [Network] 336 | DHCP=yes 337 | 338 | [DHCPv4] 339 | RouteMetric=${metric} 340 | UseRoutes=true 341 | UseGateway=true 342 | 343 | [IPv6AcceptRA] 344 | RouteMetric=${metric} 345 | UseGateway=true 346 | 347 | EOF 348 | 349 | cat <> "${dropin}.tmp" 350 | [Route] 351 | Table=${tableid} 352 | Gateway=_ipv6ra 353 | 354 | EOF 355 | if subnet_supports_ipv6 "$iface"; then 356 | for dest in $(subnet_prefixroutes "$ether" ipv6); do 357 | cat <> "${dropin}.tmp" 358 | [Route] 359 | Table=${tableid} 360 | Destination=${dest} 361 | 362 | EOF 363 | done 364 | fi 365 | if subnet_supports_ipv4 "$iface"; then 366 | # if not in a v6-only network, add IPv4 routes to the private table 367 | cat <> "${dropin}.tmp" 368 | [Route] 369 | Gateway=_dhcp4 370 | Table=${tableid} 371 | EOF 372 | local dest 373 | for dest in $(subnet_prefixroutes "$ether" ipv4); do 374 | cat <> "${dropin}.tmp" 375 | [Route] 376 | Table=${tableid} 377 | Destination=${dest} 378 | EOF 379 | done 380 | fi 381 | 382 | 383 | mv "${dropin}.tmp" "$dropin" 384 | echo 1 385 | } 386 | 387 | add_altnames() { 388 | local iface=$1 389 | local ether=$2 390 | local device_number=$3 391 | local network_card=$4 392 | local eni_id 393 | eni_id=$(get_iface_imds "$ether" interface-id) 394 | # Interface altnames can also be added using systemd .link files. 395 | # However, in order to use them, we need to wait until a 396 | # systemd-networkd reload operation completes and then trigger a 397 | # udev "move" event. We avoid that overhead by adding the 398 | # altnames directly using ip(8). 399 | if [ -n "$eni_id" ] && 400 | ! ip link show dev "$iface" | grep -q -E "altname\s+${eni_id}"; then 401 | ip link property add dev "$iface" altname "$eni_id" || true 402 | fi 403 | local device_number_alt="device-number-${device_number}" 404 | if [ -n "$network_card" ]; then 405 | # On instance types that don't support a network-card key, we 406 | # won't append a value here. A value of zero would be 407 | # appropriate, but would be a visible change to the interface 408 | # configuration on these instance types and could disrupt 409 | # existing automation. 410 | device_number_alt="${device_number_alt}.${network_card}" 411 | fi 412 | if [ -n "$device_number" ] && 413 | ! ip link show dev "$device_number_alt" > /dev/null 2>&1; then 414 | ip link property add dev "$iface" altname "${device_number_alt}" || true 415 | fi 416 | } 417 | 418 | create_interface_config() { 419 | local iface=$1 420 | local device_number=$2 421 | local network_card=$3 422 | local ether=$4 423 | 424 | local libdir=/usr/lib/systemd/network 425 | local defconfig="${libdir}/80-ec2.network" 426 | 427 | local -i retval=0 428 | 429 | local cfgfile="${unitdir}/70-${iface}.network" 430 | if [ -e "$cfgfile" ] && 431 | [ ! -v EC2_IF_INITIAL_SETUP ]; then 432 | debug "Using existing cfgfile ${cfgfile}" 433 | echo $retval 434 | return 435 | fi 436 | 437 | debug "Linking $cfgfile to $defconfig" 438 | mkdir -p "$unitdir" 439 | ln -sf "$defconfig" "$cfgfile" 440 | retval+=$(create_if_overrides "$iface" "$device_number" "$network_card" "$ether" "$cfgfile") 441 | add_altnames "$iface" "$ether" "$device_number" "$network_card" 442 | echo $retval 443 | } 444 | 445 | # The primary interface is defined as the interface whose MAC address 446 | # is in the top-level `mac` key. It will always have device-number 0 447 | # and network-card 0. It gets unique treatment in a few areas. 448 | _is_primary_interface() { 449 | local ether default_mac 450 | ether="$1" 451 | 452 | default_mac=$(get_imds mac) 453 | [ "$ether" = "$default_mac" ] 454 | } 455 | 456 | # device-number, which represents the DeviceIndex field in an EC2 457 | # NetworkInterfaceAttachment object, is not guaranteed to have 458 | # propagated to IMDS by the time a hot-plugged interface is visible to 459 | # the instance. Further complicating things, IMDS returns 0 for the 460 | # device-number before propagation is complete, which is a valid value 461 | # and represents the instance's primary interface. We cope with this 462 | # by ensuring that the only interface for which we return 0 as the 463 | # device-number is the one whose MAC address matches the instance's 464 | # top-level "mac" field, which is static and guaranteed to be 465 | # available as soon as the instance launches. 466 | _get_device_number() { 467 | local iface ether network_card_index 468 | iface="$1" 469 | ether="$2" 470 | network_card_index=${3:-0} 471 | 472 | if _is_primary_interface "$ether"; then 473 | echo 0 ; return 0 474 | fi 475 | 476 | local -i maxtries=60 ntries=0 477 | for (( ntries = 0; ntries < maxtries; ntries++ )); do 478 | device_number=$(get_iface_imds "$ether" device-number 1) 479 | # if either the device number or the card index are nonzero, 480 | # then we treat the value returned as valid. Zero values for 481 | # both is only valid for the primary interface, which we've 482 | # already concluded is not this one. 483 | if [ $device_number -ne 0 ] || [ $network_card_index -ne 0 ]; then 484 | echo "$device_number" 485 | return 0 486 | else 487 | sleep 0.1 488 | fi 489 | done 490 | error "Unable to identify device-number for $iface after $ntries attempts" 491 | echo -1 492 | return 1 493 | } 494 | 495 | # print the network-card IMDS value for the given interface 496 | # NOTE: On many instance types, this value is not defined. This 497 | # function will print the empty string on those instances. On 498 | # instances where it is defined, it will be a numeric value. 499 | _get_network_card() { 500 | local iface ether network_card 501 | iface="$1" 502 | ether="$2" 503 | 504 | if _is_primary_interface "$ether"; then 505 | echo 0 ; return 0 506 | fi 507 | network_card=$(get_iface_imds "$ether" network-card) 508 | echo ${network_card} 509 | } 510 | 511 | 512 | # Interfaces get configured with addresses and routes from 513 | # DHCP. Routes are inserted in the main table with metrics based on 514 | # their physical location (slot ID) to ensure deterministic route 515 | # ordering. Interfaces also get policy routing rules based on source 516 | # address matching and ensuring that all egress traffic with one of 517 | # the interface's IPs (primary or secondary, IPv4 or IPv6, including 518 | # addresses from delegated prefixes) will be routing according to an 519 | # interface-specific routing table. 520 | setup_interface() { 521 | local iface ether 522 | local -i device_number network_card rc 523 | iface=$1 524 | ether=$2 525 | self_iface_name=$1 526 | 527 | network_card=$(_get_network_card "$iface" "$ether") 528 | device_number=$(_get_device_number "$iface" "$ether" "$network_card") 529 | rc=$? 530 | if [ $rc -ne 0 ]; then 531 | error "Unable to identify device-number for $iface in IMDS" 532 | exit 1 533 | fi 534 | 535 | # Newly provisioned resources (new ENI attachments) take some 536 | # time to be fully reflected in IMDS. In that case, we poll 537 | # for a period of time to ensure we've captured all the 538 | # sources needed for policy routing. When refreshing an 539 | # existing ENI attachment's configuration, we skip the 540 | # polling. 541 | local -i deadline 542 | deadline=$(date -d "now+30 seconds" +%s) 543 | while [ "$(date +%s)" -lt $deadline ]; do 544 | local -i changes=0 545 | 546 | changes+=$(create_interface_config "$iface" "$device_number" "$network_card" "$ether") 547 | for family in 4 6; do 548 | if ! _is_primary_interface "$ether"; then 549 | # We only create rules for secondary interfaces so 550 | # external tools that modify the main route table can 551 | # still communicate with the host's primary IPs. For 552 | # example, considering a host with address 10.1.2.3 on 553 | # ens5 (device-number-0) and a container communicating 554 | # on a docker0 bridge interface, the expectation is 555 | # that the container can communicate with 10.1.2.3 in 556 | # both directions. If we install policy rules, 557 | # they'll redirect the return traffic out ens5 rather 558 | # than docker0, effectively blackholing it. 559 | # https://github.com/amazonlinux/amazon-ec2-net-utils/issues/97 560 | changes+=$(create_rules "$iface" "$device_number" "$network_card" $family) 561 | fi 562 | done 563 | changes+=$(create_ipv4_aliases $iface $ether) 564 | 565 | if [ ! -v EC2_IF_INITIAL_SETUP ] || 566 | [ "$changes" -gt 0 ]; then 567 | break 568 | fi 569 | done 570 | echo $changes 571 | } 572 | 573 | # All instances of this process that may reconfigure networkd register 574 | # themselves as such. When exiting, they'll reload networkd only if 575 | # they're the registered process running. 576 | maybe_reload_networkd() { 577 | rm -f "${lockdir}/${iface}" 578 | if rmdir "$lockdir" 2> /dev/null; then 579 | if [ -e "$reload_flag" ]; then 580 | rm -f "$reload_flag" 2> /dev/null 581 | networkctl reload 582 | info "Reloaded networkd" 583 | else 584 | debug "No networkd reload needed" 585 | fi 586 | else 587 | debug "Deferring networkd reload to another process" 588 | fi 589 | } 590 | 591 | 592 | register_networkd_reloader() { 593 | local -i registered=1 cnt=0 594 | local -i max=10000 595 | local -r lockfile="${lockdir}/${iface}" 596 | local old_opts=$- 597 | 598 | # Disable -o errexit in the following block so we can capture 599 | # nonzero exit codes from a redirect without considering them 600 | # fatal errors 601 | set +e 602 | while [ $cnt -lt $max ]; do 603 | cnt+=1 604 | mkdir -p "$lockdir" 605 | trap 'debug "Called trap" ; maybe_reload_networkd' EXIT 606 | # If the redirect fails, most likely because the target file 607 | # already exists and -o noclobber is in effect, $? will be set 608 | # nonzero. If it succeeds, it is set to 0 609 | echo $$ > "${lockfile}" 610 | # shellcheck disable=SC2320 611 | registered=$? 612 | [ $registered -eq 0 ] && break 613 | sleep 0.1 614 | if (( $cnt % 100 == 0 )); then 615 | info "Unable to lock ${iface} after ${cnt} tries." 616 | fi 617 | done 618 | # re-enable -o errexit if it had originally been set 619 | [[ $old_opts = *e* ]] && set -e 620 | 621 | # If registered is still nonzero when we get here, we have failed 622 | # to create the lock. Log this and exit. 623 | if [ $registered -ne 0 ]; then 624 | local msg="Unable to lock configuration for ${iface}." 625 | error "$(printf "%s Check pid %d", "$msg", "$(cat "${lockfile}")")" 626 | exit 1 627 | fi 628 | } 629 | -------------------------------------------------------------------------------- /sysctl/90-ipv6-dad.conf: -------------------------------------------------------------------------------- 1 | net.ipv6.conf.default.accept_dad=0 2 | -------------------------------------------------------------------------------- /systemd/network/80-ec2.network: -------------------------------------------------------------------------------- 1 | [Match] 2 | Driver=ena ixgbevf vif 3 | 4 | [Link] 5 | MTUBytes=9001 6 | 7 | [Network] 8 | DHCP=yes 9 | IPv6DuplicateAddressDetection=0 10 | LLMNR=no 11 | DNSDefaultRoute=yes 12 | 13 | [DHCPv4] 14 | UseHostname=no 15 | UseDNS=yes 16 | UseNTP=yes 17 | UseDomains=yes 18 | 19 | [DHCPv6] 20 | UseHostname=no 21 | UseDNS=yes 22 | UseNTP=yes 23 | WithoutRA=solicit 24 | -------------------------------------------------------------------------------- /systemd/system/policy-routes@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Set up policy routes for %I 3 | StartLimitIntervalSec=10 4 | StartLimitBurst=5 5 | Wants=refresh-policy-routes@%i.timer 6 | 7 | [Install] 8 | Also=refresh-policy-routes@%i.timer 9 | 10 | [Service] 11 | Type=oneshot 12 | RemainAfterExit=yes 13 | PrivateTmp=yes 14 | AmbientCapabilities=CAP_NET_ADMIN 15 | NoNewPrivileges=yes 16 | User=root 17 | ExecStart=/usr/bin/setup-policy-routes %i start 18 | Restart=on-failure 19 | RestartSec=1 20 | KillMode=process 21 | -------------------------------------------------------------------------------- /systemd/system/refresh-policy-routes@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Refresh policy routes for %I 3 | 4 | [Service] 5 | Type=oneshot 6 | PrivateTmp=yes 7 | AmbientCapabilities=CAP_NET_ADMIN 8 | NoNewPrivileges=yes 9 | User=root 10 | ExecStart=/usr/bin/setup-policy-routes %i refresh 11 | SuccessExitStatus=SIGTERM 12 | KillMode=process 13 | -------------------------------------------------------------------------------- /systemd/system/refresh-policy-routes@.timer: -------------------------------------------------------------------------------- 1 | [Timer] 2 | OnActiveSec=30 3 | OnUnitInactiveSec=60 4 | RandomizedDelaySec=5 5 | -------------------------------------------------------------------------------- /udev/99-vpc-policy-routes.rules: -------------------------------------------------------------------------------- 1 | SUBSYSTEM=="net", ACTION=="remove", ENV{ID_NET_DRIVER}=="vif|ena|ixgbevf", RUN+="/usr/bin/systemctl disable --now refresh-policy-routes@$name.timer policy-routes@$name.service" 2 | SUBSYSTEM=="net", ACTION=="add", ENV{ID_NET_DRIVER}=="vif|ena|ixgbevf", RUN+="/usr/bin/systemctl enable --now --no-block policy-routes@$name.service" 3 | --------------------------------------------------------------------------------