Browse Source

sync rustdesk's hbb_common here

Huabing Zhou 3 years ago
parent
commit
2314783d42

+ 30 - 0
Cargo.lock

@@ -197,6 +197,9 @@ name = "bytes"
197
 version = "1.2.0"
197
 version = "1.2.0"
198
 source = "registry+https://github.com/rust-lang/crates.io-index"
198
 source = "registry+https://github.com/rust-lang/crates.io-index"
199
 checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e"
199
 checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e"
200
+dependencies = [
201
+ "serde",
202
+]
200
 
203
 
201
 [[package]]
204
 [[package]]
202
 name = "cc"
205
 name = "cc"
@@ -458,6 +461,18 @@ version = "0.3.0"
458
 source = "registry+https://github.com/rust-lang/crates.io-index"
461
 source = "registry+https://github.com/rust-lang/crates.io-index"
459
 checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
462
 checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
460
 
463
 
464
+[[package]]
465
+name = "dns-lookup"
466
+version = "1.0.8"
467
+source = "registry+https://github.com/rust-lang/crates.io-index"
468
+checksum = "53ecafc952c4528d9b51a458d1a8904b81783feff9fde08ab6ed2545ff396872"
469
+dependencies = [
470
+ "cfg-if",
471
+ "libc",
472
+ "socket2 0.4.4",
473
+ "winapi",
474
+]
475
+
461
 [[package]]
476
 [[package]]
462
 name = "dotenv"
477
 name = "dotenv"
463
 version = "0.15.0"
478
 version = "0.15.0"
