Compare commits

...

64 Commits

Author SHA1 Message Date
matt
53a9d11b01 Updated release notes and descriptions 2022-11-29 14:16:37 +00:00
matt
ee0fccdc6a 1.0.0 release 2022-11-29 12:18:40 +00:00
matt
dcadd1e6d6 update novnc ref 2022-11-28 20:38:50 +00:00
Matthew McClaskey
848d45b411 Merge branch 'bugfix/KASM-2518-fix-opensuse-cert-dir' into 'master'
Resolve KASM-2518 "Bugfix/ fix opensuse cert dir"

Closes KASM-2518

See merge request kasm-technologies/internal/KasmVNC!79
2022-11-28 12:39:56 +00:00
Matthew McClaskey
2f7f090b36 Merge branch 'feature/KASM-3381_kasmvnc_docs' into 'master'
update docs

Closes KASM-3381

See merge request kasm-technologies/internal/KasmVNC!80
2022-11-28 12:04:38 +00:00
matt
a43ea72dc1 update docs 2022-11-25 19:59:06 +00:00
Dmitry Maksyoma
df70b38db6 openSUSE: speed up package building and testing 2022-11-25 01:22:28 +13:00
Dmitry Maksyoma
e68c9aef30 openSUSE: use proper directory to store SSL cert 2022-11-25 01:21:07 +13:00
Matthew McClaskey
39f6ee7691 Merge branch 'bugfix/KASM-3612-oracle-missing-dependency-on-hostname' into 'master'
Oracle: add hostname dependency

Closes KASM-3612

See merge request kasm-technologies/internal/KasmVNC!78
2022-11-22 17:20:36 +00:00
Dmitry Maksyoma
6089c22fb7 Oracle: add hostname dependency 2022-11-19 20:16:18 +13:00
matt
f1630b9ce7 update novnc ref 2022-11-18 22:23:18 +00:00
matt
fe978f0382 update novnc ref 2022-11-17 19:23:03 +00:00
matt
435b987c95 update noVNC ref 2022-11-15 17:17:10 +00:00
Matthew McClaskey
bd5d8b46e4 Merge branch 'configchecks' into 'master'
Fix refreshing with Extreme preset not applying correctly

See merge request kasm-technologies/internal/KasmVNC!77
2022-11-15 14:56:29 +00:00
Lauri Kasanen
6793a20eca Accidentally accessed wrong var 2022-11-15 10:26:09 +02:00
mattmcclaskey
6c4d53f9f3 update novnc ref 2022-11-11 09:33:43 -05:00
Matthew McClaskey
9fe3ac1ea5 Merge branch 'feature/KASM-3542_vncserver_new_configs' into 'master'
KASM-3542 new yaml options http-header, stun server

See merge request kasm-technologies/internal/KasmVNC!76
2022-11-10 10:36:24 +00:00
Matthew McClaskey
bd8b6d522a KASM-3542 new yaml options http-header, stun server 2022-11-10 10:36:24 +00:00
Matthew McClaskey
d9b5b5db6a Merge branch 'feature/KASM-3527_http_headers' into 'master'
Resolve KASM-3527 "Feature/ http headers"

Closes KASM-3527

See merge request kasm-technologies/internal/KasmVNC!75
2022-11-09 10:35:42 +00:00
Matthew McClaskey
7e4b5daf52 change -H to -http-header 2022-11-08 20:40:16 +00:00
Lauri Kasanen
17947d5f44 Add support for custom response headers 2022-11-08 18:16:16 +02:00
matt
6b9f28760c update novnc ref 2022-11-04 19:43:03 +00:00
mattmcclaskey
2e0d35b48f point novnc ref to tip of master 2022-11-02 13:35:06 -04:00
Matthew McClaskey
87bdac9fea Merge branch 'feature/KASM-2948_qoi_support' into 'master'
Resolve KASM-2948 "Feature/ qoi support"

Closes KASM-2948

See merge request kasm-technologies/internal/KasmVNC!61
2022-11-02 17:32:58 +00:00
Anthony Merrill
ba8e98a9f6 Resolve KASM-2948 "Feature/ qoi support" 2022-11-02 17:32:58 +00:00
mattmcclaskey
e0e4dbe173 update novnc ref 2022-11-02 08:15:15 -04:00
Matthew McClaskey
9622048753 Merge branch 'configchecks' into 'master'
Fixes dynamic quality changes by client not applying

See merge request kasm-technologies/internal/KasmVNC!74
2022-10-28 09:45:03 +00:00
Matthew McClaskey
91f619db5c Fixes dynamic quality changes by client not applying 2022-10-28 09:45:03 +00:00
mattmcclaskey
b93ead6a24 update novnc ref 2022-10-19 11:05:19 -04:00
Matthew McClaskey
3b1a72738e Merge branch 'feature/KASM-3371_keystroke_logging' into 'master'
Move keypress logging behind the DLP_Log verbose option

Closes KASM-3371

See merge request kasm-technologies/internal/KasmVNC!71
2022-10-14 18:06:37 +00:00
Matthew McClaskey
0cf6654355 Merge branch 'feature/KASM-2346_extend_mouse_buttons' into 'master'
Resolve KASM-2346 "Feature/ extend mouse buttons"

Closes KASM-2346

See merge request kasm-technologies/internal/KasmVNC!72
2022-10-14 17:28:03 +00:00
Matthew McClaskey
99c1e10789 Resolve KASM-2346 "Feature/ extend mouse buttons" 2022-10-14 17:28:02 +00:00
Lauri Kasanen
17cd5e597a Move keypress logging behind the DLP_Log verbose option 2022-10-13 13:39:49 +03:00
matt
0ef8a51945 update novnc ref, tip of master 2022-10-12 10:08:50 +00:00
Matthew McClaskey
54d2d12006 Merge branch 'feature/KASM-3329_ssl_cert_check' into 'master'
vncserver: don't require group memebership for cert readability check

Closes KASM-3329

See merge request kasm-technologies/internal/KasmVNC!70
2022-10-12 09:52:39 +00:00
Dmitry Maksyoma
34ca7595e8 vncserver: don't require group memebership for cert readability check 2022-10-12 09:52:39 +00:00
Matthew McClaskey
bef16c5b34 Merge branch 'feature/KASM-3307_auto_downgrade_tcp' into 'master'
Implement server-side downgrade from udp

Closes KASM-3307

See merge request kasm-technologies/internal/KasmVNC!68
2022-10-11 16:34:51 +00:00
Lauri Kasanen
582740b3d8 Implement server-side downgrade from udp 2022-10-03 14:54:40 +03:00
Matthew McClaskey
6c0eff0828 Merge branch 'bugfix/KASM-3184_gcc12_bug' into 'master'
KASM-3184 force the use of gcc11

See merge request kasm-technologies/internal/KasmVNC!65
2022-10-03 09:52:25 +00:00
mattmcclaskey
365f6bc33e KASM-3196 Update novnc ref 2022-09-16 18:39:10 +00:00
Matthew McClaskey
7a51039864 Merge branch 'bugfix/KASM-3180-enable-clipboard-in-config' into 'master'
Enable clipboard from and to client by default

See merge request kasm-technologies/internal/KasmVNC!66
2022-09-08 12:09:42 +00:00
Dmitry Maksyoma
9a49e96a7f Enable clipboard from and to client by default 2022-09-08 15:33:49 +12:00
Matthew McClaskey
250ca19199 KASM-3184 force the use of gcc11 2022-09-07 10:11:06 +00:00
Anthony Merrill
4a4c53d292 Merge branch 'KASM-1810_yaml_config-merged' into 'master'
Config-based KasmVNC

Closes KASM-1810

See merge request kasm-technologies/internal/KasmVNC!59
2022-08-19 17:54:11 +00:00
Dmitry Maksyoma
36a1ffc5e4 Config-based KasmVNC 2022-08-19 17:54:11 +00:00
Anthony Merrill
0ae83c02b0 Merge branch 'bugfix/KASM-3036-rpm-to-be-on-0.9.4' into 'master'
Fix CI to mangle rpms to produce 0.9.4

See merge request kasm-technologies/internal/KasmVNC!60
2022-08-19 13:11:15 +00:00
Dmitry Maksyoma
bc926f9a55 Fix CI to mangle rpms to produce 0.9.4 2022-08-19 13:11:15 +00:00
Anthony Merrill
d9cf46f83e Merge branch 'feature/KASM-2797_force_full_frame' into 'master'
Add /api/send_full_frame

Closes KASM-2797

See merge request kasm-technologies/internal/KasmVNC!50
2022-08-14 16:13:32 +00:00
Anthony Merrill
5e2a8b45da Merge branch 'feature/KASM-2837_STUN_srv_setting' into 'master'
Add -stunServer arg for on-premises STUN

Closes KASM-2837

See merge request kasm-technologies/internal/KasmVNC!57
2022-08-14 15:51:23 +00:00
Anthony Merrill
bb1bd5ff8f Merge branch 'feature/CVE-2022-2320' into 'master'
adding CVE patches for out of bounds memory access

See merge request kasm-technologies/internal/KasmVNC!55
2022-08-11 15:31:17 +00:00
Matthew McClaskey
5ecd18bf1b Merge branch 'feature/KASM-2997_udp_port_arg' into 'master'
Add -udpPort

Closes KASM-2997

See merge request kasm-technologies/internal/KasmVNC!58
2022-08-02 17:56:49 +00:00
Lauri Kasanen
f8013340ab Add -udpPort 2022-08-01 17:44:26 +03:00
Lauri Kasanen
6451cc3220 Add -stunServer arg for on-premises STUN 2022-08-01 13:26:59 +03:00
matt
6e52b24992 Update noVNC ref to tip of master 2022-07-29 16:47:20 +00:00
Matthew McClaskey
6fd2ad874f Merge branch 'udp' into 'master'
Udp

See merge request kasm-technologies/internal/KasmVNC!52
2022-07-26 10:38:15 +00:00
Lauri Kasanen
3b40a92548 Udp 2022-07-26 10:38:14 +00:00
ryan.kuba
a4ac7bee16 adding CVE patches for out of bounds memory access 2022-07-14 09:33:09 -04:00
Matthew McClaskey
ba902f8194 Merge branch 'feature/KASM-2801-remove-libnettle-dependency' into 'master'
Resolve KASM-2801 "Feature/ remove libnettle dependency"

Closes KASM-2801

See merge request kasm-technologies/internal/KasmVNC!54
2022-07-08 14:13:05 +00:00
Dmitry Maksyoma
1f5125fb24 Resolve KASM-2801 "Feature/ remove libnettle dependency" 2022-07-08 14:13:05 +00:00
Matthew McClaskey
dc60c73a81 Merge branch 'feature/KASM-2702-README-set-password' into 'master'
Resolve KASM-2702 "Feature/ readme set password"

Closes KASM-2702

See merge request kasm-technologies/internal/KasmVNC!53
2022-07-07 14:40:57 +00:00
Dmitry Maksyoma
1c54f4f921 Improve prompts to create a user 2022-07-06 01:56:32 +12:00
Dmitry Maksyoma
60de5a9791 Add instructions to add a KasmVNC user to README 2022-07-06 01:35:23 +12:00
Matthew McClaskey
480add4fe2 [skip CI] Update readme 2022-07-02 20:50:45 +00:00
Lauri Kasanen
8b71ea3cd9 Add /api/send_full_frame 2022-06-23 18:53:27 +03:00
173 changed files with 11421 additions and 1237 deletions

View File

@@ -3,7 +3,7 @@
is_kasmvnc() {
local package="$1";
echo "$package" | grep -q 'kasmvncserver_'
echo "$package" | grep -qP 'kasmvncserver(_|-)[0-9]'
}
detect_deb_package_arch() {

1
.gitignore vendored
View File

@@ -14,6 +14,7 @@ config.h
builder/build/
builder/www/
spec/tmp
# Deb building artefacts
debian/.debhelper/

View File

@@ -15,6 +15,7 @@ variables:
stages:
- www
- build
- test
- upload
.prepare_build: &prepare_build
@@ -371,6 +372,13 @@ build_opensuse_15_arm:
paths:
- output/
test:
stage: test
before_script:
- *prepare_build
script:
- bash builder/test-vncserver
upload:
stage: upload
image: ubuntu:focal

View File

@@ -79,6 +79,9 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fopenmp")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
# Enable C++ 11
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
# Tell the compiler to be stringent
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wformat=2")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wformat=2")

15
Pipfile Normal file
View File

@@ -0,0 +1,15 @@
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[packages]
mamba = "*"
expects = "*"
"path.py" = "*"
pexpect = "*"
[dev-packages]
[requires]
python_version = "3.8"

136
Pipfile.lock generated Normal file
View File

@@ -0,0 +1,136 @@
{
"_meta": {
"hash": {
"sha256": "6745d5e5d90e44a18d73a0e23bc3d3e68acb950af0b87df50b45272d25b9e615"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.8"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.python.org/simple",
"verify_ssl": true
}
]
},
"default": {
"args": {
"hashes": [
"sha256:a785b8d837625e9b61c39108532d95b85274acd679693b71ebb5156848fcf814"
],
"version": "==0.1.0"
},
"clint": {
"hashes": [
"sha256:05224c32b1075563d0b16d0015faaf9da43aa214e4a2140e51f08789e7a4c5aa"
],
"version": "==0.5.1"
},
"coverage": {
"hashes": [
"sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c",
"sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6",
"sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45",
"sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a",
"sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03",
"sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529",
"sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a",
"sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a",
"sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2",
"sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6",
"sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759",
"sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53",
"sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a",
"sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4",
"sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff",
"sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502",
"sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793",
"sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb",
"sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905",
"sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821",
"sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b",
"sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81",
"sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0",
"sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b",
"sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3",
"sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184",
"sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701",
"sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a",
"sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82",
"sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638",
"sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5",
"sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083",
"sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6",
"sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90",
"sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465",
"sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a",
"sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3",
"sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e",
"sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066",
"sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf",
"sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b",
"sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae",
"sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669",
"sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873",
"sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b",
"sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6",
"sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb",
"sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160",
"sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c",
"sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079",
"sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d",
"sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==5.5"
},
"expects": {
"hashes": [
"sha256:419902ccafe81b7e9559eeb6b7a07ef9d5c5604eddb93000f0642b3b2d594f4c"
],
"index": "pypi",
"version": "==0.9.0"
},
"mamba": {
"hashes": [
"sha256:75cfc6dfd287dcccaf86dd753cf48e0a7337487c7c3fafda05a6a67ded6da496"
],
"index": "pypi",
"version": "==0.11.2"
},
"path": {
"hashes": [
"sha256:2de925e8d421f93bcea80d511b81accfb6a7e6b249afa4a5559557b0cf817097",
"sha256:340054c5bb459fc9fd40e7eb6768c5989f3e599d18224238465b5333bc8faa7d"
],
"markers": "python_version >= '3.6'",
"version": "==16.2.0"
},
"path.py": {
"hashes": [
"sha256:8d885e8b2497aed005703d94e0fd97943401f035e42a136810308bff034529a8",
"sha256:a43e82eb2c344c3fd0b9d6352f6b856f40b8b7d3d65cc05978b42c3715668496"
],
"index": "pypi",
"version": "==12.5.0"
},
"pexpect": {
"hashes": [
"sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937",
"sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"
],
"index": "pypi",
"version": "==4.8.0"
},
"ptyprocess": {
"hashes": [
"sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35",
"sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"
],
"version": "==0.7.0"
}
},
"develop": {}
}

306
README.md
View File

@@ -1,21 +1,242 @@
# KasmVNC - Linux Web Remote Desktop
<a href="https://kasmweb.com"><img src="https://kasm-static-content.s3.amazonaws.com/logo_kasm.png" width="300"><a/>
[Kasm Technologies](https://www.kasmweb.com) developed Kasm Workspaces, the Containerized Streaming Platform. Kasm has open-sourced the Workspace docker images, which include containerized [full desktops and apps](https://github.com/kasmtech/workspaces-images) and [base images](https://github.com/kasmtech/workspaces-core-images) intended for developers to create custimized streaming containers. These containers can be used standalone or within the [Kasm Workspaces Platform](https://www.kasmweb.com) which provides a full Enterprise feature set. KasmVNC is used as the streaming tech for our container images, however, you can use KasmVNC for individual servers. While the term VNC is in the name, KasmVNC is not intended to remain compliant with the RFB spec and has different goals than other VNC projects:
- Web-based - KasmVNC is designed to provide a web accessible remote desktop. It comes with a web server and websocket server built in. There is no need to install other components. Simply run and navigate to your desktop's URL on the port you specify. While you can still tun on the legacy VNC port, it is disabled by default.
- Security - The RFB specification (VNC) limits the password field to 8 characters, so while the client may take a longer password, only the first 8 characters are sent. KasmVNC defaults to HTTPS with HTTP Basic Auth and disables the legacy VNC authentication method which is not sufficiently secure for internet accessible systems.
- Simplicity - KasmVNC aims at being simple to deploy and configure.
KasmVNC provides remote web-based access to a Desktop or application. While VNC is in the name, KasmVNC differs from other VNC variants such as TigerVNC, RealVNC, and TurboVNC. KasmVNC has broken from the RFB specification which defines VNC, in order to support modern technologies and increase security. KasmVNC is accessed by users from any modern browser and does not support legacy VNC viewer applications. KasmVNC uses a modern YAML based configuration at the server and user level, allowing for ease of management.
[Kasm Technologies](https://www.kasmweb.com) developed Kasm Workspaces, the Containerized Streaming Platform. Kasm has open-sourced the Workspace docker images, which include containerized [full desktops and apps](https://github.com/kasmtech/workspaces-images) and [base images](https://github.com/kasmtech/workspaces-core-images) intended for developers to create custimized streaming containers. These containers can be used standalone or within the [Kasm Workspaces Platform](https://www.kasmweb.com) which provides a full Enterprise feature set.
## Documentation
**Do not use the README from the master branch**, unless you are compiling KasmVNC yourself from the tip of master. Use the documentation for your specific release.
- [KasmVNC 1.0.0 Documentation](https://www.kasmweb.com/kasmvnc/docs/1.0.0/index.html)
For beta releases prior to version 1.0.0, use the README in this github project on the tagged commit for that release.
## Installation
**You must disconnect and reconnect to the server after installation, for the group membership to apply.**
### Debian/Ubuntu/Kali
```sh
# Please choose the package for your distro here (under Assets):
# https://github.com/kasmtech/KasmVNC/releases
wget <package_url>
sudo apt-get install ./kasmvncserver_*.deb
# Add your user to the ssl-cert group
sudo addgroup $USER ssl-cert
```
### Oracle 8
```sh
# Please choose the package for your distro here (under Assets):
# https://github.com/kasmtech/KasmVNC/releases
wget <package_url>
# Ensure KasmVNC dependencies are available
sudo dnf config-manager --set-enabled ol8_codeready_builder
sudo dnf install oracle-epel-release-el8
sudo dnf localinstall ./kasmvncserver_*.rpm
# Add your user to the kasmvnc-cert group
sudo usermod -a -G kasmvnc-cert $USER
```
### CentOS 7
```sh
# Please choose the package for your distro here (under Assets):
# https://github.com/kasmtech/KasmVNC/releases
wget <package_url>
# Ensure KasmVNC dependencies are available
sudo yum install epel-release
sudo yum install ./kasmvncserver_*.rpm
# Add your user to the kasmvnc-cert group
sudo usermod -a -G kasmvnc-cert $USER
```
## Getting Started
The following examples provide basic usage of KasmVNC with the tools provided. For full documentation on all the utilities and the runtime environment, see our [KasmVNC Documentation](https://www.kasmweb.com/kasmvnc/docs/latest/index.html)
```sh
# Start a session and be guided to setup a user and select a default desktop environment
vncserver
# Start a session with the mate desktop environment
vncserver -select-de mate
# Add a new user with read/write permissions
vncpasswd -u my_username -w -r
# Tail the logs
tail -f ~/.vnc/*.log
# Get a list of current sessions with display IDs
vncserver -list
# Kill the VNC session with display ID :2
vncserver -kill :2
```
## Configuration
KasmVNC is configured via YAML based configurations. The server level configuration is at `/etc/kasmvnc/kasmvnc.yaml`. Edits to this file apply to all users. Individual users can override server global configurations by specifying them in their configuration file at `~/.vnc/kasmvnc.yaml`.
The following configuration shows all default settings. Many of the encoding settings can be overridden by the client, unless the `runtime_configuration.allow_client_to_override_kasm_server_settings` setting is set tot false. By default the client is allowed to modify encoding settings.
For a full description of each setting see the [configuration reference](https://www.kasmweb/kasmvnc/docs/latest/index.html).
```yaml
desktop:
resolution:
width: 1024
height: 768
allow_resize: true
pixel_depth: 24
network:
protocol: http
interface: 0.0.0.0
websocket_port: auto
use_ipv4: true
use_ipv6: true
udp:
public_ip: auto
port: auto
stun_server: auto
ssl:
pem_certificate: /etc/ssl/certs/ssl-cert-snakeoil.pem
pem_key: /etc/ssl/private/ssl-cert-snakeoil.key
require_ssl: true
user_session:
new_session_disconnects_existing_exclusive_session: false
concurrent_connections_prompt: false
concurrent_connections_prompt_timeout: 10
idle_timeout: never
keyboard:
remap_keys:
ignore_numlock: false
raw_keyboard: false
pointer:
enabled: true
runtime_configuration:
allow_client_to_override_kasm_server_settings: true
allow_override_standard_vnc_server_settings: true
allow_override_list:
- pointer.enabled
- data_loss_prevention.clipboard.server_to_client.enabled
- data_loss_prevention.clipboard.client_to_server.enabled
- data_loss_prevention.clipboard.server_to_client.primary_clipboard_enabled
logging:
log_writer_name: all
log_dest: logfile
level: 30
security:
brute_force_protection:
blacklist_threshold: 5
blacklist_timeout: 10
data_loss_prevention:
visible_region:
# top: 10
# left: 10
# right: 40
# bottom: 40
concealed_region:
allow_click_down: false
allow_click_release: false
clipboard:
delay_between_operations: none
allow_mimetypes:
- chromium/x-web-custom-data
- text/html
- image/png
server_to_client:
enabled: true
size: unlimited
primary_clipboard_enabled: false
client_to_server:
enabled: true
size: unlimited
keyboard:
enabled: true
rate_limit: unlimited
logging:
level: off
encoding:
max_frame_rate: 60
full_frame_updates: none
rect_encoding_mode:
min_quality: 7
max_quality: 8
consider_lossless_quality: 10
rectangle_compress_threads: auto
video_encoding_mode:
jpeg_quality: -1
webp_quality: -1
max_resolution:
width: 1920
height: 1080
enter_video_encoding_mode:
time_threshold: 5
area_threshold: 45%
exit_video_encoding_mode:
time_threshold: 3
logging:
level: off
scaling_algorithm: progressive_bilinear
compare_framebuffer: auto
zrle_zlib_level: auto
hextile_improved_compression: true
server:
http:
headers:
- Cross-Origin-Embedder-Policy=require-corp
- Cross-Origin-Opener-Policy=same-origin
httpd_directory: /usr/share/kasmvnc/www
advanced:
x_font_path: auto
kasm_password_file: ${HOME}/.kasmpasswd
x_authority_file: auto
auto_shutdown:
no_user_session_timeout: never
active_user_session_timeout: never
inactive_user_session_timeout: never
command_line:
prompt: true
```
# New Features!
- Webp image compression for better bandwidth usage
- Automatic mixing of webp and jpeg based on CPU availability on server
- Multi-threaded image encoding for smoother frame rate for servers with more cores
- WebRTC UDP Transit
- Lossless QOI Image format for Local LAN
- [Full screen video detection](https://github.com/kasmtech/KasmVNC/wiki/Video-Rendering-Options#video-mode), goes into configurable video mode for better full screen videoo playback performance.
- [Dynamic jpeg/webp image coompression](https://github.com/kasmtech/KasmVNC/wiki/Video-Rendering-Options#dynamic-image-quality) quality settings based on screen change rates
- Seemless clipboard support (on Chromium based browsers)
- Binary clipboard support for text, images, and formatted text (on Chromium based browsers)
- Allow client to set/change most configuration settings
- [Data Loss Prevention features](https://github.com/kasmtech/KasmVNC/wiki/Data-Loss-Prevention)
- Key stroke logging
@@ -25,80 +246,19 @@
- Keyboard input rate limit
- Screen region selection
- Deb packages for Debian, Ubuntu, and Kali Linux included in release.
- RPM packages for CentOS, Fedora. RPM packages are currently not updatable and not released, though you can build and install them. See build documentation.
- RPM packages for CentOS, Oracle, OpenSUSE, Fedora. RPM packages are currently not updatable and not released, though you can build and install them. See build documentation.
- Web [API](https://github.com/kasmtech/KasmVNC/wiki/API) added for remotely controlling and getting information from KasmVNC
- Multi-User with ability to pass control to other users.
- Multi-User support with permissions that can be changed via the API
- Web UI uses a webpack for faster load times.
- Network and CPU bottleneck statistics
- Relative cursor support (game pointer mode)
- Cursor lock
- IME support for languages with extended characters
- Better mobile support
Future Goals:
- Support uploads and downloads
- Pre-build Packages for all major Linux distributions
### Installation
#### Debian-based
```sh
# Please choose the package for your distro here (under Assets):
# https://github.com/kasmtech/KasmVNC/releases
wget <package_url>
sudo apt-get install ./kasmvncserver_*.deb
# We provide an example script to run KasmVNC at #
# /usr/share/doc/kasmvncserver/examples/kasmvncserver-easy-start. It runs a VNC
# server on display :10 and on interface 0.0.0.0. If you're happy with those
# defaults you can just use it as is:
sudo ln -s /usr/share/doc/kasmvncserver/examples/kasmvncserver-easy-start /usr/bin/
# Add your user to the ssl-cert group
sudo addgroup $USER ssl-cert
# You will need to re-connect in order to pick up the group change
# Create ~/.vnc directory and corresponding files.
kasmvncserver-easy-start -d && kasmvncserver-easy-start -kill
# Modify vncstartup to launch your environment of choice, in this example LXDE
# This may be optional depending on your system configuration
echo '/usr/bin/lxsession -s LXDE &' >> ~/.vnc/xstartup
# Start KasmVNC with debug logging:
kasmvncserver-easy-start -d
# Tail the logs
tail -f ~/.vnc/`hostname`:10.log
```
Now navigate to your system at https://[ip-address]:8443/
To stop a running KasmVNC:
```sh
kasmvncserver-easy-start -kill
```
The options for vncserver:
| Argument | Description |
| -------- | ----------- |
| depth | Color depth, for jpeg/webp should be 24bit |
| geometry | Screensize, this will automatically be adjusted when the client connects. |
| websocketPort | The port to use for the web socket. Use a high port to avoid having to run as root. |
| cert | SSL cert to use for HTTPS |
| sslOnly | Disable HTTP |
| interface | Which interface to bind the web server to. |
### Development
Would you like to contribute to KasmVNC? Please reachout to us at info@kasmweb.com. We have investigated or are working on the following, if you have experience in these fields and would like to help please let us know.
Real-time H264 encoding using NVIDIA and Intel encoding technology.
Windows version of KasmVNC. We have been able to get it to compile for Windows and increased the performance, but still not releasable. Experienced Windows developers with a background in cross compiling would help.
ARM version of KasmVNC, we have had requests for this and at one point we did have an ARM build of KasmVNC but it takes dev cycles to mainain and bring it back to life.
- H264 encoding
### Compiling From Source
See the [builder/README.md](https://github.com/kasmtech/KasmVNC/blob/master/builder/README.md). We containerize our build systems to ensure highly repeatable builds.

View File

@@ -110,6 +110,37 @@ prepare_upload_filename "bionic/kasmvncserver_0.9.1~beta-1+libjpeg-turbo-latest_
echo $upload_filename;'
```
# vncserver development
## Building a Ubuntu Focal package
```
builder/build-package ubuntu focal
```
You don't need to `build-package` every time you change something, but you need
`Xvnc` and other files.
## Running a development environment
```
builder/devenv-vncserver
```
`devenv-vncserver` starts a dockerized development environment. Edit
`vncserver`, its modules, and configuration files as usual. When you
run`vncserver`, not `/usr/bin/vncserver` will be used, but `./unix/vncserver`.
Same for configuration files.
Python specs can be run with `ty` alias inside devenv.
### Debug output
To see `vncserver` output, run `ty -d`. Anything you print inside `vncserver`
with `say` should be visible that way.
### Finding which spec produces a warning
By default, each passing test is represented as a dot. `-v` flag causes tests to
emit test descriptions alongside the program's output. Run `ty -v`.
# ARM
KasmVNC is supported on ARM, however, the build process needs to be broken into two parts with one occuring on a x64 system and the other on an ARM system. All our testing and official builds are done on AWS Graviton instances.

View File

@@ -1,4 +1,6 @@
#!/bin/sh -e
#!/bin/bash
set -e
detect_quilt() {
if which quilt 1>/dev/null; then
@@ -13,17 +15,39 @@ ensure_crashpad_can_fetch_line_number_by_address() {
fi
}
fail_on_gcc_12() {
if [[ -n "$CC" && -n "$CXX" ]]; then
return;
fi
if gcc --version | head -1 | grep -q 12; then
cat >&2 <<EOF
Error: gcc 12 detected. It has a bug causing the build to fall because of a
-Warray-bounds bug. Please use gcc 11 in the build Dockerfile:
ENV CC=gcc-11
ENV CXX=g++-11
RUN <install gcc 11>
EOF
exit 1
fi
}
# For build-dep to work, the apt sources need to have the source server
#sudo apt-get build-dep xorg-server
#sudo apt-get install cmake git libjpeg-dev libgnutls-dev
# Gcc12 builds fail due to bug
fail_on_gcc_12
# Ubuntu applies a million patches, but here we use upstream to simplify matters
cd /tmp
# default to the version of x in Ubuntu 18.04, otherwise caller will need to specify
XORG_VER=${XORG_VER:-"1.19.6"}
XORG_PATCH=$(echo "$XORG_VER" | grep -Po '^\d.\d+' | sed 's#\.##')
wget https://www.x.org/archive/individual/xserver/xorg-server-${XORG_VER}.tar.bz2
wget --no-check-certificate https://www.x.org/archive/individual/xserver/xorg-server-${XORG_VER}.tar.bz2
#git clone https://kasmweb@bitbucket.org/kasmtech/kasmvnc.git
#cd kasmvnc
@@ -36,7 +60,8 @@ sed -i -e '/find_package(FLTK/s@^@#@' \
-e '/add_subdirectory(tests/s@^@#@' \
CMakeLists.txt
cmake -D CMAKE_BUILD_TYPE=RelWithDebInfo . -DBUILD_VIEWER:BOOL=OFF
cmake -D CMAKE_BUILD_TYPE=RelWithDebInfo . -DBUILD_VIEWER:BOOL=OFF \
-DENABLE_GNUTLS:BOOL=OFF
make -j5
tar -C unix/xserver -xf /tmp/xorg-server-${XORG_VER}.tar.bz2 --strip-components=1
@@ -45,9 +70,13 @@ cd unix/xserver
patch -Np1 -i ../xserver${XORG_PATCH}.patch
case "$XORG_VER" in
1.20.*)
patch -s -p0 < ../CVE-2022-2320-v1.20.patch
if [ -f ../xserver120.7.patch ]; then
patch -Np1 -i ../xserver120.7.patch
fi ;;
1.19.*)
patch -s -p0 < ../CVE-2022-2320-v1.19.patch
;;
esac
autoreconf -i
@@ -65,6 +94,7 @@ fi
--with-xkb-output=/var/lib/xkb \
--with-xkb-bin-directory=/usr/bin \
--with-default-font-path="/usr/share/fonts/X11/misc,/usr/share/fonts/X11/cyrillic,/usr/share/fonts/X11/100dpi/:unscaled,/usr/share/fonts/X11/75dpi/:unscaled,/usr/share/fonts/X11/Type1,/usr/share/fonts/X11/100dpi,/usr/share/fonts/X11/75dpi,built-ins" \
--with-sha1=libcrypto \
--without-dtrace --disable-dri \
--disable-static \
--disable-xinerama --disable-xvfb --disable-xnest --disable-xorg \

1
builder/common.sh Normal file
View File

@@ -0,0 +1 @@
VNC_PORT=8443

20
builder/devenv-vncserver Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
set -e
default_os=ubuntu
default_os_codename=focal
cd "$(dirname "$0")/.."
. ./builder/os_ver_cli.sh
. ./builder/common.sh
docker build --build-arg KASMVNC_PACKAGE_DIR="builder/build/${os_codename}" \
-t kasmvncdevenv_${os}:$os_codename \
-f builder/dockerfile.${os}_${os_codename}.vncserver_devenv.test .
docker run -it -v $(realpath ${PWD}):/src -p "443:$VNC_PORT" -p "$VNC_PORT:$VNC_PORT" \
-p 8444:8444 \
-p 8445:8445 --rm \
-e KASMVNC_VERBOSE_LOGGING=$KASMVNC_VERBOSE_LOGGING \
-e "VNC_USER=foo" -e "VNC_PW=foobar" \
kasmvncdevenv_${os}:$os_codename

View File

@@ -1,18 +1,20 @@
FROM centos:centos7
ENV STARTUPDIR=/dockerstartup
RUN yum install -y xterm
RUN yum install -y vim less
RUN yum install -y redhat-lsb-core
RUN yum install -y epel-release
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp
COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp/
RUN yum localinstall -y /tmp/*.rpm
RUN useradd -m foo
RUN mkdir -p $STARTUPDIR
COPY startup/vnc_startup_barebones.sh $STARTUPDIR
RUN useradd -m foo
USER foo:kasmvnc-cert
RUN mkdir ~/.vnc && echo '/usr/bin/xterm &' >> ~/.vnc/xstartup && \
chmod +x ~/.vnc/xstartup
ENTRYPOINT bash -c "echo -e \"$VNC_PW\n$VNC_PW\n\" | kasmvncpasswd -w -u \"$VNC_USER\" && vncserver :1 -interface 0.0.0.0 && vncserver -kill :1 && vncserver :1 -depth 24 -geometry 1280x1050 -websocketPort 8443 -cert /etc/pki/tls/private/kasmvnc.pem -sslOnly -FrameRate=24 -interface 0.0.0.0 -httpd /usr/share/kasmvnc/www && tail -f $HOME/.vnc/*.log "
ENTRYPOINT "/$STARTUPDIR/vnc_startup_barebones.sh"

View File

@@ -41,13 +41,16 @@ RUN yum install -y vim less
RUN echo 'source $STARTUPDIR/generate_container_user' >> $HOME/.bashrc
RUN mkdir -p $STARTUPDIR
COPY startup/ $STARTUPDIR
COPY builder/startup/ $STARTUPDIR
### START CUSTOM STUFF ####
COPY ./builder/scripts/ /tmp/scripts/
COPY ./centos/kasmvncserver.spec /tmp
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp
RUN yum localinstall -y /tmp/*.rpm
COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp/
RUN /tmp/scripts/install_kasmvncserver_package
### END CUSTOM STUFF ###

View File

@@ -37,13 +37,16 @@ RUN apt-get update && apt-get -y install lsb-release
RUN echo 'source $STARTUPDIR/generate_container_user' >> $HOME/.bashrc
RUN mkdir -p $STARTUPDIR
COPY startup/ $STARTUPDIR
COPY builder/startup/ $STARTUPDIR
### START CUSTOM STUFF ####
COPY ./builder/scripts/ /tmp/scripts/
COPY ./debian/changelog /tmp
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/kasmvncserver_*.deb /tmp/
RUN rm -f /tmp/kasmvncserver_*+*.deb; dpkg -i /tmp/*.deb; apt-get -yf install
RUN /tmp/scripts/install_kasmvncserver_package
### END CUSTOM STUFF ###

View File

@@ -1,19 +1,19 @@
FROM debian:buster-slim
ENV STARTUPDIR=/dockerstartup
COPY ./builder/scripts/ /tmp/scripts/
COPY ./debian/changelog /tmp
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/kasmvncserver_*.deb /tmp/
RUN rm -f /tmp/kasmvncserver_*+*.deb; apt-get update && dpkg -i /tmp/*.deb; apt-get -yf install
RUN /tmp/scripts/install_kasmvncserver_package
RUN apt-get update && apt-get -y install xterm
COPY startup/deb/kasmvncserver-easy-start /usr/local/bin
RUN mkdir -p $STARTUPDIR
COPY builder/startup/vnc_startup_barebones.sh $STARTUPDIR
RUN useradd -m foo
USER foo:ssl-cert
RUN mkdir ~/.vnc && echo '/usr/bin/xterm &' >> ~/.vnc/xstartup && \
chmod +x ~/.vnc/xstartup
ENTRYPOINT bash -c "echo -e \"$VNC_PW\n$VNC_PW\n\" | \
kasmvncpasswd -w -u $VNC_USER $HOME/.kasmpasswd && \
kasmvncserver-easy-start && tail -f $HOME/.vnc/*.log"
ENTRYPOINT "/$STARTUPDIR/vnc_startup_barebones.sh"

View File

@@ -34,16 +34,26 @@ RUN apt-get purge -y pm-utils xscreensaver*
RUN apt-get update && apt-get install -y vim less
RUN apt-get update && apt-get -y install lsb-release
RUN apt-get update && apt-get install -y task-cinnamon-desktop
RUN apt-get update && apt-get install -y task-gnome-desktop
RUN mkdir -p /usr/share/man/man1
RUN apt-get update && apt-get install -y apt-utils openjdk-11-jre
RUN apt-get update && apt-get install -y task-lxde-desktop
RUN apt-get update && apt-get install -y task-mate-desktop
RUN apt-get update && apt-get install -y task-kde-desktop
RUN echo 'source $STARTUPDIR/generate_container_user' >> $HOME/.bashrc
RUN mkdir -p $STARTUPDIR
COPY startup/ $STARTUPDIR
COPY builder/startup/ $STARTUPDIR
### START CUSTOM STUFF ####
COPY ./builder/scripts/ /tmp/scripts/
COPY ./debian/changelog /tmp
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/kasmvncserver_*.deb /tmp/
RUN rm -f /tmp/kasmvncserver_*+*.deb; dpkg -i /tmp/*.deb; apt-get -yf install
RUN /tmp/scripts/install_kasmvncserver_package
### END CUSTOM STUFF ###

View File

@@ -1,18 +1,19 @@
FROM fedora:33
ENV STARTUPDIR=/dockerstartup
RUN dnf install -y xterm
RUN dnf install -y vim less
RUN yum install -y redhat-lsb-core
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp
COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp/
RUN dnf localinstall -y /tmp/*.rpm
RUN useradd -m foo
RUN mkdir -p $STARTUPDIR
COPY startup/vnc_startup_barebones.sh $STARTUPDIR
RUN useradd -m foo
USER foo:kasmvnc-cert
RUN mkdir ~/.vnc && echo '/usr/bin/xterm &' >> ~/.vnc/xstartup && \
chmod +x ~/.vnc/xstartup
ENTRYPOINT bash -c "echo -e \"$VNC_PW\n$VNC_PW\n\" | kasmvncpasswd -w -u \"$VNC_USER\" && vncserver :1 -interface 0.0.0.0 && vncserver -kill :1 && vncserver :1 -depth 24 -geometry 1280x1050 -websocketPort 8443 -cert /etc/pki/tls/private/kasmvnc.pem -sslOnly -FrameRate=24 -interface 0.0.0.0 -httpd /usr/share/kasmvnc/www && tail -f $HOME/.vnc/*.log "
ENTRYPOINT "/$STARTUPDIR/vnc_startup_barebones.sh"

View File

@@ -35,22 +35,24 @@ RUN dnf install -y xorg-x11-xauth xorg-x11-xkb-utils \
xkeyboard-config xorg-x11-server-utils
# xorg-x11-server-Xorg
# RUN dnf install -y @xfce-desktop-environment
RUN dnf group install -y lxde-desktop
RUN dnf erase -y pm-utils xscreensaver*
RUN dnf install -y redhat-lsb-core
RUN dnf install -y vim less
RUN dnf install -y @xfce-desktop-environment
RUN echo 'source $STARTUPDIR/generate_container_user' >> $HOME/.bashrc
RUN mkdir -p $STARTUPDIR
COPY startup/ $STARTUPDIR
COPY builder/startup/ $STARTUPDIR
### START CUSTOM STUFF ####
COPY ./builder/scripts/ /tmp/scripts/
COPY ./centos/kasmvncserver.spec /tmp
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp
COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp/
# RUN dnf remove -y tigervnc-server-minimal
RUN dnf localinstall -y --allowerasing /tmp/*.rpm
RUN /tmp/scripts/install_kasmvncserver_package
### END CUSTOM STUFF ###
@@ -58,7 +60,4 @@ RUN chown -R 1000:0 $HOME
USER 1000:kasmvnc-cert
WORKDIR $HOME
RUN mkdir ~/.vnc && echo '/usr/bin/lxsession -s LXDE &' >> ~/.vnc/xstartup && \
chmod +x ~/.vnc/xstartup
ENTRYPOINT [ "/dockerstartup/vnc_startup.sh" ]

View File

@@ -4,6 +4,8 @@ ENV KASMVNC_BUILD_OS kali
ENV KASMVNC_BUILD_OS_CODENAME kali-rolling
ENV XORG_VER 1.20.10
ENV DEBIAN_FRONTEND noninteractive
ENV CC=gcc-11
ENV CXX=g++-11
RUN grep '^deb' /etc/apt/sources.list | sed 's#^deb#deb-src#' >> /etc/apt/sources.list
@@ -12,6 +14,7 @@ RUN apt-get update && \
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends tzdata
RUN apt-get update && apt-get -y build-dep xorg-server libxfont-dev
RUN apt-get update && apt-get -y install gcc-11 g++-11
RUN apt-get update && apt-get -y install cmake git libjpeg-dev libgnutls28-dev vim wget tightvncserver
RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev libxcursor-dev
@@ -20,7 +23,7 @@ RUN cd /tmp && wget https://storage.googleapis.com/downloads.webmproject.org/rel
RUN cd /tmp && tar -xzf /tmp/libwebp-*
RUN cd /tmp/libwebp-1.0.2 && \
./configure --enable-static --disable-shared && \
make && make install
make -j$(nproc) && make install
RUN useradd -m docker && echo "docker:docker" | chpasswd && adduser docker sudo

View File

@@ -37,13 +37,15 @@ RUN apt-get update && apt-get -y install lsb-release
RUN echo 'source $STARTUPDIR/generate_container_user' >> $HOME/.bashrc
RUN mkdir -p $STARTUPDIR
COPY startup/ $STARTUPDIR
COPY builder/startup/ $STARTUPDIR
### START CUSTOM STUFF ####
COPY ./builder/scripts/ /tmp/scripts/
COPY ./debian/changelog /tmp
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/kasmvncserver_*.deb /tmp/
RUN rm -f /tmp/kasmvncserver_*+*.deb; dpkg -i /tmp/*.deb; apt-get -yf install
RUN /tmp/scripts/install_kasmvncserver_package
### END CUSTOM STUFF ###

View File

@@ -1,39 +1,23 @@
FROM opensuse/leap:15.3
ENV STARTUPDIR=/dockerstartup
# base tools
RUN zypper -n install -y \
less \
vim \
xterm
# deps and rpm install
RUN zypper -n install -y \
libglvnd \
libgnutls30 \
libgomp1 \
libjpeg8 \
libnettle6 \
libpixman-1-0 \
libXdmcp6 \
libXfont2-2 \
libxkbcommon-x11-0 \
openssl \
perl \
x11-tools \
xauth \
xkbcomp \
xkeyboard-config && \
mkdir -p /etc/pki/tls/private
# Cache repo updates, so that package changes don't trigger it on every build.
RUN zypper refresh
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp
RUN zypper install -y --allow-unsigned-rpm /tmp/*.rpm
RUN useradd -m foo
RUN mkdir -p $STARTUPDIR
COPY startup/vnc_startup_barebones.sh $STARTUPDIR
RUN useradd -m foo
USER foo:kasmvnc-cert
RUN mkdir ~/.vnc && echo '/usr/bin/xterm &' >> ~/.vnc/xstartup && \
chmod +x ~/.vnc/xstartup
ENTRYPOINT bash -c "echo -e \"$VNC_PW\n$VNC_PW\n\" | kasmvncpasswd -w -u \"$VNC_USER\" && vncserver :1 -interface 0.0.0.0 && vncserver -kill :1 && vncserver :1 -depth 24 -geometry 1280x1050 -websocketPort 6901 -cert /etc/pki/tls/private/kasmvnc.pem -sslOnly -FrameRate=24 -interface 0.0.0.0 -httpd /usr/share/kasmvnc/www && tail -f $HOME/.vnc/*.log "
ENTRYPOINT "/$STARTUPDIR/vnc_startup_barebones.sh"

View File

@@ -14,7 +14,10 @@ RUN zypper -n install -y \
tree \
vim
# Cache repo updates, so that spec changes don't trigger it on every build.
RUN zypper refresh
COPY opensuse/*.spec /tmp
RUN zypper -n install $(grep BuildRequires /tmp/*.spec | cut -d' ' -f2 | xargs)
RUN useradd -u 1000 -m -d /home/docker docker && \

View File

@@ -1,20 +1,23 @@
FROM oraclelinux:8
ENV STARTUPDIR=/dockerstartup
RUN dnf install -y \
less \
redhat-lsb-core \
vim \
xterm
RUN dnf config-manager --set-enabled ol8_codeready_builder
RUN dnf install -y oracle-epel-release-el8
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/*.rpm /tmp
RUN dnf localinstall -y /tmp/*.rpm
RUN useradd -m foo
RUN mkdir -p $STARTUPDIR
COPY startup/vnc_startup_barebones.sh $STARTUPDIR
RUN useradd -m foo
USER foo:kasmvnc-cert
RUN mkdir ~/.vnc && echo '/usr/bin/xterm &' >> ~/.vnc/xstartup && \
chmod +x ~/.vnc/xstartup
ENTRYPOINT bash -c "echo -e \"$VNC_PW\n$VNC_PW\n\" | kasmvncpasswd -w -u \"$VNC_USER\" && vncserver :1 -interface 0.0.0.0 && vncserver -kill :1 && vncserver :1 -depth 24 -geometry 1280x1050 -websocketPort 8443 -cert /etc/pki/tls/private/kasmvnc.pem -sslOnly -FrameRate=24 -interface 0.0.0.0 -httpd /usr/share/kasmvnc/www && tail -f $HOME/.vnc/*.log "
ENTRYPOINT "/$STARTUPDIR/vnc_startup_barebones.sh"

View File

@@ -4,7 +4,7 @@ RUN apt-get update && \
apt-get -y install vim build-essential devscripts equivs
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/libjpeg-turbo*deb /tmp
COPY $KASMVNC_PACKAGE_DIR/libjpeg-turbo*deb /tmp/
RUN apt-get install /tmp/libjpeg-turbo*deb
# Install build-deps for the package.

View File

@@ -37,17 +37,19 @@ RUN apt-get update && apt-get -y install lsb-release
RUN echo 'source $STARTUPDIR/generate_container_user' >> $HOME/.bashrc
RUN mkdir -p $STARTUPDIR
COPY startup/ $STARTUPDIR
COPY builder/startup/ $STARTUPDIR
### START CUSTOM STUFF ####
COPY ./builder/scripts/ /tmp/scripts/
COPY ./debian/changelog /tmp
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/libjpeg-turbo_*.deb /tmp
COPY $KASMVNC_PACKAGE_DIR/libjpeg-turbo_*.deb /tmp/
RUN apt-get install /tmp/libjpeg-turbo*deb
ARG BUILD_DEBIAN_REVISION
COPY $KASMVNC_PACKAGE_DIR/kasmvncserver_*$BUILD_DEBIAN_REVISION*.deb /tmp
RUN dpkg -i /tmp/*.deb; apt-get -yf install
COPY $KASMVNC_PACKAGE_DIR/kasmvncserver_*$BUILD_DEBIAN_REVISION*.deb /tmp/
RUN /tmp/scripts/install_kasmvncserver_package "$BUILD_DEBIAN_REVISION"
### END CUSTOM STUFF ###

View File

@@ -37,13 +37,16 @@ RUN apt-get update && apt-get -y install lsb-release
RUN echo 'source $STARTUPDIR/generate_container_user' >> $HOME/.bashrc
RUN mkdir -p $STARTUPDIR
COPY startup/ $STARTUPDIR
COPY builder/startup/ $STARTUPDIR
### START CUSTOM STUFF ####
COPY ./builder/scripts/ /tmp/scripts/
COPY ./debian/changelog /tmp
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/kasmvncserver_*.deb /tmp/
RUN rm -f /tmp/kasmvncserver_*+*.deb; dpkg -i /tmp/*.deb; apt-get -yf install
RUN /tmp/scripts/install_kasmvncserver_package
### END CUSTOM STUFF ###

View File

@@ -35,7 +35,7 @@ RUN apt-get purge -y pm-utils xscreensaver*
RUN echo 'source $STARTUPDIR/generate_container_user' >> $HOME/.bashrc
RUN mkdir -p $STARTUPDIR
COPY startup/ $STARTUPDIR
COPY builder/startup/ $STARTUPDIR
### START CUSTOM STUFF ####

View File

@@ -1,15 +1,16 @@
FROM ubuntu:focal
ENV STARTUPDIR=/dockerstartup
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/kasmvncserver_*.deb /tmp/
RUN rm -f /tmp/kasmvncserver_*+*.deb; apt-get update && dpkg -i /tmp/*.deb; apt-get -yf install
RUN apt-get update && apt-get -y install xterm lsb-release
RUN useradd -m foo && addgroup foo ssl-cert
RUN mkdir -p $STARTUPDIR
COPY builder/startup/vnc_startup_barebones.sh $STARTUPDIR
RUN useradd -m foo && addgroup foo ssl-cert
USER foo
RUN mkdir ~/.vnc && echo '/usr/bin/xterm &' >> ~/.vnc/xstartup && \
chmod +x ~/.vnc/xstartup
ENTRYPOINT bash -c "echo -e \"$VNC_PW\n$VNC_PW\n\" | kasmvncpasswd -w -u \"$VNC_USER\" && vncserver :1 -interface 0.0.0.0 && vncserver -kill :1 && vncserver :1 -depth 24 -geometry 1280x1050 -websocketPort 8443 -cert /etc/ssl/certs/ssl-cert-snakeoil.pem -key /etc/ssl/private/ssl-cert-snakeoil.key -sslOnly -FrameRate=24 -interface 0.0.0.0 -httpd /usr/share/kasmvnc/www && tail -f $HOME/.vnc/*.log "
ENTRYPOINT "/$STARTUPDIR/vnc_startup_barebones.sh"

View File

@@ -12,7 +12,7 @@ RUN apt-get update && \
RUN apt-get update && apt-get install -y --no-install-recommends tzdata
RUN apt-get update && apt-get -y build-dep xorg-server libxfont-dev
RUN apt-get update && apt-get -y install cmake git libjpeg-dev libgnutls28-dev vim wget tightvncserver
RUN apt-get update && apt-get -y install cmake git libjpeg-dev vim wget
RUN apt-get update && apt-get -y install libpng-dev libtiff-dev libgif-dev libavcodec-dev libssl-dev libxrandr-dev libxcursor-dev
# Additions for webp

View File

@@ -37,13 +37,16 @@ RUN apt-get update && apt-get -y install lsb-release
RUN echo 'source $STARTUPDIR/generate_container_user' >> $HOME/.bashrc
RUN mkdir -p $STARTUPDIR
COPY startup/ $STARTUPDIR
COPY builder/startup/ $STARTUPDIR
### START CUSTOM STUFF ####
COPY ./builder/scripts/ /tmp/scripts/
COPY ./debian/changelog /tmp
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/kasmvncserver_*.deb /tmp/
RUN rm -f /tmp/kasmvncserver_*+*.deb; dpkg -i /tmp/*.deb; apt-get -yf install
RUN /tmp/scripts/install_kasmvncserver_package
RUN mkdir ~/.vnc && echo '/usr/bin/xfce4-session &' >> ~/.vnc/xstartup && \
chmod +x ~/.vnc/xstartup

View File

@@ -0,0 +1,30 @@
FROM ubuntu:focal
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y vim less
RUN apt-get update && apt-get install -y python3-pip
RUN apt-get update && apt-get install -y strace silversearcher-ag xfonts-base
RUN apt-get update && apt-get install -y cinnamon
RUN apt-get update && apt-get install -y mate
RUN useradd -m docker
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/kasmvncserver_*.deb /tmp/
RUN apt-get install -y /tmp/*$(dpkg --print-architecture).deb
ENV USER docker
ENV HOME /home/docker
RUN chown -R 1000:0 $HOME
USER 1000:ssl-cert
WORKDIR $HOME
RUN pip3 install --user pipenv
RUN echo 'PATH="~/.local/bin/:$PATH"' >> ~/.bashrc
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
ENTRYPOINT ["/bin/bash", "-ic", "/src/builder/run-specs-inside-docker"]

View File

@@ -35,7 +35,7 @@ RUN apt-get purge -y pm-utils xscreensaver*
RUN echo 'source $STARTUPDIR/generate_container_user' >> $HOME/.bashrc
RUN mkdir -p $STARTUPDIR
COPY startup/ $STARTUPDIR
COPY builder/startup/ $STARTUPDIR
### START CUSTOM STUFF ####

View File

@@ -0,0 +1,67 @@
FROM ubuntu:focal
ENV DEBIAN_FRONTEND=noninteractive
ENV VNC_PORT 8443
ENV VNC_PORT2 8444
ENV VNC_PORT3 8445
EXPOSE $VNC_PORT
EXPOSE $VNC_PORT2
EXPOSE $VNC_PORT3
RUN apt-get update && apt-get install -y supervisor xfce4 xfce4-terminal xterm libnss-wrapper gettext wget
RUN apt-get update && apt-get install -y vim less
RUN apt-get update && apt-get -y install lsb-release
RUN apt-get update && apt-get -y install net-tools
# RUN mkdir -p /usr/share/man/man1
# RUN apt-get update && apt-get install -y apt-utils openjdk-11-jre
RUN apt-get update && apt-get install -y ubuntu-mate-desktop
RUN apt-get update && apt-get install -y lxde
RUN apt-get update && apt-get install -y lxqt
RUN apt-get update && apt-get install -y kde-plasma-desktop
RUN apt-get update && apt-get install -y cinnamon
RUN apt-get update && apt-get install -y gnome-session
RUN apt-get purge -y pm-utils xscreensaver*
RUN apt-get purge -y clipit magnus kgpg
RUN apt-get update && apt-get install -y python3-pip
RUN apt-get update && apt-get install -y strace
RUN apt-get update && apt-get install -y silversearcher-ag
RUN apt-get update && apt-get install -y libdevel-stacktrace-perl
RUN useradd -m docker
ENV USER docker
COPY ./builder/scripts/ /tmp/scripts/
COPY ./debian/changelog /tmp
ARG KASMVNC_PACKAGE_DIR
COPY $KASMVNC_PACKAGE_DIR/kasmvncserver_*.deb /tmp/
RUN /tmp/scripts/install_kasmvncserver_package
ENV KASMVNC_DEVELOPMENT 1
ENV HOME /home/docker
RUN chown -R 1000:0 $HOME
USER 1000:ssl-cert
WORKDIR $HOME
RUN pip3 install --user pipenv
RUN echo 'PATH="/src/unix:~/.local/bin/:$PATH"' >> ~/.bashrc
RUN echo 'export PERL5LIB="/src/unix"' >> ~/.bashrc
RUN echo 'ulimit -c 0' >> ~/.bashrc
RUN echo 'alias s="vncserver"' >> ~/.bashrc
RUN echo 'alias k="vncserver -kill :1; pkill baloo_file; pkill gpg-agent; pkill ssh-agent; pkill xiccd"' >> ~/.bashrc
RUN echo 'alias r="k; s"' >> ~/.bashrc
RUN echo 'alias go="vncserver; vncserver -kill :1"' >> ~/.bashrc
RUN echo 'alias ns="netstat -nltup"' >> ~/.bashrc
RUN echo 'alias tv="./run-specs spec/vncserver_yaml_validation_spec.py"' >> ~/.bashrc
RUN echo 'alias ty="./run-specs spec/vncserver_*spec.py"' >> ~/.bashrc
RUN echo 'alias ta="./run-specs"' >> ~/.bashrc
RUN echo 'alias vd="vncserver -dry-run"' >> ~/.bashrc
ENV SET_PASSWORD_FUNC 'sp() { echo -e "$VNC_PW\\n$VNC_PW\\n" | kasmvncpasswd -w -u $USER $HOME/.kasmpasswd; }'
RUN echo $SET_PASSWORD_FUNC >> ~/.bashrc
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
ENTRYPOINT ["bash", "-ic", "cd /src && pipenv install; exec bash"]

View File

@@ -37,7 +37,7 @@ RUN apt-get update && apt-get -y install lsb-release
RUN echo 'source $STARTUPDIR/generate_container_user' >> $HOME/.bashrc
RUN mkdir -p $STARTUPDIR
COPY startup/ $STARTUPDIR
COPY builder/startup/ $STARTUPDIR
### START CUSTOM STUFF ####

View File

@@ -1,9 +1,9 @@
default_os=ubuntu
default_os_codename=bionic
default_os=${default_os:-ubuntu}
default_os_codename=${default_os_codename:-bionic}
os=${1:-$default_os}
os_codename=${2:-$default_os_codename}
build_tag="${3:-}"
build_tag="$3"
if [[ -n "$build_tag" ]]; then
build_tag_for_images="_${build_tag#+}"
build_debian_revision="$(echo $build_tag | tr _ -)"

18
builder/run-specs-inside-docker Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail
logfile=/tmp/specs.log
red='\033[0;31m'
no_color='\033[0m'
fail_on_perl_warnings() {
if grep -qP 'line \d+\.$' "$logfile"; then
echo -e "${red}Failure: Perl warnings were found${no_color}"
false
fi
}
cd /src
pipenv install
./run-specs 2>&1 | tee "$logfile"
fail_on_perl_warnings

View File

@@ -0,0 +1,64 @@
#!/bin/bash
set -e
if [[ -n "$1" ]]; then
tag="$1"
fi
is_debian() {
[[ -f /etc/debian_version ]]
}
check_package_version_exists() {
if ! stat /tmp/kasmvncserver_"$package_version"*.deb; then
>&2 echo "No package found for version $package_version"
exit 1
fi
}
remove_tagged_debs_unless_tag_used() {
if [[ -n "$tag" ]]; then
return
fi
rm -f /tmp/kasmvncserver_*+*.deb
}
detect_rpm_package_manager() {
if command -v dnf >/dev/null; then
rpm_package_manager=dnf
else
rpm_package_manager=yum
fi
}
install_package_built_for_current_branch_package_version_deb() {
remove_tagged_debs_unless_tag_used
apt-get update
apt-get install -y dpkg-dev
package_version=$(dpkg-parsechangelog --show-field Version \
--file /tmp/changelog)
check_package_version_exists
apt-get install -y /tmp/kasmvncserver_"$package_version"*"$tag"*.deb
}
install_package_built_for_current_branch_package_version_rpm() {
detect_rpm_package_manager
$rpm_package_manager install -y rpmdevtools
package_version=$(rpmspec -q --qf '%{version}\n' /tmp/kasmvncserver.spec 2>/dev/null)
if [[ $rpm_package_manager = "dnf" ]]; then
dnf localinstall -y --allowerasing /tmp/kasmvncserver-"$package_version"*.rpm
else
yum install -y /tmp/kasmvncserver-"$package_version"*.rpm
fi
}
if is_debian ; then
install_package_built_for_current_branch_package_version_deb
else
install_package_built_for_current_branch_package_version_rpm
fi

View File

@@ -0,0 +1,50 @@
#!/bin/bash
debug() {
if [ -z "$debug" ]; then return; fi
echo "$@"
}
enable_debug() {
debug=1
log_option="-log *:stderr:100"
}
kill_vnc_server() {
vncserver -kill $display
}
process_cli_options() {
for option in "$@"; do
case "$option" in
--help)
show_help
exit
;;
-d|--debug)
enable_debug
;;
-k|--kill)
kill_vnc_server
exit
;;
-s|--select-de)
action=select-de-and-start
;;
*)
echo >&2 "Unsupported argument: $option"
exit 1
esac
done
}
show_help() {
cat >&2 <<-USAGE
Usage: $(basename "$0") [options]
-d, --debug Debug output
-k, --kill Kill vncserver
-s, --select-de Select desktop environent to run
--help Show this help
USAGE
}

View File

@@ -4,43 +4,8 @@ set -e
display=:10
interface=0.0.0.0
cert_group=ssl-cert
if [[ "$1" = "--help" ]]; then
cat >&2 <<-USAGE
Usage: `basename $0` [options]
-d Debug output
-kill Kill vncserver
--help show this help
USAGE
exit
fi
if [[ "$1" = "-d" ]]; then
log_option="-log *:stderr:100"
fi
action=start
if [[ "$1" = "-kill" ]]; then
action=kill
fi
if groups | grep -qvw ssl-cert; then
cat <<-EOF
Can't access TLS certificate.
Please add your user to $cert_group via 'addgroup <user> ssl-cert'
EOF
exit 1
fi
if [[ "$action" = "kill" ]]; then
vncserver -kill $display
exit
fi
vncserver $display -interface $interface
vncserver -kill $display
vncserver $display -depth 24 -geometry 1280x1050 -websocketPort 8443 \
-cert /etc/ssl/certs/ssl-cert-snakeoil.pem \
-key /etc/ssl/private/ssl-cert-snakeoil.key -sslOnly -FrameRate=24 \
-interface $interface -httpd /usr/share/kasmvnc/www $log_option
-interface $interface -httpd /usr/share/kasmvnc/www

266
builder/startup/deb/select-de.sh Executable file
View File

@@ -0,0 +1,266 @@
#!/bin/bash
set -e
xstartup_script=~/.vnc/xstartup
de_was_selected_file="$HOME/.vnc/.de-was-selected"
debug() {
if [ -z "$debug" ]; then return; fi
echo "$@"
}
enable_debug() {
debug=1
}
process_cli_options() {
while [ $# -gt 0 ]; do
local option="$1"
shift
case "$option" in
--help|-h)
show_help
exit
;;
-d|--debug)
enable_debug
;;
-y|--assume-yes)
assume_yes=1
;;
-s|--select-de)
action=select-de
if [[ -n "$1" && "${1:0:1}" != "-" ]]; then
selected_de="$1"
assume_yes_for_xstartup_overwrite=1
if [ "$selected_de" = "manual" ]; then
selected_de="$manual_xstartup_choice"
fi
shift
fi
;;
*)
echo >&2 "Unsupported argument: $option"
exit 1
esac
done
}
show_help() {
cat >&2 <<-USAGE
Usage: $(basename "$0") [options]
-d, --debug Debug output
-y, --assume-yes Automatic "yes" to prompts
-s, --select-de Select desktop environent to run
--help Show this help
USAGE
}
add_uppercase_desktop_environment_keys() {
local de_cmd
for de in "${!all_desktop_environments[@]}"; do
de_cmd=${all_desktop_environments[$de]};
all_desktop_environments[${de^^}]="$de_cmd"
done
}
manual_xstartup_choice="Manually edit xstartup"
process_cli_options "$@"
declare -A all_desktop_environments=(
[Cinnamon]="exec cinnamon-session"
[Mate]="XDG_CURRENT_DESKTOP=MATE exec dbus-launch --exit-with-session mate-session"
[LXDE]="exec lxsession"
[Lxqt]="exec startlxqt"
[KDE]="exec startkde"
[Gnome]="XDG_CURRENT_DESKTOP=GNOME exec dbus-launch --exit-with-session /usr/bin/gnome-session"
[XFCE]="exec xfce4-session")
readarray -t sorted_desktop_environments < <(for de in "${!all_desktop_environments[@]}"; do echo "$de"; done | sort)
all_desktop_environments[$manual_xstartup_choice]=""
sorted_desktop_environments+=("$manual_xstartup_choice")
add_uppercase_desktop_environment_keys
detected_desktop_environments=()
declare -A numbered_desktop_environments
print_detected_desktop_environments() {
declare -i i=1
echo "Please choose Desktop Environment to run:"
for detected_de in "${detected_desktop_environments[@]}"; do
echo "[$i] $detected_de"
numbered_desktop_environments[$i]=$detected_de
i+=1
done
}
detect_desktop_environments() {
for de_name in "${sorted_desktop_environments[@]}"; do
if [[ "$de_name" = "$manual_xstartup_choice" ]]; then
detected_desktop_environments+=("$de_name")
continue;
fi
local executable=${all_desktop_environments[$de_name]}
executable=($executable)
executable=${executable[-1]}
if detect_desktop_environment "$de_name" "$executable"; then
detected_desktop_environments+=("$de_name")
fi
done
}
ask_user_to_choose_de() {
while : ; do
print_detected_desktop_environments
read -r de_number_to_run
if [[ -z "$de_number_to_run" ]]; then
continue
fi
de_name_from_number "$de_number_to_run"
if [[ -n "$de_name" ]]; then
break;
fi
echo "Incorrect number: $de_number_to_run"
echo
done
}
remember_de_choice() {
touch "$de_was_selected_file"
}
de_was_selected_on_previous_run() {
[[ -f "$de_was_selected_file" ]]
}
detect_desktop_environment() {
local de_name="$1"
local executable="$2"
if command -v "$executable" &>/dev/null; then
return 0
fi
return 1
}
did_user_forbid_replacing_xstartup() {
grep -q -v KasmVNC-safe-to-replace-this-file "$xstartup_script"
}
de_cmd_from_name() {
de_cmd=${all_desktop_environments[${de_name^^}]}
}
de_name_from_number() {
local de_number_to_run="$1"
de_name=${numbered_desktop_environments[$de_number_to_run]}
}
warn_xstartup_will_be_overwriten() {
if [[ -n "$assume_yes" || -n "$assume_yes_for_xstartup_overwrite" ]]; then
return 0
fi
if [ ! -f "$xstartup_script" ]; then
return 0
fi
echo -n "WARNING: $xstartup_script will be overwritten y/N?"
read -r do_overwrite_xstartup
if [[ "$do_overwrite_xstartup" = "y" || "$do_overwrite_xstartup" = "Y" ]]; then
return 0
fi
return 1
}
setup_de_to_run_via_xstartup() {
warn_xstartup_will_be_overwriten
generate_xstartup "$de_name"
}
generate_xstartup() {
local de_name="$1"
de_cmd_from_name
cat <<-SCRIPT > "$xstartup_script"
#!/bin/sh
$de_cmd
SCRIPT
chmod +x "$xstartup_script"
}
user_asked_to_select_de() {
[[ "$action" = "select-de" ]]
}
user_specified_de() {
[ -n "$selected_de" ]
}
check_de_name_is_valid() {
local selected_de="$1"
if [[ "$selected_de" = "$manual_xstartup_choice" ]]; then
return 0;
fi
local de_cmd=${all_desktop_environments["${selected_de^^}"]:-}
if [ -z "$de_cmd" ]; then
echo >&2 "'$selected_de': not supported Desktop Environment"
return 1
fi
}
de_installed() {
local de_name="${1^^}"
for de in "${detected_desktop_environments[@]}"; do
if [ "${de^^}" = "$de_name" ]; then
return 0
fi
done
return 1
}
check_de_installed() {
local de_name="$1"
if ! de_installed "$de_name"; then
echo >&2 "'$de_name': Desktop Environment not installed"
return 1
fi
}
if user_asked_to_select_de || ! de_was_selected_on_previous_run; then
if user_specified_de; then
check_de_name_is_valid "$selected_de"
fi
detect_desktop_environments
if user_specified_de; then
check_de_installed "$selected_de"
de_name="$selected_de"
else
ask_user_to_choose_de
fi
debug "You selected $de_name desktop environment"
if [[ "$de_name" != "$manual_xstartup_choice" ]]; then
setup_de_to_run_via_xstartup
fi
remember_de_choice
fi

View File

@@ -12,28 +12,6 @@ cleanup () {
}
trap cleanup SIGINT SIGTERM
detect_www_dir() {
local package_www_dir="/usr/share/kasmvnc/www"
if [[ -d "$package_www_dir" ]]; then
package_www_dir_option="-httpd $package_www_dir"
fi
}
detect_cert_location() {
local tarball_cert="$HOME/.vnc/self.pem"
local deb_cert="/etc/ssl/certs/ssl-cert-snakeoil.pem"
local deb_key="/etc/ssl/private/ssl-cert-snakeoil.key"
local rpm_cert="/etc/pki/tls/private/kasmvnc.pem"
if [[ -f "$deb_cert" ]]; then
cert_option="-cert $deb_cert -key $deb_key"
elif [[ -f "$rpm_cert" ]]; then
cert_option="-cert $rpm_cert"
else
cert_option="-cert $tarball_cert"
fi
}
add_vnc_user() {
local username="$1"
local password="$2"
@@ -61,9 +39,6 @@ kasmvncpasswd -d -u "$VNC_USER-to-delete" $HOME/.kasmpasswd
chmod 0600 $HOME/.kasmpasswd
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout $HOME/.vnc/self.pem -out $HOME/.vnc/self.pem -subj "/C=US/ST=VA/L=None/O=None/OU=DoFu/CN=kasm/emailAddress=none@none.none"
vncserver :1 -interface 0.0.0.0
vncserver -kill :1
if [[ -f $PASSWD_PATH ]]; then
rm -f $PASSWD_PATH
fi
@@ -84,12 +59,10 @@ vncserver -kill $DISPLAY &> $HOME/.vnc/vnc_startup.log \
|| echo "no locks present"
detect_www_dir
detect_cert_location
[ -n "$KASMVNC_VERBOSE_LOGGING" ] && verbose_logging_option="-log *:stderr:100"
[ -n "$KASMVNC_VERBOSE_LOGGING" ] && verbose_logging_option="-debug"
echo -e "start vncserver with param: VNC_COL_DEPTH=$VNC_COL_DEPTH, VNC_RESOLUTION=$VNC_RESOLUTION\n..."
vncserver $DISPLAY -depth $VNC_COL_DEPTH -geometry $VNC_RESOLUTION -FrameRate=$MAX_FRAME_RATE -websocketPort $VNC_PORT $cert_option -sslOnly -interface 0.0.0.0 $VNCOPTIONS $package_www_dir_option $verbose_logging_option #&> $STARTUPDIR/no_vnc_startup.log
vncserver $DISPLAY -select-de xfce -depth $VNC_COL_DEPTH -geometry $VNC_RESOLUTION -FrameRate=$MAX_FRAME_RATE -websocketPort $VNC_PORT $VNCOPTIONS $verbose_logging_option #&> $STARTUPDIR/no_vnc_startup.log
PID_SUN=$!

View File

@@ -0,0 +1,22 @@
#!/bin/bash
set -euo pipefail
set_xterm_to_run() {
mkdir "$config_dir"
echo '/usr/bin/xterm &' >> "$xstartup"
chmod +x "$xstartup"
}
create_kasm_user() {
echo -e "$VNC_PW\n$VNC_PW\n" | kasmvncpasswd -w -u "$VNC_USER"
}
config_dir="$HOME/.vnc"
xstartup="$config_dir/xstartup"
set_xterm_to_run
create_kasm_user
vncserver -select-de manual -websocketPort "$VNC_PORT"
tail -f "$config_dir"/*.log

View File

@@ -2,15 +2,17 @@
set -e
cd "$(dirname "$0")"
. ./os_ver_cli.sh
cd "$(dirname "$0")/.."
. ./builder/os_ver_cli.sh
. ./builder/common.sh
tester_image=kasmvnctester_${os}:$os_codename${build_tag_for_images}
docker build --build-arg KASMVNC_PACKAGE_DIR="build/${os_codename}" \
tester_image="builder/kasmvnctester_${os}:$os_codename${build_tag_for_images}"
docker build --build-arg KASMVNC_PACKAGE_DIR="builder/build/${os_codename}" \
--build-arg BUILD_DEBIAN_REVISION="$build_debian_revision" \
-t "$tester_image" \
-f dockerfile.${os}_${os_codename}${build_tag}.deb.test .
docker run -it -p 443:8443 --rm \
-f "builder/dockerfile.${os}_${os_codename}${build_tag}.deb.test" .
docker run -it -p "443:$VNC_PORT" --rm \
-e KASMVNC_VERBOSE_LOGGING=$KASMVNC_VERBOSE_LOGGING \
-e "VNC_USER=foo" -e "VNC_PW=foobar" \
-e "VNC_PORT=$VNC_PORT" \
"$tester_image"

View File

@@ -2,13 +2,15 @@
set -e
cd "$(dirname "$0")"
cd "$(dirname "$0")/.."
. ./builder/common.sh
os="${1:-debian}"
os_codename="${2:-buster}"
docker build --build-arg KASMVNC_PACKAGE_DIR="build/${os_codename}" \
docker build --build-arg KASMVNC_PACKAGE_DIR="builder/build/${os_codename}" \
-t kasmvnctester_barebones_${os}:$os_codename \
-f dockerfile.${os}_${os_codename}.barebones.deb.test .
-f builder/dockerfile.${os}_${os_codename}.barebones.deb.test .
echo
docker run -it -p 443:8443 --rm -e "VNC_USER=foo" -e "VNC_PW=foobar" \
docker run -it -p "443:$VNC_PORT" --rm -e "VNC_USER=foo" -e "VNC_PW=foobar" \
-e "VNC_PORT=$VNC_PORT" \
kasmvnctester_barebones_${os}:$os_codename

View File

@@ -2,16 +2,18 @@
set -e
cd "$(dirname "$0")"
. ./os_ver_cli.sh
cd "$(dirname "$0")/.."
. ./builder/os_ver_cli.sh
. ./builder/common.sh
echo kasmvnctester_${os}:$os_codename
docker build --build-arg \
KASMVNC_PACKAGE_DIR="build/${os}_${os_codename}" \
KASMVNC_PACKAGE_DIR="builder/build/${os}_${os_codename}" \
-t kasmvnctester_${os}:$os_codename \
-f dockerfile.${os}_${os_codename}.rpm.test .
-f builder/dockerfile.${os}_${os_codename}.rpm.test .
docker run -it -p 443:8443 --rm \
docker run -it -p "443:$VNC_PORT" --rm \
-e KASMVNC_VERBOSE_LOGGING=$KASMVNC_VERBOSE_LOGGING \
-e "VNC_USER=foo" -e "VNC_PW=foobar" \
-e "VNC_PORT=$VNC_PORT" \
kasmvnctester_${os}:$os_codename

View File

@@ -3,11 +3,13 @@
set -e
cd "$(dirname "$0")"
. ./common.sh
os="${1:-centos}"
os_codename="${2:-core}"
docker build --build-arg KASMVNC_PACKAGE_DIR="build/${os}_${os_codename}" \
-t kasmvnctester_barebones_${os}:$os_codename \
-f dockerfile.${os}_${os_codename}.barebones.rpm.test .
docker run -it -p 443:8443 --rm -e "VNC_USER=foo" -e "VNC_PW=foobar" \
docker run -it -p "443:$VNC_PORT" --rm -e "VNC_USER=foo" -e "VNC_PW=foobar" \
-e "VNC_PORT=$VNC_PORT" \
kasmvnctester_barebones_${os}:$os_codename

23
builder/test-vncserver Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/bash
set -e
default_os=ubuntu
default_os_codename=focal
. ./builder/os_ver_cli.sh
if [[ -n "$GITLAB_CI" ]]; then
package_dir="output/${os_codename}"
else
package_dir="builder/build/${os_codename}"
fi
docker build --build-arg KASMVNC_PACKAGE_DIR="$package_dir" \
-t kasmvnctester_${os}:$os_codename \
-f builder/dockerfile.${os}_${os_codename}.specs.test .
docker run -v $(realpath ${PWD}):/src \
--rm \
-e KASMVNC_VERBOSE_LOGGING=$KASMVNC_VERBOSE_LOGGING \
-e "VNC_USER=foo" -e "VNC_PW=foobar" \
kasmvnctester_${os}:$os_codename

View File

@@ -1,5 +1,5 @@
Name: kasmvncserver
Version: 0.9.3~beta
Version: 1.0.0
Release: 1%{?dist}
Summary: VNC server accessible from a web browser
@@ -7,29 +7,21 @@ License: GPLv2+
URL: https://github.com/kasmtech/KasmVNC
BuildRequires: rsync
Requires: xorg-x11-xauth, xorg-x11-xkb-utils, xkeyboard-config, xorg-x11-server-utils, openssl, perl
Requires: xorg-x11-xauth, xorg-x11-xkb-utils, xkeyboard-config, xorg-x11-server-utils, openssl, perl, perl-Switch, perl-YAML-Tiny, perl-Hash-Merge-Simple, perl-Scalar-List-Utils, perl-List-MoreUtils, perl-Try-Tiny
Conflicts: tigervnc-server, tigervnc-server-minimal
%description
VNC stands for Virtual Network Computing. It is, in essence, a remote
display system which allows you to view a computing `desktop' environment
not only on the machine where it is running, but from anywhere on the
Internet and from a wide variety of machine architectures.
KasmVNC provides remote web-based access to a Desktop or application.
While VNC is in the name, KasmVNC differs from other VNC variants such
as TigerVNC, RealVNC, and TurboVNC. KasmVNC has broken from the RFB
specification which defines VNC, in order to support modern technologies
and increase security. KasmVNC is accessed by users from any modern
browser and does not support legacy VNC viewer applications. KasmVNC
uses a modern YAML based configuration at the server and user level,
allowing for ease of management. KasmVNC is maintained by Kasm
Technologies Corp, www.kasmweb.com.
KasmVNC has different goals than TigerVNC:
Web-based - KasmVNC is designed to provide a web accessible remote desktop.
It comes with a web server and web-socket server built in. There is no need to
install other components. Simply run and navigate to your desktop's URL on the
port you specify. While you can still tun on the legacy VNC port, it is
disabled by default.
Security - KasmVNC defaults to HTTPS and allows for HTTP Basic Auth. VNC
Password authentication is limited by specification to 8 characters and is not
sufficient for use on an internet accessible remote desktop. Our goal is to
create a by default secure, web based experience.
Simplicity - KasmVNC aims at being simple to deploy and configure.
WARNING: this package requires EPEL.
%prep
@@ -48,17 +40,28 @@ DESTDIR=$RPM_BUILD_ROOT
DST_MAN=$DESTDIR/usr/share/man/man1
mkdir -p $DESTDIR/usr/bin $DESTDIR/usr/share/man/man1 \
$DESTDIR/usr/share/doc/kasmvncserver
$DESTDIR/usr/share/doc/kasmvncserver $DESTDIR/usr/lib \
$DESTDIR/usr/share/perl5 $DESTDIR/etc/kasmvnc
cp $SRC_BIN/Xvnc $DESTDIR/usr/bin;
cp $SRC_BIN/vncserver $DESTDIR/usr/bin;
cp -a $SRC_BIN/KasmVNC $DESTDIR/usr/share/perl5/
cp $SRC_BIN/vncconfig $DESTDIR/usr/bin;
cp $SRC_BIN/kasmvncpasswd $DESTDIR/usr/bin;
cp $SRC_BIN/kasmxproxy $DESTDIR/usr/bin;
cp -r $SRC/lib/kasmvnc/ $DESTDIR/usr/lib/kasmvncserver
cd $DESTDIR/usr/bin && ln -s kasmvncpasswd vncpasswd;
cp -r $SRC/share/doc/kasmvnc*/* $DESTDIR/usr/share/doc/kasmvncserver/
rsync -r --exclude '.git*' --exclude po2js --exclude xgettext-html \
--exclude www/utils/ --exclude .eslintrc \
--exclude www/utils/ --exclude .eslintrc --exclude configure \
$SRC/share/kasmvnc $DESTDIR/usr/share
sed -i -e 's!pem_certificate: .\+$!pem_certificate: /etc/pki/tls/private/kasmvnc.pem!' \
$DESTDIR/usr/share/kasmvnc/kasmvnc_defaults.yaml
sed -i -e 's!pem_key: .\+$!pem_key: /etc/pki/tls/private/kasmvnc.pem!' \
$DESTDIR/usr/share/kasmvnc/kasmvnc_defaults.yaml
sed -e 's/^\([^#]\)/# \1/' $DESTDIR/usr/share/kasmvnc/kasmvnc_defaults.yaml > \
$DESTDIR/etc/kasmvnc/kasmvnc.yaml
cp $SRC/man/man1/Xvnc.1 $DESTDIR/usr/share/man/man1/;
cp $SRC/share/man/man1/vncserver.1 $DST_MAN;
cp $SRC/share/man/man1/vncconfig.1 $DST_MAN;
@@ -68,14 +71,27 @@ cd $DST_MAN && ln -s vncpasswd.1 kasmvncpasswd.1;
%files
%config(noreplace) /etc/kasmvnc
/usr/bin/*
/usr/lib/kasmvncserver
/usr/share/man/man1/*
/usr/share/kasmvnc/www
/usr/share/perl5/KasmVNC
/usr/share/kasmvnc
%license /usr/share/doc/kasmvncserver/LICENSE.TXT
%doc /usr/share/doc/kasmvncserver/README.md
%changelog
* Tue Nov 29 2022 KasmTech <info@kasmweb.com> - 1.0.0-1
- WebRTC UDP transit support with support of STUN servers
- Lossless compression using multi-threaded WASM QOI decoder client side
- New yaml based configuration
- Significantly improved FPS through both client-side and server-side improvements.
- Support for the admin to define arbitrary http response headers for the built in web server
- Support for additional mouse buttons
- Refinement of vncserver checks and user prompts
- Added send_full_frame to developer API, forces full frame to be sent to all connected users that have at least read permission.
* Tue Mar 22 2022 KasmTech <info@kasmweb.com> - 0.9.3~beta-1
* Fri Feb 12 2021 KasmTech <info@kasmweb.com> - 0.9.1~beta-1
- Initial release of the rpm package.

View File

@@ -3,12 +3,29 @@ include_directories(${CMAKE_SOURCE_DIR}/common ${CMAKE_SOURCE_DIR}/unix/kasmvncp
set(NETWORK_SOURCES
GetAPIMessager.cxx
Blacklist.cxx
iceip.cxx
Socket.cxx
TcpSocket.cxx
Udp.cxx
cJSON.c
jsonescape.c
websocket.c
websockify.c
webudp/CRC32.cpp
webudp/WuArena.cpp
webudp/Wu.cpp
webudp/WuCrypto.cpp
webudp/WuHostEpoll.cpp
webudp/WuNetwork.cpp
webudp/WuPool.cpp
webudp/WuQueue.cpp
webudp/WuRng.cpp
webudp/WuSctp.cpp
webudp/WuSdp.cpp
webudp/WuString.cpp
webudp/WuStun.cpp
${CMAKE_SOURCE_DIR}/unix/kasmvncpasswd/kasmpasswd.c)
if(NOT WIN32)

View File

@@ -65,6 +65,7 @@ namespace network {
void netGetFrameStats(char *buf, uint32_t len);
void netResetFrameStatsCall();
uint8_t netServerFrameStatsReady();
void netUdpUpgrade(void *client, uint32_t ip);
enum USER_ACTION {
NONE,
@@ -72,6 +73,7 @@ namespace network {
WANT_FRAME_STATS_ALL,
WANT_FRAME_STATS_OWNER,
WANT_FRAME_STATS_SPECIFIC,
UDP_UPGRADE
};
uint8_t netRequestFrameStats(USER_ACTION what, const char *client);
@@ -81,7 +83,13 @@ namespace network {
struct action_data {
enum USER_ACTION action;
kasmpasswd_entry_t data;
union {
kasmpasswd_entry_t data;
struct {
void *client;
uint32_t ip;
} udp;
};
};
pthread_mutex_t userMutex;

View File

@@ -790,3 +790,19 @@ uint8_t GetAPIMessager::netServerFrameStatsReady() {
return ret;
}
void GetAPIMessager::netUdpUpgrade(void *client, uint32_t ip) {
// Return 1 for success
action_data act;
act.action = UDP_UPGRADE;
act.udp.client = client;
act.udp.ip = ip;
// Send it in
if (pthread_mutex_lock(&userMutex))
return;
actionQueue.push_back(act);
pthread_mutex_unlock(&userMutex);
}

View File

@@ -42,10 +42,14 @@
#include <wordexp.h>
#include <sys/types.h>
#include <unistd.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include "websocket.h"
#include <network/GetAPI.h>
#include <network/TcpSocket.h>
#include <network/Udp.h>
#include <rfb/LogWriter.h>
#include <rfb/Configuration.h>
#include <rfb/ServerCore.h>
@@ -541,6 +545,45 @@ static uint8_t serverFrameStatsReadyCb(void *messager)
return msgr->netServerFrameStatsReady();
}
#if OPENSSL_VERSION_NUMBER < 0x1010000f
static pthread_mutex_t *sslmutex;
static void openssl_lock(int mode, int n, const char *, int)
{
if (mode & CRYPTO_LOCK)
pthread_mutex_lock(&sslmutex[n]);
else
pthread_mutex_unlock(&sslmutex[n]);
}
static unsigned long openssl_id()
{
return pthread_self();
}
#endif
static void openssl_threads() {
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ERR_load_BIO_strings();
ERR_load_crypto_strings();
#if OPENSSL_VERSION_NUMBER < 0x1010000f
sslmutex = (pthread_mutex_t *) calloc(CRYPTO_num_locks(), sizeof(pthread_mutex_t));
unsigned i;
for (i = 0; i < (unsigned) CRYPTO_num_locks(); i++)
pthread_mutex_init(&sslmutex[i], NULL);
CRYPTO_set_locking_callback(openssl_lock);
CRYPTO_set_id_callback(openssl_id);
#endif
}
WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr,
socklen_t listenaddrlen,
@@ -650,8 +693,17 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr,
settings.getClientFrameStatsNumCb = getClientFrameStatsNumCb;
settings.serverFrameStatsReadyCb = serverFrameStatsReadyCb;
openssl_threads();
pthread_t tid;
pthread_create(&tid, NULL, start_server, NULL);
uint16_t *nport = (uint16_t *) calloc(1, sizeof(uint16_t));
if (rfb::Server::udpPort)
*nport = rfb::Server::udpPort;
else
*nport = ntohs(sa.u.sin.sin_port);
pthread_create(&tid, NULL, udpserver, nport);
}
Socket* WebsocketListener::createSocket(int fd) {

166
common/network/Udp.cxx Normal file
View File

@@ -0,0 +1,166 @@
/* Copyright (C) Kasm
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <time.h>
#include <network/GetAPI.h>
#include <network/Udp.h>
#include <network/webudp/WuHost.h>
#include <network/webudp/Wu.h>
#include <network/websocket.h>
#include <rfb/LogWriter.h>
#include <rfb/ServerCore.h>
#include <rfb/xxhash.h>
using namespace network;
static rfb::LogWriter vlog("WebUdp");
static WuHost *host = NULL;
rfb::IntParameter udpSize("udpSize", "UDP packet data size", 1300, 500, 1400);
extern settings_t settings;
static void udperr(const char *msg, void *) {
vlog.error("%s", msg);
}
static void udpdebug(const char *msg, void *) {
vlog.debug("%s", msg);
}
void *udpserver(void *nport) {
WuHost *myhost = NULL;
int ret = WuHostCreate(rfb::Server::publicIP, *(uint16_t *) nport, 16, &myhost);
if (ret != WU_OK) {
vlog.error("Failed to create WebUDP host");
return NULL;
}
vlog.debug("UDP listening on port %u", *(uint16_t *) nport);
__sync_bool_compare_and_swap(&host, host, myhost);
GetAPIMessager *msgr = (GetAPIMessager *) settings.messager;
WuHostSetErrorCallback(host, udperr);
WuHostSetDebugCallback(host, udpdebug);
while (1) {
WuAddress addr;
WuEvent e;
if (!WuHostServe(host, &e, 2000))
continue;
switch (e.type) {
case WuEvent_ClientJoin:
vlog.info("client join");
addr = WuClientGetAddress(e.client);
msgr->netUdpUpgrade(e.client, htonl(addr.host));
break;
case WuEvent_ClientLeave:
vlog.info("client leave");
WuHostRemoveClient(host, e.client);
break;
default:
vlog.error("client sent data, this is unexpected");
break;
}
}
return NULL;
}
// Send one packet, split into N UDP-sized pieces
static uint8_t udpsend(WuClient *client, const uint8_t *data, unsigned len, uint32_t *id) {
const uint32_t DATA_MAX = udpSize;
uint8_t buf[1400 + sizeof(uint32_t) * 4];
const uint32_t pieces = (len / DATA_MAX) + ((len % DATA_MAX) ? 1 : 0);
uint32_t i;
for (i = 0; i < pieces; i++) {
const unsigned curlen = len > DATA_MAX ? DATA_MAX : len;
const uint32_t hash = XXH64(data, curlen, 0);
memcpy(buf, id, sizeof(uint32_t));
memcpy(&buf[4], &i, sizeof(uint32_t));
memcpy(&buf[8], &pieces, sizeof(uint32_t));
memcpy(&buf[12], &hash, sizeof(uint32_t));
memcpy(&buf[16], data, curlen);
data += curlen;
len -= curlen;
if (WuHostSendBinary(host, client, buf, curlen + sizeof(uint32_t) * 4) < 0)
return 1;
}
(*id)++;
return 0;
}
UdpStream::UdpStream(): OutStream(), client(NULL), total_len(0), id(0), failed(false) {
ptr = data;
end = data + UDPSTREAM_BUFSIZE;
srand(time(NULL));
}
void UdpStream::flush() {
const unsigned len = ptr - data;
total_len += len;
if (client) {
if (udpsend(client, data, len, &id)) {
vlog.error("Error sending udp, client gone?");
failed = true;
}
} else {
vlog.error("Tried to send udp without a client");
}
ptr = data;
}
void UdpStream::overrun(size_t needed) {
vlog.error("Udp buffer overrun");
abort();
}
bool UdpStream::isFailed() const {
return failed;
}
void UdpStream::clearFailed() {
failed = false;
}
void wuGotHttp(const char msg[], const uint32_t msglen, char resp[]) {
WuGotHttp(host, msg, msglen, resp);
}

57
common/network/Udp.h Normal file
View File

@@ -0,0 +1,57 @@
/* Copyright (C) 2022 Kasm
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#ifndef __NETWORK_UDP_H__
#define __NETWORK_UDP_H__
#include <stdint.h>
#include <rdr/OutStream.h>
void *udpserver(void *unused);
typedef struct WuClient WuClient;
namespace network {
#define UDPSTREAM_BUFSIZE (1024 * 1024)
class UdpStream: public rdr::OutStream {
public:
UdpStream();
virtual void flush();
virtual size_t length() { return total_len; }
virtual void overrun(size_t needed);
void setClient(WuClient *cli) {
client = cli;
}
bool isFailed() const;
void clearFailed();
private:
uint8_t data[UDPSTREAM_BUFSIZE];
WuClient *client;
size_t total_len;
uint32_t id;
bool failed;
};
}
extern "C" void wuGotHttp(const char msg[], const uint32_t msglen, char resp[]);
#endif // __NETWORK_UDP_H__

190
common/network/iceip.cxx Normal file
View File

@@ -0,0 +1,190 @@
/* Copyright (C) Kasm
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <arpa/inet.h>
#include <errno.h>
#include <limits.h>
#include <netdb.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
#include <network/iceip.h>
#include <rfb/LogWriter.h>
#include <rfb/ServerCore.h>
static rfb::LogWriter vlog("ICE");
// Default port 3478
static const char * const servers[] = {
"stun.l.google.com:19302",
"stun1.l.google.com:19302",
"stun2.l.google.com:19302",
"stun3.l.google.com:19302",
"stun4.l.google.com:19302",
"stun.voipbuster.com",
"stun.voipstunt.com",
};
static bool tryserver(const char * const srv, const int sock) {
unsigned port = 3478;
char addr[PATH_MAX];
char buf[PATH_MAX];
const char *colon = strchr(srv, ':');
if (colon) {
memcpy(addr, srv, colon - srv);
addr[colon - srv] = '\0';
colon++;
port = atoi(colon);
} else {
strcpy(addr, srv);
}
vlog.debug("Trying '%s', port %u", addr, port);
struct hostent *ent = gethostbyname2(addr, AF_INET);
if (!ent)
return false;
struct sockaddr_in dst;
dst.sin_family = AF_INET;
dst.sin_port = htons(port);
memcpy(&dst.sin_addr, ent->h_addr, 4);
//vlog.info("Got %s, addr %s", ent->h_name, inet_ntoa(in));
// Build up a binding request packet
buf[0] = 0;
buf[1] = 1; // type
buf[2] = buf[3] = 0; // length
uint32_t tid[4]; // transaction id, 128 bits
tid[0] = rand();
tid[1] = rand();
tid[2] = rand();
tid[3] = rand();
memcpy(&buf[4], &tid[0], 4);
memcpy(&buf[8], &tid[1], 4);
memcpy(&buf[12], &tid[2], 4);
memcpy(&buf[16], &tid[3], 4);
if (sendto(sock, buf, 20, 0, (const struct sockaddr *) &dst,
sizeof(struct sockaddr_in)) != 20)
return false;
// Wait up to 10s for a reply, standard says that's the wait
struct pollfd pfd;
pfd.fd = sock;
pfd.events = POLLIN;
if (poll(&pfd, 1, 10 * 1000) <= 0)
return false;
struct sockaddr_in from;
socklen_t socklen = sizeof(struct sockaddr_in);
int len = recvfrom(sock, buf, PATH_MAX, 0, (struct sockaddr *) &from,
&socklen);
if (len < 20)
return false;
if (memcmp(&from.sin_addr, &dst.sin_addr, sizeof(struct in_addr)))
return false;
int i;
/* vlog.info("Got %u bytes", len);
for (i = 0; i < len; i++)
vlog.info("0x%02x,", buf[i]);*/
if (buf[0] != 1 || buf[1] != 1)
return false; // type not binding response
// Parse attrs
for (i = 20; i < len;) {
uint16_t type, attrlen;
memcpy(&type, &buf[i], 2);
i += 2;
memcpy(&attrlen, &buf[i], 2);
i += 2;
type = ntohs(type);
attrlen = ntohs(attrlen);
if (type != 1) {
// Not mapped-address
i += attrlen;
continue;
}
// Yay, we got a response
i += 4;
struct in_addr in;
memcpy(&in.s_addr, &buf[i], 4);
rfb::Server::publicIP.setParam(inet_ntoa(in));
vlog.info("My public IP is %s", (const char *) rfb::Server::publicIP);
return true;
}
return false;
}
void getPublicIP() {
if (rfb::Server::publicIP[0]) {
vlog.info("Using public IP %s from args",
(const char *) rfb::Server::publicIP);
return;
}
srand(time(NULL));
vlog.info("Querying public IP...");
const int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
abort();
unsigned i;
if (rfb::Server::stunServer[0]) {
if (strlen(rfb::Server::stunServer) < PATH_MAX)
tryserver(rfb::Server::stunServer, sock);
} else {
for (i = 0; i < sizeof(servers) / sizeof(servers[0]); i++) {
if (tryserver(servers[i], sock))
break;
vlog.info("STUN server %u didn't work, trying next...", i);
}
}
close(sock);
if (!rfb::Server::publicIP[0]) {
vlog.error("Failed to get public IP, please specify it with -publicIP");
exit(1);
}
}

23
common/network/iceip.h Normal file
View File

@@ -0,0 +1,23 @@
/* Copyright (C) 2022 Kasm
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#ifndef __ICEIP_H__
#define __ICEIP_H__
void getPublicIP();
#endif

View File

@@ -41,10 +41,13 @@
*
* Warning: not thread safe
*/
int ssl_initialized = 0;
int pipe_error = 0;
settings_t settings;
extern int wakeuppipe[2];
extern char *extra_headers;
extern unsigned extra_headers_len;
void traffic(const char * token) {
/*if ((settings.verbose) && (! settings.daemon)) {
@@ -272,15 +275,6 @@ ws_ctx_t *ws_socket_ssl(ws_ctx_t *ctx, int socket, const char * certfile, const
use_keyfile = certfile;
}
// Initialize the library
if (! ssl_initialized) {
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ssl_initialized = 1;
}
ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method());
if (ctx->ssl_ctx == NULL) {
ERR_print_errors_fp(stderr);
@@ -845,7 +839,8 @@ static void dirlisting(ws_ctx_t *ws_ctx, const char fullpath[], const char path[
"Location: %s/\r\n"
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"\r\n", path);
"%s"
"\r\n", path, extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
weblog(301, wsthread_handler_id, 0, origip, ip, user, 1, path, strlen(buf));
return;
@@ -859,7 +854,9 @@ static void dirlisting(ws_ctx_t *ws_ctx, const char fullpath[], const char path[
"Server: KasmVNC/4.0\r\n"
"Connection: close\r\n"
"Content-type: text/html\r\n"
"\r\n<html><title>Directory Listing</title><body><h2>%s</h2><hr><ul>", path);
"%s"
"\r\n<html><title>Directory Listing</title><body><h2>%s</h2><hr><ul>",
extra_headers ? extra_headers : "", path);
ws_send(ws_ctx, buf, strlen(buf));
unsigned totallen = strlen(buf);
@@ -946,8 +943,9 @@ static void servefile(ws_ctx_t *ws_ctx, const char *in, const char * const user,
"Connection: close\r\n"
"Content-type: %s\r\n"
"Content-length: %lu\r\n"
"%s"
"\r\n",
name2mime(path), filesize);
name2mime(path), filesize, extra_headers ? extra_headers : "");
const unsigned hdrlen = strlen(buf);
ws_send(ws_ctx, buf, hdrlen);
@@ -967,8 +965,9 @@ nope:
"Server: KasmVNC/4.0\r\n"
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"%s"
"\r\n"
"404");
"404", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
weblog(404, wsthread_handler_id, 0, origip, ip, user, 1, path, strlen(buf));
}
@@ -1008,14 +1007,16 @@ notfound:
}
static void send403(ws_ctx_t *ws_ctx, const char * const origip, const char * const ip) {
const char response[] = "HTTP/1.1 403 Forbidden\r\n"
"Server: KasmVNC/4.0\r\n"
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"\r\n"
"403 Forbidden";
ws_send(ws_ctx, response, strlen(response));
weblog(403, wsthread_handler_id, 0, origip, ip, "-", 1, "-", strlen(response));
char buf[4096];
sprintf(buf, "HTTP/1.1 403 Forbidden\r\n"
"Server: KasmVNC/4.0\r\n"
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"%s"
"\r\n"
"403 Forbidden", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
weblog(403, wsthread_handler_id, 0, origip, ip, "-", 1, "-", strlen(buf));
}
static uint8_t ownerapi_post(ws_ctx_t *ws_ctx, const char *in, const char * const user,
@@ -1088,8 +1089,9 @@ static uint8_t ownerapi_post(ws_ctx_t *ws_ctx, const char *in, const char * cons
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"Content-length: 6\r\n"
"%s"
"\r\n"
"200 OK");
"200 OK", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 0, path, strlen(buf));
@@ -1126,8 +1128,9 @@ static uint8_t ownerapi_post(ws_ctx_t *ws_ctx, const char *in, const char * cons
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"Content-length: 6\r\n"
"%s"
"\r\n"
"200 OK");
"200 OK", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 0, path, strlen(buf));
@@ -1180,8 +1183,9 @@ static uint8_t ownerapi_post(ws_ctx_t *ws_ctx, const char *in, const char * cons
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"Content-length: 6\r\n"
"%s"
"\r\n"
"200 OK");
"200 OK", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 0, path, strlen(buf));
@@ -1196,8 +1200,9 @@ nope:
"Server: KasmVNC/4.0\r\n"
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"%s"
"\r\n"
"400 Bad Request");
"400 Bad Request", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
weblog(400, wsthread_handler_id, 0, origip, ip, user, 0, path, strlen(buf));
return 1;
@@ -1282,7 +1287,8 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"Content-length: %u\r\n"
"\r\n", len);
"%s"
"\r\n", len, extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
ws_send(ws_ctx, staging, len);
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf) + len);
@@ -1295,7 +1301,8 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n"
"Content-type: image/jpeg\r\n"
"Content-length: %u\r\n"
"\r\n", len);
"%s"
"\r\n", len, extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
ws_send(ws_ctx, staging, len);
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf) + len);
@@ -1365,8 +1372,9 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"Content-length: 6\r\n"
"%s"
"\r\n"
"200 OK");
"200 OK", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf));
@@ -1394,8 +1402,9 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"Content-length: 6\r\n"
"%s"
"\r\n"
"200 OK");
"200 OK", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf));
@@ -1449,8 +1458,9 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"Content-length: 6\r\n"
"%s"
"\r\n"
"200 OK");
"200 OK", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf));
@@ -1464,7 +1474,8 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"Content-length: %lu\r\n"
"\r\n", strlen(statbuf));
"%s"
"\r\n", strlen(statbuf), extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
ws_send(ws_ctx, statbuf, strlen(statbuf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf) + strlen(statbuf));
@@ -1480,7 +1491,8 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"Content-length: %lu\r\n"
"\r\n", strlen(ptr));
"%s"
"\r\n", strlen(ptr), extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
ws_send(ws_ctx, ptr, strlen(ptr));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf) + strlen(ptr));
@@ -1562,12 +1574,28 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in, const char * const use
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"Content-length: %lu\r\n"
"\r\n", strlen(statbuf));
"%s"
"\r\n", strlen(statbuf), extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
ws_send(ws_ctx, statbuf, strlen(statbuf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf) + strlen(statbuf));
handler_msg("Sent frame stats to API caller\n");
ret = 1;
} else entry("/api/send_full_frame") {
write(wakeuppipe[1], "", 1);
sprintf(buf, "HTTP/1.1 200 OK\r\n"
"Server: KasmVNC/4.0\r\n"
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"Content-length: 6\r\n"
"%s"
"\r\n"
"200 OK", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
weblog(200, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf));
ret = 1;
}
@@ -1580,8 +1608,9 @@ nope:
"Server: KasmVNC/4.0\r\n"
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"%s"
"\r\n"
"400 Bad Request");
"400 Bad Request", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
weblog(400, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf));
return 1;
@@ -1591,8 +1620,9 @@ timeout:
"Server: KasmVNC/4.0\r\n"
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"%s"
"\r\n"
"503 Service Unavailable");
"503 Service Unavailable", extra_headers ? extra_headers : "");
ws_send(ws_ctx, buf, strlen(buf));
weblog(503, wsthread_handler_id, 0, origip, ip, user, 1, origpath, strlen(buf));
return 1;
@@ -1718,7 +1748,8 @@ ws_ctx_t *do_handshake(int sock, char * const ip) {
wserr("Authentication attempt failed, BasicAuth required, but client didn't send any\n");
sprintf(response, "HTTP/1.1 401 Unauthorized\r\n"
"WWW-Authenticate: Basic realm=\"Websockify\"\r\n"
"\r\n");
"%s"
"\r\n", extra_headers ? extra_headers : "");
ws_send(ws_ctx, response, strlen(response));
weblog(401, wsthread_handler_id, 0, origip, ip, "-", 1, url, strlen(response));
free_ws_ctx(ws_ctx);
@@ -1800,7 +1831,8 @@ ws_ctx_t *do_handshake(int sock, char * const ip) {
wserr("Authentication attempt failed, wrong password for user %s\n", inuser);
bl_addFailure(ip);
sprintf(response, "HTTP/1.1 401 Forbidden\r\n"
"\r\n");
"%s"
"\r\n", extra_headers ? extra_headers : "");
ws_send(ws_ctx, response, strlen(response));
weblog(401, wsthread_handler_id, 0, origip, ip, inuser, 1, url, strlen(response));
free_ws_ctx(ws_ctx);
@@ -1824,8 +1856,9 @@ ws_ctx_t *do_handshake(int sock, char * const ip) {
"Server: KasmVNC/4.0\r\n"
"Connection: close\r\n"
"Content-type: text/plain\r\n"
"%s"
"\r\n"
"401 Unauthorized");
"401 Unauthorized", extra_headers ? extra_headers : "");
ws_send(ws_ctx, response, strlen(response));
weblog(401, wsthread_handler_id, 0, origip, ip, inuser, 1, url, strlen(response));
goto done;

View File

@@ -0,0 +1,38 @@
## 0.6.1 (06.06.2020)
- Fixed WuHostNull build.
- Use 2048 bit RSA keys (fixes `SSL_CTX_use_certificate:ee key too small`).
## 0.6.0 (10.04.2020)
- Allow OpenSSL 1.1.
- Node addon: upgrade nan, now builds with node 12.
- Fixed compiler warnings.
## 0.5.0 (27.10.2018)
- Switch to a newer SDP format.
Newer Chrome versions can connect to the server again.
- Update required CMake version to 3.7.
## 0.4.1 (06.10.2018)
- Fix compilation with g++ 7.
## 0.4.0 (02.10.2018)
- Add C API.
- WuHost now has an explicit timeout parameter.
- Remove ES6 'let' from wusocket.js.
## 0.3.0 (16.07.2018)
- Fix potential out of bounds read when sending SDP response.
## 0.2.0 (12.01.2018)
- Add DTLS 1.2 support. Requires at least OpenSSL 1.0.2.
## 0.1.1 (01.01.2018)
- Fix WuConf uninitialized maxClients parameter.
## 0.1.0 (30.12.2017)
- Remove the old default epoll implementation.
- Split the core logic into a separate library.
- Add a new epoll host.
- Add a Node.js host.
- Add fuzz tests.
- Add a Node.js example.

View File

@@ -0,0 +1,124 @@
#include "CRC32.h"
static const uint32_t crc32Stun[] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d};
static const unsigned long crc32Sctp[256] = {
0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C,
0x26A1E7E8, 0xD4CA64EB, 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B,
0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, 0x105EC76F, 0xE235446C,
0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,
0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC,
0xBC267848, 0x4E4DFB4B, 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A,
0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, 0xAA64D611, 0x580F5512,
0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,
0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD,
0x1642AE59, 0xE4292D5A, 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A,
0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, 0x417B1DBC, 0xB3109EBF,
0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,
0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F,
0xED03A29B, 0x1F682198, 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927,
0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, 0xDBFC821C, 0x2997011F,
0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,
0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E,
0x4767748A, 0xB50CF789, 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859,
0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, 0x7198540D, 0x83F3D70E,
0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,
0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE,
0xDDE0EB2A, 0x2F8B6829, 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C,
0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, 0x082F63B7, 0xFA44E0B4,
0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,
0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B,
0xB4091BFF, 0x466298FC, 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C,
0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, 0xA24BB5A6, 0x502036A5,
0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,
0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975,
0x0E330A81, 0xFC588982, 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D,
0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, 0x38CC2A06, 0xCAA7A905,
0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,
0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8,
0xE52CC12C, 0x1747422F, 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF,
0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, 0xD3D3E1AB, 0x21B862A8,
0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,
0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78,
0x7FAB5E8C, 0x8DC0DD8F, 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE,
0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, 0x69E9F0D5, 0x9B8273D6,
0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,
0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69,
0xD5CF889D, 0x27A40B9E, 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E,
0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351};
uint32_t StunCRC32(const void* data, int32_t len) {
uint32_t crc = 0xffffffff;
const uint8_t* p = (const uint8_t*)data;
while (len--) {
uint32_t lkp = crc32Stun[(crc ^ *p++) & 0xFF];
crc = lkp ^ (crc >> 8);
}
return crc ^ 0xffffffff;
}
#define CRC32C(c, d) (c = (c >> 8) ^ (crc32Sctp)[(c ^ (d)) & 0xFF])
uint32_t SctpCRC32(const void* data, int32_t len) {
uint32_t crc = 0xFFFFFFFF;
const uint8_t* p = (const uint8_t*)data;
for (int32_t i = 0; i < len; i++) {
CRC32C(crc, p[i]);
}
uint32_t result = ~crc;
uint8_t byte0 = result & 0xff;
uint8_t byte1 = (result >> 8) & 0xff;
uint8_t byte2 = (result >> 16) & 0xff;
uint8_t byte3 = (result >> 24) & 0xff;
result = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3);
return result;
}

View File

@@ -0,0 +1,7 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
uint32_t StunCRC32(const void* data, int32_t len);
uint32_t SctpCRC32(const void* data, int32_t len);

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Siim Kallas
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,20 @@
# WebUDP
WebRTC datachannel library and server
[Echo server demo](https://www.vektor.space/webudprtt.html) (Chrome, Firefox, Safari 11+)
The library implements a minimal subset of WebRTC to achieve unreliable and out of order UDP transfer for browser clients. The core library (Wu) is platform independent. Refer to WuHostEpoll or WuHostNode for usage.
## Building
```bash
mkdir build && cd build
cmake ..
make
```
### Host platforms
* Linux (epoll)
* Node.js ```-DWITH_NODE=ON```
### Issues
* Firefox doesn't connect to a server running on localhost. Bind a different interface.

View File

@@ -0,0 +1,771 @@
#include "Wu.h"
#include <assert.h>
#include <openssl/ec.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <string.h>
#include "WuArena.h"
#include "WuClock.h"
#include "WuCrypto.h"
#include "WuMath.h"
#include "WuPool.h"
#include "WuQueue.h"
#include "WuRng.h"
#include "WuSctp.h"
#include "WuSdp.h"
#include "WuStun.h"
struct Wu {
WuArena* arena;
double time;
double dt;
char host[256];
uint16_t port;
WuQueue* pendingEvents;
int32_t maxClients;
int32_t numClients;
WuPool* clientPool;
WuClient** clients;
ssl_ctx_st* sslCtx;
char certFingerprint[96];
char errBuf[512];
void* userData;
WuErrorFn errorCallback;
WuErrorFn debugCallback;
WuWriteFn writeUdpData;
};
const double kMaxClientTtl = 9.0;
const double heartbeatInterval = 4.0;
const int kDefaultMTU = 1400;
static void DefaultErrorCallback(const char*, void*) {}
static void WriteNothing(const uint8_t*, size_t, const WuClient*, void*) {}
enum DataChannelMessageType { DCMessage_Ack = 0x02, DCMessage_Open = 0x03 };
enum DataChanProtoIdentifier {
DCProto_Control = 50,
DCProto_String = 51,
DCProto_Binary = 53,
DCProto_EmptyString = 56,
DCProto_EmptyBinary = 57
};
struct DataChannelPacket {
uint8_t messageType;
union {
struct {
uint8_t channelType;
uint16_t priority;
uint32_t reliability;
} open;
} as;
};
enum WuClientState {
WuClient_Dead,
WuClient_WaitingRemoval,
WuClient_DTLSHandshake,
WuClient_SCTPEstablished,
WuClient_DataChannelOpen
};
static int32_t ParseDataChannelControlPacket(const uint8_t* buf, size_t len,
DataChannelPacket* packet) {
ReadScalarSwapped(buf, &packet->messageType);
return 0;
}
void WuReportError(Wu* wu, const char* description) {
wu->errorCallback(description, wu->userData);
}
void WuReportDebug(Wu* wu, const char* description) {
wu->debugCallback(description, wu->userData);
}
struct WuClient {
StunUserIdentifier serverUser;
StunUserIdentifier serverPassword;
StunUserIdentifier remoteUser;
StunUserIdentifier remoteUserPassword;
WuAddress address;
WuClientState state;
uint16_t localSctpPort;
uint16_t remoteSctpPort;
uint32_t sctpVerificationTag;
uint32_t remoteTsn;
uint32_t tsn;
double ttl;
double nextHeartbeat;
SSL* ssl;
BIO* inBio;
BIO* outBio;
void* user;
};
void WuClientSetUserData(WuClient* client, void* user) { client->user = user; }
void* WuClientGetUserData(const WuClient* client) { return client->user; }
static void WuClientFinish(WuClient* client) {
SSL_free(client->ssl);
client->ssl = NULL;
client->inBio = NULL;
client->outBio = NULL;
client->state = WuClient_Dead;
}
static void WuClientStart(const Wu* wu, WuClient* client) {
client->state = WuClient_DTLSHandshake;
client->remoteSctpPort = 0;
client->sctpVerificationTag = 0;
client->remoteTsn = 0;
client->tsn = 1;
client->ttl = kMaxClientTtl;
client->nextHeartbeat = heartbeatInterval;
client->user = NULL;
client->ssl = SSL_new(wu->sslCtx);
client->inBio = BIO_new(BIO_s_mem());
BIO_set_mem_eof_return(client->inBio, -1);
client->outBio = BIO_new(BIO_s_mem());
BIO_set_mem_eof_return(client->outBio, -1);
SSL_set_bio(client->ssl, client->inBio, client->outBio);
SSL_set_options(client->ssl, SSL_OP_SINGLE_ECDH_USE);
SSL_set_options(client->ssl, SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
SSL_set_tmp_ecdh(client->ssl, EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
SSL_set_accept_state(client->ssl);
SSL_set_mtu(client->ssl, kDefaultMTU);
}
static void WuSendSctp(const Wu* wu, WuClient* client, const SctpPacket* packet,
const SctpChunk* chunks, int32_t numChunks);
static WuClient* WuNewClient(Wu* wu) {
WuClient* client = (WuClient*)WuPoolAcquire(wu->clientPool);
if (client) {
memset(client, 0, sizeof(WuClient));
WuClientStart(wu, client);
wu->clients[wu->numClients++] = client;
return client;
}
return NULL;
}
static void WuPushEvent(Wu* wu, WuEvent evt) {
WuQueuePush(wu->pendingEvents, &evt);
}
static void WuSendSctpShutdown(Wu* wu, WuClient* client) {
SctpPacket response;
response.sourcePort = client->localSctpPort;
response.destionationPort = client->remoteSctpPort;
response.verificationTag = client->sctpVerificationTag;
SctpChunk rc;
rc.type = Sctp_Shutdown;
rc.flags = 0;
rc.length = SctpChunkLength(sizeof(rc.as.shutdown.cumulativeTsnAck));
rc.as.shutdown.cumulativeTsnAck = client->remoteTsn;
WuSendSctp(wu, client, &response, &rc, 1);
}
void WuRemoveClient(Wu* wu, WuClient* client) {
for (int32_t i = 0; i < wu->numClients; i++) {
if (wu->clients[i] == client) {
WuSendSctpShutdown(wu, client);
WuClientFinish(client);
WuPoolRelease(wu->clientPool, client);
wu->clients[i] = wu->clients[wu->numClients - 1];
wu->numClients--;
return;
}
}
}
static WuClient* WuFindClient(Wu* wu, const WuAddress* address) {
for (int32_t i = 0; i < wu->numClients; i++) {
WuClient* client = wu->clients[i];
if (client->address.host == address->host &&
client->address.port == address->port) {
return client;
}
}
return NULL;
}
static WuClient* WuFindClientByCreds(Wu* wu, const StunUserIdentifier* svUser,
const StunUserIdentifier* clUser) {
for (int32_t i = 0; i < wu->numClients; i++) {
WuClient* client = wu->clients[i];
if (StunUserIdentifierEqual(&client->serverUser, svUser) &&
StunUserIdentifierEqual(&client->remoteUser, clUser)) {
return client;
}
}
return NULL;
}
static void WuClientSendPendingDTLS(const Wu* wu, WuClient* client) {
uint8_t sendBuffer[4096];
while (BIO_ctrl_pending(client->outBio) > 0) {
int bytes = BIO_read(client->outBio, sendBuffer, sizeof(sendBuffer));
if (bytes > 0) {
wu->writeUdpData(sendBuffer, bytes, client, wu->userData);
}
}
}
static void TLSSend(const Wu* wu, WuClient* client, const void* data,
int32_t length) {
if (client->state < WuClient_DTLSHandshake ||
!SSL_is_init_finished(client->ssl)) {
return;
}
SSL_write(client->ssl, data, length);
WuClientSendPendingDTLS(wu, client);
}
static void WuSendSctp(const Wu* wu, WuClient* client, const SctpPacket* packet,
const SctpChunk* chunks, int32_t numChunks) {
uint8_t outBuffer[4096];
memset(outBuffer, 0, sizeof(outBuffer));
size_t bytesWritten = SerializeSctpPacket(packet, chunks, numChunks,
outBuffer, sizeof(outBuffer));
TLSSend(wu, client, outBuffer, bytesWritten);
}
static void WuHandleSctp(Wu* wu, WuClient* client, const uint8_t* buf,
int32_t len) {
const size_t maxChunks = 8;
SctpChunk chunks[maxChunks];
SctpPacket sctpPacket;
size_t nChunk = 0;
if (!ParseSctpPacket(buf, len, &sctpPacket, chunks, maxChunks, &nChunk)) {
return;
}
for (size_t n = 0; n < nChunk; n++) {
SctpChunk* chunk = &chunks[n];
if (chunk->type == Sctp_Data) {
auto* dataChunk = &chunk->as.data;
const uint8_t* userDataBegin = dataChunk->userData;
const int32_t userDataLength = dataChunk->userDataLength;
client->remoteTsn = Max(chunk->as.data.tsn, client->remoteTsn);
client->ttl = kMaxClientTtl;
if (dataChunk->protoId == DCProto_Control) {
DataChannelPacket packet;
ParseDataChannelControlPacket(userDataBegin, userDataLength, &packet);
if (packet.messageType == DCMessage_Open) {
client->remoteSctpPort = sctpPacket.sourcePort;
uint8_t outType = DCMessage_Ack;
SctpPacket response;
response.sourcePort = sctpPacket.destionationPort;
response.destionationPort = sctpPacket.sourcePort;
response.verificationTag = client->sctpVerificationTag;
SctpChunk rc;
rc.type = Sctp_Data;
rc.flags = kSctpFlagCompleteUnreliable;
rc.length = SctpDataChunkLength(1);
auto* dc = &rc.as.data;
dc->tsn = client->tsn++;
dc->streamId = chunk->as.data.streamId;
dc->streamSeq = 0;
dc->protoId = DCProto_Control;
dc->userData = &outType;
dc->userDataLength = 1;
if (client->state != WuClient_DataChannelOpen) {
client->state = WuClient_DataChannelOpen;
WuEvent event;
event.type = WuEvent_ClientJoin;
event.client = client;
WuPushEvent(wu, event);
}
WuSendSctp(wu, client, &response, &rc, 1);
}
} else if (dataChunk->protoId == DCProto_String) {
WuEvent evt;
evt.type = WuEvent_TextData;
evt.client = client;
evt.data = dataChunk->userData;
evt.length = dataChunk->userDataLength;
WuPushEvent(wu, evt);
} else if (dataChunk->protoId == DCProto_Binary) {
WuEvent evt;
evt.type = WuEvent_BinaryData;
evt.client = client;
evt.data = dataChunk->userData;
evt.length = dataChunk->userDataLength;
WuPushEvent(wu, evt);
}
SctpPacket sack;
sack.sourcePort = sctpPacket.destionationPort;
sack.destionationPort = sctpPacket.sourcePort;
sack.verificationTag = client->sctpVerificationTag;
SctpChunk rc;
rc.type = Sctp_Sack;
rc.flags = 0;
rc.length = SctpChunkLength(12);
rc.as.sack.cumulativeTsnAck = client->remoteTsn;
rc.as.sack.advRecvWindow = kSctpDefaultBufferSpace;
rc.as.sack.numGapAckBlocks = 0;
rc.as.sack.numDupTsn = 0;
WuSendSctp(wu, client, &sack, &rc, 1);
} else if (chunk->type == Sctp_Init) {
SctpPacket response;
response.sourcePort = sctpPacket.destionationPort;
response.destionationPort = sctpPacket.sourcePort;
response.verificationTag = chunk->as.init.initiateTag;
client->sctpVerificationTag = response.verificationTag;
client->remoteTsn = chunk->as.init.initialTsn - 1;
SctpChunk rc;
rc.type = Sctp_InitAck;
rc.flags = 0;
rc.length = kSctpMinInitAckLength;
rc.as.init.initiateTag = WuRandomU32();
rc.as.init.windowCredit = kSctpDefaultBufferSpace;
rc.as.init.numOutboundStreams = chunk->as.init.numInboundStreams;
rc.as.init.numInboundStreams = chunk->as.init.numOutboundStreams;
rc.as.init.initialTsn = client->tsn;
WuSendSctp(wu, client, &response, &rc, 1);
break;
} else if (chunk->type == Sctp_CookieEcho) {
if (client->state < WuClient_SCTPEstablished) {
client->state = WuClient_SCTPEstablished;
}
SctpPacket response;
response.sourcePort = sctpPacket.destionationPort;
response.destionationPort = sctpPacket.sourcePort;
response.verificationTag = client->sctpVerificationTag;
SctpChunk rc;
rc.type = Sctp_CookieAck;
rc.flags = 0;
rc.length = SctpChunkLength(0);
WuSendSctp(wu, client, &response, &rc, 1);
} else if (chunk->type == Sctp_Heartbeat) {
SctpPacket response;
response.sourcePort = sctpPacket.destionationPort;
response.destionationPort = sctpPacket.sourcePort;
response.verificationTag = client->sctpVerificationTag;
SctpChunk rc;
rc.type = Sctp_HeartbeatAck;
rc.flags = 0;
rc.length = chunk->length;
rc.as.heartbeat.heartbeatInfoLen = chunk->as.heartbeat.heartbeatInfoLen;
rc.as.heartbeat.heartbeatInfo = chunk->as.heartbeat.heartbeatInfo;
client->ttl = kMaxClientTtl;
WuSendSctp(wu, client, &response, &rc, 1);
} else if (chunk->type == Sctp_HeartbeatAck) {
client->ttl = kMaxClientTtl;
} else if (chunk->type == Sctp_Abort) {
client->state = WuClient_WaitingRemoval;
return;
} else if (chunk->type == Sctp_Sack) {
client->ttl = kMaxClientTtl;
auto* sack = &chunk->as.sack;
if (sack->numGapAckBlocks > 0) {
SctpPacket fwdResponse;
fwdResponse.sourcePort = sctpPacket.destionationPort;
fwdResponse.destionationPort = sctpPacket.sourcePort;
fwdResponse.verificationTag = client->sctpVerificationTag;
SctpChunk fwdTsnChunk;
fwdTsnChunk.type = SctpChunk_ForwardTsn;
fwdTsnChunk.flags = 0;
fwdTsnChunk.length = SctpChunkLength(4);
fwdTsnChunk.as.forwardTsn.newCumulativeTsn = client->tsn;
WuSendSctp(wu, client, &fwdResponse, &fwdTsnChunk, 1);
}
}
}
}
static void WuReceiveDTLSPacket(Wu* wu, const uint8_t* data, size_t length,
const WuAddress* address) {
WuClient* client = WuFindClient(wu, address);
if (!client) {
WuReportDebug(wu, "DTLS: No client found");
return;
}
BIO_write(client->inBio, data, length);
if (!SSL_is_init_finished(client->ssl)) {
int r = SSL_do_handshake(client->ssl);
if (r <= 0) {
r = SSL_get_error(client->ssl, r);
if (SSL_ERROR_WANT_READ == r) {
WuClientSendPendingDTLS(wu, client);
} else if (SSL_ERROR_NONE != r) {
char* error = ERR_error_string(r, NULL);
if (error) {
WuReportError(wu, error);
}
}
}
} else {
WuClientSendPendingDTLS(wu, client);
while (BIO_ctrl_pending(client->inBio) > 0) {
uint8_t receiveBuffer[8092];
int bytes = SSL_read(client->ssl, receiveBuffer, sizeof(receiveBuffer));
if (bytes > 0) {
uint8_t* buf = (uint8_t*)WuArenaAcquire(wu->arena, bytes);
memcpy(buf, receiveBuffer, bytes);
WuHandleSctp(wu, client, buf, bytes);
}
}
}
}
static void WuHandleStun(Wu* wu, const StunPacket* packet,
const WuAddress* remote) {
WuClient* client =
WuFindClientByCreds(wu, &packet->serverUser, &packet->remoteUser);
if (!client) {
WuReportDebug(wu, "Stun: No client found");
// TODO: Send unauthorized
return;
}
StunPacket outPacket;
outPacket.type = Stun_SuccessResponse;
memcpy(outPacket.transactionId, packet->transactionId,
kStunTransactionIdLength);
outPacket.xorMappedAddress.family = Stun_IPV4;
outPacket.xorMappedAddress.port = ByteSwap(remote->port ^ kStunXorMagic);
outPacket.xorMappedAddress.address.ipv4 =
ByteSwap(remote->host ^ kStunCookie);
uint8_t stunResponse[512];
size_t serializedSize =
SerializeStunPacket(&outPacket, client->serverPassword.identifier,
client->serverPassword.length, stunResponse, 512);
client->localSctpPort = remote->port;
client->address = *remote;
wu->writeUdpData(stunResponse, serializedSize, client, wu->userData);
}
static void WuPurgeDeadClients(Wu* wu) {
for (int32_t i = 0; i < wu->numClients; i++) {
WuClient* client = wu->clients[i];
if (client->ttl <= 0.0 || client->state == WuClient_WaitingRemoval) {
if (client->ttl <= 0.0)
WuReportDebug(wu, "Removing dead client due to no messages in 9s");
else
WuReportDebug(wu, "Removing client due to its own request");
WuEvent evt;
evt.type = WuEvent_ClientLeave;
evt.client = client;
WuPushEvent(wu, evt);
}
}
}
static int32_t WuCryptoInit(Wu* wu) {
wu->sslCtx = SSL_CTX_new(DTLS_server_method());
if (!wu->sslCtx) {
ERR_print_errors_fp(stderr);
return 0;
}
int sslStatus =
SSL_CTX_set_cipher_list(wu->sslCtx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
if (sslStatus != 1) {
ERR_print_errors_fp(stderr);
return 0;
}
SSL_CTX_set_verify(wu->sslCtx, SSL_VERIFY_NONE, NULL);
WuCert cert;
sslStatus = SSL_CTX_use_PrivateKey(wu->sslCtx, cert.key);
if (sslStatus != 1) {
ERR_print_errors_fp(stderr);
return 0;
}
sslStatus = SSL_CTX_use_certificate(wu->sslCtx, cert.x509);
if (sslStatus != 1) {
ERR_print_errors_fp(stderr);
return 0;
}
sslStatus = SSL_CTX_check_private_key(wu->sslCtx);
if (sslStatus != 1) {
ERR_print_errors_fp(stderr);
return 0;
}
SSL_CTX_set_options(wu->sslCtx, SSL_OP_NO_QUERY_MTU);
memcpy(wu->certFingerprint, cert.fingerprint, sizeof(cert.fingerprint));
return 1;
}
int32_t WuCreate(const char* host, uint16_t port, int maxClients, Wu** wu) {
*wu = NULL;
Wu* ctx = (Wu*)calloc(1, sizeof(Wu));
if (!ctx) {
return WU_OUT_OF_MEMORY;
}
ctx->arena = (WuArena*)calloc(1, sizeof(WuArena));
if (!ctx->arena) {
WuDestroy(ctx);
return WU_OUT_OF_MEMORY;
}
WuArenaInit(ctx->arena, 1 << 20);
ctx->time = MsNow() * 0.001;
ctx->port = port;
ctx->pendingEvents = WuQueueCreate(sizeof(WuEvent), 1024);
ctx->errorCallback = DefaultErrorCallback;
ctx->debugCallback = DefaultErrorCallback;
ctx->writeUdpData = WriteNothing;
strncpy(ctx->host, host, sizeof(ctx->host));
if (!WuCryptoInit(ctx)) {
WuDestroy(ctx);
return WU_ERROR;
}
ctx->maxClients = maxClients <= 0 ? 256 : maxClients;
ctx->clientPool = WuPoolCreate(sizeof(WuClient), ctx->maxClients);
ctx->clients = (WuClient**)calloc(ctx->maxClients, sizeof(WuClient*));
*wu = ctx;
return WU_OK;
}
static void WuSendHeartbeat(Wu* wu, WuClient* client) {
SctpPacket packet;
packet.sourcePort = wu->port;
packet.destionationPort = client->remoteSctpPort;
packet.verificationTag = client->sctpVerificationTag;
SctpChunk rc;
rc.type = Sctp_Heartbeat;
rc.flags = kSctpFlagCompleteUnreliable;
rc.length = SctpChunkLength(4 + 8);
rc.as.heartbeat.heartbeatInfo = (const uint8_t*)&wu->time;
rc.as.heartbeat.heartbeatInfoLen = sizeof(wu->time);
WuSendSctp(wu, client, &packet, &rc, 1);
}
static void WuUpdateClients(Wu* wu) {
double t = MsNow() * 0.001;
wu->dt = t - wu->time;
wu->time = t;
for (int32_t i = 0; i < wu->numClients; i++) {
WuClient* client = wu->clients[i];
client->ttl -= wu->dt;
client->nextHeartbeat -= wu->dt;
if (client->nextHeartbeat <= 0.0) {
client->nextHeartbeat = heartbeatInterval;
WuSendHeartbeat(wu, client);
}
WuClientSendPendingDTLS(wu, client);
}
}
int32_t WuUpdate(Wu* wu, WuEvent* evt) {
if (WuQueuePop(wu->pendingEvents, evt)) {
return 1;
}
WuUpdateClients(wu);
WuArenaReset(wu->arena);
WuPurgeDeadClients(wu);
return 0;
}
static int32_t WuSendData(Wu* wu, WuClient* client, const uint8_t* data,
int32_t length, DataChanProtoIdentifier proto) {
if (client->state < WuClient_DataChannelOpen) {
return -1;
}
SctpPacket packet;
packet.sourcePort = wu->port;
packet.destionationPort = client->remoteSctpPort;
packet.verificationTag = client->sctpVerificationTag;
SctpChunk rc;
rc.type = Sctp_Data;
rc.flags = kSctpFlagCompleteUnreliable;
rc.length = SctpDataChunkLength(length);
auto* dc = &rc.as.data;
dc->tsn = client->tsn++;
dc->streamId = 0; // TODO: Does it matter?
dc->streamSeq = 0;
dc->protoId = proto;
dc->userData = data;
dc->userDataLength = length;
WuSendSctp(wu, client, &packet, &rc, 1);
return 0;
}
int32_t WuSendText(Wu* wu, WuClient* client, const char* text, int32_t length) {
return WuSendData(wu, client, (const uint8_t*)text, length, DCProto_String);
}
int32_t WuSendBinary(Wu* wu, WuClient* client, const uint8_t* data,
int32_t length) {
return WuSendData(wu, client, data, length, DCProto_Binary);
}
SDPResult WuExchangeSDP(Wu* wu, const char* sdp, int32_t length) {
ICESdpFields iceFields;
if (!ParseSdp(sdp, length, &iceFields)) {
return {WuSDPStatus_InvalidSDP, NULL, NULL, 0};
}
WuClient* client = WuNewClient(wu);
if (!client) {
return {WuSDPStatus_MaxClients, NULL, NULL, 0};
}
client->serverUser.length = 4;
WuRandomString((char*)client->serverUser.identifier,
client->serverUser.length);
client->serverPassword.length = 24;
WuRandomString((char*)client->serverPassword.identifier,
client->serverPassword.length);
memcpy(client->remoteUser.identifier, iceFields.ufrag.value,
Min(iceFields.ufrag.length, kMaxStunIdentifierLength));
client->remoteUser.length = iceFields.ufrag.length;
memcpy(client->remoteUserPassword.identifier, iceFields.password.value,
Min(iceFields.password.length, kMaxStunIdentifierLength));
int sdpLength = 0;
const char* responseSdp = GenerateSDP(
wu->arena, wu->certFingerprint, wu->host, wu->port,
(char*)client->serverUser.identifier, client->serverUser.length,
(char*)client->serverPassword.identifier, client->serverPassword.length,
&iceFields, &sdpLength);
if (!responseSdp) {
return {WuSDPStatus_Error, NULL, NULL, 0};
}
return {WuSDPStatus_Success, client, responseSdp, sdpLength};
}
void WuSetUserData(Wu* wu, void* userData) { wu->userData = userData; }
void WuHandleUDP(Wu* wu, const WuAddress* remote, const uint8_t* data,
int32_t length) {
StunPacket stunPacket;
if (ParseStun(data, length, &stunPacket)) {
//WuReportDebug(wu, "Received stun packet");
WuHandleStun(wu, &stunPacket, remote);
} else {
//WuReportDebug(wu, "Received DTLS packet");
WuReceiveDTLSPacket(wu, data, length, remote);
}
}
void WuSetUDPWriteFunction(Wu* wu, WuWriteFn write) {
wu->writeUdpData = write;
}
WuAddress WuClientGetAddress(const WuClient* client) { return client->address; }
void WuSetErrorCallback(Wu* wu, WuErrorFn callback) {
if (callback) {
wu->errorCallback = callback;
} else {
wu->errorCallback = DefaultErrorCallback;
}
}
void WuSetDebugCallback(Wu* wu, WuErrorFn callback) {
if (callback) {
wu->debugCallback = callback;
} else {
wu->debugCallback = DefaultErrorCallback;
}
}
void WuDestroy(Wu* wu) {
if (!wu) {
return;
}
free(wu);
}
WuClient* WuFindClient(const Wu* wu, WuAddress address) {
for (int32_t i = 0; i < wu->numClients; i++) {
WuClient* c = wu->clients[i];
if (c->address.host == address.host && c->address.port == address.port) {
return c;
}
}
return NULL;
}

View File

@@ -0,0 +1,76 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define WU_OK 0
#define WU_ERROR 1
#define WU_OUT_OF_MEMORY 2
typedef struct WuClient WuClient;
typedef struct Wu Wu;
typedef void (*WuErrorFn)(const char* err, void* userData);
typedef void (*WuWriteFn)(const uint8_t* data, size_t length,
const WuClient* client, void* userData);
typedef enum {
WuEvent_BinaryData,
WuEvent_ClientJoin,
WuEvent_ClientLeave,
WuEvent_TextData
} WuEventType;
typedef enum {
WuSDPStatus_Success,
WuSDPStatus_InvalidSDP,
WuSDPStatus_MaxClients,
WuSDPStatus_Error
} WuSDPStatus;
typedef struct {
WuEventType type;
WuClient* client;
const uint8_t* data;
int32_t length;
} WuEvent;
typedef struct {
WuSDPStatus status;
WuClient* client;
const char* sdp;
int32_t sdpLength;
} SDPResult;
typedef struct {
uint32_t host;
uint16_t port;
} WuAddress;
int32_t WuCreate(const char* host, uint16_t port, int maxClients, Wu** wu);
void WuDestroy(Wu* wu);
int32_t WuUpdate(Wu* wu, WuEvent* evt);
int32_t WuSendText(Wu* wu, WuClient* client, const char* text, int32_t length);
int32_t WuSendBinary(Wu* wu, WuClient* client, const uint8_t* data,
int32_t length);
void WuReportError(Wu* wu, const char* error);
void WuReportDebug(Wu* wu, const char* error);
void WuRemoveClient(Wu* wu, WuClient* client);
void WuClientSetUserData(WuClient* client, void* user);
void* WuClientGetUserData(const WuClient* client);
SDPResult WuExchangeSDP(Wu* wu, const char* sdp, int32_t length);
void WuHandleUDP(Wu* wu, const WuAddress* remote, const uint8_t* data,
int32_t length);
void WuSetUDPWriteFunction(Wu* wu, WuWriteFn write);
void WuSetUserData(Wu* wu, void* userData);
void WuSetErrorCallback(Wu* wu, WuErrorFn callback);
void WuSetDebugCallback(Wu* wu, WuErrorFn callback);
WuAddress WuClientGetAddress(const WuClient* client);
WuClient* WuFindClient(const Wu* wu, WuAddress address);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,26 @@
#include "WuArena.h"
#include <assert.h>
#include <stdlib.h>
void WuArenaInit(WuArena* arena, int32_t capacity) {
arena->memory = (uint8_t*)calloc(capacity, 1);
arena->length = 0;
arena->capacity = capacity;
}
void* WuArenaAcquire(WuArena* arena, int32_t blockSize) {
assert(blockSize > 0);
int32_t remain = arena->capacity - arena->length;
if (remain >= blockSize) {
uint8_t* m = arena->memory + arena->length;
arena->length += blockSize;
return m;
}
return NULL;
}
void WuArenaReset(WuArena* arena) { arena->length = 0; }
void WuArenaDestroy(WuArena* arena) { free(arena->memory); }

View File

@@ -0,0 +1,14 @@
#pragma once
#include <stdint.h>
struct WuArena {
uint8_t* memory;
int32_t length;
int32_t capacity;
};
void WuArenaInit(WuArena* arena, int32_t capacity);
void* WuArenaAcquire(WuArena* arena, int32_t blockSize);
void WuArenaReset(WuArena* arena);
void WuArenaDestroy(WuArena* arena);

View File

@@ -0,0 +1,48 @@
#pragma once
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
template <typename T>
T ByteSwap(T v) {
if (sizeof(T) == 1) {
return v;
} else if (sizeof(T) == 2) {
return __builtin_bswap16(uint16_t(v));
} else if (sizeof(T) == 4) {
return __builtin_bswap32(uint32_t(v));
} else if (sizeof(T) == 8) {
return __builtin_bswap64(uint64_t(v));
} else {
assert(0);
return 0;
}
}
template <typename T>
size_t WriteScalar(uint8_t* dest, T v) {
*((T*)dest) = v;
return sizeof(T);
}
template <typename T>
int32_t ReadScalar(const uint8_t* src, T* v) {
*v = *(const T*)src;
return sizeof(T);
}
template <typename T>
size_t WriteScalarSwapped(uint8_t* dest, T v) {
*((T*)dest) = ByteSwap(v);
return sizeof(T);
}
template <typename T>
int32_t ReadScalarSwapped(const uint8_t* src, T* v) {
*v = ByteSwap(*(const T*)src);
return sizeof(T);
}
inline int32_t PadSize(int32_t numBytes, int32_t alignBytes) {
return ((numBytes + alignBytes - 1) & ~(alignBytes - 1)) - numBytes;
}

View File

@@ -0,0 +1,34 @@
#pragma once
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/time.h>
#endif
#include <stdint.h>
inline int64_t HpCounter() {
#ifdef _WIN32
LARGE_INTEGER li;
QueryPerformanceCounter(&li);
int64_t i64 = li.QuadPart;
#else
struct timeval t;
gettimeofday(&t, 0);
int64_t i64 = t.tv_sec * int64_t(1000000) + t.tv_usec;
#endif
return i64;
}
inline int64_t HpFreq() {
#ifdef _WIN32
LARGE_INTEGER li;
QueryPerformanceFrequency(&li);
return li.QuadPart;
#else
return int64_t(1000000);
#endif
}
inline double MsNow() {
return double(HpCounter()) * 1000.0 / double(HpFreq());
}

View File

@@ -0,0 +1,65 @@
#include "WuCrypto.h"
#include <assert.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include "WuRng.h"
WuSHA1Digest WuSHA1(const uint8_t* src, size_t len, const void* key,
size_t keyLen) {
WuSHA1Digest digest;
HMAC(EVP_sha1(), key, keyLen, src, len, digest.bytes, NULL);
return digest;
}
WuCert::WuCert() : key(EVP_PKEY_new()), x509(X509_new()) {
RSA* rsa = RSA_new();
BIGNUM* n = BN_new();
BN_set_word(n, RSA_F4);
if (!RAND_status()) {
uint64_t seed = WuRandomU64();
RAND_seed(&seed, sizeof(seed));
}
RSA_generate_key_ex(rsa, 2048, n, NULL);
EVP_PKEY_assign_RSA(key, rsa);
BIGNUM* serial = BN_new();
X509_NAME* name = X509_NAME_new();
X509_set_pubkey(x509, key);
BN_pseudo_rand(serial, 64, 0, 0);
X509_set_version(x509, 0L);
X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_UTF8,
(unsigned char*)"wusocket", -1, -1, 0);
X509_set_subject_name(x509, name);
X509_set_issuer_name(x509, name);
X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 365 * 24 * 3600);
X509_sign(x509, key, EVP_sha1());
unsigned int len = 32;
uint8_t buf[32] = {0};
X509_digest(x509, EVP_sha256(), buf, &len);
assert(len == 32);
for (unsigned int i = 0; i < len; i++) {
if (i < 31) {
snprintf(fingerprint + i * 3, 4, "%02X:", buf[i]);
} else {
snprintf(fingerprint + i * 3, 3, "%02X", buf[i]);
}
}
fingerprint[95] = '\0';
BN_free(n);
BN_free(serial);
X509_NAME_free(name);
}
WuCert::~WuCert() {
EVP_PKEY_free(key);
X509_free(x509);
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include <openssl/x509.h>
#include <stddef.h>
#include <stdint.h>
const size_t kSHA1Length = 20;
struct WuSHA1Digest {
uint8_t bytes[kSHA1Length];
};
struct WuCert {
WuCert();
~WuCert();
EVP_PKEY* key;
X509* x509;
char fingerprint[96];
};
WuSHA1Digest WuSHA1(const uint8_t* src, size_t len, const void* key,
size_t keyLen);

View File

@@ -0,0 +1,36 @@
#pragma once
#include <stdint.h>
#include "Wu.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct WuHost WuHost;
int32_t WuHostCreate(const char* hostAddr, uint16_t port, int32_t maxClients,
WuHost** host);
void WuHostDestroy(WuHost* host);
/*
* Timeout:
* -1 = Block until an event
* 0 = Return immediately
* >0 = Block for N milliseconds
* Returns 1 if an event was received, 0 otherwise.
*/
int32_t WuHostServe(WuHost* host, WuEvent* evt, int timeout);
void WuHostRemoveClient(WuHost* wu, WuClient* client);
int32_t WuHostSendText(WuHost* host, WuClient* client, const char* text,
int32_t length);
int32_t WuHostSendBinary(WuHost* host, WuClient* client, const uint8_t* data,
int32_t length);
void WuHostSetErrorCallback(WuHost* host, WuErrorFn callback);
void WuHostSetDebugCallback(WuHost* host, WuErrorFn callback);
WuClient* WuHostFindClient(const WuHost* host, WuAddress address);
void WuGotHttp(WuHost *host, const char msg[], const uint32_t msglen,
char resp[]);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,268 @@
#include <errno.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include "WuHost.h"
#include "WuHttp.h"
#include "WuMath.h"
#include "WuNetwork.h"
#include "WuPool.h"
#include "WuRng.h"
#include "WuString.h"
static pthread_mutex_t wumutex = PTHREAD_MUTEX_INITIALIZER;
struct WuConnectionBuffer {
size_t size = 0;
int fd = -1;
uint8_t requestBuffer[kMaxHttpRequestLength];
};
struct WuHost {
Wu* wu;
int udpfd;
int epfd;
int pollTimeout;
WuPool* bufferPool;
struct epoll_event* events;
int32_t maxEvents;
uint16_t port;
char errBuf[512];
};
static void HostReclaimBuffer(WuHost* host, WuConnectionBuffer* buffer) {
buffer->fd = -1;
buffer->size = 0;
WuPoolRelease(host->bufferPool, buffer);
}
static WuConnectionBuffer* HostGetBuffer(WuHost* host) {
WuConnectionBuffer* buffer = (WuConnectionBuffer*)WuPoolAcquire(host->bufferPool);
return buffer;
}
static void HandleErrno(WuHost* host, const char* description) {
snprintf(host->errBuf, sizeof(host->errBuf), "%s: %s", description,
strerror(errno));
WuReportError(host->wu, host->errBuf);
}
static void WriteUDPData(const uint8_t* data, size_t length,
const WuClient* client, void* userData) {
WuHost* host = (WuHost*)userData;
WuAddress address = WuClientGetAddress(client);
struct sockaddr_in netaddr;
netaddr.sin_family = AF_INET;
netaddr.sin_port = htons(address.port);
netaddr.sin_addr.s_addr = htonl(address.host);
sendto(host->udpfd, data, length, 0, (struct sockaddr*)&netaddr,
sizeof(netaddr));
}
int32_t WuHostServe(WuHost* host, WuEvent* evt, int timeout) {
if (pthread_mutex_lock(&wumutex))
abort();
int32_t hres = WuUpdate(host->wu, evt);
pthread_mutex_unlock(&wumutex);
if (hres) {
return hres;
}
int n =
epoll_wait(host->epfd, host->events, host->maxEvents, timeout);
for (int i = 0; i < n; i++) {
struct epoll_event* e = &host->events[i];
WuConnectionBuffer* c = (WuConnectionBuffer*)e->data.ptr;
if ((e->events & EPOLLERR) || (e->events & EPOLLHUP) ||
(!(e->events & EPOLLIN))) {
close(c->fd);
HostReclaimBuffer(host, c);
continue;
}
if (host->udpfd == c->fd) {
struct sockaddr_in remote;
socklen_t remoteLen = sizeof(remote);
uint8_t buf[4096];
ssize_t r = 0;
while ((r = recvfrom(host->udpfd, buf, sizeof(buf), 0,
(struct sockaddr*)&remote, &remoteLen)) > 0) {
WuAddress address;
address.host = ntohl(remote.sin_addr.s_addr);
address.port = ntohs(remote.sin_port);
if (pthread_mutex_lock(&wumutex))
abort();
WuHandleUDP(host->wu, &address, buf, r);
pthread_mutex_unlock(&wumutex);
}
} else {
WuReportError(host->wu, "Unknown epoll source");
}
}
return 0;
}
int32_t WuHostCreate(const char* hostAddr, uint16_t port, int32_t maxClients, WuHost** host) {
*host = NULL;
WuHost* ctx = (WuHost*)calloc(1, sizeof(WuHost));
if (!ctx) {
return WU_OUT_OF_MEMORY;
}
int32_t status = WuCreate(hostAddr, port, maxClients, &ctx->wu);
if (status != WU_OK) {
free(ctx);
return status;
}
ctx->udpfd = CreateSocket(port);
if (ctx->udpfd == -1) {
WuHostDestroy(ctx);
return WU_ERROR;
}
status = MakeNonBlocking(ctx->udpfd);
if (status == -1) {
WuHostDestroy(ctx);
return WU_ERROR;
}
ctx->epfd = epoll_create(1024);
if (ctx->epfd == -1) {
WuHostDestroy(ctx);
return WU_ERROR;
}
const int32_t maxEvents = 128;
ctx->bufferPool = WuPoolCreate(sizeof(WuConnectionBuffer), maxEvents + 2);
if (!ctx->bufferPool) {
WuHostDestroy(ctx);
return WU_OUT_OF_MEMORY;
}
WuConnectionBuffer* udpBuf = HostGetBuffer(ctx);
udpBuf->fd = ctx->udpfd;
struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
event.data.ptr = udpBuf;
status = epoll_ctl(ctx->epfd, EPOLL_CTL_ADD, ctx->udpfd, &event);
if (status == -1) {
WuHostDestroy(ctx);
return WU_ERROR;
}
ctx->maxEvents = maxEvents;
ctx->events = (struct epoll_event*)calloc(ctx->maxEvents, sizeof(event));
if (!ctx->events) {
WuHostDestroy(ctx);
return WU_OUT_OF_MEMORY;
}
WuSetUserData(ctx->wu, ctx);
WuSetUDPWriteFunction(ctx->wu, WriteUDPData);
*host = ctx;
return WU_OK;
}
void WuHostRemoveClient(WuHost* host, WuClient* client) {
WuRemoveClient(host->wu, client);
}
int32_t WuHostSendText(WuHost* host, WuClient* client, const char* text,
int32_t length) {
return WuSendText(host->wu, client, text, length);
}
int32_t WuHostSendBinary(WuHost* host, WuClient* client, const uint8_t* data,
int32_t length) {
if (pthread_mutex_lock(&wumutex))
abort();
int32_t ret = WuSendBinary(host->wu, client, data, length);
pthread_mutex_unlock(&wumutex);
return ret;
}
void WuHostSetErrorCallback(WuHost* host, WuErrorFn callback) {
WuSetErrorCallback(host->wu, callback);
}
void WuHostSetDebugCallback(WuHost* host, WuErrorFn callback) {
WuSetDebugCallback(host->wu, callback);
}
void WuHostDestroy(WuHost* host) {
if (!host) {
return;
}
WuDestroy(host->wu);
if (host->udpfd != -1) {
close(host->udpfd);
}
if (host->epfd != -1) {
close(host->epfd);
}
if (host->bufferPool) {
free(host->bufferPool);
}
if (host->events) {
free(host->events);
}
}
WuClient* WuHostFindClient(const WuHost* host, WuAddress address) {
return WuFindClient(host->wu, address);
}
void WuGotHttp(WuHost *host, const char msg[], const uint32_t msglen,
char resp[]) {
const SDPResult sdp = WuExchangeSDP(
host->wu, msg, msglen);
if (sdp.status == WuSDPStatus_Success) {
snprintf(resp, 4096,
"%.*s",
sdp.sdpLength, sdp.sdp);
} else if (sdp.status == WuSDPStatus_MaxClients) {
WuReportError(host->wu, "Too many connections");
strcpy(resp, HTTP_UNAVAILABLE);
} else if (sdp.status == WuSDPStatus_InvalidSDP) {
WuReportError(host->wu, "Invalid SDP");
strcpy(resp, HTTP_BAD_REQUEST);
} else {
WuReportError(host->wu, "Other error");
strcpy(resp, HTTP_SERVER_ERROR);
}
}

View File

@@ -0,0 +1,11 @@
#include "WuHost.h"
int32_t WuHostCreate(const char*, const char*, int32_t, WuHost** host) {
*host = NULL;
return WU_OK;
}
int32_t WuHostServe(WuHost*, WuEvent*, int) { return 0; }
void WuHostRemoveClient(WuHost*, WuClient*) {}
int32_t WuHostSendText(WuHost*, WuClient*, const char*, int32_t) { return 0; }
int32_t WuHostSendBinary(WuHost*, WuClient*, const uint8_t*, int32_t) { return 0; }
void WuHostSetErrorCallback(WuHost*, WuErrorFn) {}

View File

@@ -0,0 +1,9 @@
#pragma once
#include <stddef.h>
#define HTTP_BAD_REQUEST "HTTP/1.1 400 Bad request\r\n\r\n"
#define HTTP_UNAVAILABLE "HTTP/1.1 503 Service Unavailable\r\n\r\n"
#define HTTP_SERVER_ERROR "HTTP/1.1 500 Internal Server Error\r\n\r\n"
const size_t kMaxHttpRequestLength = 4096;

View File

@@ -0,0 +1,15 @@
#pragma once
template <typename T>
const T& Min(const T& a, const T& b) {
if (a < b) return a;
return b;
}
template <typename T>
const T& Max(const T& a, const T& b) {
if (a > b) return a;
return b;
}

View File

@@ -0,0 +1,58 @@
#include "WuNetwork.h"
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
void HexDump(const uint8_t* src, size_t len) {
for (size_t i = 0; i < len; i++) {
if (i % 8 == 0) printf("%04x ", uint32_t(i));
printf("%02x ", src[i]);
if ((i + 1) % 8 == 0) printf("\n");
}
printf("\n");
}
int MakeNonBlocking(int sfd) {
int flags = fcntl(sfd, F_GETFL, 0);
if (flags == -1) {
return -1;
}
flags |= O_NONBLOCK;
int s = fcntl(sfd, F_SETFL, flags);
if (s == -1) {
return -1;
}
return 0;
}
int CreateSocket(uint16_t port) {
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sfd == -1) {
return -1;
}
int enable = 1;
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == 0)
return sfd;
close(sfd);
return -1;
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
void HexDump(const uint8_t* src, size_t len);
int MakeNonBlocking(int sfd);
int CreateSocket(uint16_t port);

View File

@@ -0,0 +1,60 @@
#include "WuPool.h"
#include <assert.h>
#include <stdlib.h>
struct BlockHeader {
int32_t index;
};
struct WuPool {
int32_t slotSize;
int32_t numBytes;
int32_t numBlocks;
uint8_t* memory;
int32_t freeIndicesCount;
int32_t* freeIndices;
};
WuPool* WuPoolCreate(int32_t blockSize, int32_t numBlocks) {
WuPool* pool = (WuPool*)calloc(1, sizeof(WuPool));
pool->slotSize = blockSize + sizeof(BlockHeader);
pool->numBytes = pool->slotSize * numBlocks;
pool->numBlocks = numBlocks;
pool->memory = (uint8_t*)calloc(pool->numBytes, 1);
pool->freeIndicesCount = numBlocks;
pool->freeIndices = (int32_t*)calloc(numBlocks, sizeof(int32_t));
for (int32_t i = 0; i < numBlocks; i++) {
pool->freeIndices[i] = numBlocks - i - 1;
}
return pool;
}
void WuPoolDestroy(WuPool* pool) {
free(pool->memory);
free(pool->freeIndices);
free(pool);
}
void* WuPoolAcquire(WuPool* pool) {
if (pool->freeIndicesCount == 0) return NULL;
const int32_t index = pool->freeIndices[pool->freeIndicesCount - 1];
pool->freeIndicesCount--;
const int32_t offset = index * pool->slotSize;
uint8_t* block = pool->memory + offset;
BlockHeader* header = (BlockHeader*)block;
header->index = index;
uint8_t* userMem = block + sizeof(BlockHeader);
return userMem;
}
void WuPoolRelease(WuPool* pool, void* ptr) {
uint8_t* mem = (uint8_t*)ptr - sizeof(BlockHeader);
BlockHeader* header = (BlockHeader*)mem;
pool->freeIndices[pool->freeIndicesCount++] = header->index;
}

View File

@@ -0,0 +1,10 @@
#pragma once
#include <stdint.h>
struct WuPool;
WuPool* WuPoolCreate(int32_t blockSize, int32_t numBlocks);
void WuPoolDestroy(WuPool* pool);
void* WuPoolAcquire(WuPool* pool);
void WuPoolRelease(WuPool* pool, void* ptr);

View File

@@ -0,0 +1,58 @@
#include "WuQueue.h"
#include <stdlib.h>
#include <string.h>
static int32_t WuQueueFull(const WuQueue* q) {
if (q->length == q->capacity) {
return 1;
}
return 0;
}
WuQueue* WuQueueCreate(int32_t itemSize, int32_t capacity) {
WuQueue* q = (WuQueue*)calloc(1, sizeof(WuQueue));
WuQueueInit(q, itemSize, capacity);
return q;
}
void WuQueueInit(WuQueue* q, int32_t itemSize, int32_t capacity) {
memset(q, 0, sizeof(WuQueue));
q->itemSize = itemSize;
q->capacity = capacity;
q->items = (uint8_t*)calloc(q->capacity, itemSize);
}
void WuQueuePush(WuQueue* q, const void* item) {
if (WuQueueFull(q)) {
int32_t newCap = q->capacity * 1.5;
uint8_t* newItems = (uint8_t*)calloc(newCap, q->itemSize);
int32_t nUpper = q->length - q->start;
int32_t nLower = q->length - nUpper;
memcpy(newItems, q->items + q->start * q->itemSize, q->itemSize * nUpper);
memcpy(newItems + q->itemSize * nUpper, q->items, q->itemSize * nLower);
free(q->items);
q->start = 0;
q->capacity = newCap;
q->items = newItems;
}
const int32_t insertIdx =
((q->start + q->length) % q->capacity) * q->itemSize;
memcpy(q->items + insertIdx, item, q->itemSize);
q->length++;
}
int32_t WuQueuePop(WuQueue* q, void* item) {
if (q->length > 0) {
memcpy(item, q->items + q->start * q->itemSize, q->itemSize);
q->start = (q->start + 1) % q->capacity;
q->length--;
return 1;
}
return 0;
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include <stdint.h>
struct WuQueue {
int32_t itemSize;
int32_t start;
int32_t length;
int32_t capacity;
uint8_t* items;
};
WuQueue* WuQueueCreate(int32_t itemSize, int32_t capacity);
void WuQueueInit(WuQueue* q, int32_t itemSize, int32_t capacity);
void WuQueuePush(WuQueue* q, const void* item);
int32_t WuQueuePop(WuQueue* q, void* item);

View File

@@ -0,0 +1,51 @@
#include <stdlib.h>
#include "WuRng.h"
static const char kCharacterTable[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
static inline uint64_t rotl(const uint64_t x, int k) {
return (x << k) | (x >> (64 - k));
}
static uint64_t WuGetRngSeed() {
uint64_t x = rand();
uint64_t z = (x += UINT64_C(0x9E3779B97F4A7C15));
z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4E5B9);
z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB);
return z ^ (z >> 31);
}
static void WuRngInit(WuRngState* state, uint64_t seed) {
state->s[0] = seed;
state->s[1] = seed;
}
static uint64_t WuRngNext(WuRngState* state) {
const uint64_t s0 = state->s[0];
uint64_t s1 = state->s[1];
const uint64_t result = s0 + s1;
s1 ^= s0;
state->s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14);
state->s[1] = rotl(s1, 36);
return result;
}
void WuRandomString(char* out, size_t length) {
WuRngState state;
WuRngInit(&state, WuGetRngSeed());
for (size_t i = 0; i < length; i++) {
out[i] = kCharacterTable[WuRngNext(&state) % (sizeof(kCharacterTable) - 1)];
}
}
uint64_t WuRandomU64() {
WuRngState state;
WuRngInit(&state, WuGetRngSeed());
return WuRngNext(&state);
}
uint32_t WuRandomU32() { return (uint32_t)WuRandomU64(); }

View File

@@ -0,0 +1,14 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
// http://xoroshiro.di.unimi.it/xoroshiro128plus.c
struct WuRngState {
uint64_t s[2];
};
uint64_t WuRandomU64();
uint32_t WuRandomU32();
void WuRandomString(char* out, size_t length);

View File

@@ -0,0 +1,169 @@
#include "WuSctp.h"
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include "CRC32.h"
#include "WuBufferOp.h"
#include "WuMath.h"
#include "WuNetwork.h"
int32_t ParseSctpPacket(const uint8_t* buf, size_t len, SctpPacket* packet,
SctpChunk* chunks, size_t maxChunks, size_t* nChunk) {
if (len < 16) {
return 0;
}
int32_t offset = ReadScalarSwapped(buf, &packet->sourcePort);
offset += ReadScalarSwapped(buf + offset, &packet->destionationPort);
offset += ReadScalarSwapped(buf + offset, &packet->verificationTag);
offset += ReadScalarSwapped(buf + offset, &packet->checkSum);
int32_t left = len - offset;
size_t chunkNum = 0;
while (left >= 4 && chunkNum < maxChunks) {
SctpChunk* chunk = &chunks[chunkNum++];
offset += ReadScalarSwapped(buf + offset, &chunk->type);
offset += ReadScalarSwapped(buf + offset, &chunk->flags);
offset += ReadScalarSwapped(buf + offset, &chunk->length);
*nChunk += 1;
if (chunk->type == Sctp_Data) {
auto* p = &chunk->as.data;
size_t chunkOffset = ReadScalarSwapped(buf + offset, &p->tsn);
chunkOffset +=
ReadScalarSwapped(buf + offset + chunkOffset, &p->streamId);
chunkOffset +=
ReadScalarSwapped(buf + offset + chunkOffset, &p->streamSeq);
chunkOffset += ReadScalarSwapped(buf + offset + chunkOffset, &p->protoId);
p->userDataLength = Max(int32_t(chunk->length) - 16, 0);
p->userData = buf + offset + chunkOffset;
} else if (chunk->type == Sctp_Sack) {
auto* sack = &chunk->as.sack;
size_t chunkOffset =
ReadScalarSwapped(buf + offset, &sack->cumulativeTsnAck);
chunkOffset +=
ReadScalarSwapped(buf + offset + chunkOffset, &sack->advRecvWindow);
chunkOffset +=
ReadScalarSwapped(buf + offset + chunkOffset, &sack->numGapAckBlocks);
ReadScalarSwapped(buf + offset + chunkOffset, &sack->numDupTsn);
} else if (chunk->type == Sctp_Heartbeat) {
auto* p = &chunk->as.heartbeat;
size_t chunkOffset = 2; // skip type
uint16_t heartbeatLen;
chunkOffset +=
ReadScalarSwapped(buf + offset + chunkOffset, &heartbeatLen);
p->heartbeatInfoLen = int32_t(heartbeatLen) - 4;
p->heartbeatInfo = buf + offset + chunkOffset;
} else if (chunk->type == Sctp_Init) {
size_t chunkOffset =
ReadScalarSwapped(buf + offset, &chunk->as.init.initiateTag);
chunkOffset += ReadScalarSwapped(buf + offset + chunkOffset,
&chunk->as.init.windowCredit);
chunkOffset += ReadScalarSwapped(buf + offset + chunkOffset,
&chunk->as.init.numOutboundStreams);
chunkOffset += ReadScalarSwapped(buf + offset + chunkOffset,
&chunk->as.init.numInboundStreams);
ReadScalarSwapped(buf + offset + chunkOffset, &chunk->as.init.initialTsn);
}
int32_t valueLength = chunk->length - 4;
int32_t pad = PadSize(valueLength, 4);
offset += valueLength + pad;
left = len - offset;
}
return 1;
}
size_t SerializeSctpPacket(const SctpPacket* packet, const SctpChunk* chunks,
size_t numChunks, uint8_t* dst, size_t dstLen) {
size_t offset = WriteScalar(dst, htons(packet->sourcePort));
offset += WriteScalar(dst + offset, htons(packet->destionationPort));
offset += WriteScalar(dst + offset, htonl(packet->verificationTag));
size_t crcOffset = offset;
offset += WriteScalar(dst + offset, uint32_t(0));
for (size_t i = 0; i < numChunks; i++) {
const SctpChunk* chunk = &chunks[i];
offset += WriteScalar(dst + offset, chunk->type);
offset += WriteScalar(dst + offset, chunk->flags);
offset += WriteScalar(dst + offset, htons(chunk->length));
switch (chunk->type) {
case Sctp_Data: {
auto* dc = &chunk->as.data;
offset += WriteScalar(dst + offset, htonl(dc->tsn));
offset += WriteScalar(dst + offset, htons(dc->streamId));
offset += WriteScalar(dst + offset, htons(dc->streamSeq));
offset += WriteScalar(dst + offset, htonl(dc->protoId));
memcpy(dst + offset, dc->userData, dc->userDataLength);
int32_t pad = PadSize(dc->userDataLength, 4);
offset += dc->userDataLength + pad;
break;
}
case Sctp_InitAck: {
offset += WriteScalar(dst + offset, htonl(chunk->as.init.initiateTag));
offset += WriteScalar(dst + offset, htonl(chunk->as.init.windowCredit));
offset +=
WriteScalar(dst + offset, htons(chunk->as.init.numOutboundStreams));
offset +=
WriteScalar(dst + offset, htons(chunk->as.init.numInboundStreams));
offset += WriteScalar(dst + offset, htonl(chunk->as.init.initialTsn));
offset += WriteScalar(dst + offset, htons(Sctp_StateCookie));
offset += WriteScalar(dst + offset, htons(8));
offset += WriteScalar(dst + offset, htonl(0xB00B1E5));
offset += WriteScalar(dst + offset, htons(Sctp_ForwardTsn));
offset += WriteScalar(dst + offset, htons(4));
break;
}
case Sctp_Sack: {
auto* sack = &chunk->as.sack;
offset += WriteScalar(dst + offset, htonl(sack->cumulativeTsnAck));
offset += WriteScalar(dst + offset, htonl(sack->advRecvWindow));
offset += WriteScalar(dst + offset, htons(sack->numGapAckBlocks));
offset += WriteScalar(dst + offset, htons(sack->numDupTsn));
break;
}
case Sctp_Heartbeat:
case Sctp_HeartbeatAck: {
auto* hb = &chunk->as.heartbeat;
offset += WriteScalar(dst + offset, htons(1));
offset += WriteScalar(dst + offset, htons(hb->heartbeatInfoLen + 4));
memcpy(dst + offset, hb->heartbeatInfo, hb->heartbeatInfoLen);
offset += hb->heartbeatInfoLen + PadSize(hb->heartbeatInfoLen, 4);
break;
}
case Sctp_Shutdown: {
auto* shutdown = &chunk->as.shutdown;
offset += WriteScalar(dst + offset, htonl(shutdown->cumulativeTsnAck));
break;
}
case SctpChunk_ForwardTsn: {
auto* forwardTsn = &chunk->as.forwardTsn;
offset +=
WriteScalar(dst + offset, htonl(forwardTsn->newCumulativeTsn));
break;
}
default:
break;
}
}
uint32_t crc = SctpCRC32(dst, offset);
WriteScalar(dst + crcOffset, htonl(crc));
return offset;
}
int32_t SctpDataChunkLength(int32_t userDataLength) {
return 16 + userDataLength;
}
int32_t SctpChunkLength(int32_t contentLength) { return 4 + contentLength; }

View File

@@ -0,0 +1,100 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
const uint32_t kSctpDefaultBufferSpace = 1 << 18;
const uint32_t kSctpMinInitAckLength = 32;
enum SctpFlag {
SctpFlagEndFragment = 0x01,
SctpFlagBeginFragment = 0x02,
SctpFlagUnreliable = 0x04
};
const uint8_t kSctpFlagCompleteUnreliable =
SctpFlagEndFragment | SctpFlagBeginFragment | SctpFlagUnreliable;
enum SctpChunkType {
Sctp_Data = 0x00,
Sctp_Init = 0x01,
Sctp_InitAck = 0x02,
Sctp_Sack = 0x03,
Sctp_Heartbeat = 0x04,
Sctp_HeartbeatAck = 0x05,
Sctp_Abort = 0x06,
Sctp_Shutdown = 0x07,
Sctp_CookieEcho = 0x0A,
Sctp_CookieAck = 0x0B,
SctpChunk_ForwardTsn = 0xC0
};
enum SctpParamType {
Sctp_StateCookie = 0x07,
Sctp_ForwardTsn = 0xC000,
Sctp_Random = 0x8002,
Sctp_AuthChunkList = 0x8003,
Sctp_HMACAlgo = 0x8004,
Sctp_SupportedExts = 0x8008
};
struct SctpChunk {
uint8_t type;
uint8_t flags;
uint16_t length;
union {
struct {
uint32_t tsn;
uint16_t streamId;
uint16_t streamSeq;
uint32_t protoId;
int32_t userDataLength;
const uint8_t* userData;
} data;
struct {
uint32_t initiateTag;
uint32_t windowCredit;
uint16_t numOutboundStreams;
uint16_t numInboundStreams;
uint32_t initialTsn;
} init;
struct {
int32_t heartbeatInfoLen;
const uint8_t* heartbeatInfo;
} heartbeat;
struct {
uint32_t cumulativeTsnAck;
uint32_t advRecvWindow;
uint16_t numGapAckBlocks;
uint16_t numDupTsn;
} sack;
struct {
uint32_t cumulativeTsnAck;
} shutdown;
struct {
uint32_t newCumulativeTsn;
} forwardTsn;
} as;
};
struct SctpPacket {
uint16_t sourcePort;
uint16_t destionationPort;
uint32_t verificationTag;
uint32_t checkSum;
};
int32_t ParseSctpPacket(const uint8_t* buf, size_t len, SctpPacket* packet,
SctpChunk* chunks, size_t maxChunks, size_t* nChunk);
size_t SerializeSctpPacket(const SctpPacket* packet, const SctpChunk* chunks,
size_t numChunks, uint8_t* dst, size_t dstLen);
int32_t SctpDataChunkLength(int32_t userDataLength);
int32_t SctpChunkLength(int32_t contentLength);

View File

@@ -0,0 +1,152 @@
#include "WuSdp.h"
#include <stdio.h>
#include <string.h>
#include "WuArena.h"
#include "WuRng.h"
enum SdpParseState { kParseIgnore, kParseType, kParseEq, kParseField };
static bool ValidField(const IceField* field) { return field->length > 0; }
static bool BeginsWith(const char* s, size_t len, const char* prefix,
size_t plen) {
if (plen > len) return false;
for (size_t i = 0; i < plen; i++) {
char a = s[i];
char b = prefix[i];
if (a != b) return false;
}
return true;
}
static bool GetIceValue(const char* field, size_t len, const char* name,
IceField* o) {
if (BeginsWith(field, len, name, strlen(name))) {
for (size_t i = 0; i < len; i++) {
char c = field[i];
if (c == ':') {
size_t valueBegin = i + 1;
if (valueBegin < len) {
size_t valueLength = len - valueBegin;
o->value = field + valueBegin;
o->length = int32_t(valueLength);
return true;
}
break;
}
}
}
return false;
}
static void ParseSdpField(const char* field, size_t len, ICESdpFields* fields) {
GetIceValue(field, len, "ice-ufrag", &fields->ufrag);
GetIceValue(field, len, "ice-pwd", &fields->password);
GetIceValue(field, len, "mid", &fields->mid);
}
bool ParseSdp(const char* sdp, size_t len, ICESdpFields* fields) {
memset(fields, 0, sizeof(ICESdpFields));
SdpParseState state = kParseType;
size_t begin = 0;
size_t length = 0;
for (size_t i = 0; i < len; i++) {
char c = sdp[i];
switch (state) {
case kParseType: {
if (c == 'a') {
state = kParseEq;
} else {
state = kParseIgnore;
}
break;
}
case kParseEq: {
if (c == '=') {
state = kParseField;
begin = i + 1;
length = 0;
break;
} else {
return false;
}
}
case kParseField: {
switch (c) {
case '\n': {
ParseSdpField(sdp + begin, length, fields);
length = 0;
state = kParseType;
break;
}
case '\r': {
state = kParseIgnore;
ParseSdpField(sdp + begin, length, fields);
length = 0;
break;
};
default: { length++; }
}
}
default: {
if (c == '\n') state = kParseType;
}
}
}
return ValidField(&fields->ufrag) && ValidField(&fields->password) &&
ValidField(&fields->mid);
}
const char* GenerateSDP(WuArena* arena, const char* certFingerprint,
const char* serverIp, uint16_t serverPort,
const char* ufrag, int32_t ufragLen, const char* pass,
int32_t passLen, const ICESdpFields* remote,
int* outLength) {
const uint32_t port = uint32_t(serverPort);
char buf[4096];
int32_t length = snprintf(
buf, sizeof(buf),
"{\"answer\":{\"sdp\":\"v=0\\r\\n"
"o=- %u 1 IN IP4 %u\\r\\n"
"s=-\\r\\n"
"t=0 0\\r\\n"
"m=application %u UDP/DTLS/SCTP webrtc-datachannel\\r\\n"
"c=IN IP4 %s\\r\\n"
"a=ice-lite\\r\\n"
"a=ice-ufrag:%.*s\\r\\n"
"a=ice-pwd:%.*s\\r\\n"
"a=fingerprint:sha-256 %s\\r\\n"
"a=ice-options:trickle\\r\\n"
"a=setup:passive\\r\\n"
"a=mid:%.*s\\r\\n"
"a=sctp-port:%u\\r\\n\","
"\"type\":\"answer\"},\"candidate\":{\"sdpMLineIndex\":0,"
"\"sdpMid\":\"%.*s\",\"candidate\":\"candidate:1 1 UDP %u %s %u typ "
"host\"}}",
WuRandomU32(), port, port, serverIp, ufragLen, ufrag, passLen, pass,
certFingerprint, remote->mid.length, remote->mid.value, port,
remote->mid.length, remote->mid.value, WuRandomU32(), serverIp, port);
if (length <= 0 || length >= int32_t(sizeof(buf))) {
return NULL;
}
char* sdp = (char*)WuArenaAcquire(arena, length);
if (!sdp) {
return NULL;
}
memcpy(sdp, buf, length);
*outLength = length;
return sdp;
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
struct WuArena;
struct IceField {
const char* value;
int32_t length;
};
struct ICESdpFields {
IceField ufrag;
IceField password;
IceField mid;
};
bool ParseSdp(const char* sdp, size_t len, ICESdpFields* fields);
const char* GenerateSDP(WuArena* arena, const char* certFingerprint,
const char* serverIp, uint16_t serverPort,
const char* ufrag, int32_t ufragLen, const char* pass,
int32_t passLen, const ICESdpFields* remote,
int* outLength);

View File

@@ -0,0 +1,19 @@
#include "WuString.h"
#include <ctype.h>
#include <stdint.h>
#include <string.h>
int32_t FindTokenIndex(const char* s, size_t len, char token) {
for (size_t i = 0; i < len; i++) {
if (s[i] == token) return i;
}
return -1;
}
bool MemEqual(const void* first, size_t firstLen, const void* second,
size_t secondLen) {
if (firstLen != secondLen) return false;
return memcmp(first, second, firstLen) == 0;
}

View File

@@ -0,0 +1,6 @@
#include <stddef.h>
#include <stdint.h>
int32_t FindTokenIndex(const char* s, size_t len, char token);
bool MemEqual(const void* first, size_t firstLen, const void* second,
size_t secondLen);

View File

@@ -0,0 +1,134 @@
#include "WuStun.h"
#include <arpa/inet.h>
#include <string.h>
#include "CRC32.h"
#include "WuCrypto.h"
const int32_t kStunHeaderLength = 20;
const int32_t kStunAlignment = 4;
bool ParseStun(const uint8_t* src, int32_t len, StunPacket* packet) {
if (len < kStunHeaderLength || src[0] != 0 || src[1] != 1) {
return false;
}
src += ReadScalarSwapped(src, &packet->type);
if (packet->type != Stun_BindingRequest) {
return false;
}
src += ReadScalarSwapped(src, &packet->length);
if (packet->length < 4 || packet->length > len - kStunHeaderLength) {
// Need at least 1 attribute
return false;
}
src += ReadScalarSwapped(src, &packet->cookie);
for (int32_t i = 0; i < kStunTransactionIdLength; i++) {
packet->transactionId[i] = src[i];
}
src += kStunTransactionIdLength;
int32_t maxOffset = int32_t(packet->length) - 1;
int32_t payloadOffset = 0;
while (payloadOffset < maxOffset) {
int32_t remain = len - kStunHeaderLength - payloadOffset;
if (remain >= 4) {
uint16_t payloadType = 0;
uint16_t payloadLength = 0;
payloadOffset += ReadScalarSwapped(src + payloadOffset, &payloadType);
payloadOffset += ReadScalarSwapped(src + payloadOffset, &payloadLength);
remain -= 4;
int32_t paddedLength =
payloadLength + PadSize(payloadLength, kStunAlignment);
if (payloadType == StunAttrib_User) {
// fragment = min 4 chars
// username = fragment:fragment (at least 9 characters)
if (paddedLength <= remain && payloadLength >= 9) {
const char* uname = (const char*)src + payloadOffset;
int32_t colonIndex = FindTokenIndex(uname, payloadLength, ':');
if (colonIndex >= 4) {
int32_t serverUserLength = colonIndex;
int32_t remoteUserLength = payloadLength - colonIndex - 1;
if (serverUserLength > kMaxStunIdentifierLength ||
remoteUserLength > kMaxStunIdentifierLength) {
return false;
} else {
packet->serverUser.length = serverUserLength;
packet->remoteUser.length = remoteUserLength;
memcpy(packet->serverUser.identifier, uname, serverUserLength);
memcpy(packet->remoteUser.identifier, uname + colonIndex + 1,
remoteUserLength);
return true;
}
} else {
return false;
}
} else {
// Actual length > reported length
return false;
}
}
payloadOffset += paddedLength;
} else {
return false;
}
}
return true;
}
int32_t SerializeStunPacket(const StunPacket* packet, const uint8_t* password,
int32_t passwordLen, uint8_t* dest, int32_t len) {
memset(dest, 0, len);
int32_t offset = WriteScalar(dest, htons(Stun_SuccessResponse));
// X-MAPPED-ADDRESS (ip4) + MESSAGE-INTEGRITY SHA1
int32_t contentLength = 12 + 24;
int32_t contentLengthIntegrity = contentLength + 8;
const int32_t contentLengthOffset = offset;
offset += WriteScalar(dest + offset, htons(contentLength));
offset += WriteScalar(dest + offset, htonl(kStunCookie));
for (int32_t i = 0; i < 12; i++) {
dest[i + offset] = packet->transactionId[i];
}
offset += 12;
// xor mapped address attribute ipv4
offset += WriteScalar(dest + offset, htons(StunAttrib_XorMappedAddress));
offset += WriteScalar(dest + offset, htons(8));
offset += WriteScalar(dest + offset, uint8_t(0)); // reserved
offset += WriteScalar(dest + offset, packet->xorMappedAddress.family);
offset += WriteScalar(dest + offset, packet->xorMappedAddress.port);
offset += WriteScalar(dest + offset, packet->xorMappedAddress.address.ipv4);
WuSHA1Digest digest = WuSHA1(dest, offset, password, passwordLen);
offset += WriteScalar(dest + offset, htons(StunAttrib_MessageIntegrity));
offset += WriteScalar(dest + offset, htons(20));
for (int32_t i = 0; i < 20; i++) {
dest[i + offset] = digest.bytes[i];
}
offset += 20;
WriteScalar(dest + contentLengthOffset, htons(contentLengthIntegrity));
uint32_t crc = StunCRC32(dest, offset) ^ 0x5354554e;
offset += WriteScalar(dest + offset, htons(StunAttrib_Fingerprint));
offset += WriteScalar(dest + offset, htons(4));
offset += WriteScalar(dest + offset, htonl(crc));
return offset;
}

View File

@@ -0,0 +1,57 @@
#pragma once
#include "WuBufferOp.h"
#include "WuString.h"
const int32_t kMaxStunIdentifierLength = 128;
const int32_t kStunTransactionIdLength = 12;
const uint32_t kStunCookie = 0x2112a442;
const uint16_t kStunXorMagic = 0x2112;
struct StunUserIdentifier {
uint8_t identifier[kMaxStunIdentifierLength];
int32_t length;
};
enum StunAddressFamily { Stun_IPV4 = 0x01, Stun_IPV6 = 0x02 };
enum StunType { Stun_BindingRequest = 0x0001, Stun_SuccessResponse = 0x0101 };
enum StunAttributeType {
StunAttrib_User = 0x06,
StunAttrib_MessageIntegrity = 0x08,
StunAttrib_XorMappedAddress = 0x20,
StunAttrib_Fingerprint = 0x8028
};
struct StunAddress {
uint8_t family;
uint16_t port;
union {
uint32_t ipv4;
uint8_t ipv6[16];
} address;
};
inline bool StunUserIdentifierEqual(const StunUserIdentifier* a,
const StunUserIdentifier* b) {
return MemEqual(a->identifier, a->length, b->identifier, b->length);
}
struct StunPacket {
uint16_t type;
uint16_t length;
uint32_t cookie;
uint8_t transactionId[kStunTransactionIdLength];
StunUserIdentifier remoteUser;
StunUserIdentifier serverUser;
StunAddress xorMappedAddress;
};
bool ParseStun(const uint8_t* src, int32_t len, StunPacket* packet);
int32_t SerializeStunPacket(const StunPacket* packet, const uint8_t* password,
int32_t passwordLen, uint8_t* dest, int32_t len);

View File

@@ -0,0 +1,19 @@
{
"name": "webudp",
"version": "1.0.0",
"description": "",
"main": "webudp.js",
"scripts": {
"configure": "node-gyp configure",
"build": "node-gyp build"
},
"dependencies": {
"express": "4.16.3",
"cors": "2.8.4",
"body-parser": "1.18.3"
},
"devDependencies": {
"node-gyp": "6.1.0",
"nan": "2.14.0"
}
}

View File

@@ -194,3 +194,9 @@ void ZlibOutStream::checkCompressionLevel()
compressionLevel = newLevel;
}
}
void ZlibOutStream::resetDeflate() {
int ret = deflateReset(zs);
if (ret != Z_OK)
throw Exception("ZlibOutStream: deflateReset failed");
}

View File

@@ -43,6 +43,8 @@ namespace rdr {
void flush();
size_t length();
void resetDeflate();
private:
virtual void overrun(size_t needed);

View File

@@ -59,6 +59,7 @@ set(RFB_SOURCES
TightEncoder.cxx
TightJPEGEncoder.cxx
TightWEBPEncoder.cxx
TightQOIEncoder.cxx
UpdateTracker.cxx
VNCSConnectionST.cxx
VNCServerST.cxx

View File

@@ -42,9 +42,10 @@ ConnParams::ConnParams()
supportsDesktopResize(false), supportsExtendedDesktopSize(false),
supportsDesktopRename(false), supportsLastRect(false),
supportsLEDState(false), supportsQEMUKeyEvent(false),
supportsWEBP(false),
supportsWEBP(false), supportsQOI(false),
supportsSetDesktopSize(false), supportsFence(false),
supportsContinuousUpdates(false), supportsExtendedClipboard(false),
supportsUdp(false),
compressLevel(2), qualityLevel(-1), fineQualityLevel(-1),
subsampling(subsampleUndefined), name_(0), cursorPos_(0, 0), verStrPos(0),
ledState_(ledUnknown), shandler(NULL)
@@ -131,6 +132,7 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings)
supportsLastRect = false;
supportsQEMUKeyEvent = false;
supportsWEBP = false;
supportsQOI = false;
compressLevel = -1;
qualityLevel = -1;
fineQualityLevel = -1;
@@ -182,6 +184,9 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings)
case pseudoEncodingWEBP:
supportsWEBP = true;
break;
case pseudoEncodingQOI:
supportsQOI = true;
break;
case pseudoEncodingFence:
supportsFence = true;
break;
@@ -211,7 +216,7 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings)
break;
case pseudoEncodingPreferBandwidth:
if (!rfb::Server::ignoreClientSettingsKasm && canChangeSettings)
Server::preferBandwidth.setParam();
Server::preferBandwidth.setParam(true);
break;
case pseudoEncodingMaxVideoResolution:
if (!rfb::Server::ignoreClientSettingsKasm && canChangeSettings)
@@ -276,6 +281,10 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings)
if (encodings[i] > 0)
encodings_.insert(encodings[i]);
}
// QOI-specific overrides
if (supportsQOI)
useCopyRect = false;
}
void ConnParams::setLEDState(unsigned int state)

View File

@@ -111,12 +111,15 @@ namespace rfb {
bool supportsLEDState;
bool supportsQEMUKeyEvent;
bool supportsWEBP;
bool supportsQOI;
bool supportsSetDesktopSize;
bool supportsFence;
bool supportsContinuousUpdates;
bool supportsExtendedClipboard;
bool supportsUdp;
int compressLevel;
int qualityLevel;
int fineQualityLevel;

View File

@@ -42,6 +42,7 @@
#include <rfb/TightEncoder.h>
#include <rfb/TightJPEGEncoder.h>
#include <rfb/TightWEBPEncoder.h>
#include <rfb/TightQOIEncoder.h>
using namespace rfb;
@@ -70,6 +71,7 @@ enum EncoderClass {
encoderTight,
encoderTightJPEG,
encoderTightWEBP,
encoderTightQOI,
encoderZRLE,
encoderClassMax,
};
@@ -112,6 +114,8 @@ static const char *encoderClassName(EncoderClass klass)
return "Tight (JPEG)";
case encoderTightWEBP:
return "Tight (WEBP)";
case encoderTightQOI:
return "Tight (QOI)";
case encoderZRLE:
return "ZRLE";
case encoderClassMax:
@@ -172,6 +176,7 @@ EncodeManager::EncodeManager(SConnection* conn_, EncCache *encCache_) : conn(con
encoders[encoderTight] = new TightEncoder(conn);
encoders[encoderTightJPEG] = new TightJPEGEncoder(conn);
encoders[encoderTightWEBP] = new TightWEBPEncoder(conn);
encoders[encoderTightQOI] = new TightQOIEncoder(conn);
encoders[encoderZRLE] = new ZRLEEncoder(conn);
webpBenchResult = ((TightWEBPEncoder *) encoders[encoderTightWEBP])->benchmark();
@@ -356,6 +361,16 @@ void EncodeManager::doUpdate(bool allowLossy, const Region& changed_,
if (conn->cp.kasmPassed[ConnParams::KASM_MAX_VIDEO_RESOLUTION])
updateMaxVideoRes(&maxVideoX, &maxVideoY);
// The dynamic quality params may have changed
if (Server::dynamicQualityMax && Server::dynamicQualityMax <= 9 &&
Server::dynamicQualityMax > Server::dynamicQualityMin) {
dynamicQualityMin = Server::dynamicQualityMin;
dynamicQualityOff = Server::dynamicQualityMax - Server::dynamicQualityMin;
} else if (Server::dynamicQualityMin >= 0) {
dynamicQualityMin = Server::dynamicQualityMin;
dynamicQualityOff = 0;
}
prepareEncoders(allowLossy);
changed = changed_;
@@ -404,7 +419,7 @@ void EncodeManager::doUpdate(bool allowLossy, const Region& changed_,
* We start by searching for solid rects, which are then removed
* from the changed region.
*/
if (conn->cp.supportsLastRect)
if (conn->cp.supportsLastRect && !conn->cp.supportsQOI)
writeSolidRects(&changed, pb);
writeRects(changed, pb,
@@ -441,7 +456,10 @@ void EncodeManager::prepareEncoders(bool allowLossy)
bitmapRLE = indexedRLE = fullColour = encoderHextile;
break;
case encodingTight:
if (encoders[encoderTightWEBP]->isSupported() &&
if (encoders[encoderTightQOI]->isSupported() &&
(conn->cp.pf().bpp >= 16))
fullColour = encoderTightQOI;
else if (encoders[encoderTightWEBP]->isSupported() &&
(conn->cp.pf().bpp >= 16) && allowLossy)
fullColour = encoderTightWEBP;
else if (encoders[encoderTightJPEG]->isSupported() &&
@@ -462,7 +480,10 @@ void EncodeManager::prepareEncoders(bool allowLossy)
// Any encoders still unassigned?
if (fullColour == encoderRaw) {
if (encoders[encoderTightWEBP]->isSupported() &&
if (encoders[encoderTightQOI]->isSupported() &&
(conn->cp.pf().bpp >= 16))
fullColour = encoderTightQOI;
else if (encoders[encoderTightWEBP]->isSupported() &&
(conn->cp.pf().bpp >= 16) && allowLossy)
fullColour = encoderTightWEBP;
else if (encoders[encoderTightJPEG]->isSupported() &&
@@ -622,7 +643,7 @@ Encoder *EncodeManager::startRect(const Rect& rect, int type, const bool trackQu
if (isWebp)
klass = encoderTightWEBP;
beforeLength = conn->getOutStream()->length();
beforeLength = conn->getOutStream(conn->cp.supportsUdp)->length();
stats[klass][activeType].rects++;
stats[klass][activeType].pixels += rect.area();
@@ -655,7 +676,7 @@ void EncodeManager::endRect(const uint8_t isWebp)
conn->writer()->endRect();
length = conn->getOutStream()->length() - beforeLength;
length = conn->getOutStream(conn->cp.supportsUdp)->length() - beforeLength;
klass = activeEncoders[activeType];
if (isWebp)
@@ -669,7 +690,7 @@ void EncodeManager::writeCopyPassRects(const std::vector<CopyPassRect>& copypass
Region lossyCopy;
beforeLength = conn->getOutStream()->length();
beforeLength = conn->getOutStream(conn->cp.supportsUdp)->length();
for (rect = copypassed.begin(); rect != copypassed.end(); ++rect) {
int equiv;
@@ -689,7 +710,7 @@ void EncodeManager::writeCopyPassRects(const std::vector<CopyPassRect>& copypass
lossyRegion.assign_union(lossyCopy);
}
copyStats.bytes += conn->getOutStream()->length() - beforeLength;
copyStats.bytes += conn->getOutStream(conn->cp.supportsUdp)->length() - beforeLength;
}
void EncodeManager::writeCopyRects(const Region& copied, const Point& delta)
@@ -699,7 +720,7 @@ void EncodeManager::writeCopyRects(const Region& copied, const Point& delta)
Region lossyCopy;
beforeLength = conn->getOutStream()->length();
beforeLength = conn->getOutStream(conn->cp.supportsUdp)->length();
copied.get_rects(&rects, delta.x <= 0, delta.y <= 0);
for (rect = rects.begin(); rect != rects.end(); ++rect) {
@@ -714,7 +735,7 @@ void EncodeManager::writeCopyRects(const Region& copied, const Point& delta)
rect->tl.y - delta.y);
}
copyStats.bytes += conn->getOutStream()->length() - beforeLength;
copyStats.bytes += conn->getOutStream(conn->cp.supportsUdp)->length() - beforeLength;
lossyCopy = lossyRegion;
lossyCopy.translate(delta);
@@ -1211,7 +1232,7 @@ void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb,
if (isWebp[i])
webpstats.ms += ms[i];
else
jpegstats.ms += ms[i];
jpegstats.ms += ms[i]; // Also covers QOI for now
}
}
@@ -1237,7 +1258,8 @@ void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb,
activeEncoders[encoderFullColour] = encoderTightJPEG;
for (i = 0; i < subrects.size(); ++i) {
if (encCache->enabled && compresseds[i].size() && !fromCache[i]) {
if (encCache->enabled && compresseds[i].size() && !fromCache[i] &&
!encoders[encoderTightQOI]->isSupported()) {
void *tmp = malloc(compresseds[i].size());
memcpy(tmp, &compresseds[i][0], compresseds[i].size());
encCache->add(isWebp[i] ? encoderTightWEBP : encoderTightJPEG,
@@ -1304,7 +1326,7 @@ uint8_t EncodeManager::getEncoderType(const Rect& rect, const PixelBuffer *pb,
type = encoderIndexed;
}
if (scaledpb)
if (scaledpb || conn->cp.supportsQOI)
type = encoderFullColour;
*isWebp = 0;
@@ -1339,6 +1361,21 @@ uint8_t EncodeManager::getEncoderType(const Rect& rect, const PixelBuffer *pb,
compressed,
videoDetected);
*isWebp = 1;
} else if (activeEncoders[encoderFullColour] == encoderTightQOI) {
if (scaledpb) {
delete ppb;
ppb = preparePixelBuffer(scaledrect, scaledpb,
encoders[encoderTightQOI]->flags & EncoderUseNativePF ?
false : true);
} else if (encoders[encoderTightQOI]->flags & EncoderUseNativePF) {
delete ppb;
ppb = preparePixelBuffer(rect, pb, false);
}
((TightQOIEncoder *) encoders[encoderTightQOI])->compressOnly(ppb,
scaledQuality(rect),
compressed,
videoDetected);
} else if (activeEncoders[encoderFullColour] == encoderTightJPEG || webpTookTooLong) {
if (scaledpb) {
delete ppb;
@@ -1379,6 +1416,10 @@ void EncodeManager::writeSubRect(const Rect& rect, const PixelBuffer *pb,
((TightWEBPEncoder *) encoder)->writeOnly(compressed);
webpstats.area += rect.area();
webpstats.rects++;
} else if (encoders[encoderTightQOI]->isSupported()) {
((TightQOIEncoder *) encoder)->writeOnly(compressed);
jpegstats.area += rect.area(); // Also QOI for now
jpegstats.rects++;
} else {
((TightJPEGEncoder *) encoder)->writeOnly(compressed);
jpegstats.area += rect.area();
@@ -1716,3 +1757,7 @@ unsigned EncodeManager::scaledQuality(const Rect& rect) const {
return dynamic;
}
void EncodeManager::resetZlib() {
((TightEncoder *) encoders[encoderTight])->resetZlib();
}

View File

@@ -79,6 +79,8 @@ namespace rfb {
return scalingTime;
};
void resetZlib();
struct codecstats_t {
uint32_t ms;
uint32_t area;

View File

@@ -62,6 +62,8 @@ SConnection::SConnection()
defaultMinorVersion = 3;
cp.setVersion(defaultMajorVersion, defaultMinorVersion);
udps = new network::UdpStream;
}
SConnection::~SConnection()
@@ -72,6 +74,7 @@ SConnection::~SConnection()
delete writer_;
writer_ = 0;
strFree(clientClipboard);
delete udps;
}
void SConnection::setStreams(rdr::InStream* is_, rdr::OutStream* os_)
@@ -348,7 +351,7 @@ void SConnection::approveConnection(bool accept, const char* reason)
if (accept) {
state_ = RFBSTATE_INITIALISATION;
reader_ = new SMsgReader(this, is);
writer_ = new SMsgWriter(&cp, os);
writer_ = new SMsgWriter(&cp, os, udps);
authSuccess();
} else {
state_ = RFBSTATE_INVALID;

Some files were not shown because too many files have changed in this diff Show More