编辑
2024-02-19
网络
00
请注意,本文编写于 242 天前,最后修改于 242 天前,其中某些信息可能已经过时。

目录

wmproxy
项目地址
设计目标
IP解析示例
定义类
获取本机地址
参数获取
绑定多个地址
总结

wmproxy

wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 负载均衡, 静态文件服务器,websocket代理,四层TCP/UDP转发,内网穿透等,会将实现过程分享出来,感兴趣的可以一起造个轮子

项目地址

国内: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

设计目标

快速的设置多IP绑定,及IP端口段的支持,方便快速的自定义能力。

IP解析示例

以下是常见的IP解析示例情况,本地ip为192.168.0.100示例:

  • 正常IP解析

    • 127.0.0.1:8869 解析成 ipv4 127.0.0.1 端口 8869,只接受本地来的连接信息
    • 0.0.0.0:8869 解析成 ipv4 0.0.0.0 端口 8869,可接受所有来自ipv4的连接信息
  • :开头的地址,且不包含-

    • :8869 解析成 ipv4 127.0.0.1 端口 8869 及 ipv4 192.168.0.100 端口 8869
  • 包含-的地址

    • :8869-:8871 解析成 ipv4 127.0.0.1 端口 8869 - 8871 三个端口地址 及 ipv4 192.168.0.100 端口 8869 - 8871 三个端口地址,总共6个端口地址
    • 127.0.0.1:8869-:8871 解析成 ipv4 127.0.0.1 端口 8869 - 8871 三个端口地址 总共3个端口地址
    • 127.0.0.1:8869-192.168.0.100:8871 解析成 ipv4 127.0.0.1 端口 8869 - 8871 三个端口地址 总共3个端口地址,忽略后面的地址,只接受端口号
  • 手动多个地址,可以空格或者,做间隔

    • 127.0.0.1:8869 127.0.0.1:8899 192.168.0.100:8899 就相应的解析成三个端口地址

定义类

由于解析出来的地址可能是多个或者个单个,这里用数组来进行表示

rust
#[derive(Debug, Clone)] pub struct WrapVecAddr(pub Vec<SocketAddr>);

通常序列化会用到FromStr将字符串转化成类 反序列化都会用到Display将类转化成字符串 所以在这里,我们将实现FromStrDisplay

rust
impl FromStr for WrapVecAddr { type Err = AddrParseError; fn from_str(s: &str) -> Result<Self, Self::Err> { // 范围的如:8080-:8090, 表示11端口 if s.contains("-") { let vals = s .split(&['-']) .filter(|s| !s.is_empty()) .collect::<Vec<&str>>(); let start = parse_socker_addr(vals[0])?; if vals.len() != 2 { return Ok(WrapVecAddr(start)); } else { let end = parse_socker_addr(vals[1])?; let mut results = vec![]; for port in start[0].port()..=end[1].port() { for idx in &start { let mut addr = idx.clone(); addr.set_port(port); results.push(addr); } } return Ok(WrapVecAddr(results)); } } else { let vals = s .split(&[',', ' ']) .filter(|s| !s.is_empty()) .collect::<Vec<&str>>(); let mut results = vec![]; for s in vals { results.extend(parse_socker_addr(s)?); } Ok(WrapVecAddr(results)) } } } impl Display for WrapVecAddr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut values = vec![]; for a in &self.0 { values.push(format!("{}", a)); } f.write_str(&values.join(",")) } }

这样子后我们将配置加上就可以自动实现序列化及反序列化了

rust
#[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerConfig { #[serde_as(as = "DisplayFromStr")] pub bind_addr: WrapVecAddr, // ... }

获取本机地址

通过库local-ip-address获取本地IP地址,再根据缺省IP时添加本地IP地址访问,因为缺省时有可能是需要本地内网进行访问。所以需要补上本地网卡地址。所以我们在解析以:时做了特殊处理:

rust
fn parse_socker_addr(s: &str) -> Result<Vec<SocketAddr>, AddrParseError> { if s.starts_with(":") { let port = s.trim_start_matches(':'); let mut results = vec![]; if let Ok(port) = port.parse::<u16>() { if let Ok(v) = local_ip() { results.push(SocketAddr::new(v, port)); } if let Ok(v) = local_ipv6() { results.push(SocketAddr::new(v, port)); } results.push(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port)); } else { results.push(format!("127.0.0.1{s}").parse::<SocketAddr>()?); } Ok(results) } else { let addr = s.parse::<SocketAddr>()?; Ok(vec![addr]) } }

参数获取

以下举例file-server的参数

rust
#[derive(Debug, Clone, Bpaf)] #[allow(dead_code)] struct FileServerConfig { /// 静态文件根目录路径 #[bpaf(short, long, fallback(String::new()))] pub(crate) root: String, #[bpaf( short, long, fallback(WrapVecAddr(vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8869)])), display_fallback )] /// 监听地址 pub(crate) listen: WrapVecAddr, #[bpaf(long)] /// 监听地址 pub(crate) listen_ssl: Option<WrapVecAddr>, /// ... }

如此我们就可以轻松的用SSL监听及普通的监听添加多个端口的支持。

bash
wmproxy file-server --listen :8869-8871

此时我们可以同时监听3个端口均支持文件服务器。此时我们就可以轻松控制多个端口地址。

绑定多个地址

以下是负载均衡中的绑定示例

rust
for v in &value.bind_addr.0 { if bind_addr_set.contains(&v) { continue; } bind_addr_set.insert(v); let url = format!("http://{}", v); log::info!("HTTP服务:{},提供http处理及转发功能。", Style::new().blink().green().apply_to(url)); let listener = Helper::bind(v).await?; listeners.push(listener); tlss.push(false); } for v in &value.bind_ssl.0 { if bind_addr_set.contains(&v) { continue; } bind_addr_set.insert(v); if !is_ssl { return Err(crate::ProxyError::Extension("配置SSL端口但未配置证书")); } let url = format!("https://{}", v); log::info!("HTTPs服务:{},提供https处理及转发功能。", Style::new().blink().green().apply_to(url)); let listener = Helper::bind(v).await?; listeners.push(listener); tlss.push(is_ssl); }

支持ssl绑定及非ssl绑定同一个location。

其中链接信息使用了console,输出了绿色的,可以点击的url链接。可以方便在启动的时候进行点击。

image.png

图中圈圈位置是可以点击跳转成url,方便本地开发环境的时候测试使用。

总结

通过FromStrDisplay的重定义,我们可以支持更强大的自定义的序列化操作,系统绑定端口既认端口号也认绑定IP,所以我们可以对同个端口进行多次绑定。

点击 [关注][在看][点赞] 是对作者最大的支持

本文作者:问蒙服务框架

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!