@@ -738,6 +753,7 @@ version = "0.1.0"
738
 dependencies = [
753
 dependencies = [
739
  "anyhow",
754
  "anyhow",
740
  "bytes",
755
  "bytes",
756
+ "chrono",
741
  "confy",
757
  "confy",
742
  "directories-next",
758
  "directories-next",
743
  "dirs-next",
759
  "dirs-next",
@@ -748,6 +764,7 @@ dependencies = [
748
  "lazy_static",
764
  "lazy_static",
749
  "log",
765
  "log",
750
  "mac_address",
766
  "mac_address",
767
+ "machine-uid",
751
  "protobuf",
768
  "protobuf",
752
  "protobuf-codegen",
769
  "protobuf-codegen",
753
  "quinn",
770
  "quinn",
@@ -778,6 +795,7 @@ dependencies = [
778
  "chrono",
795
  "chrono",
779
  "clap",
796
  "clap",
780
  "deadpool",
797
  "deadpool",
798
+ "dns-lookup",
781
  "flexi_logger",
799
  "flexi_logger",
782
  "hbb_common",
800
  "hbb_common",
783
  "headers",
801
  "headers",
@@ -790,6 +808,7 @@ dependencies = [
790
  "machine-uid",
808
  "machine-uid",
791
  "minreq",
809
  "minreq",
792
  "once_cell",
810
  "once_cell",
811
+ "ping",
793
  "regex",
812
  "regex",
794
  "rust-ini",
813
  "rust-ini",
795
  "serde",
814
  "serde",
@@ -1440,6 +1459,17 @@ version = "0.1.0"
1440
 source = "registry+https://github.com/rust-lang/crates.io-index"
1459
 source = "registry+https://github.com/rust-lang/crates.io-index"
1441
 checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1460
 checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1442
 
1461
 
1462
+[[package]]
1463
+name = "ping"
1464
+version = "0.4.0"
1465
+source = "registry+https://github.com/rust-lang/crates.io-index"
1466
+checksum = "69044d1c00894fc1f43d9485aadb6ab6e68df90608fa52cf1074cda6420c6b76"
1467
+dependencies = [
1468
+ "rand",
1469
+ "socket2 0.4.4",
1470
+ "thiserror",
1471
+]
1472
+
1443
 [[package]]
1473
 [[package]]
1444
 name = "pkg-config"
1474
 name = "pkg-config"
1445
 version = "0.3.25"
1475
 version = "0.3.25"

+ 5 - 2
libs/hbb_common/Cargo.toml

@@ -11,7 +11,7 @@ protobuf = { version = "3.1", features = ["with-bytes"] }
11
 tokio = { version = "1.20", features = ["full"] }
11
 tokio = { version = "1.20", features = ["full"] }
12
 tokio-util = { version = "0.7", features = ["full"] }
12
 tokio-util = { version = "0.7", features = ["full"] }
13
 futures = "0.3"
13
 futures = "0.3"
14
-bytes = "1.2"
14
+bytes = { version = "1.2", features = ["serde"] }
15
 log = "0.4"
15
 log = "0.4"
16
 env_logger = "0.9"
16
 env_logger = "0.9"
17
 socket2 = { version = "0.3", features = ["reuseport"] }
17
 socket2 = { version = "0.3", features = ["reuseport"] }
@@ -30,15 +30,18 @@ filetime = "0.2"
30
 sodiumoxide = "0.2"
30
 sodiumoxide = "0.2"
31
 regex = "1.4"
31
 regex = "1.4"
32
 tokio-socks = { git = "https://github.com/open-trade/tokio-socks" }
32
 tokio-socks = { git = "https://github.com/open-trade/tokio-socks" }
33
+chrono = "0.4"
33
 
34
 
34
 [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
35
 [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
35
 mac_address = "1.1"
36
 mac_address = "1.1"
37
+machine-uid = "0.2"
36
 
38
 
37
 [features]
39
 [features]
38
 quic = []
40
 quic = []
41
+flatpak = []
39
 
42
 
40
 [build-dependencies]
43
 [build-dependencies]
41
-protobuf-codegen = "3.1"
44
+protobuf-codegen = { version = "3.1" }
42
 
45
 
43
 [target.'cfg(target_os = "windows")'.dependencies]
46
 [target.'cfg(target_os = "windows")'.dependencies]
44
 winapi = { version = "0.3", features = ["winuser"] }
47
 winapi = { version = "0.3", features = ["winuser"] }

+ 127 - 17
libs/hbb_common/protos/message.proto

@@ -1,13 +1,13 @@
1
 syntax = "proto3";
1
 syntax = "proto3";
2
 package hbb;
2
 package hbb;
3
 
3
 
4
-message VP9 {
4
+message EncodedVideoFrame {
5
   bytes data = 1;
5
   bytes data = 1;
6
   bool key = 2;
6
   bool key = 2;
7
   int64 pts = 3;
7
   int64 pts = 3;
8
 }
8
 }
9
 
9
 
10
-message VP9s { repeated VP9 frames = 1; }
10
+message EncodedVideoFrames { repeated EncodedVideoFrame frames = 1; }
11
 
11
 
12
 message RGB { bool compress = 1; }
12
 message RGB { bool compress = 1; }
13
 
13
 
@@ -19,9 +19,11 @@ message YUV {
19
 
19
 
20
 message VideoFrame {
20
 message VideoFrame {
21
   oneof union {
21
   oneof union {
22
-    VP9s vp9s = 6;
22
+    EncodedVideoFrames vp9s = 6;
23
     RGB rgb = 7;
23
     RGB rgb = 7;
24
     YUV yuv = 8;
24
     YUV yuv = 8;
25
+    EncodedVideoFrames h264s = 10;
26
+    EncodedVideoFrames h265s = 11;
25
   }
27
   }
26
   int64 timestamp = 9;
28
   int64 timestamp = 9;
27
 }
29
 }
@@ -38,6 +40,7 @@ message DisplayInfo {
38
   int32 height = 4;
40
   int32 height = 4;
39
   string name = 5;
41
   string name = 5;
40
   bool online = 6;
42
   bool online = 6;
43
+  bool cursor_embedded = 7;
41
 }
44
 }
42
 
45
 
43
 message PortForward {
46
 message PortForward {
@@ -61,10 +64,21 @@ message LoginRequest {
61
     PortForward port_forward = 8;
64
     PortForward port_forward = 8;
62
   }
65
   }
63
   bool video_ack_required = 9;
66
   bool video_ack_required = 9;
67
+  uint64 session_id = 10;
68
+  string version = 11;
64
 }
69
 }
65
 
70
 
66
 message ChatMessage { string text = 1; }
71
 message ChatMessage { string text = 1; }
67
 
72
 
73
+message Features {
74
+  bool privacy_mode = 1;
75
+}
76
+
77
+message SupportedEncoding {
78
+  bool h264 = 1;
79
+  bool h265 = 2;
80
+}
81
+
68
 message PeerInfo {
82
 message PeerInfo {
69
   string username = 1;
83
   string username = 1;
70
   string hostname = 2;
84
   string hostname = 2;
@@ -74,6 +88,8 @@ message PeerInfo {
74
   bool sas_enabled = 6;
88
   bool sas_enabled = 6;
75
   string version = 7;
89
   string version = 7;
76
   int32 conn_id = 8;
90
   int32 conn_id = 8;
91
+  Features features = 9;
92
+  SupportedEncoding encoding = 10;
77
 }
93
 }
78
 
94
 
79
 message LoginResponse {
95
 message LoginResponse {
@@ -90,6 +106,13 @@ message MouseEvent {
90
   repeated ControlKey modifiers = 4;
106
   repeated ControlKey modifiers = 4;
91
 }
107
 }
92
 
108
 
109
+enum KeyboardMode{
110
+  Legacy = 0;
111
+  Map = 1;
112
+  Translate = 2;
113
+  Auto = 3;
114
+}
115
+
93
 enum ControlKey {
116
 enum ControlKey {
94
   Unknown = 0;
117
   Unknown = 0;
95
   Alt = 1;
118
   Alt = 1;
@@ -183,6 +206,7 @@ message KeyEvent {
183
     string seq = 6;
206
     string seq = 6;
184
   }
207
   }
185
   repeated ControlKey modifiers = 8;
208
   repeated ControlKey modifiers = 8;
209
+  KeyboardMode mode = 9;
186
 }
210
 }
187
 
211
 
188
 message CursorData {
212
 message CursorData {
@@ -252,6 +276,7 @@ message FileAction {
252
     FileRemoveFile remove_file = 6;
276
     FileRemoveFile remove_file = 6;
253
     ReadAllFiles all_files = 7;
277
     ReadAllFiles all_files = 7;
254
     FileTransferCancel cancel = 8;
278
     FileTransferCancel cancel = 8;
279
+    FileTransferSendConfirmRequest send_confirm = 9;
255
   }
280
   }
256
 }
281
 }
257
 
282
 
@@ -263,14 +288,24 @@ message FileResponse {
263
     FileTransferBlock block = 2;
288
     FileTransferBlock block = 2;
264
     FileTransferError error = 3;
289
     FileTransferError error = 3;
265
     FileTransferDone done = 4;
290
     FileTransferDone done = 4;
291
+    FileTransferDigest digest = 5;
266
   }
292
   }
267
 }
293
 }
268
 
294
 
295
+message FileTransferDigest {
296
+  int32 id = 1;
297
+  sint32 file_num = 2;
298
+  uint64 last_modified = 3;
299
+  uint64 file_size = 4;
300
+  bool is_upload = 5;
301
+}
302
+
269
 message FileTransferBlock {
303
 message FileTransferBlock {
270
   int32 id = 1;
304
   int32 id = 1;
271
   sint32 file_num = 2;
305
   sint32 file_num = 2;
272
   bytes data = 3;
306
   bytes data = 3;
273
   bool compressed = 4;
307
   bool compressed = 4;
308
+  uint32 blk_id = 5;
274
 }
309
 }
275
 
310
 
276
 message FileTransferError {
311
 message FileTransferError {
@@ -283,6 +318,16 @@ message FileTransferSendRequest {
283
   int32 id = 1;
318
   int32 id = 1;
284
   string path = 2;
319
   string path = 2;
285
   bool include_hidden = 3;
320
   bool include_hidden = 3;
321
+  int32 file_num = 4;
322
+}
323
+
324
+message FileTransferSendConfirmRequest {
325
+  int32 id = 1;
326
+  sint32 file_num = 2;
327
+  oneof union {
328
+    bool skip = 3;
329
+    uint32 offset_blk = 4;
330
+  }
286
 }
331
 }
287
 
332
 
288
 message FileTransferDone {
333
 message FileTransferDone {
@@ -294,6 +339,7 @@ message FileTransferReceiveRequest {
294
   int32 id = 1;
339
   int32 id = 1;
295
   string path = 2; // path written to
340
   string path = 2; // path written to
296
   repeated FileEntry files = 3;
341
   repeated FileEntry files = 3;
342
+  int32 file_num = 4;
297
 }
343
 }
298
 
344
 
299
 message FileRemoveDir {
345
 message FileRemoveDir {
@@ -315,38 +361,31 @@ message FileDirCreate {
315
 
361
 
316
 // main logic from freeRDP
362
 // main logic from freeRDP
317
 message CliprdrMonitorReady {
363
 message CliprdrMonitorReady {
318
-  int32 conn_id = 1;
319
 }
364
 }
320
 
365
 
321
 message CliprdrFormat {
366
 message CliprdrFormat {
322
-  int32 conn_id = 1;
323
   int32 id = 2;
367
   int32 id = 2;
324
   string format = 3;
368
   string format = 3;
325
 }
369
 }
326
 
370
 
327
 message CliprdrServerFormatList {
371
 message CliprdrServerFormatList {
328
-  int32 conn_id = 1;
329
   repeated CliprdrFormat formats = 2;
372
   repeated CliprdrFormat formats = 2;
330
 }
373
 }
331
 
374
 
332
 message CliprdrServerFormatListResponse {
375
 message CliprdrServerFormatListResponse {
333
-  int32 conn_id = 1;
334
   int32 msg_flags = 2;
376
   int32 msg_flags = 2;
335
 }
377
 }
336
 
378
 
337
 message CliprdrServerFormatDataRequest {
379
 message CliprdrServerFormatDataRequest {
338
-  int32 conn_id = 1;
339
   int32 requested_format_id = 2;
380
   int32 requested_format_id = 2;
340
 }
381
 }
341
 
382
 
342
 message CliprdrServerFormatDataResponse {
383
 message CliprdrServerFormatDataResponse {
343
-  int32 conn_id = 1;
344
   int32 msg_flags = 2;
384
   int32 msg_flags = 2;
345
   bytes format_data = 3;
385
   bytes format_data = 3;
346
 }
386
 }
347
 
387
 
348
 message CliprdrFileContentsRequest {
388
 message CliprdrFileContentsRequest {
349
-  int32 conn_id = 1;
350
   int32 stream_id = 2;
389
   int32 stream_id = 2;
351
   int32 list_index = 3;
390
   int32 list_index = 3;
352
   int32 dw_flags = 4;
391
   int32 dw_flags = 4;
@@ -358,7 +397,6 @@ message CliprdrFileContentsRequest {
358
 }
397
 }
359
 
398
 
360
 message CliprdrFileContentsResponse {
399
 message CliprdrFileContentsResponse {
361
-  int32 conn_id = 1;
362
   int32 msg_flags = 3;
400
   int32 msg_flags = 3;
363
   int32 stream_id = 4;
401
   int32 stream_id = 4;
364
   bytes requested_data = 5;
402
   bytes requested_data = 5;
@@ -382,6 +420,7 @@ message SwitchDisplay {
382
   sint32 y = 3;
420
   sint32 y = 3;
383
   int32 width = 4;
421
   int32 width = 4;
384
   int32 height = 5;
422
   int32 height = 5;
423
+  bool cursor_embedded = 6;
385
 }
424
 }
386
 
425
 
387
 message PermissionInfo {
426
 message PermissionInfo {
@@ -390,6 +429,8 @@ message PermissionInfo {
390
     Clipboard = 2;
429
     Clipboard = 2;
391
     Audio = 3;
430
     Audio = 3;
392
     File = 4;
431
     File = 4;
432
+    Restart = 5;
433
+    Recording = 6;
393
   }
434
   }
394
 
435
 
395
   Permission permission = 1;
436
   Permission permission = 1;
@@ -403,6 +444,20 @@ enum ImageQuality {
403
   Best = 4;
444
   Best = 4;
404
 }
445
 }
405
 
446
 
447
+message VideoCodecState {
448
+  enum PerferCodec {
449
+    Auto = 0;
450
+    VPX = 1;
451
+    H264 = 2;
452
+    H265 = 3;
453
+  }
454
+
455
+  int32 score_vpx = 1;
456
+  int32 score_h264 = 2;
457
+  int32 score_h265 = 3;
458
+  PerferCodec perfer = 4;
459
+}
460
+
406
 message OptionMessage {
461
 message OptionMessage {
407
   enum BoolOption {
462
   enum BoolOption {
408
     NotSet = 0;
463
     NotSet = 0;
@@ -418,16 +473,15 @@ message OptionMessage {
418
   BoolOption disable_audio = 7;
473
   BoolOption disable_audio = 7;
419
   BoolOption disable_clipboard = 8;
474
   BoolOption disable_clipboard = 8;
420
   BoolOption enable_file_transfer = 9;
475
   BoolOption enable_file_transfer = 9;
421
-}
422
-
423
-message OptionResponse {
424
-  OptionMessage opt = 1;
425
-  string error = 2;
476
+  VideoCodecState video_codec_state = 10;
477
+  int32 custom_fps = 11;
426
 }
478
 }
427
 
479
 
428
 message TestDelay {
480
 message TestDelay {
429
   int64 time = 1;
481
   int64 time = 1;
430
   bool from_client = 2;
482
   bool from_client = 2;
483
+  uint32 last_delay = 3;
484
+  uint32 target_bitrate = 4;
431
 }
485
 }
432
 
486
 
433
 message PublicKey {
487
 message PublicKey {
@@ -447,6 +501,57 @@ message AudioFrame {
447
   int64 timestamp = 2;
501
   int64 timestamp = 2;
448
 }
502
 }
449
 
503
 
504
+// Notify peer to show message box.
505
+message MessageBox {
506
+  // Message type. Refer to flutter/lib/commom.dart/msgBox().
507
+  string msgtype = 1;
508
+  string title = 2;
509
+  // English
510
+  string text = 3;
511
+  // If not empty, msgbox provides a button to following the link.
512
+  // The link here can't be directly http url.
513
+  // It must be the key of http url configed in peer side or "rustdesk://*" (jump in app).
514
+  string link = 4;
515
+}
516
+
517
+message BackNotification {
518
+  // no need to consider block input by someone else
519
+  enum BlockInputState {
520
+    BlkStateUnknown = 0;
521
+    BlkOnSucceeded = 2;
522
+    BlkOnFailed = 3;
523
+    BlkOffSucceeded = 4;
524
+    BlkOffFailed = 5;
525
+  }
526
+  enum PrivacyModeState {
527
+    PrvStateUnknown = 0;
528
+    // Privacy mode on by someone else
529
+    PrvOnByOther = 2;
530
+    // Privacy mode is not supported on the remote side
531
+    PrvNotSupported = 3;
532
+    // Privacy mode on by self
533
+    PrvOnSucceeded = 4;
534
+    // Privacy mode on by self, but denied
535
+    PrvOnFailedDenied = 5;
536
+    // Some plugins are not found
537
+    PrvOnFailedPlugin = 6;
538
+    // Privacy mode on by self, but failed
539
+    PrvOnFailed = 7;
540
+    // Privacy mode off by self
541
+    PrvOffSucceeded = 8;
542
+    // Ctrl + P
543
+    PrvOffByPeer = 9;
544
+    // Privacy mode off by self, but failed
545
+    PrvOffFailed = 10;
546
+    PrvOffUnknown = 11;
547
+  }
548
+
549
+  oneof union {
550
+    PrivacyModeState privacy_mode_state = 1;
551
+    BlockInputState block_input_state = 2;
552
+  }
553
+}
554
+
450
 message Misc {
555
 message Misc {
451
   oneof union {
556
   oneof union {
452
     ChatMessage chat_message = 4;
557
     ChatMessage chat_message = 4;
@@ -456,8 +561,12 @@ message Misc {
456
     AudioFormat audio_format = 8;
561
     AudioFormat audio_format = 8;
457
     string close_reason = 9;
562
     string close_reason = 9;
458
     bool refresh_video = 10;
563
     bool refresh_video = 10;
459
-    OptionResponse option_response = 11;
460
     bool video_received = 12;
564
     bool video_received = 12;
565
+    BackNotification back_notification = 13;
566
+    bool restart_remote_device = 14;
567
+    bool uac = 15;
568
+    bool foreground_window_elevated = 16;
569
+    bool stop_service = 17;
461
   }
570
   }
462
 }
571
 }
463
 
572
 
@@ -481,5 +590,6 @@ message Message {
481
     FileResponse file_response = 18;
590
     FileResponse file_response = 18;
482
     Misc misc = 19;
591
     Misc misc = 19;
483
     Cliprdr cliprdr = 20;
592
     Cliprdr cliprdr = 20;
593
+    MessageBox message_box = 21;
484
   }
594
   }
485
 }
595
 }

File diff suppressed because it is too large
+ 413 - 85
libs/hbb_common/src/config.rs


+ 310 - 28
libs/hbb_common/src/fs.rs

@@ -1,13 +1,17 @@
1
-use crate::{bail, message_proto::*, ResultType};
1
+#[cfg(windows)]
2
+use std::os::windows::prelude::*;
2
 use std::path::{Path, PathBuf};
3
 use std::path::{Path, PathBuf};
4
+use std::time::{Duration, SystemTime, UNIX_EPOCH};
5
+
6
+use serde_derive::{Deserialize, Serialize};
7
+use tokio::{fs::File, io::*};
8
+
9
+use crate::{bail, get_version_number, message_proto::*, ResultType, Stream};
3
 // https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html
10
 // https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html
4
 use crate::{
11
 use crate::{
5
     compress::{compress, decompress},
12
     compress::{compress, decompress},
6
     config::{Config, COMPRESS_LEVEL},
13
     config::{Config, COMPRESS_LEVEL},
7
 };
14
 };
8
-#[cfg(windows)]
9
-use std::os::windows::prelude::*;
10
-use tokio::{fs::File, io::*};
11
 
15
 
12
 pub fn read_dir(path: &PathBuf, include_hidden: bool) -> ResultType<FileDirectory> {
16
 pub fn read_dir(path: &PathBuf, include_hidden: bool) -> ResultType<FileDirectory> {
13
     let mut dir = FileDirectory {
17
     let mut dir = FileDirectory {
@@ -184,16 +188,63 @@ pub fn get_recursive_files(path: &str, include_hidden: bool) -> ResultType<Vec<F
184
     read_dir_recursive(&get_path(path), &get_path(""), include_hidden)
188
     read_dir_recursive(&get_path(path), &get_path(""), include_hidden)
185
 }
189
 }
186
 
190
 
191
+#[inline]
192
+pub fn is_file_exists(file_path: &str) -> bool {
193
+    return Path::new(file_path).exists();
194
+}
195
+
196
+#[inline]
197
+pub fn can_enable_overwrite_detection(version: i64) -> bool {
198
+    version >= get_version_number("1.1.10")
199
+}
200
+
187
 #[derive(Default)]
201
 #[derive(Default)]
188
 pub struct TransferJob {
202
 pub struct TransferJob {
189
-    id: i32,
190
-    path: PathBuf,
191
-    files: Vec<FileEntry>,
192
-    file_num: i32,
203
+    pub id: i32,
204
+    pub remote: String,
205
+    pub path: PathBuf,
206
+    pub show_hidden: bool,
207
+    pub is_remote: bool,
208
+    pub is_last_job: bool,
209
+    pub file_num: i32,
210
+    pub files: Vec<FileEntry>,
211
+
193
     file: Option<File>,
212
     file: Option<File>,
194
     total_size: u64,
213
     total_size: u64,
195
     finished_size: u64,
214
     finished_size: u64,
196
     transferred: u64,
215
     transferred: u64,
216
+    enable_overwrite_detection: bool,
217
+    file_confirmed: bool,
218
+    // indicating the last file is skipped
219
+    file_skipped: bool,
220
+    file_is_waiting: bool,
221
+    default_overwrite_strategy: Option<bool>,
222
+}
223
+
224
+#[derive(Debug, Default, Serialize, Deserialize, Clone)]
225
+pub struct TransferJobMeta {
226
+    #[serde(default)]
227
+    pub id: i32,
228
+    #[serde(default)]
229
+    pub remote: String,
230
+    #[serde(default)]
231
+    pub to: String,
232
+    #[serde(default)]
233
+    pub show_hidden: bool,
234
+    #[serde(default)]
235
+    pub file_num: i32,
236
+    #[serde(default)]
237
+    pub is_remote: bool,
238
+}
239
+
240
+#[derive(Debug, Default, Serialize, Deserialize, Clone)]
241
+pub struct RemoveJobMeta {
242
+    #[serde(default)]
243
+    pub path: String,
244
+    #[serde(default)]
245
+    pub is_remote: bool,
246
+    #[serde(default)]
247
+    pub no_confirm: bool,
197
 }
248
 }
198
 
249
 
199
 #[inline]
250
 #[inline]
@@ -219,25 +270,54 @@ fn is_compressed_file(name: &str) -> bool {
219
 }
270
 }
220
 
271
 
221
 impl TransferJob {
272
 impl TransferJob {
222
-    pub fn new_write(id: i32, path: String, files: Vec<FileEntry>) -> Self {
273
+    pub fn new_write(
274
+        id: i32,
275
+        remote: String,
276
+        path: String,
277
+        file_num: i32,
278
+        show_hidden: bool,
279
+        is_remote: bool,
280
+        files: Vec<FileEntry>,
281
+        enable_overwrite_detection: bool,
282
+    ) -> Self {
283
+        log::info!("new write {}", path);
223
         let total_size = files.iter().map(|x| x.size as u64).sum();
284
         let total_size = files.iter().map(|x| x.size as u64).sum();
224
         Self {
285
         Self {
225
             id,
286
             id,
287
+            remote,
226
             path: get_path(&path),
288
             path: get_path(&path),
289
+            file_num,
290
+            show_hidden,
291
+            is_remote,
227
             files,
292
             files,
228
             total_size,
293
             total_size,
294
+            enable_overwrite_detection,
229
             ..Default::default()
295
             ..Default::default()
230
         }
296
         }
231
     }
297
     }
232
 
298
 
233
-    pub fn new_read(id: i32, path: String, include_hidden: bool) -> ResultType<Self> {
234
-        let files = get_recursive_files(&path, include_hidden)?;
299
+    pub fn new_read(
300
+        id: i32,
301
+        remote: String,
302
+        path: String,
303
+        file_num: i32,
304
+        show_hidden: bool,
305
+        is_remote: bool,
306
+        enable_overwrite_detection: bool,
307
+    ) -> ResultType<Self> {
308
+        log::info!("new read {}", path);
309
+        let files = get_recursive_files(&path, show_hidden)?;
235
         let total_size = files.iter().map(|x| x.size as u64).sum();
310
         let total_size = files.iter().map(|x| x.size as u64).sum();
236
         Ok(Self {
311
         Ok(Self {
237
             id,
312
             id,
313
+            remote,
238
             path: get_path(&path),
314
             path: get_path(&path),
315
+            file_num,
316
+            show_hidden,
317
+            is_remote,
239
             files,
318
             files,
240
             total_size,
319
             total_size,
320
+            enable_overwrite_detection,
241
             ..Default::default()
321
             ..Default::default()
242
         })
322
         })
243
     }
323
     }
@@ -302,7 +382,7 @@ impl TransferJob {
302
         }
382
         }
303
     }
383
     }
304
 
384
 
305
-    pub async fn write(&mut self, block: FileTransferBlock, raw: Option<&[u8]>) -> ResultType<()> {
385
+    pub async fn write(&mut self, block: FileTransferBlock) -> ResultType<()> {
306
         if block.id != self.id {
386
         if block.id != self.id {
307
             bail!("Wrong id");
387
             bail!("Wrong id");
308
         }
388
         }
@@ -324,25 +404,20 @@ impl TransferJob {
324
             let path = format!("{}.download", get_string(&path));
404
             let path = format!("{}.download", get_string(&path));
325
             self.file = Some(File::create(&path).await?);
405
             self.file = Some(File::create(&path).await?);
326
         }
406
         }
327
-        let data = if let Some(data) = raw {
328
-            data
329
-        } else {
330
-            &block.data
331
-        };
332
         if block.compressed {
407
         if block.compressed {
333
-            let tmp = decompress(data);
408
+            let tmp = decompress(&block.data);
334
             self.file.as_mut().unwrap().write_all(&tmp).await?;
409
             self.file.as_mut().unwrap().write_all(&tmp).await?;
335
             self.finished_size += tmp.len() as u64;
410
             self.finished_size += tmp.len() as u64;
336
         } else {
411
         } else {
337
-            self.file.as_mut().unwrap().write_all(data).await?;
338
-            self.finished_size += data.len() as u64;
412
+            self.file.as_mut().unwrap().write_all(&block.data).await?;
413
+            self.finished_size += block.data.len() as u64;
339
         }
414
         }
340
-        self.transferred += data.len() as u64;
415
+        self.transferred += block.data.len() as u64;
341
         Ok(())
416
         Ok(())
342
     }
417
     }
343
 
418
 
344
     #[inline]
419
     #[inline]
345
-    fn join(&self, name: &str) -> PathBuf {
420
+    pub fn join(&self, name: &str) -> PathBuf {
346
         if name.is_empty() {
421
         if name.is_empty() {
347
             self.path.clone()
422
             self.path.clone()
348
         } else {
423
         } else {
@@ -350,7 +425,7 @@ impl TransferJob {
350
         }
425
         }
351
     }
426
     }
352
 
427
 
353
-    pub async fn read(&mut self) -> ResultType<Option<FileTransferBlock>> {
428
+    pub async fn read(&mut self, stream: &mut Stream) -> ResultType<Option<FileTransferBlock>> {
354
         let file_num = self.file_num as usize;
429
         let file_num = self.file_num as usize;
355
         if file_num >= self.files.len() {
430
         if file_num >= self.files.len() {
356
             self.file.take();
431
             self.file.take();
@@ -361,13 +436,26 @@ impl TransferJob {
361
             match File::open(self.join(&name)).await {
436
             match File::open(self.join(&name)).await {
362
                 Ok(file) => {
437
                 Ok(file) => {
363
                     self.file = Some(file);
438
                     self.file = Some(file);
439
+                    self.file_confirmed = false;
440
+                    self.file_is_waiting = false;
364
                 }
441
                 }
365
                 Err(err) => {
442
                 Err(err) => {
366
                     self.file_num += 1;
443
                     self.file_num += 1;
444
+                    self.file_confirmed = false;
445
+                    self.file_is_waiting = false;
367
                     return Err(err.into());
446
                     return Err(err.into());
368
                 }
447
                 }
369
             }
448
             }
370
         }
449
         }
450
+        if self.enable_overwrite_detection {
451
+            if !self.file_confirmed() {
452
+                if !self.file_is_waiting() {
453
+                    self.send_current_digest(stream).await?;
454
+                    self.set_file_is_waiting(true);
455
+                }
456
+                return Ok(None);
457
+            }
458
+        }
371
         const BUF_SIZE: usize = 128 * 1024;
459
         const BUF_SIZE: usize = 128 * 1024;
372
         let mut buf: Vec<u8> = Vec::with_capacity(BUF_SIZE);
460
         let mut buf: Vec<u8> = Vec::with_capacity(BUF_SIZE);
373
         unsafe {
461
         unsafe {
@@ -380,6 +468,8 @@ impl TransferJob {
380
                 Err(err) => {
468
                 Err(err) => {
381
                     self.file_num += 1;
469
                     self.file_num += 1;
382
                     self.file = None;
470
                     self.file = None;
471
+                    self.file_confirmed = false;
472
+                    self.file_is_waiting = false;
383
                     return Err(err.into());
473
                     return Err(err.into());
384
                 }
474
                 }
385
                 Ok(n) => {
475
                 Ok(n) => {
@@ -394,6 +484,8 @@ impl TransferJob {
394
         if offset == 0 {
484
         if offset == 0 {
395
             self.file_num += 1;
485
             self.file_num += 1;
396
             self.file = None;
486
             self.file = None;
487
+            self.file_confirmed = false;
488
+            self.file_is_waiting = false;
397
         } else {
489
         } else {
398
             self.finished_size += offset as u64;
490
             self.finished_size += offset as u64;
399
             if !is_compressed_file(name) {
491
             if !is_compressed_file(name) {
@@ -413,6 +505,139 @@ impl TransferJob {
413
             ..Default::default()
505
             ..Default::default()
414
         }))
506
         }))
415
     }
507
     }
508
+
509
+    async fn send_current_digest(&mut self, stream: &mut Stream) -> ResultType<()> {
510
+        let mut msg = Message::new();
511
+        let mut resp = FileResponse::new();
512
+        let meta = self.file.as_ref().unwrap().metadata().await?;
513
+        let last_modified = meta
514
+            .modified()?
515
+            .duration_since(SystemTime::UNIX_EPOCH)?
516
+            .as_secs();
517
+        resp.set_digest(FileTransferDigest {
518
+            id: self.id,
519
+            file_num: self.file_num,
520
+            last_modified,
521
+            file_size: meta.len(),
522
+            ..Default::default()
523
+        });
524
+        msg.set_file_response(resp);
525
+        stream.send(&msg).await?;
526
+        log::info!(
527
+            "id: {}, file_num:{}, digest message is sent. waiting for confirm. msg: {:?}",
528
+            self.id,
529
+            self.file_num,
530
+            msg
531
+        );
532
+        Ok(())
533
+    }
534
+
535
+    pub fn set_overwrite_strategy(&mut self, overwrite_strategy: Option<bool>) {
536
+        self.default_overwrite_strategy = overwrite_strategy;
537
+    }
538
+
539
+    pub fn default_overwrite_strategy(&self) -> Option<bool> {
540
+        self.default_overwrite_strategy
541
+    }
542
+
543
+    pub fn set_file_confirmed(&mut self, file_confirmed: bool) {
544
+        log::info!("id: {}, file_confirmed: {}", self.id, file_confirmed);
545
+        self.file_confirmed = file_confirmed;
546
+        self.file_skipped = false;
547
+    }
548
+
549
+    pub fn set_file_is_waiting(&mut self, file_is_waiting: bool) {
550
+        self.file_is_waiting = file_is_waiting;
551
+    }
552
+
553
+    #[inline]
554
+    pub fn file_is_waiting(&self) -> bool {
555
+        self.file_is_waiting
556
+    }
557
+
558
+    #[inline]
559
+    pub fn file_confirmed(&self) -> bool {
560
+        self.file_confirmed
561
+    }
562
+
563
+    /// Indicating whether the last file is skipped
564
+    #[inline]
565
+    pub fn file_skipped(&self) -> bool {
566
+        self.file_skipped
567
+    }
568
+
569
+    /// Indicating whether the whole task is skipped
570
+    #[inline]
571
+    pub fn job_skipped(&self) -> bool {
572
+        self.file_skipped() && self.files.len() == 1
573
+    }
574
+
575
+    /// Check whether the job is completed after `read` returns `None`
576
+    /// This is a helper function which gives additional lifecycle when the job reads `None`.
577
+    /// If returns `true`, it means we can delete the job automatically. `False` otherwise.
578
+    ///
579
+    /// [`Note`]
580
+    /// Conditions:
581
+    /// 1. Files are not waiting for confirmation by peers.
582
+    #[inline]
583
+    pub fn job_completed(&self) -> bool {
584
+        // has no error, Condition 2
585
+        if !self.enable_overwrite_detection || (!self.file_confirmed && !self.file_is_waiting) {
586
+            return true;
587
+        }
588
+        return false;
589
+    }
590
+
591
+    /// Get job error message, useful for getting status when job had finished
592
+    pub fn job_error(&self) -> Option<String> {
593
+        if self.job_skipped() {
594
+            return Some("skipped".to_string());
595
+        }
596
+        None
597
+    }
598
+
599
+    pub fn set_file_skipped(&mut self) -> bool {
600
+        log::debug!("skip file {} in job {}", self.file_num, self.id);
601
+        self.file.take();
602
+        self.set_file_confirmed(false);
603
+        self.set_file_is_waiting(false);
604
+        self.file_num += 1;
605
+        self.file_skipped = true;
606
+        true
607
+    }
608
+
609
+    pub fn confirm(&mut self, r: &FileTransferSendConfirmRequest) -> bool {
610
+        if self.file_num() != r.file_num {
611
+            log::info!("file num truncated, ignoring");
612
+        } else {
613
+            match r.union {
614
+                Some(file_transfer_send_confirm_request::Union::Skip(s)) => {
615
+                    if s {
616
+                        self.set_file_skipped();
617
+                    } else {
618
+                        self.set_file_confirmed(true);
619
+                    }
620
+                }
621
+                Some(file_transfer_send_confirm_request::Union::OffsetBlk(_offset)) => {
622
+                    self.set_file_confirmed(true);
623
+                }
624
+                _ => {}
625
+            }
626
+        }
627
+        true
628
+    }
629
+
630
+    #[inline]
631
+    pub fn gen_meta(&self) -> TransferJobMeta {
632
+        TransferJobMeta {
633
+            id: self.id,
634
+            remote: self.remote.to_string(),
635
+            to: self.path.to_string_lossy().to_string(),
636
+            file_num: self.file_num,
637
+            show_hidden: self.show_hidden,
638
+            is_remote: self.is_remote,
639
+        }
640
+    }
416
 }
641
 }
417
 
642
 
418
 #[inline]
643
 #[inline]
@@ -453,12 +678,22 @@ pub fn new_block(block: FileTransferBlock) -> Message {
453
 }
678
 }
454
 
679
 
455
 #[inline]
680
 #[inline]
456
-pub fn new_receive(id: i32, path: String, files: Vec<FileEntry>) -> Message {
681
+pub fn new_send_confirm(r: FileTransferSendConfirmRequest) -> Message {
682
+    let mut msg_out = Message::new();
683
+    let mut action = FileAction::new();
684
+    action.set_send_confirm(r);
685
+    msg_out.set_file_action(action);
686
+    msg_out
687
+}
688
+
689
+#[inline]
690
+pub fn new_receive(id: i32, path: String, file_num: i32, files: Vec<FileEntry>) -> Message {
457
     let mut action = FileAction::new();
691
     let mut action = FileAction::new();
458
     action.set_receive(FileTransferReceiveRequest {
692
     action.set_receive(FileTransferReceiveRequest {
459
         id,
693
         id,
460
         path,
694
         path,
461
         files: files.into(),
695
         files: files.into(),
696
+        file_num,
462
         ..Default::default()
697
         ..Default::default()
463
     });
698
     });
464
     let mut msg_out = Message::new();
699
     let mut msg_out = Message::new();
@@ -467,12 +702,14 @@ pub fn new_receive(id: i32, path: String, files: Vec<FileEntry>) -> Message {
467
 }
702
 }
468
 
703
 
469
 #[inline]
704
 #[inline]
470
-pub fn new_send(id: i32, path: String, include_hidden: bool) -> Message {
705
+pub fn new_send(id: i32, path: String, file_num: i32, include_hidden: bool) -> Message {
706
+    log::info!("new send: {},id : {}", path, id);
471
     let mut action = FileAction::new();
707
     let mut action = FileAction::new();
472
     action.set_send(FileTransferSendRequest {
708
     action.set_send(FileTransferSendRequest {
473
         id,
709
         id,
474
         path,
710
         path,
475
         include_hidden,
711
         include_hidden,
712
+        file_num,
476
         ..Default::default()
713
         ..Default::default()
477
     });
714
     });
478
     let mut msg_out = Message::new();
715
     let mut msg_out = Message::new();
@@ -509,7 +746,10 @@ pub async fn handle_read_jobs(
509
 ) -> ResultType<()> {
746
 ) -> ResultType<()> {
510
     let mut finished = Vec::new();
747
     let mut finished = Vec::new();
511
     for job in jobs.iter_mut() {
748
     for job in jobs.iter_mut() {
512
-        match job.read().await {
749
+        if job.is_last_job {
750
+            continue;
751
+        }
752
+        match job.read(stream).await {
513
             Err(err) => {
753
             Err(err) => {
514
                 stream
754
                 stream
515
                     .send(&new_error(job.id(), err, job.file_num()))
755
                     .send(&new_error(job.id(), err, job.file_num()))
@@ -519,8 +759,19 @@ pub async fn handle_read_jobs(
519
                 stream.send(&new_block(block)).await?;
759
                 stream.send(&new_block(block)).await?;
520
             }
760
             }
521
             Ok(None) => {
761
             Ok(None) => {
522
-                finished.push(job.id());
523
-                stream.send(&new_done(job.id(), job.file_num())).await?;
762
+                if job.job_completed() {
763
+                    finished.push(job.id());
764
+                    let err = job.job_error();
765
+                    if err.is_some() {
766
+                        stream
767
+                            .send(&new_error(job.id(), err.unwrap(), job.file_num()))
768
+                            .await?;
769
+                    } else {
770
+                        stream.send(&new_done(job.id(), job.file_num())).await?;
771
+                    }
772
+                } else {
773
+                    // waiting confirmation.
774
+                }
524
             }
775
             }
525
         }
776
         }
526
     }
777
     }
@@ -566,3 +817,34 @@ pub fn transform_windows_path(entries: &mut Vec<FileEntry>) {
566
     }
817
     }
567
 }
818
 }
568
 
819
 
820
+pub enum DigestCheckResult {
821
+    IsSame,
822
+    NeedConfirm(FileTransferDigest),
823
+    NoSuchFile,
824
+}
825
+
826
+#[inline]
827
+pub fn is_write_need_confirmation(
828
+    file_path: &str,
829
+    digest: &FileTransferDigest,
830
+) -> ResultType<DigestCheckResult> {
831
+    let path = Path::new(file_path);
832
+    if path.exists() && path.is_file() {
833
+        let metadata = std::fs::metadata(path)?;
834
+        let modified_time = metadata.modified()?;
835
+        let remote_mt = Duration::from_secs(digest.last_modified);
836
+        let local_mt = modified_time.duration_since(UNIX_EPOCH)?;
837
+        if remote_mt == local_mt && digest.file_size == metadata.len() {
838
+            return Ok(DigestCheckResult::IsSame);
839
+        }
840
+        Ok(DigestCheckResult::NeedConfirm(FileTransferDigest {
841
+            id: digest.id,
842
+            file_num: digest.file_num,
843
+            last_modified: local_mt.as_secs(),
844
+            file_size: metadata.len(),
845
+            ..Default::default()
846
+        }))
847
+    } else {
848
+        Ok(DigestCheckResult::NoSuchFile)
849
+    }
850
+}

+ 142 - 10
libs/hbb_common/src/lib.rs

@@ -1,15 +1,16 @@
1
 pub mod compress;
1
 pub mod compress;
2
-#[path = "./protos/message.rs"]
3
-pub mod message_proto;
4
-#[path = "./protos/rendezvous.rs"]
5
-pub mod rendezvous_proto;
2
+pub mod platform;
3
+pub mod protos;
6
 pub use bytes;
4
 pub use bytes;
5
+use config::Config;
7
 pub use futures;
6
 pub use futures;
8
 pub use protobuf;
7
 pub use protobuf;
8
+pub use protos::message as message_proto;
9
+pub use protos::rendezvous as rendezvous_proto;
9
 use std::{
10
 use std::{
10
     fs::File,
11
     fs::File,
11
     io::{self, BufRead},
12
     io::{self, BufRead},
12
-    net::{Ipv4Addr, SocketAddr, SocketAddrV4},
13
+    net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4},
13
     path::Path,
14
     path::Path,
14
     time::{self, SystemTime, UNIX_EPOCH},
15
     time::{self, SystemTime, UNIX_EPOCH},
15
 };
16
 };
@@ -27,6 +28,7 @@ pub use anyhow::{self, bail};
27
 pub use futures_util;
28
 pub use futures_util;
28
 pub mod config;
29
 pub mod config;
29
 pub mod fs;
30
 pub mod fs;
31
+pub use lazy_static;
30
 #[cfg(not(any(target_os = "android", target_os = "ios")))]
32
 #[cfg(not(any(target_os = "android", target_os = "ios")))]
31
 pub use mac_address;
33
 pub use mac_address;
32
 pub use rand;
34
 pub use rand;
@@ -35,6 +37,9 @@ pub use sodiumoxide;
35
 pub use tokio_socks;
37
 pub use tokio_socks;
36
 pub use tokio_socks::IntoTargetAddr;
38
 pub use tokio_socks::IntoTargetAddr;
37
 pub use tokio_socks::TargetAddr;
39
 pub use tokio_socks::TargetAddr;
40
+pub mod password_security;
41
+pub use chrono;
42
+pub use directories_next;
38
 
43
 
39
 #[cfg(feature = "quic")]
44
 #[cfg(feature = "quic")]
40
 pub type Stream = quic::Connection;
45
 pub type Stream = quic::Connection;
@@ -61,6 +66,21 @@ macro_rules! allow_err {
61
         } else {
66
         } else {
62
         }
67
         }
63
     };
68
     };
69
+
70
+    ($e:expr, $($arg:tt)*) => {
71
+        if let Err(err) = $e {
72
+            log::debug!(
73
+                "{:?}, {}, {}:{}:{}:{}",
74
+                err,
75
+                format_args!($($arg)*),
76
+                module_path!(),
77
+                file!(),
78
+                line!(),
79
+                column!()
80
+            );
81
+        } else {
82
+        }
83
+    };
64
 }
84
 }
65
 
85
 
66
 #[inline]
86
 #[inline]
@@ -97,13 +117,31 @@ impl AddrMangle {
97
                 }
117
                 }
98
                 bytes[..(16 - n_padding)].to_vec()
118
                 bytes[..(16 - n_padding)].to_vec()
99
             }
119
             }
100
-            _ => {
101
-                panic!("Only support ipv4");
120
+            SocketAddr::V6(addr_v6) => {
121
+                let mut x = addr_v6.ip().octets().to_vec();
122
+                let port: [u8; 2] = addr_v6.port().to_le_bytes();
123
+                x.push(port[0]);
124
+                x.push(port[1]);
125
+                x
102
             }
126
             }
103
         }
127
         }
104
     }
128
     }
105
 
129
 
106
     pub fn decode(bytes: &[u8]) -> SocketAddr {
130
     pub fn decode(bytes: &[u8]) -> SocketAddr {
131
+        if bytes.len() > 16 {
132
+            if bytes.len() != 18 {
133
+                return Config::get_any_listen_addr(false);
134
+            }
135
+            #[allow(invalid_value)]
136
+            let mut tmp: [u8; 2] = unsafe { std::mem::MaybeUninit::uninit().assume_init() };
137
+            tmp.copy_from_slice(&bytes[16..]);
138
+            let port = u16::from_le_bytes(tmp);
139
+            #[allow(invalid_value)]
140
+            let mut tmp: [u8; 16] = unsafe { std::mem::MaybeUninit::uninit().assume_init() };
141
+            tmp.copy_from_slice(&bytes[..16]);
142
+            let ip = std::net::Ipv6Addr::from(tmp);
143
+            return SocketAddr::new(IpAddr::V6(ip), port);
144
+        }
107
         let mut padded = [0u8; 16];
145
         let mut padded = [0u8; 16];
108
         padded[..bytes.len()].copy_from_slice(&bytes);
146
         padded[..bytes.len()].copy_from_slice(&bytes);
109
         let number = u128::from_le_bytes(padded);
147
         let number = u128::from_le_bytes(padded);
@@ -156,19 +194,23 @@ pub fn get_version_from_url(url: &str) -> String {
156
 }
194
 }
157
 
195
 
158
 pub fn gen_version() {
196
 pub fn gen_version() {
197
+    use std::io::prelude::*;
159
     let mut file = File::create("./src/version.rs").unwrap();
198
     let mut file = File::create("./src/version.rs").unwrap();
160
     for line in read_lines("Cargo.toml").unwrap() {
199
     for line in read_lines("Cargo.toml").unwrap() {
161
         if let Ok(line) = line {
200
         if let Ok(line) = line {
162
             let ab: Vec<&str> = line.split("=").map(|x| x.trim()).collect();
201
             let ab: Vec<&str> = line.split("=").map(|x| x.trim()).collect();
163
             if ab.len() == 2 && ab[0] == "version" {
202
             if ab.len() == 2 && ab[0] == "version" {
164
-                use std::io::prelude::*;
165
-                file.write_all(format!("pub const VERSION: &str = {};", ab[1]).as_bytes())
203
+                file.write_all(format!("pub const VERSION: &str = {};\n", ab[1]).as_bytes())
166
                     .ok();
204
                     .ok();
167
-                file.sync_all().ok();
168
                 break;
205
                 break;
169
             }
206
             }
170
         }
207
         }
171
     }
208
     }
209
+    // generate build date
210
+    let build_date = format!("{}", chrono::Local::now().format("%Y-%m-%d %H:%M"));
211
+    file.write_all(format!("pub const BUILD_DATE: &str = \"{}\";", build_date).as_bytes())
212
+        .ok();
213
+    file.sync_all().ok();
172
 }
214
 }
173
 
215
 
174
 fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
216
 fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
@@ -199,6 +241,40 @@ pub fn get_modified_time(path: &std::path::Path) -> SystemTime {
199
         .unwrap_or(UNIX_EPOCH)
241
         .unwrap_or(UNIX_EPOCH)
200
 }
242
 }
201
 
243
 
244
+pub fn get_created_time(path: &std::path::Path) -> SystemTime {
245
+    std::fs::metadata(&path)
246
+        .map(|m| m.created().unwrap_or(UNIX_EPOCH))
247
+        .unwrap_or(UNIX_EPOCH)
248
+}
249
+
250
+pub fn get_exe_time() -> SystemTime {
251
+    std::env::current_exe().map_or(UNIX_EPOCH, |path| {
252
+        let m = get_modified_time(&path);
253
+        let c = get_created_time(&path);
254
+        if m > c {
255
+            m
256
+        } else {
257
+            c
258
+        }
259
+    })
260
+}
261
+
262
+pub fn get_uuid() -> Vec<u8> {
263
+    #[cfg(not(any(target_os = "android", target_os = "ios")))]
264
+    if let Ok(id) = machine_uid::get() {
265
+        return id.into();
266
+    }
267
+    Config::get_key_pair().1
268
+}
269
+
270
+#[inline]
271
+pub fn get_time() -> i64 {
272
+    std::time::SystemTime::now()
273
+        .duration_since(std::time::UNIX_EPOCH)
274
+        .map(|d| d.as_millis())
275
+        .unwrap_or(0) as _
276
+}
277
+
202
 #[cfg(test)]
278
 #[cfg(test)]
203
 mod tests {
279
 mod tests {
204
     use super::*;
280
     use super::*;
@@ -206,5 +282,61 @@ mod tests {
206
     fn test_mangle() {
282
     fn test_mangle() {
207
         let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 168, 16, 32), 21116));
283
         let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 168, 16, 32), 21116));
208
         assert_eq!(addr, AddrMangle::decode(&AddrMangle::encode(addr)));
284
         assert_eq!(addr, AddrMangle::decode(&AddrMangle::encode(addr)));
285
+
286
+        let addr = "[2001:db8::1]:8080".parse::<SocketAddr>().unwrap();
287
+        assert_eq!(addr, AddrMangle::decode(&AddrMangle::encode(addr)));
288
+
289
+        let addr = "[2001:db8:ff::1111]:80".parse::<SocketAddr>().unwrap();
290
+        assert_eq!(addr, AddrMangle::decode(&AddrMangle::encode(addr)));
291
+    }
292
+
293
+    #[test]
294
+    fn test_allow_err() {
295
+        allow_err!(Err("test err") as Result<(), &str>);
296
+        allow_err!(
297
+            Err("test err with msg") as Result<(), &str>,
298
+            "prompt {}",
299
+            "failed"
300
+        );
301
+    }
302
+}
303
+
304
+#[inline]
305
+pub fn is_ipv4_str(id: &str) -> bool {
306
+    regex::Regex::new(r"^\d+\.\d+\.\d+\.\d+(:\d+)?$")
307
+        .unwrap()
308
+        .is_match(id)
309
+}
310
+
311
+#[inline]
312
+pub fn is_ipv6_str(id: &str) -> bool {
313
+    regex::Regex::new(r"^((([a-fA-F0-9]{1,4}:{1,2})+[a-fA-F0-9]{1,4})|(\[([a-fA-F0-9]{1,4}:{1,2})+[a-fA-F0-9]{1,4}\]:\d+))$")
314
+        .unwrap()
315
+        .is_match(id)
316
+}
317
+
318
+#[inline]
319
+pub fn is_ip_str(id: &str) -> bool {
320
+    is_ipv4_str(id) || is_ipv6_str(id)
321
+}
322
+
323
+#[cfg(test)]
324
+mod test_lib {
325
+    use super::*;
326
+
327
+    #[test]
328
+    fn test_ipv6() {
329
+        assert_eq!(is_ipv6_str("1:2:3"), true);
330
+        assert_eq!(is_ipv6_str("[ab:2:3]:12"), true);
331
+        assert_eq!(is_ipv6_str("[ABEF:2a:3]:12"), true);
332
+        assert_eq!(is_ipv6_str("[ABEG:2a:3]:12"), false);
333
+        assert_eq!(is_ipv6_str("1[ab:2:3]:12"), false);
334
+        assert_eq!(is_ipv6_str("1.1.1.1"), false);
335
+        assert_eq!(is_ip_str("1.1.1.1"), true);
336
+        assert_eq!(is_ipv6_str("1:2:"), false);
337
+        assert_eq!(is_ipv6_str("1:2::0"), true);
338
+        assert_eq!(is_ipv6_str("[1:2::0]:1"), true);
339
+        assert_eq!(is_ipv6_str("[1:2::0]:"), false);
340
+        assert_eq!(is_ipv6_str("1:2::0]:1"), false);
209
     }
341
     }
210
 }
