3

49从零开始用Rust编写nginx,我竟然在同一个端口上绑定了多少IP - 问蒙服务框架

 7 months ago
source link: https://www.cnblogs.com/wmproxy/p/18020327/wmproxy49
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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 就相应的解析成三个端口地址

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

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

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

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(",")) }}

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

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

获取本机地址

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

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的参数

#[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监听及普通的监听添加多个端口的支持。

wmproxy file-server --listen :8869-8871

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

绑定多个地址

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

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链接。可以方便在启动的时候进行点击。
2631821-20240219083722305-320450475.png

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

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK