ngrok/config/
tcp.rs

1use std::{
2    collections::HashMap,
3    convert::From,
4};
5
6use url::Url;
7
8use super::{
9    common::ProxyProto,
10    Policy,
11};
12// These are used for doc comment links.
13#[allow(unused_imports)]
14use crate::config::{
15    ForwarderBuilder,
16    TunnelBuilder,
17};
18use crate::{
19    config::common::{
20        default_forwards_to,
21        CommonOpts,
22        TunnelConfig,
23    },
24    internals::proto::{
25        self,
26        BindExtra,
27        BindOpts,
28    },
29    tunnel::TcpTunnel,
30    Session,
31};
32
33/// The options for a TCP edge.
34#[derive(Default, Clone)]
35struct TcpOptions {
36    pub(crate) common_opts: CommonOpts,
37    pub(crate) remote_addr: Option<String>,
38    pub(crate) bindings: Vec<String>,
39}
40
41impl TunnelConfig for TcpOptions {
42    fn forwards_to(&self) -> String {
43        self.common_opts
44            .forwards_to
45            .clone()
46            .unwrap_or(default_forwards_to().into())
47    }
48    fn extra(&self) -> BindExtra {
49        BindExtra {
50            token: Default::default(),
51            ip_policy_ref: Default::default(),
52            metadata: self.common_opts.metadata.clone().unwrap_or_default(),
53            bindings: self.bindings.clone(),
54            pooling_enabled: self.common_opts.pooling_enabled.unwrap_or(false),
55        }
56    }
57    fn proto(&self) -> String {
58        "tcp".into()
59    }
60
61    fn forwards_proto(&self) -> String {
62        // not supported
63        String::new()
64    }
65
66    fn verify_upstream_tls(&self) -> bool {
67        self.common_opts.verify_upstream_tls()
68    }
69
70    fn opts(&self) -> Option<BindOpts> {
71        // fill out all the options, translating to proto here
72        let mut tcp_endpoint = proto::TcpEndpoint::default();
73
74        if let Some(remote_addr) = self.remote_addr.as_ref() {
75            tcp_endpoint.addr = remote_addr.clone();
76        }
77        tcp_endpoint.proxy_proto = self.common_opts.proxy_proto;
78
79        tcp_endpoint.ip_restriction = self.common_opts.ip_restriction();
80
81        tcp_endpoint.traffic_policy = if self.common_opts.traffic_policy.is_some() {
82            self.common_opts.traffic_policy.clone().map(From::from)
83        } else if self.common_opts.policy.is_some() {
84            self.common_opts.policy.clone().map(From::from)
85        } else {
86            None
87        };
88        Some(BindOpts::Tcp(tcp_endpoint))
89    }
90    fn labels(&self) -> HashMap<String, String> {
91        HashMap::new()
92    }
93}
94
95impl_builder! {
96    /// A builder for a tunnel backing a TCP endpoint.
97    ///
98    /// https://ngrok.com/docs/tcp/
99    TcpTunnelBuilder, TcpOptions, TcpTunnel, endpoint
100}
101
102/// The options for a TCP edge.
103impl TcpTunnelBuilder {
104    /// Add the provided CIDR to the allowlist.
105    ///
106    /// https://ngrok.com/docs/tcp/ip-restrictions/
107    pub fn allow_cidr(&mut self, cidr: impl Into<String>) -> &mut Self {
108        self.options.common_opts.cidr_restrictions.allow(cidr);
109        self
110    }
111    /// Add the provided CIDR to the denylist.
112    ///
113    /// https://ngrok.com/docs/tcp/ip-restrictions/
114    pub fn deny_cidr(&mut self, cidr: impl Into<String>) -> &mut Self {
115        self.options.common_opts.cidr_restrictions.deny(cidr);
116        self
117    }
118    /// Sets the PROXY protocol version for connections over this tunnel.
119    pub fn proxy_proto(&mut self, proxy_proto: ProxyProto) -> &mut Self {
120        self.options.common_opts.proxy_proto = proxy_proto;
121        self
122    }
123    /// Sets the opaque metadata string for this tunnel.
124    ///
125    /// https://ngrok.com/docs/api/resources/tunnels/#tunnel-fields
126    pub fn metadata(&mut self, metadata: impl Into<String>) -> &mut Self {
127        self.options.common_opts.metadata = Some(metadata.into());
128        self
129    }
130    /// Sets the ingress configuration for this endpoint
131    pub fn binding(&mut self, binding: impl Into<String>) -> &mut Self {
132        self.options.bindings.push(binding.into());
133        self
134    }
135    /// Sets the ForwardsTo string for this tunnel. This can be viewed via the
136    /// API or dashboard.
137    ///
138    /// This overrides the default process info if using
139    /// [TunnelBuilder::listen], and is in turn overridden by the url provided
140    /// to [ForwarderBuilder::listen_and_forward].
141    ///
142    /// https://ngrok.com/docs/api/resources/tunnels/#tunnel-fields
143    pub fn forwards_to(&mut self, forwards_to: impl Into<String>) -> &mut Self {
144        self.options.common_opts.forwards_to = Some(forwards_to.into());
145        self
146    }
147
148    /// Disables backend TLS certificate verification for forwards from this tunnel.
149    pub fn verify_upstream_tls(&mut self, verify_upstream_tls: bool) -> &mut Self {
150        self.options
151            .common_opts
152            .set_verify_upstream_tls(verify_upstream_tls);
153        self
154    }
155
156    /// Sets the TCP address to request for this edge.
157    ///
158    /// https://ngrok.com/docs/network-edge/domains-and-tcp-addresses/#tcp-addresses
159    pub fn remote_addr(&mut self, remote_addr: impl Into<String>) -> &mut Self {
160        self.options.remote_addr = Some(remote_addr.into());
161        self
162    }
163
164    /// DEPRECATED: use traffic_policy instead.
165    pub fn policy<S>(&mut self, s: S) -> Result<&mut Self, S::Error>
166    where
167        S: TryInto<Policy>,
168    {
169        self.options.common_opts.policy = Some(s.try_into()?);
170        Ok(self)
171    }
172
173    /// Set policy for this edge.
174    pub fn traffic_policy(&mut self, policy_str: impl Into<String>) -> &mut Self {
175        self.options.common_opts.traffic_policy = Some(policy_str.into());
176        self
177    }
178
179    pub(crate) async fn for_forwarding_to(&mut self, to_url: &Url) -> &mut Self {
180        self.options.common_opts.for_forwarding_to(to_url);
181        self
182    }
183
184    /// Allows the endpoint to pool with other endpoints with the same host/port/binding
185    pub fn pooling_enabled(&mut self, pooling_enabled: impl Into<bool>) -> &mut Self {
186        self.options.common_opts.pooling_enabled = Some(pooling_enabled.into());
187        self
188    }
189}
190
191#[cfg(test)]
192mod test {
193    use super::*;
194    use crate::config::policies::test::POLICY_JSON;
195    const BINDING: &str = "public";
196    const METADATA: &str = "testmeta";
197    const TEST_FORWARD: &str = "testforward";
198    const REMOTE_ADDR: &str = "4.tcp.ngrok.io:1337";
199    const ALLOW_CIDR: &str = "0.0.0.0/0";
200    const DENY_CIDR: &str = "10.1.1.1/32";
201
202    #[test]
203    fn test_interface_to_proto() {
204        // pass to a function accepting the trait to avoid
205        // "creates a temporary which is freed while still in use"
206        tunnel_test(
207            &TcpTunnelBuilder {
208                session: None,
209                options: Default::default(),
210            }
211            .allow_cidr(ALLOW_CIDR)
212            .deny_cidr(DENY_CIDR)
213            .proxy_proto(ProxyProto::V2)
214            .metadata(METADATA)
215            .binding(BINDING)
216            .remote_addr(REMOTE_ADDR)
217            .forwards_to(TEST_FORWARD)
218            .policy(POLICY_JSON)
219            .unwrap()
220            .options,
221        );
222    }
223
224    fn tunnel_test<C>(tunnel_cfg: &C)
225    where
226        C: TunnelConfig,
227    {
228        assert_eq!(TEST_FORWARD, tunnel_cfg.forwards_to());
229
230        let extra = tunnel_cfg.extra();
231        assert_eq!(String::default(), *extra.token);
232        assert_eq!(METADATA, extra.metadata);
233        assert_eq!(Vec::from([BINDING]), extra.bindings);
234        assert_eq!(String::default(), extra.ip_policy_ref);
235
236        assert_eq!("tcp", tunnel_cfg.proto());
237
238        let opts = tunnel_cfg.opts().unwrap();
239        assert!(matches!(opts, BindOpts::Tcp { .. }));
240        if let BindOpts::Tcp(endpoint) = opts {
241            assert_eq!(REMOTE_ADDR, endpoint.addr);
242            assert!(matches!(endpoint.proxy_proto, ProxyProto::V2));
243
244            let ip_restriction = endpoint.ip_restriction.unwrap();
245            assert_eq!(Vec::from([ALLOW_CIDR]), ip_restriction.allow_cidrs);
246            assert_eq!(Vec::from([DENY_CIDR]), ip_restriction.deny_cidrs);
247        }
248
249        assert_eq!(HashMap::new(), tunnel_cfg.labels());
250    }
251}