342
 }

+ 242 - 0
libs/hbb_common/src/password_security.rs

@@ -0,0 +1,242 @@
1
+use crate::config::Config;
2
+use sodiumoxide::base64;
3
+use std::sync::{Arc, RwLock};
4
+
5
+lazy_static::lazy_static! {
6
+    pub static ref TEMPORARY_PASSWORD:Arc<RwLock<String>> = Arc::new(RwLock::new(Config::get_auto_password(temporary_password_length())));
7
+}
8
+
9
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10
+enum VerificationMethod {
11
+    OnlyUseTemporaryPassword,
12
+    OnlyUsePermanentPassword,
13
+    UseBothPasswords,
14
+}
15
+
16
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17
+pub enum ApproveMode {
18
+    Both,
19
+    Password,
20
+    Click,
21
+}
22
+
23
+// Should only be called in server
24
+pub fn update_temporary_password() {
25
+    *TEMPORARY_PASSWORD.write().unwrap() = Config::get_auto_password(temporary_password_length());
26
+}
27
+
28
+// Should only be called in server
29
+pub fn temporary_password() -> String {
30
+    TEMPORARY_PASSWORD.read().unwrap().clone()
31
+}
32
+
33
+fn verification_method() -> VerificationMethod {
34
+    let method = Config::get_option("verification-method");
35
+    if method == "use-temporary-password" {
36
+        VerificationMethod::OnlyUseTemporaryPassword
37
+    } else if method == "use-permanent-password" {
38
+        VerificationMethod::OnlyUsePermanentPassword
39
+    } else {
40
+        VerificationMethod::UseBothPasswords // default
41
+    }
42
+}
43
+
44
+pub fn temporary_password_length() -> usize {
45
+    let length = Config::get_option("temporary-password-length");
46
+    if length == "8" {
47
+        8
48
+    } else if length == "10" {
49
+        10
50
+    } else {
51
+        6 // default
52
+    }
53
+}
54
+
55
+pub fn temporary_enabled() -> bool {
56
+    verification_method() != VerificationMethod::OnlyUsePermanentPassword
57
+}
58
+
59
+pub fn permanent_enabled() -> bool {
60
+    verification_method() != VerificationMethod::OnlyUseTemporaryPassword
61
+}
62
+
63
+pub fn has_valid_password() -> bool {
64
+    temporary_enabled() && !temporary_password().is_empty()
65
+        || permanent_enabled() && !Config::get_permanent_password().is_empty()
66
+}
67
+
68
+pub fn approve_mode() -> ApproveMode {
69
+    let mode = Config::get_option("approve-mode");
70
+    if mode == "password" {
71
+        ApproveMode::Password
72
+    } else if mode == "click" {
73
+        ApproveMode::Click
74
+    } else {
75
+        ApproveMode::Both
76
+    }
77
+}
78
+
79
+pub fn hide_cm() -> bool {
80
+    approve_mode() == ApproveMode::Password
81
+        && verification_method() == VerificationMethod::OnlyUsePermanentPassword
82
+        && !Config::get_option("allow-hide-cm").is_empty()
83
+}
84
+
85
+const VERSION_LEN: usize = 2;
86
+
87
+pub fn encrypt_str_or_original(s: &str, version: &str) -> String {
88
+    if decrypt_str_or_original(s, version).1 {
89
+        log::error!("Duplicate encryption!");
90
+        return s.to_owned();
91
+    }
92
+    if version == "00" {
93
+        if let Ok(s) = encrypt(s.as_bytes()) {
94
+            return version.to_owned() + &s;
95
+        }
96
+    }
97
+    s.to_owned()
98
+}
99
+
100
+// String: password
101
+// bool: whether decryption is successful
102
+// bool: whether should store to re-encrypt when load
103
+pub fn decrypt_str_or_original(s: &str, current_version: &str) -> (String, bool, bool) {
104
+    if s.len() > VERSION_LEN {
105
+        let version = &s[..VERSION_LEN];
106
+        if version == "00" {
107
+            if let Ok(v) = decrypt(&s[VERSION_LEN..].as_bytes()) {
108
+                return (
109
+                    String::from_utf8_lossy(&v).to_string(),
110
+                    true,
111
+                    version != current_version,
112
+                );
113
+            }
114
+        }
115
+    }
116
+
117
+    (s.to_owned(), false, !s.is_empty())
118
+}
119
+
120
+pub fn encrypt_vec_or_original(v: &[u8], version: &str) -> Vec<u8> {
121
+    if decrypt_vec_or_original(v, version).1 {
122
+        log::error!("Duplicate encryption!");
123
+        return v.to_owned();
124
+    }
125
+    if version == "00" {
126
+        if let Ok(s) = encrypt(v) {
127
+            let mut version = version.to_owned().into_bytes();
128
+            version.append(&mut s.into_bytes());
129
+            return version;
130
+        }
131
+    }
132
+    v.to_owned()
133
+}
134
+
135
+// Vec<u8>: password
136
+// bool: whether decryption is successful
137
+// bool: whether should store to re-encrypt when load
138
+pub fn decrypt_vec_or_original(v: &[u8], current_version: &str) -> (Vec<u8>, bool, bool) {
139
+    if v.len() > VERSION_LEN {
140
+        let version = String::from_utf8_lossy(&v[..VERSION_LEN]);
141
+        if version == "00" {
142
+            if let Ok(v) = decrypt(&v[VERSION_LEN..]) {
143
+                return (v, true, version != current_version);
144
+            }
145
+        }
146
+    }
147
+
148
+    (v.to_owned(), false, !v.is_empty())
149
+}
150
+
151
+fn encrypt(v: &[u8]) -> Result<String, ()> {
152
+    if v.len() > 0 {
153
+        symmetric_crypt(v, true).map(|v| base64::encode(v, base64::Variant::Original))
154
+    } else {
155
+        Err(())
156
+    }
157
+}
158
+
159
+fn decrypt(v: &[u8]) -> Result<Vec<u8>, ()> {
160
+    if v.len() > 0 {
161
+        base64::decode(v, base64::Variant::Original).and_then(|v| symmetric_crypt(&v, false))
162
+    } else {
163
+        Err(())
164
+    }
165
+}
166
+
167
+fn symmetric_crypt(data: &[u8], encrypt: bool) -> Result<Vec<u8>, ()> {
168
+    use sodiumoxide::crypto::secretbox;
169
+    use std::convert::TryInto;
170
+
171
+    let mut keybuf = crate::get_uuid();
172
+    keybuf.resize(secretbox::KEYBYTES, 0);
173
+    let key = secretbox::Key(keybuf.try_into().map_err(|_| ())?);
174
+    let nonce = secretbox::Nonce([0; secretbox::NONCEBYTES]);
175
+
176
+    if encrypt {
177
+        Ok(secretbox::seal(data, &nonce, &key))
178
+    } else {
179
+        secretbox::open(data, &nonce, &key)
180
+    }
181
+}
182
+
183
+mod test {
184
+
185
+    #[test]
186
+    fn test() {
187
+        use super::*;
188
+
189
+        let version = "00";
190
+
191
+        println!("test str");
192
+        let data = "Hello World";
193
+        let encrypted = encrypt_str_or_original(data, version);
194
+        let (decrypted, succ, store) = decrypt_str_or_original(&encrypted, version);
195
+        println!("data: {}", data);
196
+        println!("encrypted: {}", encrypted);
197
+        println!("decrypted: {}", decrypted);
198
+        assert_eq!(data, decrypted);
199
+        assert_eq!(version, &encrypted[..2]);
200
+        assert_eq!(succ, true);
201
+        assert_eq!(store, false);
202
+        let (_, _, store) = decrypt_str_or_original(&encrypted, "99");
203
+        assert_eq!(store, true);
204
+        assert_eq!(decrypt_str_or_original(&decrypted, version).1, false);
205
+        assert_eq!(encrypt_str_or_original(&encrypted, version), encrypted);
206
+
207
+        println!("test vec");
208
+        let data: Vec<u8> = vec![1, 2, 3, 4, 5, 6];
209
+        let encrypted = encrypt_vec_or_original(&data, version);
210
+        let (decrypted, succ, store) = decrypt_vec_or_original(&encrypted, version);
211
+        println!("data: {:?}", data);
212
+        println!("encrypted: {:?}", encrypted);
213
+        println!("decrypted: {:?}", decrypted);
214
+        assert_eq!(data, decrypted);
215
+        assert_eq!(version.as_bytes(), &encrypted[..2]);
216
+        assert_eq!(store, false);
217
+        assert_eq!(succ, true);
218
+        let (_, _, store) = decrypt_vec_or_original(&encrypted, "99");
219
+        assert_eq!(store, true);
220
+        assert_eq!(decrypt_vec_or_original(&decrypted, version).1, false);
221
+        assert_eq!(encrypt_vec_or_original(&encrypted, version), encrypted);
222
+
223
+        println!("test original");
224
+        let data = version.to_string() + "Hello World";
225
+        let (decrypted, succ, store) = decrypt_str_or_original(&data, version);
226
+        assert_eq!(data, decrypted);
227
+        assert_eq!(store, true);
228
+        assert_eq!(succ, false);
229
+        let verbytes = version.as_bytes();
230
+        let data: Vec<u8> = vec![verbytes[0] as u8, verbytes[1] as u8, 1, 2, 3, 4, 5, 6];
231
+        let (decrypted, succ, store) = decrypt_vec_or_original(&data, version);
232
+        assert_eq!(data, decrypted);
233
+        assert_eq!(store, true);
234
+        assert_eq!(succ, false);
235
+        let (_, succ, store) = decrypt_str_or_original("", version);
236
+        assert_eq!(store, false);
237
+        assert_eq!(succ, false);
238
+        let (_, succ, store) = decrypt_vec_or_original(&vec![], version);
239
+        assert_eq!(store, false);
240
+        assert_eq!(succ, false);
241
+    }
242
+}

+ 157 - 0
libs/hbb_common/src/platform/linux.rs

@@ -0,0 +1,157 @@
1
+use crate::ResultType;
2
+
3
+lazy_static::lazy_static! {
4
+    pub static ref DISTRO: Disto = Disto::new();
5
+}
6
+
7
+pub struct Disto {
8
+    pub name: String,
9
+    pub version_id: String,
10
+}
11
+
12
+impl Disto {
13
+    fn new() -> Self {
14
+        let name = run_cmds("awk -F'=' '/^NAME=/ {print $2}' /etc/os-release".to_owned())
15
+            .unwrap_or_default()
16
+            .trim()
17
+            .trim_matches('"')
18
+            .to_string();
19
+        let version_id =
20
+            run_cmds("awk -F'=' '/^VERSION_ID=/ {print $2}' /etc/os-release".to_owned())
21
+                .unwrap_or_default()
22
+                .trim()
23
+                .trim_matches('"')
24
+                .to_string();
25
+        Self { name, version_id }
26
+    }
27
+}
28
+
29
+pub fn get_display_server() -> String {
30
+    let mut session = get_values_of_seat0([0].to_vec())[0].clone();
31
+    if session.is_empty() {
32
+        // loginctl has not given the expected output.  try something else.
33
+        if let Ok(sid) = std::env::var("XDG_SESSION_ID") {
34
+            // could also execute "cat /proc/self/sessionid"
35
+            session = sid.to_owned();
36
+        }
37
+        if session.is_empty() {
38
+            session = run_cmds("cat /proc/self/sessionid".to_owned()).unwrap_or_default();
39
+        }
40
+    }
41
+
42
+    get_display_server_of_session(&session)
43
+}
44
+
45
+fn get_display_server_of_session(session: &str) -> String {
46
+    let mut display_server = if let Ok(output) =
47
+        run_loginctl(Some(vec!["show-session", "-p", "Type", session]))
48
+    // Check session type of the session
49
+    {
50
+        let display_server = String::from_utf8_lossy(&output.stdout)
51
+            .replace("Type=", "")
52
+            .trim_end()
53
+            .into();
54
+        if display_server == "tty" {
55
+            // If the type is tty...
56
+            if let Ok(output) = run_loginctl(Some(vec!["show-session", "-p", "TTY", session]))
57
+            // Get the tty number
58
+            {
59
+                let tty: String = String::from_utf8_lossy(&output.stdout)
60
+                    .replace("TTY=", "")
61
+                    .trim_end()
62
+                    .into();
63
+                if let Ok(xorg_results) = run_cmds(format!("ps -e | grep \"{}.\\\\+Xorg\"", tty))
64
+                // And check if Xorg is running on that tty
65
+                {
66
+                    if xorg_results.trim_end().to_string() != "" {
67
+                        // If it is, manually return "x11", otherwise return tty
68
+                        return "x11".to_owned();
69
+                    }
70
+                }
71
+            }
72
+        }
73
+        display_server
74
+    } else {
75
+        "".to_owned()
76
+    };
77
+    if display_server.is_empty() {
78
+        // loginctl has not given the expected output.  try something else.
79
+        if let Ok(sestype) = std::env::var("XDG_SESSION_TYPE") {
80
+            display_server = sestype;
81
+        }
82
+    }
83
+    // If the session is not a tty, then just return the type as usual
84
+    display_server
85
+}
86
+
87
+pub fn get_values_of_seat0(indices: Vec<usize>) -> Vec<String> {
88
+    if let Ok(output) = run_loginctl(None) {
89
+        for line in String::from_utf8_lossy(&output.stdout).lines() {
90
+            if line.contains("seat0") {
91
+                if let Some(sid) = line.split_whitespace().nth(0) {
92
+                    if is_active(sid) {
93
+                        return indices
94
+                            .into_iter()
95
+                            .map(|idx| line.split_whitespace().nth(idx).unwrap_or("").to_owned())
96
+                            .collect::<Vec<String>>();
97
+                    }
98
+                }
99
+            }
100
+        }
101
+    }
102
+
103
+    // some case, there is no seat0 https://github.com/rustdesk/rustdesk/issues/73
104
+    if let Ok(output) = run_loginctl(None) {
105
+        for line in String::from_utf8_lossy(&output.stdout).lines() {
106
+            if let Some(sid) = line.split_whitespace().nth(0) {
107
+                let d = get_display_server_of_session(sid);
108
+                if is_active(sid) && d != "tty" {
109
+                    return indices
110
+                        .into_iter()
111
+                        .map(|idx| line.split_whitespace().nth(idx).unwrap_or("").to_owned())
112
+                        .collect::<Vec<String>>();
113
+                }
114
+            }
115
+        }
116
+    }
117
+
118
+    return indices
119
+        .iter()
120
+        .map(|_x| "".to_owned())
121
+        .collect::<Vec<String>>();
122
+}
123
+
124
+fn is_active(sid: &str) -> bool {
125
+    if let Ok(output) = run_loginctl(Some(vec!["show-session", "-p", "State", sid])) {
126
+        String::from_utf8_lossy(&output.stdout).contains("active")
127
+    } else {
128
+        false
129
+    }
130
+}
131
+
132
+pub fn run_cmds(cmds: String) -> ResultType<String> {
133
+    let output = std::process::Command::new("sh")
134
+        .args(vec!["-c", &cmds])
135
+        .output()?;
136
+    Ok(String::from_utf8_lossy(&output.stdout).to_string())
137
+}
138
+
139
+#[cfg(not(feature = "flatpak"))]
140
+fn run_loginctl(args: Option<Vec<&str>>) -> std::io::Result<std::process::Output> {
141
+    let mut cmd = std::process::Command::new("loginctl");
142
+    if let Some(a) = args {
143
+        return cmd.args(a).output();
144
+    }
145
+    cmd.output()
146
+}
147
+
148
+#[cfg(feature = "flatpak")]
149
+fn run_loginctl(args: Option<Vec<&str>>) -> std::io::Result<std::process::Output> {
150
+    let mut l_args = String::from("loginctl");
151
+    if let Some(a) = args {
152
+        l_args = format!("{} {}", l_args, a.join(" "));
153
+    }
154
+    std::process::Command::new("flatpak-spawn")
155
+        .args(vec![String::from("--host"), l_args])
156
+        .output()
157
+}

+ 2 - 0
libs/hbb_common/src/platform/mod.rs

@@ -0,0 +1,2 @@
1
+#[cfg(target_os = "linux")]
2
+pub mod linux;

+ 159 - 35
libs/hbb_common/src/socket_client.rs

@@ -9,31 +9,15 @@ use std::net::SocketAddr;
9
 use tokio::net::ToSocketAddrs;
9
 use tokio::net::ToSocketAddrs;
10
 use tokio_socks::{IntoTargetAddr, TargetAddr};
10
 use tokio_socks::{IntoTargetAddr, TargetAddr};
11
 
11
 
12
-fn to_socket_addr(host: &str) -> ResultType<SocketAddr> {
13
-    use std::net::ToSocketAddrs;
14
-    host.to_socket_addrs()?
15
-        .filter(|x| x.is_ipv4())
16
-        .next()
17
-        .context("Failed to solve")
18
-}
19
-
20
-pub fn get_target_addr(host: &str) -> ResultType<TargetAddr<'static>> {
21
-    let addr = match Config::get_network_type() {
22
-        NetworkType::Direct => to_socket_addr(&host)?.into_target_addr()?,
23
-        NetworkType::ProxySocks => host.into_target_addr()?,
24
-    }
25
-    .to_owned();
26
-    Ok(addr)
27
-}
28
-
29
 pub fn test_if_valid_server(host: &str) -> String {
12
 pub fn test_if_valid_server(host: &str) -> String {
30
     let mut host = host.to_owned();
13
     let mut host = host.to_owned();
31
     if !host.contains(":") {
14
     if !host.contains(":") {
32
         host = format!("{}:{}", host, 0);
15
         host = format!("{}:{}", host, 0);
33
     }
16
     }
34
 
17
 
18
+    use std::net::ToSocketAddrs;
35
     match Config::get_network_type() {
19
     match Config::get_network_type() {
36
-        NetworkType::Direct => match to_socket_addr(&host) {
20
+        NetworkType::Direct => match host.to_socket_addrs() {
37
             Err(err) => err.to_string(),
21
             Err(err) => err.to_string(),
38
             Ok(_) => "".to_owned(),
22
             Ok(_) => "".to_owned(),
39
         },
23
         },
@@ -44,33 +28,126 @@ pub fn test_if_valid_server(host: &str) -> String {
44
     }
28
     }
45
 }
29
 }
46
 
30
 
47
-pub async fn connect_tcp<'t, T: IntoTargetAddr<'t>>(
31
+pub trait IsResolvedSocketAddr {
32
+    fn resolve(&self) -> Option<&SocketAddr>;
33
+}
34
+
35
+impl IsResolvedSocketAddr for SocketAddr {
36
+    fn resolve(&self) -> Option<&SocketAddr> {
37
+        Some(&self)
38
+    }
39
+}
40
+
41
+impl IsResolvedSocketAddr for String {
42
+    fn resolve(&self) -> Option<&SocketAddr> {
43
+        None
44
+    }
45
+}
46
+
47
+impl IsResolvedSocketAddr for &str {
48
+    fn resolve(&self) -> Option<&SocketAddr> {
49
+        None
50
+    }
51
+}
52
+
53
+#[inline]
54
+pub async fn connect_tcp<
55
+    't,
56
+    T: IntoTargetAddr<'t> + ToSocketAddrs + IsResolvedSocketAddr + std::fmt::Display,
57
+>(
48
     target: T,
58
     target: T,
49
-    local: SocketAddr,
50
     ms_timeout: u64,
59
     ms_timeout: u64,
51
 ) -> ResultType<FramedStream> {
60
 ) -> ResultType<FramedStream> {
52
-    let target_addr = target.into_target_addr()?;
61
+    connect_tcp_local(target, None, ms_timeout).await
62
+}
53
 
63
 
64
+pub async fn connect_tcp_local<
65
+    't,
66
+    T: IntoTargetAddr<'t> + ToSocketAddrs + IsResolvedSocketAddr + std::fmt::Display,
67
+>(
68
+    target: T,
69
+    local: Option<SocketAddr>,
70
+    ms_timeout: u64,
71
+) -> ResultType<FramedStream> {
54
     if let Some(conf) = Config::get_socks() {
72
     if let Some(conf) = Config::get_socks() {
55
-        FramedStream::connect(
73
+        return FramedStream::connect(
56
             conf.proxy.as_str(),
74
             conf.proxy.as_str(),
57
-            target_addr,
75
+            target,
58
             local,
76
             local,
59
             conf.username.as_str(),
77
             conf.username.as_str(),
60
             conf.password.as_str(),
78
             conf.password.as_str(),
61
             ms_timeout,
79
             ms_timeout,
62
         )
80
         )
63
-        .await
64
-    } else {
65
-        let addr = std::net::ToSocketAddrs::to_socket_addrs(&target_addr)?
66
-            .filter(|x| x.is_ipv4())
67
-            .next()
68
-            .context("Invalid target addr, no valid ipv4 address can be resolved.")?;
69
-        Ok(FramedStream::new(addr, local, ms_timeout).await?)
81
+        .await;
70
     }
82
     }
83
+    if let Some(target) = target.resolve() {
84
+        if let Some(local) = local {
85
+            if local.is_ipv6() && target.is_ipv4() {
86
+                let target = query_nip_io(&target).await?;
87
+                return Ok(FramedStream::new(target, Some(local), ms_timeout).await?);
88
+            }
89
+        }
90
+    }
91
+    Ok(FramedStream::new(target, local, ms_timeout).await?)
71
 }
92
 }
72
 
93
 
73
-pub async fn new_udp<T: ToSocketAddrs>(local: T, ms_timeout: u64) -> ResultType<FramedSocket> {
94
+#[inline]
95
+pub fn is_ipv4(target: &TargetAddr<'_>) -> bool {
96
+    match target {
97
+        TargetAddr::Ip(addr) => addr.is_ipv4(),
98
+        _ => true,
99
+    }
100
+}
101
+
102
+#[inline]
103
+pub async fn query_nip_io(addr: &SocketAddr) -> ResultType<SocketAddr> {
104
+    tokio::net::lookup_host(format!("{}.nip.io:{}", addr.ip(), addr.port()))
105
+        .await?
106
+        .filter(|x| x.is_ipv6())
107
+        .next()
108
+        .context("Failed to get ipv6 from nip.io")
109
+}
110
+
111
+#[inline]
112
+pub fn ipv4_to_ipv6(addr: String, ipv4: bool) -> String {
113
+    if !ipv4 && crate::is_ipv4_str(&addr) {
114
+        if let Some(ip) = addr.split(":").next() {
115
+            return addr.replace(ip, &format!("{}.nip.io", ip));
116
+        }
117
+    }
118
+    addr
119
+}
120
+
121
+async fn test_target(target: &str) -> ResultType<SocketAddr> {
122
+    if let Ok(Ok(s)) = super::timeout(1000, tokio::net::TcpStream::connect(target)).await {
123
+        if let Ok(addr) = s.peer_addr() {
124
+            return Ok(addr);
125
+        }
126
+    }
127
+    tokio::net::lookup_host(target)
128
+        .await?
129
+        .next()
130
+        .context(format!("Failed to look up host for {}", target))
131
+}
132
+
133
+#[inline]
134
+pub async fn new_udp_for(
135
+    target: &str,
136
+    ms_timeout: u64,
137
+) -> ResultType<(FramedSocket, TargetAddr<'static>)> {
138
+    let (ipv4, target) = if NetworkType::Direct == Config::get_network_type() {
139
+        let addr = test_target(target).await?;
140
+        (addr.is_ipv4(), addr.into_target_addr()?)
141
+    } else {
142
+        (true, target.into_target_addr()?)
143
+    };
144
+    Ok((
145
+        new_udp(Config::get_any_listen_addr(ipv4), ms_timeout).await?,
146
+        target.to_owned(),
147
+    ))
148
+}
149
+
150
+async fn new_udp<T: ToSocketAddrs>(local: T, ms_timeout: u64) -> ResultType<FramedSocket> {
74
     match Config::get_socks() {
151
     match Config::get_socks() {
75
         None => Ok(FramedSocket::new(local).await?),
152
         None => Ok(FramedSocket::new(local).await?),
76
         Some(conf) => {
153
         Some(conf) => {
@@ -87,9 +164,56 @@ pub async fn new_udp<T: ToSocketAddrs>(local: T, ms_timeout: u64) -> ResultType<
87
     }
164
     }
88
 }
165
 }
89
 
166
 
90
-pub async fn rebind_udp<T: ToSocketAddrs>(local: T) -> ResultType<Option<FramedSocket>> {
91
-    match Config::get_network_type() {
92
-        NetworkType::Direct => Ok(Some(FramedSocket::new(local).await?)),
93
-        _ => Ok(None),
167
+pub async fn rebind_udp_for(
168
+    target: &str,
169
+) -> ResultType<Option<(FramedSocket, TargetAddr<'static>)>> {
170
+    if Config::get_network_type() != NetworkType::Direct {
171
+        return Ok(None);
172
+    }
173
+    let addr = test_target(target).await?;
174
+    let v4 = addr.is_ipv4();
175
+    Ok(Some((
176
+        FramedSocket::new(Config::get_any_listen_addr(v4)).await?,
177
+        addr.into_target_addr()?.to_owned(),
178
+    )))
179
+}
180
+
181
+#[cfg(test)]
182
+mod tests {
183
+    use std::net::ToSocketAddrs;
184
+
185
+    use super::*;
186
+
187
+    #[test]
188
+    fn test_nat64() {
189
+        test_nat64_async();
190
+    }
191
+
192
+    #[tokio::main(flavor = "current_thread")]
193
+    async fn test_nat64_async() {
194
+        assert_eq!(ipv4_to_ipv6("1.1.1.1".to_owned(), true), "1.1.1.1");
195
+        assert_eq!(ipv4_to_ipv6("1.1.1.1".to_owned(), false), "1.1.1.1.nip.io");
196
+        assert_eq!(
197
+            ipv4_to_ipv6("1.1.1.1:8080".to_owned(), false),
198
+            "1.1.1.1.nip.io:8080"
199
+        );
200
+        assert_eq!(
201
+            ipv4_to_ipv6("rustdesk.com".to_owned(), false),
202
+            "rustdesk.com"
203
+        );
204
+        if ("rustdesk.com:80")
205
+            .to_socket_addrs()
206
+            .unwrap()
207
+            .next()
208
+            .unwrap()
209
+            .is_ipv6()
210
+        {
211
+            assert!(query_nip_io(&"1.1.1.1:80".parse().unwrap())
212
+                .await
213
+                .unwrap()
214
+                .is_ipv6());
215
+            return;
216
+        }
217
+        assert!(query_nip_io(&"1.1.1.1:80".parse().unwrap()).await.is_err());
94
     }
218
     }
95
 }
219
 }

+ 91 - 53
libs/hbb_common/src/tcp.rs

@@ -5,7 +5,7 @@ use protobuf::Message;
5
 use sodiumoxide::crypto::secretbox::{self, Key, Nonce};
5
 use sodiumoxide::crypto::secretbox::{self, Key, Nonce};
6
 use std::{
6
 use std::{
7
     io::{self, Error, ErrorKind},
7
     io::{self, Error, ErrorKind},
8
-    net::SocketAddr,
8
+    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
9
     ops::{Deref, DerefMut},
9
     ops::{Deref, DerefMut},
10
     pin::Pin,
10
     pin::Pin,
11
     task::{Context, Poll},
11
     task::{Context, Poll},
@@ -73,73 +73,79 @@ fn new_socket(addr: std::net::SocketAddr, reuse: bool) -> Result<TcpSocket, std:
73
 }
73
 }
74
 
74
 
75
 impl FramedStream {
75
 impl FramedStream {
76
-    pub async fn new<T1: ToSocketAddrs, T2: ToSocketAddrs>(
77
-        remote_addr: T1,
78
-        local_addr: T2,
76
+    pub async fn new<T: ToSocketAddrs + std::fmt::Display>(
77
+        remote_addr: T,
78
+        local_addr: Option<SocketAddr>,
79
         ms_timeout: u64,
79
         ms_timeout: u64,
80
     ) -> ResultType<Self> {
80
     ) -> ResultType<Self> {
81
-        for local_addr in lookup_host(&local_addr).await? {
82
-            for remote_addr in lookup_host(&remote_addr).await? {
83
-                let stream = super::timeout(
84
-                    ms_timeout,
85
-                    new_socket(local_addr, true)?.connect(remote_addr),
86
-                )
87
-                .await??;
88
-                stream.set_nodelay(true).ok();
89
-                let addr = stream.local_addr()?;
90
-                return Ok(Self(
91
-                    Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
92
-                    addr,
93
-                    None,
94
-                    0,
95
-                ));
81
+        for remote_addr in lookup_host(&remote_addr).await? {
82
+            let local = if let Some(addr) = local_addr {
83
+                addr
84
+            } else {
85
+                crate::config::Config::get_any_listen_addr(remote_addr.is_ipv4())
86
+            };
87
+            if let Ok(socket) = new_socket(local, true) {
88
+                if let Ok(Ok(stream)) =
89
+                    super::timeout(ms_timeout, socket.connect(remote_addr)).await
90
+                {
91
+                    stream.set_nodelay(true).ok();
92
+                    let addr = stream.local_addr()?;
93
+                    return Ok(Self(
94
+                        Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
95
+                        addr,
96
+                        None,
97
+                        0,
98
+                    ));
99
+                }
96
             }
100
             }
97
         }
101
         }
98
-        bail!("could not resolve to any address");
102
+        bail!(format!("Failed to connect to {}", remote_addr));
99
     }
103
     }
100
 
104
 
101
-    pub async fn connect<'a, 't, P, T1, T2>(
105
+    pub async fn connect<'a, 't, P, T>(
102
         proxy: P,
106
         proxy: P,
103
-        target: T1,
104
-        local: T2,
107
+        target: T,
108
+        local_addr: Option<SocketAddr>,
105
         username: &'a str,
109
         username: &'a str,
106
         password: &'a str,
110
         password: &'a str,
107
         ms_timeout: u64,
111
         ms_timeout: u64,
108
     ) -> ResultType<Self>
112
     ) -> ResultType<Self>
109
     where
113
     where
110
         P: ToProxyAddrs,
114
         P: ToProxyAddrs,
111
-        T1: IntoTargetAddr<'t>,
112
-        T2: ToSocketAddrs,
115
+        T: IntoTargetAddr<'t>,
113
     {
116
     {
114
-        if let Some(local) = lookup_host(&local).await?.next() {
115
-            if let Some(proxy) = proxy.to_proxy_addrs().next().await {
116
-                let stream =
117
-                    super::timeout(ms_timeout, new_socket(local, true)?.connect(proxy?)).await??;
118
-                stream.set_nodelay(true).ok();
119
-                let stream = if username.trim().is_empty() {
120
-                    super::timeout(
121
-                        ms_timeout,
122
-                        Socks5Stream::connect_with_socket(stream, target),
123
-                    )
124
-                    .await??
125
-                } else {
126
-                    super::timeout(
127
-                        ms_timeout,
128
-                        Socks5Stream::connect_with_password_and_socket(
129
-                            stream, target, username, password,
130
-                        ),
131
-                    )
132
-                    .await??
133
-                };
134
-                let addr = stream.local_addr()?;
135
-                return Ok(Self(
136
-                    Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
137
-                    addr,
138
-                    None,
139
-                    0,
140
-                ));
117
+        if let Some(Ok(proxy)) = proxy.to_proxy_addrs().next().await {
118
+            let local = if let Some(addr) = local_addr {
119
+                addr
120
+            } else {
121
+                crate::config::Config::get_any_listen_addr(proxy.is_ipv4())
122
+            };
123
+            let stream =
124
+                super::timeout(ms_timeout, new_socket(local, true)?.connect(proxy)).await??;
125
+            stream.set_nodelay(true).ok();
126
+            let stream = if username.trim().is_empty() {
127
+                super::timeout(
128
+                    ms_timeout,
129
+                    Socks5Stream::connect_with_socket(stream, target),
130
+                )
131
+                .await??
132
+            } else {
133
+                super::timeout(
134
+                    ms_timeout,
135
+                    Socks5Stream::connect_with_password_and_socket(
136
+                        stream, target, username, password,
137
+                    ),
138
+                )
139
+                .await??
141
             };
140
             };
142
-        };
141
+            let addr = stream.local_addr()?;
142
+            return Ok(Self(
143
+                Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
144
+                addr,
145
+                None,
146
+                0,
147
+            ));
148
+        }
143
         bail!("could not resolve to any address");
149
         bail!("could not resolve to any address");
144
     }
150
     }
145
 
151
 
@@ -252,6 +258,38 @@ pub async fn new_listener<T: ToSocketAddrs>(addr: T, reuse: bool) -> ResultType<
252
     }
258
     }
253
 }
259
 }
254
 
260
 
261
+pub async fn listen_any(port: u16) -> ResultType<TcpListener> {
262
+    if let Ok(mut socket) = TcpSocket::new_v6() {
263
+        #[cfg(unix)]
264
+        {
265
+            use std::os::unix::io::{FromRawFd, IntoRawFd};
266
+            let raw_fd = socket.into_raw_fd();
267
+            let sock2 = unsafe { socket2::Socket::from_raw_fd(raw_fd) };
268
+            sock2.set_only_v6(false).ok();
269
+            socket = unsafe { TcpSocket::from_raw_fd(sock2.into_raw_fd()) };
270
+        }
271
+        #[cfg(windows)]
272
+        {
273
+            use std::os::windows::prelude::{FromRawSocket, IntoRawSocket};
274
+            let raw_socket = socket.into_raw_socket();
275
+            let sock2 = unsafe { socket2::Socket::from_raw_socket(raw_socket) };
276
+            sock2.set_only_v6(false).ok();
277
+            socket = unsafe { TcpSocket::from_raw_socket(sock2.into_raw_socket()) };
278
+        }
279
+        if socket
280
+            .bind(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port))
281
+            .is_ok()
282
+        {
283
+            if let Ok(l) = socket.listen(DEFAULT_BACKLOG) {
284
+                return Ok(l);
285
+            }
286
+        }
287
+    }
288
+    let s = TcpSocket::new_v4()?;
289
+    s.bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port))?;
290
+    Ok(s.listen(DEFAULT_BACKLOG)?)
291
+}
292
+
255
 impl Unpin for DynTcpStream {}
293
 impl Unpin for DynTcpStream {}
256
 
294
 
257
 impl AsyncRead for DynTcpStream {
295
 impl AsyncRead for DynTcpStream {

+ 11 - 2
libs/hbb_common/src/udp.rs

@@ -49,7 +49,7 @@ impl FramedSocket {
49
 
49
 
50
     #[allow(clippy::never_loop)]
50
     #[allow(clippy::never_loop)]
51
     pub async fn new_reuse<T: std::net::ToSocketAddrs>(addr: T) -> ResultType<Self> {
51
     pub async fn new_reuse<T: std::net::ToSocketAddrs>(addr: T) -> ResultType<Self> {
52
-        for addr in addr.to_socket_addrs()?.filter(|x| x.is_ipv4()) {
52
+        for addr in addr.to_socket_addrs()? {
53
             let socket = new_socket(addr, true, 0)?.into_udp_socket();
53
             let socket = new_socket(addr, true, 0)?.into_udp_socket();
54
             return Ok(Self::Direct(UdpFramed::new(
54
             return Ok(Self::Direct(UdpFramed::new(
55
                 UdpSocket::from_std(socket)?,
55
                 UdpSocket::from_std(socket)?,
@@ -63,7 +63,7 @@ impl FramedSocket {
63
         addr: T,
63
         addr: T,
64
         buf_size: usize,
64
         buf_size: usize,
65
     ) -> ResultType<Self> {
65
     ) -> ResultType<Self> {
66
-        for addr in addr.to_socket_addrs()?.filter(|x| x.is_ipv4()) {
66
+        for addr in addr.to_socket_addrs()? {
67
             return Ok(Self::Direct(UdpFramed::new(
67
             return Ok(Self::Direct(UdpFramed::new(
68
                 UdpSocket::from_std(new_socket(addr, false, buf_size)?.into_udp_socket())?,
68
                 UdpSocket::from_std(new_socket(addr, false, buf_size)?.into_udp_socket())?,
69
                 BytesCodec::new(),
69
                 BytesCodec::new(),
@@ -164,4 +164,13 @@ impl FramedSocket {
164
             None
164
             None
165
         }
165
         }
166
     }
166
     }
167
+
168
+    pub fn is_ipv4(&self) -> bool {
169
+        if let FramedSocket::Direct(x) = self {
170
+            if let Ok(v) = x.get_ref().local_addr() {
171
+                return v.is_ipv4();
172
+            }
173
+        }
174
+        true
175
+    }
167
 }
176
 }

+ 1 - 1
src/rendezvous_server.rs

@@ -1202,7 +1202,7 @@ async fn check_relay_servers(rs0: Arc<RelayServers>, tx: Sender) {
1202
         let rs = rs.clone();
1202
         let rs = rs.clone();
1203
         let x = x.clone();
1203
         let x = x.clone();
1204
         futs.push(tokio::spawn(async move {
1204
         futs.push(tokio::spawn(async move {
1205
-            if FramedStream::new(&host, "0.0.0.0:0", CHECK_RELAY_TIMEOUT)
1205
+            if FramedStream::new(&host, None, CHECK_RELAY_TIMEOUT)
1206
                 .await
1206
                 .await
1207
                 .is_ok()
1207
                 .is_ok()
1208
             {
1208
             {

+ 2 - 1
src/version.rs

@@ -1 +1,2 @@
1
-pub const VERSION: &str = "1.1.6";
1
+pub const VERSION: &str = "1.1.6";
2
+pub const BUILD_DATE: &str = "2023-01-06 10:39";