Commit 6278aae7 authored by zu1k's avatar zu1k

parse ipv6 cidr

Signed-off-by: default avatarzu1k <i@zu1k.com>
parent a94d5962
This diff is collapsed.
...@@ -2,18 +2,20 @@ ...@@ -2,18 +2,20 @@
name = "http-proxy-ipv6-pool" name = "http-proxy-ipv6-pool"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
authors = ["zu1k <i@lgf.im>"]
description = "Http proxy, every request from a separate IPv6 address."
readme = "README.md"
license = "MIT"
homepage = "https://github.com/zu1k/http-proxy-ipv6-pool"
repository = "https://github.com/zu1k/http-proxy-ipv6-pool"
[dependencies] [dependencies]
async-trait = "0.1" cidr = "0.2"
async-compression = { version = "0.3", features = ["tokio", "brotli", "gzip", "zlib", "zstd"] } getopts = "0.2"
env_logger = "0.9" hyper = { version = "0.14", features = ["client", "server", "http1", "runtime"] }
futures = "0.3" tokio = { version = "1", features = ["net", "rt-multi-thread", "macros", "io-util"] }
http = "0.2" rand = "0.8"
hyper = { version = "0.14", features = ["full"] }
log = "0.4"
tokio = { version = "1", features = ["full"] }
rand = "0.8.5"
[profile.release] [profile.release]
......
MIT License
Copyright (c) 2022 zu1k
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.
# Http Proxy IPv6 Pool
Make every request from a separate IPv6 address.
## Tutorial
Assuming you already have an entire IPv6 subnet routed to your server, for me I purchased [Vultr's server](https://www.vultr.com/?ref=9039594-8H) to get one.
Get your IPv6 subnet prefix, for me is `2001:19f0:6001:48e4::/64`.
```sh
$ ip a
......
2: enp1s0: <BROADCAST,MULTICAST,ALLMULTI,UP,LOWER_UP> mtu 1500 qdisc fq state UP group default qlen 1000
......
inet6 2001:19f0:6001:48e4:5400:3ff:fefa:a71d/64 scope global dynamic mngtmpaddr
valid_lft 2591171sec preferred_lft 603971sec
......
```
Bind the whole IPv6 /64 subnet to loopback interface, and add route via default internet interface
```sh
ip add add local 2001:19f0:6001:48e4::/64 dev lo
ip route add local 2001:19f0:6001:48e4::/64 dev enp1s0
```
Open `ip_nonlocal_bind` for binding any IP address:
```sh
sysctl net.ipv6.ip_nonlocal_bind=1
```
For IPv6 NDP, install `ndppd`:
```sh
apt install ndppd
```
then edit `/etc/ndppd.conf`:
```conf
route-ttl 30000
proxy eth0 {
router no
timeout 500
ttl 30000
rule 2001:19f0:6001:48e4::/64 {
static
}
}
```
Now you can test by using `curl`:
```sh
$ curl --interface 2001:19f0:6001:48e4::1 ipv6.ip.sb
2001:19f0:6001:48e4::1
$ curl --interface 2001:19f0:6001:48e4::2 ipv6.ip.sb
2001:19f0:6001:48e4::2
```
Great!
Finally, use the http proxy provided by this project:
```sh
$ while true; do curl -x http://127.0.0.1:51080 ipv6.ip.sb; done
2001:19f0:6001:48e4:971e:f12c:e2e7:d92a
2001:19f0:6001:48e4:6d1c:90fe:ee79:1123
2001:19f0:6001:48e4:f7b9:b506:99d7:1be9
2001:19f0:6001:48e4:a06a:393b:e82f:bffc
2001:19f0:6001:48e4:245f:8272:2dfb:72ce
2001:19f0:6001:48e4:df9e:422c:f804:94f7
2001:19f0:6001:48e4:dd48:6ba2:ff76:f1af
2001:19f0:6001:48e4:1306:4a84:570c:f829
2001:19f0:6001:48e4:6f3:4eb:c958:ddfa
2001:19f0:6001:48e4:aa26:3bf9:6598:9e82
2001:19f0:6001:48e4:be6b:6a62:f8f7:a14d
2001:19f0:6001:48e4:b598:409d:b946:17c
```
## Author
**Http Proxy IPv6 Pool** © [zu1k](https://github.com/zu1k), Released under the [MIT](./LICENSE) License.<br>
> Blog [zu1k.com](https://zu1k.com) · GitHub [@zu1k](https://github.com/zu1k) · Twitter [@zu1k_lv](https://twitter.com/zu1k_lv) · Telegram Channel [@peekfun](https://t.me/peekfun)
mod proxy; mod proxy;
use log::{error, LevelFilter}; use cidr::Ipv6Cidr;
use getopts::Options;
use proxy::start_proxy; use proxy::start_proxy;
use std::{env, process::exit};
fn print_usage(program: &str, opts: Options) {
let brief = format!("Usage: {} [options]", program);
print!("{}", opts.usage(&brief));
}
fn main() {
let args: Vec<String> = env::args().collect();
let program = args[0].clone();
let mut opts = Options::new();
opts.optopt("b", "bind", "http proxy bind address", "BIND");
opts.optopt(
"i",
"ipv6-subnet",
"IPv6 Subnet: 2001:19f0:6001:48e4::/64",
"IPv6_SUBNET",
);
opts.optflag("h", "help", "print this help menu");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => {
panic!("{}", f.to_string())
}
};
if matches.opt_present("h") {
print_usage(&program, opts);
return;
}
let bind_addr = matches.opt_str("b").unwrap_or("0.0.0.0:51080".to_string());
let ipve_subnet = matches
.opt_str("i")
.unwrap_or("2001:19f0:6001:48e4::/64".to_string());
run(bind_addr, ipve_subnet)
}
#[tokio::main] #[tokio::main]
async fn main() { async fn run(bind_addr: String, ipv6_subnet: String) {
env_logger::builder() let ipv6 = match ipv6_subnet.parse::<Ipv6Cidr>() {
.filter_level(LevelFilter::Info) Ok(cidr) => {
.format_target(false) let a = cidr.first_address();
.parse_default_env() let b = cidr.network_length();
.init(); (a, b)
}
Err(_) => {
println!("invalid IPv6 subnet");
exit(1);
}
};
let bind_addr = match "0.0.0.0:51080".parse() { let bind_addr = match bind_addr.parse() {
Ok(b) => b, Ok(b) => b,
Err(e) => { Err(e) => {
error!("bind address not valid: {}", e); println!("bind address not valid: {}", e);
return; return;
} }
}; };
if let Err(e) = start_proxy(bind_addr).await { if let Err(e) = start_proxy(bind_addr, ipv6).await {
error!("{}", e); println!("{}", e);
} }
} }
...@@ -5,21 +5,24 @@ use hyper::{ ...@@ -5,21 +5,24 @@ use hyper::{
Body, Client, Method, Request, Response, Server, Body, Client, Method, Request, Response, Server,
}; };
use rand::Rng; use rand::Rng;
use std::{ use std::net::{IpAddr, Ipv6Addr, SocketAddr, ToSocketAddrs};
net::{IpAddr, Ipv6Addr, SocketAddr, ToSocketAddrs},
sync::{atomic::AtomicU64, Arc},
};
use tokio::{ use tokio::{
io::{AsyncRead, AsyncWrite}, io::{AsyncRead, AsyncWrite},
net::TcpSocket, net::TcpSocket,
}; };
pub async fn start_proxy(listen_addr: SocketAddr) -> Result<(), Box<dyn std::error::Error>> { pub async fn start_proxy(
let id = Arc::new(AtomicU64::new(0)); listen_addr: SocketAddr,
(ipv6, prefix_len): (Ipv6Addr, u8),
let make_service = make_service_fn(move |_: &AddrStream| { ) -> Result<(), Box<dyn std::error::Error>> {
let id = Arc::clone(&id); let make_service = make_service_fn(move |_: &AddrStream| async move {
async move { Ok::<_, hyper::Error>(service_fn(move |req| Proxy { id: id.clone() }.proxy(req))) } Ok::<_, hyper::Error>(service_fn(move |req| {
Proxy {
ipv6: ipv6.octets(),
prefix_len,
}
.proxy(req)
}))
}); });
Server::bind(&listen_addr) Server::bind(&listen_addr)
...@@ -32,42 +35,33 @@ pub async fn start_proxy(listen_addr: SocketAddr) -> Result<(), Box<dyn std::err ...@@ -32,42 +35,33 @@ pub async fn start_proxy(listen_addr: SocketAddr) -> Result<(), Box<dyn std::err
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct Proxy { pub(crate) struct Proxy {
pub id: Arc<AtomicU64>, pub ipv6: [u8; 16],
pub prefix_len: u8,
} }
impl Proxy { impl Proxy {
pub(crate) async fn proxy(self, req: Request<Body>) -> Result<Response<Body>, hyper::Error> { pub(crate) async fn proxy(self, req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
let id = self.id.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
match if req.method() == Method::CONNECT { match if req.method() == Method::CONNECT {
self.process_connect(req, id).await self.process_connect(req).await
} else { } else {
self.process_request(req, id).await self.process_request(req).await
} { } {
Ok(resp) => Ok(resp), Ok(resp) => Ok(resp),
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
async fn process_connect( async fn process_connect(self, req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
self,
req: Request<Body>,
id: u64,
) -> Result<Response<Body>, hyper::Error> {
tokio::task::spawn(async move { tokio::task::spawn(async move {
let remote_addr = req.uri().authority().map(|auth| auth.to_string()).unwrap(); let remote_addr = req.uri().authority().map(|auth| auth.to_string()).unwrap();
let mut upgraded = hyper::upgrade::on(req).await.unwrap(); let mut upgraded = hyper::upgrade::on(req).await.unwrap();
tunnel(&mut upgraded, remote_addr, id).await self.tunnel(&mut upgraded, remote_addr).await
}); });
Ok(Response::new(Body::empty())) Ok(Response::new(Body::empty()))
} }
async fn process_request( async fn process_request(self, req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
self, let bind_addr = get_rand_ipv6(self.ipv6, self.prefix_len);
req: Request<Body>,
_id: u64,
) -> Result<Response<Body>, hyper::Error> {
let bind_addr = get_rand_ipv6();
let mut http = HttpConnector::new(); let mut http = HttpConnector::new();
http.set_local_address(Some(bind_addr)); http.set_local_address(Some(bind_addr));
...@@ -78,49 +72,54 @@ impl Proxy { ...@@ -78,49 +72,54 @@ impl Proxy {
let res = client.request(req).await?; let res = client.request(req).await?;
Ok(res) Ok(res)
} }
}
async fn tunnel<A>(upgraded: &mut A, addr_str: String, _id: u64) -> std::io::Result<()> async fn tunnel<A>(self, upgraded: &mut A, addr_str: String) -> std::io::Result<()>
where where
A: AsyncRead + AsyncWrite + Unpin + ?Sized, A: AsyncRead + AsyncWrite + Unpin + ?Sized,
{ {
if let Ok(addrs) = addr_str.to_socket_addrs() { if let Ok(addrs) = addr_str.to_socket_addrs() {
for addr in addrs { for addr in addrs {
let socket = TcpSocket::new_v6()?; let socket = TcpSocket::new_v6()?;
let bind_addr = get_rand_ipv6_socket_addr(self.ipv6, self.prefix_len);
let bind_addr = get_rand_ipv6_socket_addr(); if socket.bind(bind_addr).is_ok() {
println!("{addr_str} via {bind_addr}");
println!("{addr_str} via {bind_addr}"); if let Ok(mut server) = socket.connect(addr).await {
tokio::io::copy_bidirectional(upgraded, &mut server).await?;
socket.bind(bind_addr).unwrap(); return Ok(());
if let Ok(mut server) = socket.connect(addr).await { }
tokio::io::copy_bidirectional(upgraded, &mut server).await?; }
return Ok(());
} }
} else {
println!("error: {addr_str}");
} }
} else {
println!("error: {addr_str}");
}
Ok(()) Ok(())
}
} }
fn get_rand_ipv6_socket_addr() -> SocketAddr { fn get_rand_ipv6_socket_addr(ipv6: [u8; 16], prefix_len: u8) -> SocketAddr {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
SocketAddr::new(get_rand_ipv6(), rng.gen::<u16>()) SocketAddr::new(get_rand_ipv6(ipv6, prefix_len), rng.gen::<u16>())
} }
fn get_rand_ipv6() -> IpAddr { fn get_rand_ipv6(ipv6: [u8; 16], prefix_len: u8) -> IpAddr {
let mut ipv6 = ipv6;
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let ipv6 = Ipv6Addr::new(
0x2001, let net_part = (prefix_len + 7) / 8;
0x19f0, let las = prefix_len & 8;
0x6001,
0x48e4, let mut cur = 15;
rng.gen::<u16>(), while cur > net_part - 1 {
rng.gen::<u16>(), ipv6[cur as usize] = rng.gen();
rng.gen::<u16>(), cur -= 1;
rng.gen::<u16>(), }
);
if las > 0 {
let mix: u8 = rng.gen();
ipv6[cur as usize] = ipv6[cur as usize] + mix >> las;
}
let ipv6 = Ipv6Addr::from(ipv6);
IpAddr::V6(ipv6) IpAddr::V6(ipv6)
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment