show TCP/UDP/IPv6 in tooltip (#12613)

* add punch type log

Signed-off-by: 21pages <sunboeasy@gmail.com>

* show TCP/UDP/IPv6 in tooltip

Signed-off-by: 21pages <sunboeasy@gmail.com>

* Skip udp punch if udp nat port is 0

Signed-off-by: 21pages <sunboeasy@gmail.com>

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
21pages
2025-08-11 16:13:31 +08:00
committed by GitHub
parent 77064cc2f8
commit a0659a277a
14 changed files with 156 additions and 52 deletions

View File

@@ -3910,3 +3910,24 @@ String get appName {
} }
return _appName; return _appName;
} }
String getConnectionText(bool secure, bool direct, String streamType) {
String connectionText;
if (secure && direct) {
connectionText = translate("Direct and encrypted connection");
} else if (secure && !direct) {
connectionText = translate("Relayed and encrypted connection");
} else if (!secure && direct) {
connectionText = translate("Direct and unencrypted connection");
} else {
connectionText = translate("Relayed and unencrypted connection");
}
if (streamType == 'Relay') {
streamType = 'TCP';
}
if (streamType.isEmpty) {
return connectionText;
} else {
return '$connectionText ($streamType)';
}
}

View File

@@ -77,9 +77,11 @@ class CurrentDisplayState {
class ConnectionType { class ConnectionType {
final Rx<String> _secure = kInvalidValueStr.obs; final Rx<String> _secure = kInvalidValueStr.obs;
final Rx<String> _direct = kInvalidValueStr.obs; final Rx<String> _direct = kInvalidValueStr.obs;
final Rx<String> _stream_type = kInvalidValueStr.obs;
Rx<String> get secure => _secure; Rx<String> get secure => _secure;
Rx<String> get direct => _direct; Rx<String> get direct => _direct;
Rx<String> get stream_type => _stream_type;
static String get strSecure => 'secure'; static String get strSecure => 'secure';
static String get strInsecure => 'insecure'; static String get strInsecure => 'insecure';
@@ -94,9 +96,14 @@ class ConnectionType {
_direct.value = v ? strDirect : strIndirect; _direct.value = v ? strDirect : strIndirect;
} }
void setStreamType(String v) {
_stream_type.value = v;
}
bool isValid() { bool isValid() {
return _secure.value != kInvalidValueStr && return _secure.value != kInvalidValueStr &&
_direct.value != kInvalidValueStr; _direct.value != kInvalidValueStr &&
_stream_type.value != kInvalidValueStr;
} }
} }

View File

@@ -146,16 +146,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
connectionType.secure.value == ConnectionType.strSecure; connectionType.secure.value == ConnectionType.strSecure;
bool direct = bool direct =
connectionType.direct.value == ConnectionType.strDirect; connectionType.direct.value == ConnectionType.strDirect;
String msgConn; String msgConn = getConnectionText(
if (secure && direct) { secure, direct, connectionType.stream_type.value);
msgConn = translate("Direct and encrypted connection");
} else if (secure && !direct) {
msgConn = translate("Relayed and encrypted connection");
} else if (!secure && direct) {
msgConn = translate("Direct and unencrypted connection");
} else {
msgConn = translate("Relayed and unencrypted connection");
}
var msgFingerprint = '${translate('Fingerprint')}:\n'; var msgFingerprint = '${translate('Fingerprint')}:\n';
var fingerprint = FingerprintState.find(key).value; var fingerprint = FingerprintState.find(key).value;
if (fingerprint.isEmpty) { if (fingerprint.isEmpty) {

View File

@@ -145,16 +145,8 @@ class _ViewCameraTabPageState extends State<ViewCameraTabPage> {
connectionType.secure.value == ConnectionType.strSecure; connectionType.secure.value == ConnectionType.strSecure;
bool direct = bool direct =
connectionType.direct.value == ConnectionType.strDirect; connectionType.direct.value == ConnectionType.strDirect;
String msgConn; String msgConn = getConnectionText(
if (secure && direct) { secure, direct, connectionType.stream_type.value);
msgConn = translate("Direct and encrypted connection");
} else if (secure && !direct) {
msgConn = translate("Relayed and encrypted connection");
} else if (!secure && direct) {
msgConn = translate("Direct and unencrypted connection");
} else {
msgConn = translate("Relayed and unencrypted connection");
}
var msgFingerprint = '${translate('Fingerprint')}:\n'; var msgFingerprint = '${translate('Fingerprint')}:\n';
var fingerprint = FingerprintState.find(key).value; var fingerprint = FingerprintState.find(key).value;
if (fingerprint.isEmpty) { if (fingerprint.isEmpty) {

View File

@@ -40,7 +40,12 @@ void _disableAndroidSoftKeyboard({bool? isKeyboardVisible}) {
} }
class RemotePage extends StatefulWidget { class RemotePage extends StatefulWidget {
RemotePage({Key? key, required this.id, this.password, this.isSharedPassword, this.forceRelay}) RemotePage(
{Key? key,
required this.id,
this.password,
this.isSharedPassword,
this.forceRelay})
: super(key: key); : super(key: key);
final String id; final String id;
@@ -1105,7 +1110,7 @@ void showOptions(
BuildContext context, String id, OverlayDialogManager dialogManager) async { BuildContext context, String id, OverlayDialogManager dialogManager) async {
var displays = <Widget>[]; var displays = <Widget>[];
final pi = gFFI.ffiModel.pi; final pi = gFFI.ffiModel.pi;
final image = gFFI.ffiModel.getConnectionImage(); final image = gFFI.ffiModel.getConnectionImageText();
if (image != null) { if (image != null) {
displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image)); displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image));
} }

View File

@@ -39,7 +39,11 @@ void _disableAndroidSoftKeyboard({bool? isKeyboardVisible}) {
class ViewCameraPage extends StatefulWidget { class ViewCameraPage extends StatefulWidget {
ViewCameraPage( ViewCameraPage(
{Key? key, required this.id, this.password, this.isSharedPassword, this.forceRelay}) {Key? key,
required this.id,
this.password,
this.isSharedPassword,
this.forceRelay})
: super(key: key); : super(key: key);
final String id; final String id;
@@ -579,7 +583,7 @@ void showOptions(
BuildContext context, String id, OverlayDialogManager dialogManager) async { BuildContext context, String id, OverlayDialogManager dialogManager) async {
var displays = <Widget>[]; var displays = <Widget>[];
final pi = gFFI.ffiModel.pi; final pi = gFFI.ffiModel.pi;
final image = gFFI.ffiModel.getConnectionImage(); final image = gFFI.ffiModel.getConnectionImageText();
if (image != null) { if (image != null) {
displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image)); displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image));
} }

View File

@@ -61,6 +61,7 @@ class CachedPeerData {
bool secure = false; bool secure = false;
bool direct = false; bool direct = false;
String streamType = '';
CachedPeerData(); CachedPeerData();
@@ -74,6 +75,7 @@ class CachedPeerData {
'permissions': permissions, 'permissions': permissions,
'secure': secure, 'secure': secure,
'direct': direct, 'direct': direct,
'streamType': streamType,
}); });
} }
@@ -92,6 +94,7 @@ class CachedPeerData {
}); });
data.secure = map['secure']; data.secure = map['secure'];
data.direct = map['direct']; data.direct = map['direct'];
data.streamType = map['streamType'];
return data; return data;
} catch (e) { } catch (e) {
debugPrint('Failed to parse CachedPeerData: $e'); debugPrint('Failed to parse CachedPeerData: $e');
@@ -223,27 +226,45 @@ class FfiModel with ChangeNotifier {
timerScreenshot?.cancel(); timerScreenshot?.cancel();
} }
setConnectionType(String peerId, bool secure, bool direct) { setConnectionType(
String peerId, bool secure, bool direct, String streamType) {
cachedPeerData.secure = secure; cachedPeerData.secure = secure;
cachedPeerData.direct = direct; cachedPeerData.direct = direct;
cachedPeerData.streamType = streamType;
_secure = secure; _secure = secure;
_direct = direct; _direct = direct;
try { try {
var connectionType = ConnectionTypeState.find(peerId); var connectionType = ConnectionTypeState.find(peerId);
connectionType.setSecure(secure); connectionType.setSecure(secure);
connectionType.setDirect(direct); connectionType.setDirect(direct);
connectionType.setStreamType(streamType);
} catch (e) { } catch (e) {
// //
} }
} }
Widget? getConnectionImage() { Widget? getConnectionImageText() {
if (secure == null || direct == null) { if (secure == null || direct == null) {
return null; return null;
} else { } else {
final icon = final icon =
'${secure == true ? 'secure' : 'insecure'}${direct == true ? '' : '_relay'}'; '${secure == true ? 'secure' : 'insecure'}${direct == true ? '' : '_relay'}';
return SvgPicture.asset('assets/$icon.svg', width: 48, height: 48); final iconWidget =
SvgPicture.asset('assets/$icon.svg', width: 48, height: 48);
String connectionText =
getConnectionText(secure!, direct!, cachedPeerData.streamType);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
iconWidget,
SizedBox(height: 4),
Text(
connectionText,
style: TextStyle(fontSize: 12),
textAlign: TextAlign.center,
),
],
);
} }
} }
@@ -260,7 +281,7 @@ class FfiModel with ChangeNotifier {
'link': '', 'link': '',
}, sessionId, peerId); }, sessionId, peerId);
updatePrivacyMode(data.updatePrivacyMode, sessionId, peerId); updatePrivacyMode(data.updatePrivacyMode, sessionId, peerId);
setConnectionType(peerId, data.secure, data.direct); setConnectionType(peerId, data.secure, data.direct, data.streamType);
await handlePeerInfo(data.peerInfo, peerId, true); await handlePeerInfo(data.peerInfo, peerId, true);
for (final element in data.cursorDataList) { for (final element in data.cursorDataList) {
updateLastCursorId(element); updateLastCursorId(element);
@@ -289,8 +310,8 @@ class FfiModel with ChangeNotifier {
} else if (name == 'sync_platform_additions') { } else if (name == 'sync_platform_additions') {
handlePlatformAdditions(evt, sessionId, peerId); handlePlatformAdditions(evt, sessionId, peerId);
} else if (name == 'connection_ready') { } else if (name == 'connection_ready') {
setConnectionType( setConnectionType(peerId, evt['secure'] == 'true',
peerId, evt['secure'] == 'true', evt['direct'] == 'true'); evt['direct'] == 'true', evt['stream_type'] ?? '');
} else if (name == 'switch_display') { } else if (name == 'switch_display') {
// switch display is kept for backward compatibility // switch display is kept for backward compatibility
handleSwitchDisplay(evt, sessionId, peerId); handleSwitchDisplay(evt, sessionId, peerId);

View File

@@ -192,7 +192,13 @@ impl Client {
conn_type: ConnType, conn_type: ConnType,
interface: impl Interface, interface: impl Interface,
) -> ResultType<( ) -> ResultType<(
(Stream, bool, Option<Vec<u8>>, Option<KcpStream>), (
Stream,
bool,
Option<Vec<u8>>,
Option<KcpStream>,
&'static str,
),
(i32, String), (i32, String),
)> { )> {
debug_assert!(peer == interface.get_id()); debug_assert!(peer == interface.get_id());
@@ -219,7 +225,13 @@ impl Client {
conn_type: ConnType, conn_type: ConnType,
interface: impl Interface, interface: impl Interface,
) -> ResultType<( ) -> ResultType<(
(Stream, bool, Option<Vec<u8>>, Option<KcpStream>), (
Stream,
bool,
Option<Vec<u8>>,
Option<KcpStream>,
&'static str,
),
(i32, String), (i32, String),
)> { )> {
if config::is_incoming_only() { if config::is_incoming_only() {
@@ -234,6 +246,7 @@ impl Client {
true, true,
None, None,
None, None,
"TCP",
), ),
(0, "".to_owned()), (0, "".to_owned()),
)); ));
@@ -246,6 +259,7 @@ impl Client {
true, true,
None, None,
None, None,
"TCP",
), ),
(0, "".to_owned()), (0, "".to_owned()),
)); ));
@@ -257,7 +271,7 @@ impl Client {
} else { } else {
(peer, "", key, token) (peer, "", key, token)
}; };
let (mut rendezvous_server, servers, contained) = if other_server.is_empty() { let (rendezvous_server, servers, contained) = if other_server.is_empty() {
crate::get_rendezvous_server(1_000).await crate::get_rendezvous_server(1_000).await
} else { } else {
if other_server == PUBLIC_SERVER { if other_server == PUBLIC_SERVER {
@@ -346,7 +360,13 @@ impl Client {
servers: Vec<String>, servers: Vec<String>,
contained: bool, contained: bool,
) -> ResultType<( ) -> ResultType<(
(Stream, bool, Option<Vec<u8>>, Option<KcpStream>), (
Stream,
bool,
Option<Vec<u8>>,
Option<KcpStream>,
&'static str,
),
(i32, String), (i32, String),
)> { )> {
let mut start = Instant::now(); let mut start = Instant::now();
@@ -417,6 +437,12 @@ impl Client {
(None, None) (None, None)
}; };
let udp_nat_port = udp.1.map(|x| *x.lock().unwrap()).unwrap_or(0); let udp_nat_port = udp.1.map(|x| *x.lock().unwrap()).unwrap_or(0);
if udp.0.is_some() && udp_nat_port == 0 {
let err_msg = "skip udp punch because udp nat port is 0";
log::info!("{}", err_msg);
bail!(err_msg);
}
let punch_type = if udp_nat_port > 0 { "UDP" } else { "TCP" };
msg_out.set_punch_hole_request(PunchHoleRequest { msg_out.set_punch_hole_request(PunchHoleRequest {
id: peer.to_owned(), id: peer.to_owned(),
token: token.to_owned(), token: token.to_owned(),
@@ -430,7 +456,13 @@ impl Client {
..Default::default() ..Default::default()
}); });
for i in 1..=3 { for i in 1..=3 {
log::info!("#{} punch attempt with {}, id: {}", i, my_addr, peer); log::info!(
"#{} {} punch attempt with {}, id: {}",
i,
punch_type,
my_addr,
peer
);
socket.send(&msg_out).await?; socket.send(&msg_out).await?;
// below timeout should not bigger than hbbs's connection timeout. // below timeout should not bigger than hbbs's connection timeout.
if let Some(msg_in) = if let Some(msg_in) =
@@ -481,7 +513,7 @@ impl Client {
} }
} }
} }
log::info!("Hole Punched {} = {}", peer, peer_addr); log::info!("{} Hole Punched {} = {}", punch_type, peer, peer_addr);
break; break;
} }
} }
@@ -529,7 +561,7 @@ impl Client {
log::info!("{:?} used to establish {typ} connection", start.elapsed()); log::info!("{:?} used to establish {typ} connection", start.elapsed());
let pk = let pk =
Self::secure_connection(&peer, signed_id_pk, &key, &mut conn).await?; Self::secure_connection(&peer, signed_id_pk, &key, &mut conn).await?;
return Ok(((conn, false, pk, kcp), (feedback, rendezvous_server))); return Ok(((conn, false, pk, kcp, typ), (feedback, rendezvous_server)));
} }
_ => { _ => {
log::error!("Unexpected protobuf msg received: {:?}", msg_in); log::error!("Unexpected protobuf msg received: {:?}", msg_in);
@@ -543,8 +575,9 @@ impl Client {
} }
let time_used = start.elapsed().as_millis() as u64; let time_used = start.elapsed().as_millis() as u64;
log::info!( log::info!(
"{} ms used to punch hole, relay_server: {}, {}", "{} ms used to {} punch hole, relay_server: {}, {}",
time_used, time_used,
punch_type,
relay_server, relay_server,
if is_local { if is_local {
"is_local: true".to_owned() "is_local: true".to_owned()
@@ -570,6 +603,7 @@ impl Client {
interface, interface,
udp.0, udp.0,
ipv6.0, ipv6.0,
punch_type,
) )
.await?, .await?,
(feedback, rendezvous_server), (feedback, rendezvous_server),
@@ -594,7 +628,14 @@ impl Client {
interface: impl Interface, interface: impl Interface,
udp_socket_nat: Option<Arc<UdpSocket>>, udp_socket_nat: Option<Arc<UdpSocket>>,
udp_socket_v6: Option<Arc<UdpSocket>>, udp_socket_v6: Option<Arc<UdpSocket>>,
) -> ResultType<(Stream, bool, Option<Vec<u8>>, Option<KcpStream>)> { punch_type: &str,
) -> ResultType<(
Stream,
bool,
Option<Vec<u8>>,
Option<KcpStream>,
&'static str,
)> {
let direct_failures = interface.get_lch().read().unwrap().direct_failures; let direct_failures = interface.get_lch().read().unwrap().direct_failures;
let mut connect_timeout = 0; let mut connect_timeout = 0;
const MIN: u64 = 1000; const MIN: u64 = 1000;
@@ -681,9 +722,14 @@ impl Client {
interface.get_lch().write().unwrap().set_direct_failure(n); interface.get_lch().write().unwrap().set_direct_failure(n);
} }
let mut conn = conn?; let mut conn = conn?;
log::info!("{:?} used to establish {typ} connection", start.elapsed()); log::info!(
"{:?} used to establish {typ} connection with {} punch",
start.elapsed(),
punch_type
);
let pk = Self::secure_connection(peer_id, signed_id_pk, key, &mut conn).await?; let pk = Self::secure_connection(peer_id, signed_id_pk, key, &mut conn).await?;
Ok((conn, direct, pk, kcp)) log::info!("{} punch secure_connection ok", punch_type);
Ok((conn, direct, pk, kcp, typ))
} }
/// Establish secure connection with the server. /// Establish secure connection with the server.

View File

@@ -174,13 +174,14 @@ impl<T: InvokeUiSession> Remote<T> {
) )
.await .await
{ {
Ok(((mut peer, direct, pk, kcp), (feedback, rendezvous_server))) => { Ok(((mut peer, direct, pk, kcp, stream_type), (feedback, rendezvous_server))) => {
self.handler self.handler
.connection_round_state .connection_round_state
.lock() .lock()
.unwrap() .unwrap()
.set_connected(); .set_connected();
self.handler.set_connection_type(peer.is_secured(), direct); // flutter -> connection_ready self.handler
.set_connection_type(peer.is_secured(), direct, stream_type); // flutter -> connection_ready
self.handler.update_direct(Some(direct)); self.handler.update_direct(Some(direct));
if conn_type == ConnType::DEFAULT_CONN || conn_type == ConnType::VIEW_CAMERA { if conn_type == ConnType::DEFAULT_CONN || conn_type == ConnType::VIEW_CAMERA {
self.handler self.handler

View File

@@ -716,12 +716,13 @@ impl InvokeUiSession for FlutterHandler {
); );
} }
fn set_connection_type(&self, is_secured: bool, direct: bool) { fn set_connection_type(&self, is_secured: bool, direct: bool, stream_type: &str) {
self.push_event( self.push_event(
"connection_ready", "connection_ready",
&[ &[
("secure", &is_secured.to_string()), ("secure", &is_secured.to_string()),
("direct", &direct.to_string()), ("direct", &direct.to_string()),
("stream_type", &stream_type.to_string()),
], ],
&[], &[],
); );

View File

@@ -118,7 +118,7 @@ async fn connect_and_login(
} else { } else {
ConnType::PORT_FORWARD ConnType::PORT_FORWARD
}; };
let ((mut stream, direct, _pk, _kcp), (feedback, rendezvous_server)) = let ((mut stream, direct, _pk, _kcp, _stream_type), (feedback, rendezvous_server)) =
Client::start(id, key, token, conn_type, interface.clone()).await?; Client::start(id, key, token, conn_type, interface.clone()).await?;
interface.update_direct(Some(direct)); interface.update_direct(Some(direct));
let mut buffer = Vec::new(); let mut buffer = Vec::new();

View File

@@ -117,6 +117,13 @@ class Header: Reactor.Component {
icon_conn = svg_insecure_relay; icon_conn = svg_insecure_relay;
title_conn = translate("Relayed and unencrypted connection"); title_conn = translate("Relayed and unencrypted connection");
} }
var stream_type = this.stream_type;
if (stream_type == "Relay") {
stream_type = "TCP";
}
if (stream_type) {
title_conn += " (" + stream_type + ")";
}
var title = get_id(); var title = get_id();
if (pi.hostname) title += "(" + pi.username + "@" + pi.hostname + ")"; if (pi.hostname) title += "(" + pi.username + "@" + pi.hostname + ")";
if ((pi.displays || []).length == 0) { if ((pi.displays || []).length == 0) {
@@ -695,10 +702,11 @@ function startChat() {
chatbox = view.window(params); chatbox = view.window(params);
} }
handler.setConnectionType = function(secured, direct) { handler.setConnectionType = function(secured, direct, stream_type) {
header.update({ header.update({
secure_connection: secured, secure_connection: secured,
direct_connection: direct, direct_connection: direct,
stream_type: stream_type,
}); });
} }

View File

@@ -178,8 +178,11 @@ impl InvokeUiSession for SciterHandler {
self.call("setCursorPosition", &make_args!(cp.x, cp.y)); self.call("setCursorPosition", &make_args!(cp.x, cp.y));
} }
fn set_connection_type(&self, is_secured: bool, direct: bool) { fn set_connection_type(&self, is_secured: bool, direct: bool, stream_type: &str) {
self.call("setConnectionType", &make_args!(is_secured, direct)); self.call(
"setConnectionType",
&make_args!(is_secured, direct, stream_type.to_string()),
);
} }
fn set_fingerprint(&self, _fingerprint: String) {} fn set_fingerprint(&self, _fingerprint: String) {}

View File

@@ -192,7 +192,11 @@ impl<T: InvokeUiSession> Session<T> {
} }
pub fn is_default(&self) -> bool { pub fn is_default(&self) -> bool {
self.lc.read().unwrap().conn_type.eq(&ConnType::DEFAULT_CONN) self.lc
.read()
.unwrap()
.conn_type
.eq(&ConnType::DEFAULT_CONN)
} }
pub fn is_view_camera(&self) -> bool { pub fn is_view_camera(&self) -> bool {
@@ -804,7 +808,6 @@ impl<T: InvokeUiSession> Session<T> {
self.send(Data::Message(msg_out)); self.send(Data::Message(msg_out));
} }
pub fn capture_displays(&self, add: Vec<i32>, sub: Vec<i32>, set: Vec<i32>) { pub fn capture_displays(&self, add: Vec<i32>, sub: Vec<i32>, set: Vec<i32>) {
let mut misc = Misc::new(); let mut misc = Misc::new();
misc.set_capture_displays(CaptureDisplays { misc.set_capture_displays(CaptureDisplays {
@@ -1611,7 +1614,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
fn set_permission(&self, name: &str, value: bool); fn set_permission(&self, name: &str, value: bool);
fn close_success(&self); fn close_success(&self);
fn update_quality_status(&self, qs: QualityStatus); fn update_quality_status(&self, qs: QualityStatus);
fn set_connection_type(&self, is_secured: bool, direct: bool); fn set_connection_type(&self, is_secured: bool, direct: bool, stream_type: &str);
fn set_fingerprint(&self, fingerprint: String); fn set_fingerprint(&self, fingerprint: String);
fn job_error(&self, id: i32, err: String, file_num: i32); fn job_error(&self, id: i32, err: String, file_num: i32);
fn job_done(&self, id: i32, file_num: i32); fn job_done(&self, id: i32, file_num: i32);