diff --git a/cli/src/tunnels/dev_tunnels.rs b/cli/src/tunnels/dev_tunnels.rs index 19ee3c2bf42..a964b446384 100644 --- a/cli/src/tunnels/dev_tunnels.rs +++ b/cli/src/tunnels/dev_tunnels.rs @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -use super::protocol::{self, PortPrivacy}; +use super::protocol::{self, PortPrivacy, PortProtocol}; use crate::auth; use crate::constants::{IS_INTERACTIVE_CLI, PROTOCOL_VERSION_TAG, TUNNEL_SERVICE_USER_AGENT}; use crate::state::{LauncherPaths, PersistedState}; @@ -221,8 +221,11 @@ impl ActiveTunnel { &self, port_number: u16, privacy: PortPrivacy, + protocol: PortProtocol, ) -> Result<(), AnyError> { - self.manager.add_port_tcp(port_number, privacy).await?; + self.manager + .add_port_tcp(port_number, privacy, protocol) + .await?; Ok(()) } @@ -972,13 +975,14 @@ impl ActiveTunnelManager { &self, port_number: u16, privacy: PortPrivacy, + protocol: PortProtocol, ) -> Result<(), WrappedError> { self.relay .lock() .await .add_port(&TunnelPort { port_number, - protocol: Some(TUNNEL_PROTOCOL_AUTO.to_owned()), + protocol: Some(protocol.to_contract_str().to_string()), access_control: Some(privacy_to_tunnel_acl(privacy)), ..Default::default() }) diff --git a/cli/src/tunnels/local_forwarding.rs b/cli/src/tunnels/local_forwarding.rs index e6410860cb0..93c2d244159 100644 --- a/cli/src/tunnels/local_forwarding.rs +++ b/cli/src/tunnels/local_forwarding.rs @@ -27,7 +27,7 @@ use super::{ protocol::{ self, forward_singleton::{PortList, SetPortsResponse}, - PortPrivacy, + PortPrivacy, PortProtocol, }, shutdown_signal::ShutdownSignal, }; @@ -71,8 +71,13 @@ impl PortCount { } } } +#[derive(Clone)] +struct PortMapRec { + count: PortCount, + protocol: PortProtocol, +} -type PortMap = HashMap; +type PortMap = HashMap; /// The PortForwardingHandle is given out to multiple consumers to allow /// them to set_ports that they want to be forwarded. @@ -99,8 +104,8 @@ impl PortForwardingSender { for p in current.iter() { if !ports.contains(p) { let n = v.get_mut(&p.number).expect("expected port in map"); - n[p.privacy] -= 1; - if n.is_empty() { + n.count[p.privacy] -= 1; + if n.count.is_empty() { v.remove(&p.number); } } @@ -110,12 +115,19 @@ impl PortForwardingSender { if !current.contains(p) { match v.get_mut(&p.number) { Some(n) => { - n[p.privacy] += 1; + n.count[p.privacy] += 1; + n.protocol = p.protocol; } None => { - let mut pc = PortCount::default(); - pc[p.privacy] += 1; - v.insert(p.number, pc); + let mut count = PortCount::default(); + count[p.privacy] += 1; + v.insert( + p.number, + PortMapRec { + count, + protocol: p.protocol, + }, + ); } }; } @@ -164,22 +176,34 @@ impl PortForwardingReceiver { while self.receiver.changed().await.is_ok() { let next = self.receiver.borrow().clone(); - for (port, count) in current.iter() { - let privacy = count.primary_privacy(); - if !matches!(next.get(port), Some(n) if n.primary_privacy() == privacy) { + for (port, rec) in current.iter() { + let privacy = rec.count.primary_privacy(); + if !matches!(next.get(port), Some(n) if n.count.primary_privacy() == privacy) { match tunnel.remove_port(*port).await { - Ok(_) => info!(log, "stopped forwarding port {} at {:?}", *port, privacy), - Err(e) => error!(log, "failed to stop forwarding port {}: {}", port, e), + Ok(_) => info!( + log, + "stopped forwarding {} port {} at {:?}", rec.protocol, *port, privacy + ), + Err(e) => error!( + log, + "failed to stop forwarding {} port {}: {}", rec.protocol, port, e + ), } } } - for (port, count) in next.iter() { - let privacy = count.primary_privacy(); - if !matches!(current.get(port), Some(n) if n.primary_privacy() == privacy) { - match tunnel.add_port_tcp(*port, privacy).await { - Ok(_) => info!(log, "forwarding port {} at {:?}", port, privacy), - Err(e) => error!(log, "failed to forward port {}: {}", port, e), + for (port, rec) in next.iter() { + let privacy = rec.count.primary_privacy(); + if !matches!(current.get(port), Some(n) if n.count.primary_privacy() == privacy) { + match tunnel.add_port_tcp(*port, privacy, rec.protocol).await { + Ok(_) => info!( + log, + "forwarding {} port {} at {:?}", rec.protocol, port, privacy + ), + Err(e) => error!( + log, + "failed to forward {} port {}: {}", rec.protocol, port, e + ), } } } diff --git a/cli/src/tunnels/port_forwarder.rs b/cli/src/tunnels/port_forwarder.rs index 30267e8bc86..b05ae95ae40 100644 --- a/cli/src/tunnels/port_forwarder.rs +++ b/cli/src/tunnels/port_forwarder.rs @@ -12,7 +12,10 @@ use crate::{ util::errors::{AnyError, CannotForwardControlPort, ServerHasClosed}, }; -use super::{dev_tunnels::ActiveTunnel, protocol::PortPrivacy}; +use super::{ + dev_tunnels::ActiveTunnel, + protocol::{PortPrivacy, PortProtocol}, +}; pub enum PortForwardingRec { Forward(u16, PortPrivacy, oneshot::Sender>), @@ -89,7 +92,9 @@ impl PortForwardingProcessor { } if !self.forwarded.contains(&port) { - tunnel.add_port_tcp(port, privacy).await?; + tunnel + .add_port_tcp(port, privacy, PortProtocol::Auto) + .await?; self.forwarded.insert(port); } diff --git a/cli/src/tunnels/protocol.rs b/cli/src/tunnels/protocol.rs index d26ea978068..3654826c57e 100644 --- a/cli/src/tunnels/protocol.rs +++ b/cli/src/tunnels/protocol.rs @@ -299,10 +299,40 @@ pub enum PortPrivacy { Private, } +#[derive(Serialize, Deserialize, PartialEq, Copy, Eq, Clone, Debug)] +#[serde(rename_all = "lowercase")] +pub enum PortProtocol { + Auto, + Http, + Https, +} + +impl std::fmt::Display for PortProtocol { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_contract_str()) + } +} + +impl Default for PortProtocol { + fn default() -> Self { + Self::Auto + } +} + +impl PortProtocol { + pub fn to_contract_str(&self) -> &'static str { + match *self { + Self::Auto => tunnels::contracts::TUNNEL_PROTOCOL_AUTO, + Self::Http => tunnels::contracts::TUNNEL_PROTOCOL_HTTP, + Self::Https => tunnels::contracts::TUNNEL_PROTOCOL_HTTPS, + } + } +} + pub mod forward_singleton { use serde::{Deserialize, Serialize}; - use super::PortPrivacy; + use super::{PortPrivacy, PortProtocol}; pub const METHOD_SET_PORTS: &str = "set_ports"; @@ -310,6 +340,7 @@ pub mod forward_singleton { pub struct PortRec { pub number: u16, pub privacy: PortPrivacy, + pub protocol: PortProtocol, } pub type PortList = Vec; diff --git a/extensions/tunnel-forwarding/src/extension.ts b/extensions/tunnel-forwarding/src/extension.ts index 3dc88224aaa..299c728719f 100644 --- a/extensions/tunnel-forwarding/src/extension.ts +++ b/extensions/tunnel-forwarding/src/extension.ts @@ -37,6 +37,7 @@ class Tunnel implements vscode.Tunnel { constructor( public readonly remoteAddress: { port: number; host: string }, public readonly privacy: TunnelPrivacyId, + public readonly protocol: 'http' | 'https', ) { } public setPortFormat(formatString: string) { @@ -82,7 +83,7 @@ export async function activate(context: vscode.ExtensionContext) { { tunnelFeatures: { elevation: false, - protocol: false, + protocol: true, privacyOptions: [ { themeIcon: 'globe', id: TunnelPrivacyId.Public, label: vscode.l10n.t('Public') }, { themeIcon: 'lock', id: TunnelPrivacyId.Private, label: vscode.l10n.t('Private') }, @@ -152,6 +153,7 @@ class TunnelProvider implements vscode.TunnelProvider { const tunnel = new Tunnel( tunnelOptions.remoteAddress, (tunnelOptions.privacy as TunnelPrivacyId) || TunnelPrivacyId.Private, + tunnelOptions.protocol === 'https' ? 'https' : 'http', ); this.tunnels.add(tunnel); @@ -238,7 +240,7 @@ class TunnelProvider implements vscode.TunnelProvider { return; } - const ports = [...this.tunnels].map(t => ({ number: t.remoteAddress.port, privacy: t.privacy })); + const ports = [...this.tunnels].map(t => ({ number: t.remoteAddress.port, privacy: t.privacy, protocol: t.protocol })); this.state.process.stdin.write(`${JSON.stringify(ports)}\n`); if (ports.length === 0 && !this.state.cleanupTimeout) {