Compare commits
64 Commits
release/0.
...
v1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53a9d11b01 | ||
|
|
ee0fccdc6a | ||
|
|
dcadd1e6d6 | ||
|
|
848d45b411 | ||
|
|
2f7f090b36 | ||
|
|
a43ea72dc1 | ||
|
|
df70b38db6 | ||
|
|
e68c9aef30 | ||
|
|
39f6ee7691 | ||
|
|
6089c22fb7 | ||
|
|
f1630b9ce7 | ||
|
|
fe978f0382 | ||
|
|
435b987c95 | ||
|
|
bd5d8b46e4 | ||
|
|
6793a20eca | ||
|
|
6c4d53f9f3 | ||
|
|
9fe3ac1ea5 | ||
|
|
bd8b6d522a | ||
|
|
d9b5b5db6a | ||
|
|
7e4b5daf52 | ||
|
|
17947d5f44 | ||
|
|
6b9f28760c | ||
|
|
2e0d35b48f | ||
|
|
87bdac9fea | ||
|
|
ba8e98a9f6 | ||
|
|
e0e4dbe173 | ||
|
|
9622048753 | ||
|
|
91f619db5c | ||
|
|
b93ead6a24 | ||
|
|
3b1a72738e | ||
|
|
0cf6654355 | ||
|
|
99c1e10789 | ||
|
|
17cd5e597a | ||
|
|
0ef8a51945 | ||
|
|
54d2d12006 | ||
|
|
34ca7595e8 | ||
|
|
bef16c5b34 | ||
|
|
582740b3d8 | ||
|
|
6c0eff0828 | ||
|
|
365f6bc33e | ||
|
|
7a51039864 | ||
|
|
9a49e96a7f | ||
|
|
250ca19199 | ||
|
|
4a4c53d292 | ||
|
|
36a1ffc5e4 | ||
|
|
0ae83c02b0 | ||
|
|
bc926f9a55 | ||
|
|
d9cf46f83e | ||
|
|
5e2a8b45da | ||
|
|
bb1bd5ff8f | ||
|
|
5ecd18bf1b | ||
|
|
f8013340ab | ||
|
|
6451cc3220 | ||
|
|
6e52b24992 | ||
|
|
6fd2ad874f | ||
|
|
3b40a92548 | ||
|
|
a4ac7bee16 | ||
|
|
ba902f8194 | ||
|
|
1f5125fb24 | ||
|
|
dc60c73a81 | ||
|
|
1c54f4f921 | ||
|
|
60de5a9791 | ||
|
|
480add4fe2 | ||
|
|
8b71ea3cd9 |
@@ -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
1
.gitignore
vendored
@@ -14,6 +14,7 @@ config.h
|
||||
|
||||
builder/build/
|
||||
builder/www/
|
||||
spec/tmp
|
||||
|
||||
# Deb building artefacts
|
||||
debian/.debhelper/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
15
Pipfile
Normal 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
136
Pipfile.lock
generated
Normal 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
306
README.md
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
1
builder/common.sh
Normal file
@@ -0,0 +1 @@
|
||||
VNC_PORT=8443
|
||||
20
builder/devenv-vncserver
Executable file
20
builder/devenv-vncserver
Executable 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
|
||||
@@ -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"
|
||||
|
||||
@@ -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 ###
|
||||
|
||||
|
||||
@@ -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 ###
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 ###
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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" ]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 ###
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 && \
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 ###
|
||||
|
||||
|
||||
@@ -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 ###
|
||||
|
||||
|
||||
@@ -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 ####
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
30
builder/dockerfile.ubuntu_focal.specs.test
Normal file
30
builder/dockerfile.ubuntu_focal.specs.test
Normal 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"]
|
||||
@@ -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 ####
|
||||
|
||||
|
||||
67
builder/dockerfile.ubuntu_focal.vncserver_devenv.test
Normal file
67
builder/dockerfile.ubuntu_focal.vncserver_devenv.test
Normal 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"]
|
||||
@@ -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 ####
|
||||
|
||||
|
||||
@@ -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
18
builder/run-specs-inside-docker
Executable 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
|
||||
64
builder/scripts/install_kasmvncserver_package
Executable file
64
builder/scripts/install_kasmvncserver_package
Executable 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
|
||||
50
builder/startup/deb/cli-processing.sh
Normal file
50
builder/startup/deb/cli-processing.sh
Normal 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
|
||||
}
|
||||
@@ -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
266
builder/startup/deb/select-de.sh
Executable 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
|
||||
@@ -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=$!
|
||||
|
||||
|
||||
22
builder/startup/vnc_startup_barebones.sh
Executable file
22
builder/startup/vnc_startup_barebones.sh
Executable 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
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
23
builder/test-vncserver
Executable 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
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
166
common/network/Udp.cxx
Normal 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
57
common/network/Udp.h
Normal 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
190
common/network/iceip.cxx
Normal 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
23
common/network/iceip.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
38
common/network/webudp/CHANGELOG.md
Normal file
38
common/network/webudp/CHANGELOG.md
Normal 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.
|
||||
124
common/network/webudp/CRC32.cpp
Normal file
124
common/network/webudp/CRC32.cpp
Normal 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;
|
||||
}
|
||||
7
common/network/webudp/CRC32.h
Normal file
7
common/network/webudp/CRC32.h
Normal 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);
|
||||
21
common/network/webudp/LICENSE
Normal file
21
common/network/webudp/LICENSE
Normal 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.
|
||||
20
common/network/webudp/README.md
Normal file
20
common/network/webudp/README.md
Normal 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.
|
||||
771
common/network/webudp/Wu.cpp
Normal file
771
common/network/webudp/Wu.cpp
Normal 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;
|
||||
}
|
||||
76
common/network/webudp/Wu.h
Normal file
76
common/network/webudp/Wu.h
Normal 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
|
||||
26
common/network/webudp/WuArena.cpp
Normal file
26
common/network/webudp/WuArena.cpp
Normal 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); }
|
||||
14
common/network/webudp/WuArena.h
Normal file
14
common/network/webudp/WuArena.h
Normal 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);
|
||||
48
common/network/webudp/WuBufferOp.h
Normal file
48
common/network/webudp/WuBufferOp.h
Normal 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;
|
||||
}
|
||||
34
common/network/webudp/WuClock.h
Normal file
34
common/network/webudp/WuClock.h
Normal 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());
|
||||
}
|
||||
65
common/network/webudp/WuCrypto.cpp
Normal file
65
common/network/webudp/WuCrypto.cpp
Normal 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);
|
||||
}
|
||||
23
common/network/webudp/WuCrypto.h
Normal file
23
common/network/webudp/WuCrypto.h
Normal 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);
|
||||
36
common/network/webudp/WuHost.h
Normal file
36
common/network/webudp/WuHost.h
Normal 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
|
||||
268
common/network/webudp/WuHostEpoll.cpp
Normal file
268
common/network/webudp/WuHostEpoll.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
11
common/network/webudp/WuHostNull.cpp
Normal file
11
common/network/webudp/WuHostNull.cpp
Normal 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) {}
|
||||
9
common/network/webudp/WuHttp.h
Normal file
9
common/network/webudp/WuHttp.h
Normal 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;
|
||||
15
common/network/webudp/WuMath.h
Normal file
15
common/network/webudp/WuMath.h
Normal 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;
|
||||
}
|
||||
58
common/network/webudp/WuNetwork.cpp
Normal file
58
common/network/webudp/WuNetwork.cpp
Normal 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;
|
||||
}
|
||||
9
common/network/webudp/WuNetwork.h
Normal file
9
common/network/webudp/WuNetwork.h
Normal 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);
|
||||
60
common/network/webudp/WuPool.cpp
Normal file
60
common/network/webudp/WuPool.cpp
Normal 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;
|
||||
}
|
||||
10
common/network/webudp/WuPool.h
Normal file
10
common/network/webudp/WuPool.h
Normal 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);
|
||||
58
common/network/webudp/WuQueue.cpp
Normal file
58
common/network/webudp/WuQueue.cpp
Normal 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;
|
||||
}
|
||||
16
common/network/webudp/WuQueue.h
Normal file
16
common/network/webudp/WuQueue.h
Normal 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);
|
||||
51
common/network/webudp/WuRng.cpp
Normal file
51
common/network/webudp/WuRng.cpp
Normal 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(); }
|
||||
14
common/network/webudp/WuRng.h
Normal file
14
common/network/webudp/WuRng.h
Normal 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);
|
||||
169
common/network/webudp/WuSctp.cpp
Normal file
169
common/network/webudp/WuSctp.cpp
Normal 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; }
|
||||
100
common/network/webudp/WuSctp.h
Normal file
100
common/network/webudp/WuSctp.h
Normal 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);
|
||||
152
common/network/webudp/WuSdp.cpp
Normal file
152
common/network/webudp/WuSdp.cpp
Normal 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;
|
||||
}
|
||||
24
common/network/webudp/WuSdp.h
Normal file
24
common/network/webudp/WuSdp.h
Normal 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);
|
||||
19
common/network/webudp/WuString.cpp
Normal file
19
common/network/webudp/WuString.cpp
Normal 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;
|
||||
}
|
||||
6
common/network/webudp/WuString.h
Normal file
6
common/network/webudp/WuString.h
Normal 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);
|
||||
134
common/network/webudp/WuStun.cpp
Normal file
134
common/network/webudp/WuStun.cpp
Normal 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;
|
||||
}
|
||||
57
common/network/webudp/WuStun.h
Normal file
57
common/network/webudp/WuStun.h
Normal 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);
|
||||
19
common/network/webudp/package.json
Normal file
19
common/network/webudp/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace rdr {
|
||||
void flush();
|
||||
size_t length();
|
||||
|
||||
void resetDeflate();
|
||||
|
||||
private:
|
||||
|
||||
virtual void overrun(size_t needed);
|
||||
|
||||
@@ -59,6 +59,7 @@ set(RFB_SOURCES
|
||||
TightEncoder.cxx
|
||||
TightJPEGEncoder.cxx
|
||||
TightWEBPEncoder.cxx
|
||||
TightQOIEncoder.cxx
|
||||
UpdateTracker.cxx
|
||||
VNCSConnectionST.cxx
|
||||
VNCServerST.cxx
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -79,6 +79,8 @@ namespace rfb {
|
||||
return scalingTime;
|
||||
};
|
||||
|
||||
void resetZlib();
|
||||
|
||||
struct codecstats_t {
|
||||
uint32_t ms;
|
||||
uint32_t area;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user