From 5c05f01fd5cbb20b9363748355542007150a6542 Mon Sep 17 00:00:00 2001 From: Kumi Date: Sun, 5 Nov 2023 10:47:50 +0100 Subject: [PATCH] Untested base implementation --- .gitignore | 2 ++ LICENSE | 19 ++++++++++++++++ README.md | 19 ++++++++++++++++ rotator.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 rotator.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..029519e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.py[cod] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2cab9cb --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023 Private.coffee Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..02758b6 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# IPv6 Rotator + +This is a simple IPv6 rotator script that will assign a new random IPv6 address to a given interface and use it for requests to given subnets. + +## Usage + +```bash +python3 rotator.py +``` + +The script will automatically detect the interface with the default route and use it for the requests, and select a random IPv6 address from the /64 subnet of the default IP address. It will then add the IP to the network interface and create the required routes for the given subnets. + +## Note + +This script is not complete and is not intended to be used in production. It is currently only a PoC to show how to rotate IPv6 addresses on a Linux machine. Running it may cause your machine to lose IPv6 connectivity altogether. Don't run it on a machine you don't have physical access to. + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/rotator.py b/rotator.py new file mode 100644 index 0000000..8c05050 --- /dev/null +++ b/rotator.py @@ -0,0 +1,64 @@ +import socket +import random +import os +import subprocess +import ipaddress + +# Google subnets to route through the random IP +SUBNETS = [ + "2001:4860:4000::/36", + "2404:6800:4000::/36", + "2607:f8b0:4000::/36", + "2800:3f0:4000::/36", + "2a00:1450:4000::/36", + "2c0f:fb50:4000::/36", + ] + +TEST_IP_EXTERNAL = "2a0d:f302:111:38cc::1" # A public IPv6 NOT in the subnet + +# 1) Get the machine's current primary IPv6 address +def get_current_ipv6(): + s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) + s.connect((TEST_IP_EXTERNAL, 80)) + current_ip = s.getsockname()[0] + return current_ip + +# 2) Generate a random IP address within the /64 subnet of the current primary IPv6 +def generate_random_ipv6(ipv6): + # Parse the original IP address and network + original = ipaddress.ip_interface(ipv6) + + # Generate a random interface identifier + random_int = random.randint(1, 2**64 - 1) + new_ip = ipaddress.IPv6Interface((int(original.network.network_address) | random_int, original.network.prefixlen)) + + return str(new_ip.ip) + +def main(): + current_ipv6 = get_current_ipv6() + + # Get network interface and gateway + default_route = subprocess.check_output("ip -6 route show default", shell=True).decode('utf-8') + + iface = default_route.split("dev ")[1].split()[0] + current_gateway = default_route.split("via ")[1].split()[0] + + random_ipv6 = generate_random_ipv6(current_ipv6) + + # 2.5) Remove any existing non-given IPv6 addresses from previous runs of the scripts + prev_ips_raw_output = subprocess.check_output(f"ip -6 addr show {iface} scope global", shell=True).decode('utf-8') + + prev_ips = [line.strip().split(" ")[1].split("/")[0] for line in prev_ips_raw_output.split("\n") if "inet6" in line] + for ip in prev_ips: + if ip != current_ipv6: + os.popen(f"sudo ip -6 addr del {ip} dev {iface}") + + # 3) Add new IP to the interface + os.popen(f"sudo ip -6 addr add {random_ipv6} dev {iface}") + + # 4) Set up routes such that the random IP address is now used as the source address for requests to given IPv6 subnets + for subnet in SUBNETS: + os.popen(f"sudo ip -6 route add {subnet} from {random_ipv6} dev {iface} via {current_gateway}") + +if __name__ == "__main__": + main() \ No newline at end of file