forwarding: make https work for port forwarding (#213943)

Closes https://github.com/microsoft/vscode/issues/201465
This commit is contained in:
Connor Peet 2024-05-30 21:42:51 -07:00 committed by GitHub
parent b82c3b9087
commit 54dd0ecc65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 93 additions and 27 deletions

View File

@ -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()
})

View File

@ -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<u16, PortCount>;
type PortMap = HashMap<u16, PortMapRec>;
/// 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
),
}
}
}

View File

@ -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<Result<String, AnyError>>),
@ -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);
}

View File

@ -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<PortRec>;

View File

@ -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) {