nreplops_tool/
routes.rs

1// routes.rs
2// Copyright 2022 Matti Hänninen
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5// use this file except in compliance with the License. You may obtain a copy of
6// the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13// License for the specific language governing permissions and limitations under
14// the License.
15
16use std::net;
17
18use crate::{
19  conn_expr::{Addr, ConnectionExpr, Port, PortSet, RouteExpr},
20  error::Error,
21  host_options::HostOptionsTable,
22};
23
24pub fn resolve_routes(
25  conn_expr: &ConnectionExpr,
26  host_opts_table: &HostOptionsTable,
27) -> Result<Routes, Error> {
28  use ConnectionExpr::*;
29  let route_expr = match conn_expr {
30    RouteExpr(ref e) => e,
31    HostKey(ref k) => host_opts_table
32      .get(k)
33      .ok_or_else(|| Error::HostKeyNotFound(k.to_string()))?
34      .conn_expr
35      .try_as_route_expr()
36      .ok_or_else(|| Error::RecursiveHostKeysNotSupported(k.to_string()))?,
37  };
38  Ok(Routes {
39    inner: RoutesInner::try_from_route_expr(route_expr)?,
40    pos: 0,
41  })
42}
43
44#[derive(Clone, Debug)]
45pub enum Route {
46  Direct(net::SocketAddr),
47  // Note that we let the ssh client to resolve the ssh server's address and,
48  // likewise, the ssh server to resolve to final host's address.  This way
49  // the name resolution behaves the same as it would when you debug it by
50  // hand with the actual ssh client.
51  Tunneled(TunnelOptions),
52}
53
54#[derive(Clone, Debug)]
55pub struct TunnelOptions {
56  pub ssh_user: Option<String>,
57  pub ssh_addr: Addr,
58  pub ssh_port: Option<Port>,
59  pub host_addr: Addr,
60  pub host_port: Port,
61}
62
63#[derive(Clone, Debug)]
64pub struct Routes {
65  inner: RoutesInner,
66  pos: usize,
67}
68
69impl Iterator for Routes {
70  type Item = Route;
71
72  fn next(&mut self) -> Option<Self::Item> {
73    if self.pos < self.inner.len() {
74      let item = self.inner.produce(self.pos);
75      self.pos += 1;
76      Some(item)
77    } else {
78      None
79    }
80  }
81}
82
83#[derive(Clone, Debug)]
84enum RoutesInner {
85  Direct {
86    ips: Vec<net::IpAddr>,
87    ports: PortSet,
88  },
89  Tunneled {
90    ssh_user: Option<String>,
91    ssh_addr: Addr,
92    ssh_ports: Option<PortSet>,
93    host_addr: Addr,
94    host_ports: PortSet,
95  },
96}
97
98impl RoutesInner {
99  fn try_from_route_expr(route_expr: &RouteExpr) -> Result<Self, Error> {
100    if let Some(ref tunnel) = route_expr.tunnel {
101      let host_addr = route_expr
102        .addr
103        .as_ref()
104        .expect("tunneling should guarantee final host address")
105        .clone();
106      Ok(RoutesInner::Tunneled {
107        ssh_user: tunnel.user.clone(),
108        ssh_addr: tunnel.addr.clone(),
109        ssh_ports: tunnel.ports.clone(),
110        host_addr,
111        host_ports: route_expr.ports.clone(),
112      })
113    } else {
114      let mut ips = match route_expr.addr {
115        None => dns_lookup::lookup_host("localhost")
116          .map_err(|_| Error::DomainNotFound("localhost".to_owned()))?,
117        Some(Addr::Domain(ref domain)) => dns_lookup::lookup_host(domain)
118          .map_err(|_| Error::DomainNotFound(domain.clone()))?,
119        Some(Addr::IP(ip)) => vec![ip],
120      };
121      ips.sort();
122      Ok(RoutesInner::Direct {
123        ips,
124        ports: route_expr.ports.clone(),
125      })
126    }
127  }
128
129  fn len(&self) -> usize {
130    match self {
131      RoutesInner::Direct { ips: addrs, ports } => {
132        addrs.len() * ports.as_slice().len()
133      }
134      RoutesInner::Tunneled {
135        ssh_ports: None,
136        host_ports,
137        ..
138      } => host_ports.as_slice().len(),
139      RoutesInner::Tunneled {
140        ssh_ports: Some(ssh_ports),
141        host_ports,
142        ..
143      } => ssh_ports.as_slice().len() * host_ports.as_slice().len(),
144    }
145  }
146
147  fn produce(&self, ix: usize) -> Route {
148    assert!(ix < self.len());
149    match self {
150      RoutesInner::Direct { ips: addrs, ports } => {
151        // Iterate resolved addresses first and given ports second
152        let ix_addr = ix % addrs.len();
153        let ix_port = ix / addrs.len();
154        Route::Direct(net::SocketAddr::new(
155          addrs[ix_addr],
156          ports.as_slice()[ix_port],
157        ))
158      }
159      RoutesInner::Tunneled {
160        ssh_user,
161        ssh_addr,
162        ssh_ports: None,
163        host_addr,
164        host_ports,
165      } => Route::Tunneled(TunnelOptions {
166        ssh_user: ssh_user.clone(),
167        ssh_addr: ssh_addr.clone(),
168        ssh_port: None,
169        host_addr: host_addr.clone(),
170        host_port: host_ports.as_slice()[ix],
171      }),
172      RoutesInner::Tunneled {
173        ssh_user,
174        ssh_addr,
175
176        ssh_ports: Some(ssh_ports),
177        host_addr,
178        host_ports,
179      } => {
180        // Iterate ssh host's ports first and final host's ports second
181        let ssh_ports = ssh_ports.as_slice();
182        let ix_ssh_port = ix % ssh_ports.len();
183        let ix_host_port = ix / ssh_ports.len();
184        Route::Tunneled(TunnelOptions {
185          ssh_user: ssh_user.clone(),
186          ssh_addr: ssh_addr.clone(),
187          ssh_port: Some(ssh_ports[ix_ssh_port]),
188          host_addr: host_addr.clone(),
189          host_port: host_ports.as_slice()[ix_host_port],
190        })
191      }
192    }
193  }
194}
195
196// FIXME: Tests