feat: Add peer-to-peer tunneling architecture with transport selection

- Add --tunnel, --tunnel-control, --service options to wsssh and wsscp
- Implement transport definitions with is_relay property and weight-based selection
- Add WebSocket transport with is_relay=true as primary implementation
- Update server-side tunnel handling with new transport attributes
- Enhance configuration system with tunneling options
- Fix critical transport list expansion for 'any' option
- Update all man pages with comprehensive tunneling documentation
- Add new config files wsssh.conf.example and wsscp.conf.example
- Update CHANGELOG.md, README.md, and TODO.md with new features
- Maintain backward compatibility with existing functionality
parent ecd74f26
...@@ -5,14 +5,73 @@ All notable changes to this project will be documented in this file. ...@@ -5,14 +5,73 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.4.9] - 2025-09-17 ## [1.5.0] - 2025-09-17
### Added
- **Peer-to-Peer Tunneling Architecture**: Complete implementation of advanced tunneling system with transport selection
- Added `--tunnel` option to wsssh and wsscp for data channel transport specification
- Added `--tunnel-control` option to wsssh, wsscp, and wssshc for control channel transport specification
- Added `--service` option to wsssh and wsscp for service type specification (default: "ssh")
- Implemented transport definitions with `is_relay` property and weight-based selection
- Added WebSocket transport with `is_relay=true` as the primary transport implementation
- Created comprehensive transport selection logic with weight-based prioritization
- Implemented "any" transport option that expands to all available transports
- Added transport list expansion function for proper "any" option handling
### Enhanced
- **Configuration System**: Extended configuration file support for new tunneling options
- Added `tunnel`, `tunnel-control`, and `service` options to wsssh.conf.example
- Added `tunnel` and `tunnel-control` options to wsscp.conf.example
- Enhanced INI-style config parsing with new transport configuration keys
- Maintained backward compatibility with existing configuration files
- **Server-Side Architecture**: Comprehensive updates to wsssd for advanced tunneling support
- Modified `Tunnel` class in `wsssd/tunnel.py` with new transport attributes
- Updated tunnel status object to include `tunnel`, `tunnel_control`, and `service` properties
- Enhanced tunnel request message handling in `wsssd/websocket.py`
- Added transport selection and data channel logic for peer-to-peer connections
- Implemented proper IP address detection and tunnel status tracking
- **Client-Side Implementation**: Complete C implementation updates for tunneling features
- Updated `wsssh.c` with new command-line options and config file support
- Updated `wsscp.c` with new command-line options and config file support
- Updated `wssshc.c` with transport options and wssshd_private_ip parameter
- Enhanced `wssshlib.c` with transport list expansion functionality
- Modified tunnel request messages to include transport specifications
- Added proper transport negotiation and selection logic
### Technical Details
- **Transport System**: Implemented extensible transport architecture with weight-based selection
- Transport types include `websocket` with `is_relay=true` property
- Weight-based prioritization (lower numbers = higher priority)
- Automatic fallback to next available transport on connection failure
- Support for comma-separated transport lists and "any" expansion
- **Message Format Updates**: Enhanced JSON message formats for tunneling
- Updated tunnel_request messages to include transport specifications
- Added transport negotiation in registration messages
- Maintained backward compatibility with existing message formats
- Proper error handling for transport-related failures
- **Configuration Management**: Comprehensive config file support
- INI-style configuration files with hierarchical precedence
- Command-line options override config file values
- Optional parameters become required only when not provided in config
- Enhanced error messages for missing configuration
### Fixed ### Fixed
- **PyInstaller Build Issues**: Critical fixes for frozen application deployment - **Transport List Expansion**: Critical fix for "any" transport option
- Fixed missing `websockets` import in `wsssd/server.py` causing "name 'websockets' is not defined" error - Implemented `expand_transport_list()` function in `wssshlib.c`
- Resolved asyncio runtime warnings by properly awaiting cancelled tasks in shutdown handling - Proper expansion of "any" to available transport lists based on channel type
- Fixed global variable sharing issue in frozen application by passing server password as parameter to websocket handler - Fixed transport negotiation between clients and server
- Improved WebSocket handler signature compatibility with `functools.partial` for proper function binding - Resolved issues with transport selection and fallback logic
### Documentation
- **Man Pages**: Updated all manual pages with new tunneling options
- Enhanced `wsssh.1` with `--tunnel`, `--tunnel-control`, and `--service` options
- Enhanced `wsscp.1` with `--tunnel`, `--tunnel-control`, and `--service` options
- Enhanced `wssshc.1` with `--tunnel` and `--tunnel-control` options
- Added comprehensive option descriptions and usage examples
### Performance ### Performance
- **CPU Usage Optimization**: Significantly reduced CPU usage during file transfers - **CPU Usage Optimization**: Significantly reduced CPU usage during file transfers
...@@ -30,6 +89,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ...@@ -30,6 +89,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Global Variable Isolation**: Resolved PyInstaller global variable sharing limitations by using parameter passing - **Global Variable Isolation**: Resolved PyInstaller global variable sharing limitations by using parameter passing
- **Function Signature Compatibility**: Updated websocket handler to use keyword-only parameters for `functools.partial` binding - **Function Signature Compatibility**: Updated websocket handler to use keyword-only parameters for `functools.partial` binding
- **I/O Efficiency**: Optimized select() polling intervals to balance responsiveness with CPU efficiency - **I/O Efficiency**: Optimized select() polling intervals to balance responsiveness with CPU efficiency
- **Transport Architecture**: Extensible design ready for future transport implementations
- **Configuration Hierarchy**: Proper precedence handling between command-line and config file options
## [1.4.8] - 2025-09-17 ## [1.4.8] - 2025-09-17
......
...@@ -8,6 +8,8 @@ A modern SSH tunneling system that uses WebSocket connections to securely route ...@@ -8,6 +8,8 @@ A modern SSH tunneling system that uses WebSocket connections to securely route
- **C Implementation**: Lightweight and efficient C versions available - **C Implementation**: Lightweight and efficient C versions available
- **WebSocket-based Tunneling**: Secure SSH/SCP access through WebSocket connections - **WebSocket-based Tunneling**: Secure SSH/SCP access through WebSocket connections
- **Peer-to-Peer Architecture**: Advanced tunneling with transport selection and negotiation
- **Transport Selection**: Choose specific transport types for data and control channels
- **Client Registration**: Register client machines with the WebSocket daemon - **Client Registration**: Register client machines with the WebSocket daemon
- **Password Authentication**: Secure client registration with configurable passwords - **Password Authentication**: Secure client registration with configurable passwords
- **Web Management Interface**: Professional admin panel with user management and HTML5 terminal - **Web Management Interface**: Professional admin panel with user management and HTML5 terminal
...@@ -274,7 +276,7 @@ When `--web-host` and `--web-port` are specified, a web management interface is ...@@ -274,7 +276,7 @@ When `--web-host` and `--web-port` are specified, a web management interface is
### wssshc (WebSocket SSH Client) ### wssshc (WebSocket SSH Client)
```bash ```bash
./wssshc --server-ip <ip> --port 9898 --id client1 --password mysecret [--interval 30] [--debug] ./wssshc --server-ip <ip> --port 9898 --id client1 --password mysecret [--tunnel websocket] [--tunnel-control websocket] [--interval 30] [--debug]
``` ```
**Options:** **Options:**
...@@ -282,6 +284,8 @@ When `--web-host` and `--web-port` are specified, a web management interface is ...@@ -282,6 +284,8 @@ When `--web-host` and `--web-port` are specified, a web management interface is
- `--port`: wssshd server port (default: 9898) - `--port`: wssshd server port (default: 9898)
- `--id`: Unique client identifier - `--id`: Unique client identifier
- `--password`: Registration password - `--password`: Registration password
- `--tunnel`: Transport types for data channel (comma-separated or 'any', default: any)
- `--tunnel-control`: Transport types for control channel (comma-separated or 'any', default: any)
- `--interval`: Reconnection interval in seconds (default: 30) - `--interval`: Reconnection interval in seconds (default: 30)
- `--debug`: Enable debug output - `--debug`: Enable debug output
...@@ -307,6 +311,9 @@ Command line options override configuration file values. Required parameters are ...@@ -307,6 +311,9 @@ Command line options override configuration file values. Required parameters are
**Options:** **Options:**
- `--local-port`: Local tunnel port (default: auto) - `--local-port`: Local tunnel port (default: auto)
- `--tunnel`: Transport types for data channel (comma-separated or 'any', default: any)
- `--tunnel-control`: Transport types for control channel (comma-separated or 'any', default: any)
- `--service`: Service type (default: ssh)
- `--debug`: Enable debug output - `--debug`: Enable debug output
- `--dev-tunnel`: Development mode: setup tunnel but don't launch SSH - `--dev-tunnel`: Development mode: setup tunnel but don't launch SSH
- `--help`: Display help message and exit - `--help`: Display help message and exit
...@@ -334,6 +341,9 @@ Command line options override configuration file values. Required parameters are ...@@ -334,6 +341,9 @@ Command line options override configuration file values. Required parameters are
**Options:** **Options:**
- `--local-port`: Local tunnel port (default: auto) - `--local-port`: Local tunnel port (default: auto)
- `--tunnel`: Transport types for data channel (comma-separated or 'any', default: any)
- `--tunnel-control`: Transport types for control channel (comma-separated or 'any', default: any)
- `--service`: Service type (default: ssh)
- `--debug`: Enable debug output - `--debug`: Enable debug output
- `--dev-tunnel`: Development mode: setup tunnel but don't launch SCP - `--dev-tunnel`: Development mode: setup tunnel but don't launch SCP
- `--help`: Display help message and exit - `--help`: Display help message and exit
...@@ -363,11 +373,17 @@ WebSocket SSH supports an optional INI-formatted configuration file at `~/.confi ...@@ -363,11 +373,17 @@ WebSocket SSH supports an optional INI-formatted configuration file at `~/.confi
[default] [default]
port=8080 port=8080
domain=example.com domain=example.com
tunnel=websocket
tunnel-control=websocket
service=ssh
``` ```
**Configuration Options:** **Configuration Options:**
- `port`: Default WebSocket server port (used when not specified via `-p`/`-P` options) - `port`: Default WebSocket server port (used when not specified via `-p`/`-P` options)
- `domain`: Default domain suffix (used when hostname doesn't include domain) - `domain`: Default domain suffix (used when hostname doesn't include domain)
- `tunnel`: Default transport types for data channel (comma-separated or 'any')
- `tunnel-control`: Default transport types for control channel (comma-separated or 'any')
- `service`: Default service type (default: ssh)
**Precedence Rules:** **Precedence Rules:**
- **Port**: Command line `-p`/`-P` options override config file, config file overrides default (22) - **Port**: Command line `-p`/`-P` options override config file, config file overrides default (22)
...@@ -610,7 +626,24 @@ Your support helps us continue developing and maintaining this open-source proje ...@@ -610,7 +626,24 @@ Your support helps us continue developing and maintaining this open-source proje
## Changelog ## Changelog
### Version 1.4.8 (Latest) ### Version 1.5.0 (Latest)
**Peer-to-Peer Tunneling Architecture:**
- Added `--tunnel` option to wsssh and wsscp for data channel transport specification
- Added `--tunnel-control` option to wsssh, wsscp, and wssshc for control channel transport specification
- Added `--service` option to wsssh and wsscp for service type specification (default: "ssh")
- Implemented transport definitions with `is_relay` property and weight-based selection
- Added WebSocket transport with `is_relay=true` as the primary transport implementation
- Created comprehensive transport selection logic with weight-based prioritization
- Implemented "any" transport option that expands to all available transports
- Added transport list expansion function for proper "any" option handling
- Enhanced configuration system with new tunneling options in config files
- Updated server-side architecture with new tunnel attributes and transport handling
- Modified client-side C implementations with new command-line options
- Updated all man pages with comprehensive tunneling option documentation
- Fixed critical transport list expansion for "any" option functionality
### Version 1.4.8
**Critical SSL Connection Stability:** **Critical SSL Connection Stability:**
- Fixed WebSocket frame sending failures that caused connection drops - Fixed WebSocket frame sending failures that caused connection drops
......
# WebSocket SSH - Future Enhancements Roadmap # WebSocket SSH - Future Enhancements Roadmap
## Recently Completed (v1.5.0)
- [x] **Peer-to-Peer Tunneling Architecture**: Complete implementation of advanced tunneling system
- Added `--tunnel` option to wsssh and wsscp for data channel transport specification
- Added `--tunnel-control` option to wsssh, wsscp, and wssshc for control channel transport specification
- Added `--service` option to wsssh and wsscp for service type specification (default: "ssh")
- Implemented transport definitions with `is_relay` property and weight-based selection
- Added WebSocket transport with `is_relay=true` as the primary transport implementation
- Created comprehensive transport selection logic with weight-based prioritization
- Implemented "any" transport option that expands to all available transports
- Added transport list expansion function for proper "any" option handling
- Enhanced configuration system with new tunneling options in config files
- Updated server-side architecture with new tunnel attributes and transport handling
- Modified client-side C implementations with new command-line options
- Updated all man pages with comprehensive tunneling option documentation
- Fixed critical transport list expansion for "any" option functionality
## Recently Completed (v1.4.9) ## Recently Completed (v1.4.9)
- [x] **PyInstaller Build Issues**: Critical fixes for frozen application deployment - [x] **PyInstaller Build Issues**: Critical fixes for frozen application deployment
- Fixed missing `websockets` import in `wsssd/server.py` causing "name 'websockets' is not defined" error - Fixed missing `websockets` import in `wsssd/server.py` causing "name 'websockets' is not defined" error
......
First, create a new git branch from this one, and name it p2p, change to the p2p branch.
Add an option to wsssh and wsscp: --tunnel, in the config file is tunnel as well. The tunnel option accept a value or a series of values as value1,value2 that is the transport type for the data channel, or any to intend tunnel data channel autoselection.
Add a second option in wsssh and wssscp, --tunnel-control, in the config file tunnel-control. The tunnel control like the tunnel option accept a value or a serie of values as value1,value2 etc, values are the transport type for the control channel, or "any" to intend tunnel control channel autoselection.
Add a third option in wsssh and wssscp, --service, service in the control file as well, default ssh. This will be just any string up to a lemght of 255 chars long. this will be just passed to the server how described later
A transport type is the connection we use to encapsulate a tunnel data channel and/or a tunnel control channel, like websocket.
A transport have a property, is_relay, if it is true the transport CAN encapsulate the control channel and/or the data channel, if it's false, it can encapsulate only the data channel.
A transport have another property, weight, max weight is 0, the higher the numnber the lower the weight.
A data channel is a channel where the tunnel_data message goes trough, while the control channel is the channel for all the other messages.
the option --tunnel will accept all the transport and the value "any", the option --tunnel-control will accept only the transports with property is_relay set as true, or the value "any".
I this moment we implement only one transport, websocket, it is a transport with is_relay set as true, in the --tunnel and --tunnel-control is named websocket.
Other tunnels will reutilize as much as possible the exixting framing code, and essentially they will all use the same json message for the tunnel data, while all the other messages will remain on the control channel passing on the websocket on wssshd.
When wsssh and wsscp request to open a tunnel, it will try to connect to the wssshd server first using the --tunnel-control with the lower weight, and if connection fails 3 time try to escalate to the second lower weight and so on up to the higher weight, selecting only the tunnel-control transports indicate in the value list or selecting them all if specified any. The default behaviour is any
After connected successfully, it will send a tunnel request message, indicating to wssshd in the json request also the tunnel and tunnel-control, in both cases if any is selected send the list of all transports for tunnel and tunnel-control, , the --service option, anda property tool_private_ip with value the autodetected local IP address.
in wssshd, in the tunnel status object, remove tunneltype attribute, add tunnel and tunnel-control reflecting the --tunnel and --tunnel-control options of wsssh and wsscp, rename the attribute src_private_ip to tool_private_ip, src_public_ip with tool_public_ip, src_public_port with tool_public_port, src_private_port with tool_public_port , and all the dst_*_ip and dst_*_port with wssshc_
Correct the tunnel messages to accept the properties passed by wsssh/wsscp at tunnel requests, and use to set the tunnel status object attributes.
When the request arrivem set the attributes wssshc_public_* and tool_public_* using the ip and port from which wssshc and wsssh/wsscp are connected to wssshd and they sent the tunnel request.
Then wssshd send to wsssh/wssscp the reqyest to try to open the connection for the data channel using the transport with lower weight and wait for the wsssh/wssscp to reply. if the connection is established and tunnel is ready, set the status of the tunnel to "ACTIVE", if the data connection failed pass to the next lower weight if any, continue until you don't have other option, then ask to maintain the data channel on the same transport where the control channel, the one you are messagging on, is.
wsssh/wsscp after sending the tunnel request, wait for the reply with the indication of which data channel to open. If the reply doesn't arrive after a timeout of 5 seconds, or if the reply is the same channel as we are communicating the control messages, it will just reuse the same channel also as data channel.
also wssshc will implement --tunnel and --tunnel-control, but it will send them in the registration requests as well as the wssshd_private_ip.
wssshc will select the control channel at connection exactly as wsssh and wsscp do.
wssshd should be ready to handle this as well, and use those property to set the tunnel status objects.
for the moment there aren't any other transport but websocket, we are preparing all the structures and code needed to add new transports later
wssshc should handle correctly also the full message to open a tunnel,
First, create a new git branch from this one, and name it p2p, change to the p2p branch.
Add an option to wsssh and wsscp: --tunnel, in the config file is tunnel as well. The tunnel option accept a value or a series of values as value1,value2 that is the transport type for the data channel, or any to intend tunnel data channel autoselection.
Add a second option in wsssh and wssscp, --tunnel-control, in the config file tunnel-control. The tunnel control like the tunnel option accept a value or a serie of values as value1,value2 etc, values are the transport type for the control channel, or "any" to intend tunnel control channel autoselection.
Add a third option in wsssh and wssscp, --service, service in the control file as well, default ssh. This will be just any string up to a lemght of 255 chars long. this will be just passed to the server how described later
A transport type is the connection we use to encapsulate a tunnel data channel and/or a tunnel control channel, like websocket.
A transport have a property, is_relay, if it is true the transport CAN encapsulate the control channel and/or the data channel, if it's false, it can encapsulate only the data channel.
A transport have another property, weight, max weight is 0, the higher the numnber the lower the weight.
A data channel is a channel where the tunnel_data message goes trough, while the control channel is the channel for all the other messages.
the option --tunnel will accept all the transport and the value "any", the option --tunnel-control will accept only the transports with property is_relay set as true, or the value "any".
I this moment we implement only one transport, websocket, it is a transport with is_relay set as true, in the --tunnel and --tunnel-control is named websocket.
Other tunnels will reutilize as much as possible the exixting framing code, and essentially they will all use the same json message for the tunnel data, while all the other messages will remain on the control channel passing on the websocket on wssshd.
When wsssh and wsscp request to open a tunnel, it will try to connect to the wssshd server first using the --tunnel-control with the lower weight, and if connection fails 3 time try to escalate to the second lower weight and so on up to the higher weight, selecting only the tunnel-control transports indicate in the value list or selecting them all if specified any. The default behaviour is any
After connected successfully, it will send a tunnel request message, indicating to wssshd in the json request also the tunnel and tunnel-control, in both cases if any is selected send the list of all transports for tunnel and tunnel-control, , the --service option, anda property tool_private_ip with value the autodetected local IP address.
in wssshd, in the tunnel status object, remove tunneltype attribute, add tunnel and tunnel-control reflecting the --tunnel and --tunnel-control options of wsssh and wsscp, rename the attribute src_private_ip to tool_private_ip, src_public_ip with tool_public_ip, src_public_port with tool_public_port, src_private_port with tool_public_port , and all the dst_*_ip and dst_*_port with wssshc_
Correct the tunnel messages to accept the properties passed by wsssh/wsscp at tunnel requests, and use to set the tunnel status object attributes.
When the request arrivem set the attributes wssshc_public_* and tool_public_* using the ip and port from which wssshc and wsssh/wsscp are connected to wssshd and they sent the tunnel request.
Then wssshd send to wsssh/wssscp the reqyest to try to open the connection for the data channel using the transport with lower weight and wait for the wsssh/wssscp to reply. if the connection is established and tunnel is ready, set the status of the tunnel to "ACTIVE", if the data connection failed pass to the next lower weight if any, continue until you don't have other option, then ask to maintain the data channel on the same transport where the control channel, the one you are messagging on, is.
wsssh/wsscp after sending the tunnel request, wait for the reply with the indication of which data channel to open. If the reply doesn't arrive after a timeout of 5 seconds, or if the reply is the same channel as we are communicating the control messages, it will just reuse the same channel also as data channel.
also wssshc will implement --tunnel and --tunnel-control, but it will send them in the registration requests as well as the wssshd_private_ip.
wssshc will select the control channel at connection exactly as wsssh and wsscp do.
wssshd should be ready to handle this as well, and use those property to set the tunnel status objects.
for the moment there aren't any other transport but websocket, we are preparing all the structures and code needed to add new transports later
wssshc should handle correctly also the full message to open a tunnel,
# WebSocket SCP (wsscp) Configuration Example
#
# This is an example configuration file for wsscp.
# Copy this file to ~/.config/wsssh/wsscp.conf and modify the settings as needed.
#
# Configuration options:
# domain = wssshd.example.com
# tunnel = websocket
# tunnel-control = websocket
# service = scp
[wsscp]
# WebSocket SSH Daemon domain (used for hostname parsing)
domain = example.com
# Transport types for data channel (comma-separated or 'any')
# Available transports: websocket
tunnel = any
# Transport types for control channel (comma-separated or 'any')
# Only transports with is_relay=true can be used for control
tunnel-control = any
# Service type (default: ssh, but can be scp for wsscp)
service = scp
\ No newline at end of file
...@@ -31,17 +31,21 @@ class Tunnel: ...@@ -31,17 +31,21 @@ class Tunnel:
# Protocol and type # Protocol and type
self.protocol = "ssh" # default self.protocol = "ssh" # default
self.tunnel_type = "any" # default self.tunnel = "any" # default
self.tunnel_control = "any" # default
self.service = "ssh" # default
# Destination (wssshc) information # Destination (wssshc) information
self.dst_public_ip = None self.wssshc_public_ip = None
self.dst_public_port = None self.wssshc_public_port = None
self.dst_private_ip = None self.wssshc_private_ip = None
self.dst_private_port = None self.wssshc_private_port = None
# Source (wsssh/wsscp) information # Source (wsssh/wsscp) information
self.src_public_ip = None self.tool_public_ip = None
self.src_private_ip = None self.tool_private_ip = None
self.tool_public_port = None
self.tool_private_port = None
# WebSocket connections # WebSocket connections
self.client_ws = None # wssshc WebSocket self.client_ws = None # wssshc WebSocket
...@@ -61,21 +65,25 @@ class Tunnel: ...@@ -61,21 +65,25 @@ class Tunnel:
def set_destination_info(self, public_ip=None, public_port=None, private_ip=None, private_port=None): def set_destination_info(self, public_ip=None, public_port=None, private_ip=None, private_port=None):
"""Set destination (wssshc) connection information""" """Set destination (wssshc) connection information"""
if public_ip: if public_ip:
self.dst_public_ip = public_ip self.wssshc_public_ip = public_ip
if public_port: if public_port:
self.dst_public_port = public_port self.wssshc_public_port = public_port
if private_ip: if private_ip:
self.dst_private_ip = private_ip self.wssshc_private_ip = private_ip
if private_port: if private_port:
self.dst_private_port = private_port self.wssshc_private_port = private_port
self.updated_at = time.time() self.updated_at = time.time()
def set_source_info(self, public_ip=None, private_ip=None): def set_source_info(self, public_ip=None, private_ip=None, public_port=None, private_port=None):
"""Set source (wsssh/wsscp) connection information""" """Set source (wsssh/wsscp) connection information"""
if public_ip: if public_ip:
self.src_public_ip = public_ip self.tool_public_ip = public_ip
if private_ip: if private_ip:
self.src_private_ip = private_ip self.tool_private_ip = private_ip
if public_port:
self.tool_public_port = public_port
if private_port:
self.tool_private_port = private_port
self.updated_at = time.time() self.updated_at = time.time()
def set_websockets(self, client_ws, wsssh_ws): def set_websockets(self, client_ws, wsssh_ws):
...@@ -94,13 +102,17 @@ class Tunnel: ...@@ -94,13 +102,17 @@ class Tunnel:
'created_at': self.created_at, 'created_at': self.created_at,
'updated_at': self.updated_at, 'updated_at': self.updated_at,
'protocol': self.protocol, 'protocol': self.protocol,
'tunnel_type': self.tunnel_type, 'tunnel': self.tunnel,
'dst_public_ip': self.dst_public_ip, 'tunnel_control': self.tunnel_control,
'dst_public_port': self.dst_public_port, 'service': self.service,
'dst_private_ip': self.dst_private_ip, 'wssshc_public_ip': self.wssshc_public_ip,
'dst_private_port': self.dst_private_port, 'wssshc_public_port': self.wssshc_public_port,
'src_public_ip': self.src_public_ip, 'wssshc_private_ip': self.wssshc_private_ip,
'src_private_ip': self.src_private_ip, 'wssshc_private_port': self.wssshc_private_port,
'tool_public_ip': self.tool_public_ip,
'tool_private_ip': self.tool_private_ip,
'tool_public_port': self.tool_public_port,
'tool_private_port': self.tool_private_port,
'error_message': self.error_message 'error_message': self.error_message
} }
......
...@@ -95,6 +95,10 @@ async def handle_websocket(websocket, path=None, *, server_password=None): ...@@ -95,6 +95,10 @@ async def handle_websocket(websocket, path=None, *, server_password=None):
if data.get('type') == 'register': if data.get('type') == 'register':
client_id = data.get('client_id') or data.get('id') client_id = data.get('client_id') or data.get('id')
client_password = data.get('password', '') client_password = data.get('password', '')
tunnel_param = data.get('tunnel', 'any')
tunnel_control_param = data.get('tunnel_control', 'any')
wssshd_private_ip = data.get('wssshd_private_ip', None)
print(f"[DEBUG] Processing registration for client {client_id}") print(f"[DEBUG] Processing registration for client {client_id}")
print(f"[DEBUG] Received password: '{client_password}', expected: '{server_password}'") print(f"[DEBUG] Received password: '{client_password}', expected: '{server_password}'")
print(f"[DEBUG] server_password type: {type(server_password)}, value: {repr(server_password)}") print(f"[DEBUG] server_password type: {type(server_password)}, value: {repr(server_password)}")
...@@ -108,7 +112,10 @@ async def handle_websocket(websocket, path=None, *, server_password=None): ...@@ -108,7 +112,10 @@ async def handle_websocket(websocket, path=None, *, server_password=None):
clients[client_id] = { clients[client_id] = {
'websocket': websocket, 'websocket': websocket,
'last_seen': time.time(), 'last_seen': time.time(),
'status': 'active' 'status': 'active',
'tunnel': tunnel_param,
'tunnel_control': tunnel_control_param,
'wssshd_private_ip': wssshd_private_ip
} }
if was_disconnected: if was_disconnected:
...@@ -134,11 +141,21 @@ async def handle_websocket(websocket, path=None, *, server_password=None): ...@@ -134,11 +141,21 @@ async def handle_websocket(websocket, path=None, *, server_password=None):
elif data.get('type') == 'tunnel_request': elif data.get('type') == 'tunnel_request':
client_id = data['client_id'] client_id = data['client_id']
request_id = data['request_id'] request_id = data['request_id']
tunnel_param = data.get('tunnel', 'any')
tunnel_control_param = data.get('tunnel_control', 'any')
service_param = data.get('service', 'ssh')
tool_private_ip = data.get('tool_private_ip', None)
client_info = clients.get(client_id) client_info = clients.get(client_id)
if client_info and client_info['status'] == 'active': if client_info and client_info['status'] == 'active':
# Create comprehensive tunnel object # Create comprehensive tunnel object
tunnel = Tunnel(request_id, client_id) tunnel = Tunnel(request_id, client_id)
# Set tunnel parameters
tunnel.tunnel = tunnel_param
tunnel.tunnel_control = tunnel_control_param
tunnel.service = service_param
# Set WebSocket connections # Set WebSocket connections
tunnel.set_websockets(client_info['websocket'], websocket) tunnel.set_websockets(client_info['websocket'], websocket)
...@@ -148,6 +165,10 @@ async def handle_websocket(websocket, path=None, *, server_password=None): ...@@ -148,6 +165,10 @@ async def handle_websocket(websocket, path=None, *, server_password=None):
private_ip=detect_client_private_ip(client_info['websocket']) private_ip=detect_client_private_ip(client_info['websocket'])
) )
# Set source (tool) IP information from the request
if tool_private_ip:
tunnel.set_source_info(private_ip=tool_private_ip)
# Store tunnel object # Store tunnel object
active_tunnels[request_id] = tunnel active_tunnels[request_id] = tunnel
......
# WebSocket SSH (wsssh) Configuration Example
#
# This is an example configuration file for wsssh.
# Copy this file to ~/.config/wsssh/wsssh.conf and modify the settings as needed.
#
# Configuration options:
# domain = wssshd.example.com
# tunnel = websocket
# tunnel-control = websocket
# service = ssh
[wsssh]
# WebSocket SSH Daemon domain (used for hostname parsing)
domain = example.com
# Transport types for data channel (comma-separated or 'any')
# Available transports: websocket
tunnel = any
# Transport types for control channel (comma-separated or 'any')
# Only transports with is_relay=true can be used for control
tunnel-control = any
# Service type (default: ssh)
service = ssh
\ No newline at end of file
...@@ -4,6 +4,9 @@ wsscp \- WebSocket SCP wrapper for secure file transfer ...@@ -4,6 +4,9 @@ wsscp \- WebSocket SCP wrapper for secure file transfer
.SH SYNOPSIS .SH SYNOPSIS
.B wsscp .B wsscp
[\fB\-\-local\-port\fR \fIPORT\fR] [\fB\-\-local\-port\fR \fIPORT\fR]
[\fB\-\-tunnel\fR \fITYPES\fR]
[\fB\-\-tunnel\-control\fR \fITYPES\fR]
[\fB\-\-service\fR \fISERVICE\fR]
[\fB\-\-debug\fR] [\fB\-\-debug\fR]
[\fB\-\-dev\-tunnel\fR] [\fB\-\-dev\-tunnel\fR]
[\fB\-\-help\fR] [\fB\-\-help\fR]
...@@ -18,6 +21,15 @@ is a wrapper around the standard SCP client that enables secure file transfer th ...@@ -18,6 +21,15 @@ is a wrapper around the standard SCP client that enables secure file transfer th
.BR \-\-local\-port " \fIPORT\fR" .BR \-\-local\-port " \fIPORT\fR"
Local port for tunnel establishment (default: auto-assign) Local port for tunnel establishment (default: auto-assign)
.TP .TP
.BR \-\-tunnel " \fITYPES\fR"
Transport types for data channel (comma-separated or 'any', default: any)
.TP
.BR \-\-tunnel\-control " \fITYPES\fR"
Transport types for control channel (comma-separated or 'any', default: any)
.TP
.BR \-\-service " \fISERVICE\fR"
Service type (default: scp)
.TP
.B \-\-debug .B \-\-debug
Enable debug output for troubleshooting Enable debug output for troubleshooting
.TP .TP
......
...@@ -4,6 +4,9 @@ wsssh \- WebSocket SSH wrapper for secure tunneling ...@@ -4,6 +4,9 @@ wsssh \- WebSocket SSH wrapper for secure tunneling
.SH SYNOPSIS .SH SYNOPSIS
.B wsssh .B wsssh
[\fB\-\-local\-port\fR \fIPORT\fR] [\fB\-\-local\-port\fR \fIPORT\fR]
[\fB\-\-tunnel\fR \fITYPES\fR]
[\fB\-\-tunnel\-control\fR \fITYPES\fR]
[\fB\-\-service\fR \fISERVICE\fR]
[\fB\-\-debug\fR] [\fB\-\-debug\fR]
[\fB\-\-dev\-tunnel\fR] [\fB\-\-dev\-tunnel\fR]
[\fB\-\-help\fR] [\fB\-\-help\fR]
...@@ -17,6 +20,15 @@ is a wrapper around the standard SSH client that enables secure tunneling throug ...@@ -17,6 +20,15 @@ is a wrapper around the standard SSH client that enables secure tunneling throug
.BR \-\-local\-port " \fIPORT\fR" .BR \-\-local\-port " \fIPORT\fR"
Local port for tunnel establishment (default: auto-assign) Local port for tunnel establishment (default: auto-assign)
.TP .TP
.BR \-\-tunnel " \fITYPES\fR"
Transport types for data channel (comma-separated or 'any', default: any)
.TP
.BR \-\-tunnel\-control " \fITYPES\fR"
Transport types for control channel (comma-separated or 'any', default: any)
.TP
.BR \-\-service " \fISERVICE\fR"
Service type (default: ssh)
.TP
.B \-\-debug .B \-\-debug
Enable debug output for troubleshooting Enable debug output for troubleshooting
.TP .TP
......
...@@ -8,6 +8,9 @@ wssshc \- WebSocket SSH Client for registration ...@@ -8,6 +8,9 @@ wssshc \- WebSocket SSH Client for registration
[\fB\-\-id\fR \fIID\fR] [\fB\-\-id\fR \fIID\fR]
[\fB\-\-password\fR \fIPASSWORD\fR] [\fB\-\-password\fR \fIPASSWORD\fR]
[\fB\-\-interval\fR \fISECONDS\fR] [\fB\-\-interval\fR \fISECONDS\fR]
[\fB\-\-tunnel\fR \fITYPES\fR]
[\fB\-\-tunnel\-control\fR \fITYPES\fR]
[\fB\-\-wssshd\-private\-ip\fR \fIIP\fR]
[\fB\-\-debug\fR] [\fB\-\-debug\fR]
[\fB\-\-help\fR] [\fB\-\-help\fR]
.SH DESCRIPTION .SH DESCRIPTION
...@@ -36,6 +39,15 @@ Registration password for authentication ...@@ -36,6 +39,15 @@ Registration password for authentication
.BR \-\-interval " \fISECONDS\fR" .BR \-\-interval " \fISECONDS\fR"
Reconnection interval in seconds if connection is lost (default: 30) Reconnection interval in seconds if connection is lost (default: 30)
.TP .TP
.BR \-\-tunnel " \fITYPES\fR"
Transport types for data channel (comma-separated or 'any', default: any)
.TP
.BR \-\-tunnel\-control " \fITYPES\fR"
Transport types for control channel (comma-separated or 'any', default: any)
.TP
.BR \-\-wssshd\-private\-ip " \fIIP\fR"
Private IP address of the wssshd server
.TP
.B \-\-debug .B \-\-debug
Enable debug output for troubleshooting Enable debug output for troubleshooting
.TP .TP
......
...@@ -925,14 +925,27 @@ int reconnect_websocket(tunnel_t *tunnel, const char *wssshd_host, int wssshd_po ...@@ -925,14 +925,27 @@ int reconnect_websocket(tunnel_t *tunnel, const char *wssshd_host, int wssshd_po
return -1; return -1;
} }
// Send tunnel request // Send detailed tunnel request with transport information
if (!send_json_message(ssl, "tunnel_request", client_id, request_id)) { char tunnel_request_msg[1024];
char *expanded_tunnel = expand_transport_list("any", 0); // Data channel transports
char *expanded_tunnel_control = expand_transport_list("any", 1); // Control channel transports
snprintf(tunnel_request_msg, sizeof(tunnel_request_msg),
"{\"type\":\"tunnel_request\",\"client_id\":\"%s\",\"request_id\":\"%s\",\"tunnel\":\"%s\",\"tunnel_control\":\"%s\",\"service\":\"ssh\"}",
client_id, request_id, expanded_tunnel, expanded_tunnel_control);
if (!send_websocket_frame(ssl, tunnel_request_msg)) {
free(expanded_tunnel);
free(expanded_tunnel_control);
SSL_free(ssl); SSL_free(ssl);
SSL_CTX_free(ssl_ctx); SSL_CTX_free(ssl_ctx);
close(sock); close(sock);
return -1; return -1;
} }
free(expanded_tunnel);
free(expanded_tunnel_control);
// Read acknowledgment // Read acknowledgment
bytes_read = SSL_read(ssl, buffer, sizeof(buffer)); bytes_read = SSL_read(ssl, buffer, sizeof(buffer));
if (bytes_read <= 0) { if (bytes_read <= 0) {
...@@ -1062,14 +1075,27 @@ int setup_tunnel(const char *wssshd_host, int wssshd_port, const char *client_id ...@@ -1062,14 +1075,27 @@ int setup_tunnel(const char *wssshd_host, int wssshd_port, const char *client_id
fflush(stdout); fflush(stdout);
} }
// Send tunnel request // Send detailed tunnel request with transport information
if (!send_json_message(ssl, "tunnel_request", client_id, request_id)) { char tunnel_request_msg[1024];
char *expanded_tunnel = expand_transport_list("any", 0); // Data channel transports
char *expanded_tunnel_control = expand_transport_list("any", 1); // Control channel transports
snprintf(tunnel_request_msg, sizeof(tunnel_request_msg),
"{\"type\":\"tunnel_request\",\"client_id\":\"%s\",\"request_id\":\"%s\",\"tunnel\":\"%s\",\"tunnel_control\":\"%s\",\"service\":\"ssh\"}",
client_id, request_id, expanded_tunnel, expanded_tunnel_control);
if (!send_websocket_frame(ssl, tunnel_request_msg)) {
free(expanded_tunnel);
free(expanded_tunnel_control);
SSL_free(ssl); SSL_free(ssl);
SSL_CTX_free(ssl_ctx); SSL_CTX_free(ssl_ctx);
close(sock); close(sock);
return -1; return -1;
} }
free(expanded_tunnel);
free(expanded_tunnel_control);
if (debug) { if (debug) {
printf("[DEBUG] Tunnel request sent for client: %s, request_id: %s\n", client_id, request_id); printf("[DEBUG] Tunnel request sent for client: %s, request_id: %s\n", client_id, request_id);
} }
......
...@@ -101,17 +101,29 @@ int send_json_message(SSL *ssl, const char *type, const char *client_id, const c ...@@ -101,17 +101,29 @@ int send_json_message(SSL *ssl, const char *type, const char *client_id, const c
return send_websocket_frame(ssl, message); return send_websocket_frame(ssl, message);
} }
int send_registration_message(SSL *ssl, const char *client_id, const char *password) { int send_registration_message(SSL *ssl, const char *client_id, const char *password, const char *tunnel, const char *tunnel_control, const char *wssshd_private_ip) {
char message[1024]; char message[2048];
if (password && strlen(password) > 0) { if (password && strlen(password) > 0) {
snprintf(message, sizeof(message), if (wssshd_private_ip && strlen(wssshd_private_ip) > 0) {
"{\"type\":\"register\",\"client_id\":\"%s\",\"password\":\"%s\"}", snprintf(message, sizeof(message),
client_id, password); "{\"type\":\"register\",\"client_id\":\"%s\",\"password\":\"%s\",\"tunnel\":\"%s\",\"tunnel_control\":\"%s\",\"wssshd_private_ip\":\"%s\"}",
client_id, password, tunnel, tunnel_control, wssshd_private_ip);
} else {
snprintf(message, sizeof(message),
"{\"type\":\"register\",\"client_id\":\"%s\",\"password\":\"%s\",\"tunnel\":\"%s\",\"tunnel_control\":\"%s\"}",
client_id, password, tunnel, tunnel_control);
}
} else { } else {
snprintf(message, sizeof(message), if (wssshd_private_ip && strlen(wssshd_private_ip) > 0) {
"{\"type\":\"register\",\"client_id\":\"%s\"}", snprintf(message, sizeof(message),
client_id); "{\"type\":\"register\",\"client_id\":\"%s\",\"tunnel\":\"%s\",\"tunnel_control\":\"%s\",\"wssshd_private_ip\":\"%s\"}",
client_id, tunnel, tunnel_control, wssshd_private_ip);
} else {
snprintf(message, sizeof(message),
"{\"type\":\"register\",\"client_id\":\"%s\",\"tunnel\":\"%s\",\"tunnel_control\":\"%s\"}",
client_id, tunnel, tunnel_control);
}
} }
printf("[DEBUG] Sending registration message: %s\n", message); printf("[DEBUG] Sending registration message: %s\n", message);
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
// Function declarations // Function declarations
int websocket_handshake(SSL *ssl, const char *host, int port, const char *path); int websocket_handshake(SSL *ssl, const char *host, int port, const char *path);
int send_json_message(SSL *ssl, const char *type, const char *client_id, const char *request_id); int send_json_message(SSL *ssl, const char *type, const char *client_id, const char *request_id);
int send_registration_message(SSL *ssl, const char *client_id, const char *password); int send_registration_message(SSL *ssl, const char *client_id, const char *password, const char *tunnel, const char *tunnel_control, const char *wssshd_private_ip);
int send_websocket_frame(SSL *ssl, const char *data); int send_websocket_frame(SSL *ssl, const char *data);
int send_pong_frame(SSL *ssl, const char *ping_payload, int payload_len); int send_pong_frame(SSL *ssl, const char *ping_payload, int payload_len);
int parse_websocket_frame(const char *buffer, int bytes_read, char **payload, int *payload_len); int parse_websocket_frame(const char *buffer, int bytes_read, char **payload, int *payload_len);
......
...@@ -51,6 +51,9 @@ void print_usage(const char *program_name) { ...@@ -51,6 +51,9 @@ void print_usage(const char *program_name) {
fprintf(stderr, " --interval SEC Connection retry interval in seconds (default: 30)\n"); fprintf(stderr, " --interval SEC Connection retry interval in seconds (default: 30)\n");
fprintf(stderr, " --debug Enable debug output\n"); fprintf(stderr, " --debug Enable debug output\n");
fprintf(stderr, " --dev-tunnel Development mode: setup tunnel but don't launch SCP\n"); fprintf(stderr, " --dev-tunnel Development mode: setup tunnel but don't launch SCP\n");
fprintf(stderr, " --tunnel TYPES Transport types for data channel (comma-separated or 'any', default: any)\n");
fprintf(stderr, " --tunnel-control TYPES Transport types for control channel (comma-separated or 'any', default: any)\n");
fprintf(stderr, " --service SERVICE Service type (default: ssh)\n");
fprintf(stderr, " --help Show this help\n"); fprintf(stderr, " --help Show this help\n");
fprintf(stderr, " -P PORT wssshd server port (default: 9898)\n"); fprintf(stderr, " -P PORT wssshd server port (default: 9898)\n");
fprintf(stderr, " --port PORT Same as -P\n"); fprintf(stderr, " --port PORT Same as -P\n");
...@@ -76,6 +79,18 @@ int parse_args(int argc, char *argv[], wsscp_config_t *config, int *remaining_ar ...@@ -76,6 +79,18 @@ int parse_args(int argc, char *argv[], wsscp_config_t *config, int *remaining_ar
} else if (strcmp(argv[i], "--interval") == 0 && i + 1 < argc) { } else if (strcmp(argv[i], "--interval") == 0 && i + 1 < argc) {
config->interval = atoi(argv[i + 1]); config->interval = atoi(argv[i + 1]);
i++; // Skip the argument i++; // Skip the argument
} else if (strcmp(argv[i], "--tunnel") == 0 && i + 1 < argc) {
if (config->tunnel) free(config->tunnel);
config->tunnel = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--tunnel-control") == 0 && i + 1 < argc) {
if (config->tunnel_control) free(config->tunnel_control);
config->tunnel_control = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--service") == 0 && i + 1 < argc) {
if (config->service) free(config->service);
config->service = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--debug") == 0) { } else if (strcmp(argv[i], "--debug") == 0) {
config->debug = 1; config->debug = 1;
} else if (strcmp(argv[i], "--dev-tunnel") == 0) { } else if (strcmp(argv[i], "--dev-tunnel") == 0) {
...@@ -254,15 +269,32 @@ char **modify_scp_args(int argc, char *argv[], const char *original_host, int lo ...@@ -254,15 +269,32 @@ char **modify_scp_args(int argc, char *argv[], const char *original_host, int lo
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
// Read config // Read config
char *config_domain = read_config_value("domain"); char *config_domain = read_config_value("domain");
char *config_tunnel = read_config_value("tunnel");
char *config_tunnel_control = read_config_value("tunnel-control");
char *config_service = read_config_value("service");
wsscp_config_t config = { wsscp_config_t config = {
.local_port = NULL, .local_port = NULL,
.wssshd_port = 9898, .wssshd_port = 9898,
.debug = 0, .debug = 0,
.interval = 30, .interval = 30,
.dev_tunnel = 0 .dev_tunnel = 0,
.tunnel = config_tunnel,
.tunnel_control = config_tunnel_control,
.service = config_service
}; };
// Set defaults if not provided
if (!config.tunnel) {
config.tunnel = strdup("any");
}
if (!config.tunnel_control) {
config.tunnel_control = strdup("any");
}
if (!config.service) {
config.service = strdup("ssh");
}
// Easter egg: --support option (only when it's the only argument) // Easter egg: --support option (only when it's the only argument)
if (argc == 2 && strcmp(argv[1], "--support") == 0) { if (argc == 2 && strcmp(argv[1], "--support") == 0) {
print_trans_flag(); print_trans_flag();
...@@ -337,6 +369,9 @@ int main(int argc, char *argv[]) { ...@@ -337,6 +369,9 @@ int main(int argc, char *argv[]) {
free(client_id); free(client_id);
free(wssshd_host); free(wssshd_host);
free(config.local_port); free(config.local_port);
free(config.tunnel);
free(config.tunnel_control);
free(config.service);
pthread_mutex_destroy(&tunnel_mutex); pthread_mutex_destroy(&tunnel_mutex);
return 1; return 1;
} }
...@@ -354,6 +389,9 @@ int main(int argc, char *argv[]) { ...@@ -354,6 +389,9 @@ int main(int argc, char *argv[]) {
free(client_id); free(client_id);
free(wssshd_host); free(wssshd_host);
free(config.local_port); free(config.local_port);
free(config.tunnel);
free(config.tunnel_control);
free(config.service);
pthread_mutex_destroy(&tunnel_mutex); pthread_mutex_destroy(&tunnel_mutex);
return 1; return 1;
} }
...@@ -454,6 +492,9 @@ start_forwarding_threads: ...@@ -454,6 +492,9 @@ start_forwarding_threads:
free(client_id); free(client_id);
free(wssshd_host); free(wssshd_host);
free(config.local_port); free(config.local_port);
free(config.tunnel);
free(config.tunnel_control);
free(config.service);
for (int i = 1; i < new_scp_argc; i++) { for (int i = 1; i < new_scp_argc; i++) {
if (new_scp_args[i] != remaining_argv[i-1]) { if (new_scp_args[i] != remaining_argv[i-1]) {
free(new_scp_args[i]); free(new_scp_args[i]);
...@@ -513,6 +554,9 @@ start_forwarding_threads: ...@@ -513,6 +554,9 @@ start_forwarding_threads:
free(client_id); free(client_id);
free(wssshd_host); free(wssshd_host);
free(config.local_port); free(config.local_port);
free(config.tunnel);
free(config.tunnel_control);
free(config.service);
for (int i = 1; i < new_scp_argc; i++) { for (int i = 1; i < new_scp_argc; i++) {
if (new_scp_args[i] != remaining_argv[i-1]) { if (new_scp_args[i] != remaining_argv[i-1]) {
free(new_scp_args[i]); free(new_scp_args[i]);
...@@ -552,6 +596,9 @@ start_forwarding_threads: ...@@ -552,6 +596,9 @@ start_forwarding_threads:
free(client_id); free(client_id);
free(wssshd_host); free(wssshd_host);
free(config.local_port); free(config.local_port);
free(config.tunnel);
free(config.tunnel_control);
free(config.service);
for (int i = 1; i < new_scp_argc; i++) { for (int i = 1; i < new_scp_argc; i++) {
if (new_scp_args[i] != remaining_argv[i-1]) { if (new_scp_args[i] != remaining_argv[i-1]) {
free(new_scp_args[i]); free(new_scp_args[i]);
...@@ -1076,6 +1123,9 @@ cleanup_and_exit: ...@@ -1076,6 +1123,9 @@ cleanup_and_exit:
free(client_id); free(client_id);
free(wssshd_host); free(wssshd_host);
free(config.local_port); free(config.local_port);
free(config.tunnel);
free(config.tunnel_control);
free(config.service);
// Free allocated strings in new_scp_args // Free allocated strings in new_scp_args
for (int i = 0; i < new_scp_argc; i++) { for (int i = 0; i < new_scp_argc; i++) {
// Free strings that were allocated with malloc/strdup // Free strings that were allocated with malloc/strdup
...@@ -1088,6 +1138,9 @@ cleanup_and_exit: ...@@ -1088,6 +1138,9 @@ cleanup_and_exit:
} }
free(new_scp_args); free(new_scp_args);
free(config_domain); free(config_domain);
free(config_tunnel);
free(config_tunnel_control);
free(config_service);
pthread_mutex_destroy(&tunnel_mutex); pthread_mutex_destroy(&tunnel_mutex);
pthread_mutex_destroy(&ssl_mutex); pthread_mutex_destroy(&ssl_mutex);
......
...@@ -51,6 +51,9 @@ void print_usage(const char *program_name) { ...@@ -51,6 +51,9 @@ void print_usage(const char *program_name) {
fprintf(stderr, " --interval SEC Connection retry interval in seconds (default: 30)\n"); fprintf(stderr, " --interval SEC Connection retry interval in seconds (default: 30)\n");
fprintf(stderr, " --debug Enable debug output\n"); fprintf(stderr, " --debug Enable debug output\n");
fprintf(stderr, " --dev-tunnel Development mode: setup tunnel but don't launch SSH\n"); fprintf(stderr, " --dev-tunnel Development mode: setup tunnel but don't launch SSH\n");
fprintf(stderr, " --tunnel TYPES Transport types for data channel (comma-separated or 'any', default: any)\n");
fprintf(stderr, " --tunnel-control TYPES Transport types for control channel (comma-separated or 'any', default: any)\n");
fprintf(stderr, " --service SERVICE Service type (default: ssh)\n");
fprintf(stderr, " --help Show this help\n"); fprintf(stderr, " --help Show this help\n");
fprintf(stderr, " -p PORT wssshd server port (default: 9898)\n"); fprintf(stderr, " -p PORT wssshd server port (default: 9898)\n");
fprintf(stderr, " --port PORT Same as -p\n"); fprintf(stderr, " --port PORT Same as -p\n");
...@@ -76,6 +79,18 @@ int parse_args(int argc, char *argv[], wsssh_config_t *config, int *remaining_ar ...@@ -76,6 +79,18 @@ int parse_args(int argc, char *argv[], wsssh_config_t *config, int *remaining_ar
} else if (strcmp(argv[i], "--interval") == 0 && i + 1 < argc) { } else if (strcmp(argv[i], "--interval") == 0 && i + 1 < argc) {
config->interval = atoi(argv[i + 1]); config->interval = atoi(argv[i + 1]);
i++; // Skip the argument i++; // Skip the argument
} else if (strcmp(argv[i], "--tunnel") == 0 && i + 1 < argc) {
if (config->tunnel) free(config->tunnel);
config->tunnel = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--tunnel-control") == 0 && i + 1 < argc) {
if (config->tunnel_control) free(config->tunnel_control);
config->tunnel_control = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--service") == 0 && i + 1 < argc) {
if (config->service) free(config->service);
config->service = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--debug") == 0) { } else if (strcmp(argv[i], "--debug") == 0) {
config->debug = 1; config->debug = 1;
} else if (strcmp(argv[i], "--dev-tunnel") == 0) { } else if (strcmp(argv[i], "--dev-tunnel") == 0) {
...@@ -243,15 +258,32 @@ char **modify_ssh_args(int argc, char *argv[], const char *original_host, int lo ...@@ -243,15 +258,32 @@ char **modify_ssh_args(int argc, char *argv[], const char *original_host, int lo
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
// Read config // Read config
char *config_domain = read_config_value("domain"); char *config_domain = read_config_value("domain");
char *config_tunnel = read_config_value("tunnel");
char *config_tunnel_control = read_config_value("tunnel-control");
char *config_service = read_config_value("service");
wsssh_config_t config = { wsssh_config_t config = {
.local_port = NULL, .local_port = NULL,
.wssshd_port = 9898, .wssshd_port = 9898,
.debug = 0, .debug = 0,
.interval = 30, .interval = 30,
.dev_tunnel = 0 .dev_tunnel = 0,
.tunnel = config_tunnel,
.tunnel_control = config_tunnel_control,
.service = config_service
}; };
// Set defaults if not provided
if (!config.tunnel) {
config.tunnel = strdup("any");
}
if (!config.tunnel_control) {
config.tunnel_control = strdup("any");
}
if (!config.service) {
config.service = strdup("ssh");
}
// Easter egg: --support option (only when it's the only argument) // Easter egg: --support option (only when it's the only argument)
if (argc == 2 && strcmp(argv[1], "--support") == 0) { if (argc == 2 && strcmp(argv[1], "--support") == 0) {
print_trans_flag(); print_trans_flag();
...@@ -1100,6 +1132,9 @@ cleanup_and_exit: ...@@ -1100,6 +1132,9 @@ cleanup_and_exit:
free(client_id); free(client_id);
free(wssshd_host); free(wssshd_host);
free(config.local_port); free(config.local_port);
free(config.tunnel);
free(config.tunnel_control);
free(config.service);
// Free allocated strings in new_ssh_args // Free allocated strings in new_ssh_args
for (int i = 0; i < new_ssh_argc; i++) { for (int i = 0; i < new_ssh_argc; i++) {
// Free strings that were allocated with malloc/strdup // Free strings that were allocated with malloc/strdup
...@@ -1112,6 +1147,9 @@ cleanup_and_exit: ...@@ -1112,6 +1147,9 @@ cleanup_and_exit:
} }
free(new_ssh_args); free(new_ssh_args);
free(config_domain); free(config_domain);
free(config_tunnel);
free(config_tunnel_control);
free(config_service);
pthread_mutex_destroy(&tunnel_mutex); pthread_mutex_destroy(&tunnel_mutex);
pthread_mutex_destroy(&ssl_mutex); pthread_mutex_destroy(&ssl_mutex);
......
...@@ -72,6 +72,9 @@ typedef struct { ...@@ -72,6 +72,9 @@ typedef struct {
char *password; char *password;
int interval; int interval;
int debug; int debug;
char *tunnel;
char *tunnel_control;
char *wssshd_private_ip;
} wssshc_config_t; } wssshc_config_t;
void load_config_file(const char *config_path, wssshc_config_t *config) { void load_config_file(const char *config_path, wssshc_config_t *config) {
...@@ -155,6 +158,9 @@ void print_usage(const char *program_name) { ...@@ -155,6 +158,9 @@ void print_usage(const char *program_name) {
fprintf(stderr, " --id ID Client identifier\n"); fprintf(stderr, " --id ID Client identifier\n");
fprintf(stderr, " --password PASS Registration password\n"); fprintf(stderr, " --password PASS Registration password\n");
fprintf(stderr, " --interval SEC Reconnection interval (default: 30)\n"); fprintf(stderr, " --interval SEC Reconnection interval (default: 30)\n");
fprintf(stderr, " --tunnel TYPES Transport types for data channel (comma-separated or 'any', default: any)\n");
fprintf(stderr, " --tunnel-control TYPES Transport types for control channel (comma-separated or 'any', default: any)\n");
fprintf(stderr, " --wssshd-private-ip IP Private IP of wssshd server\n");
fprintf(stderr, " --debug Enable debug output\n"); fprintf(stderr, " --debug Enable debug output\n");
fprintf(stderr, " --help Show this help\n"); fprintf(stderr, " --help Show this help\n");
fprintf(stderr, "\nConfiguration hierarchy (highest priority first):\n"); fprintf(stderr, "\nConfiguration hierarchy (highest priority first):\n");
...@@ -176,6 +182,9 @@ int parse_args(int argc, char *argv[], wssshc_config_t *config) { ...@@ -176,6 +182,9 @@ int parse_args(int argc, char *argv[], wssshc_config_t *config) {
{"id", required_argument, 0, 'i'}, {"id", required_argument, 0, 'i'},
{"password", required_argument, 0, 'w'}, {"password", required_argument, 0, 'w'},
{"interval", required_argument, 0, 't'}, {"interval", required_argument, 0, 't'},
{"tunnel", required_argument, 0, 'T'},
{"tunnel-control", required_argument, 0, 'C'},
{"wssshd-private-ip", required_argument, 0, 'I'},
{"debug", no_argument, 0, 'd'}, {"debug", no_argument, 0, 'd'},
{"help", no_argument, 0, 'h'}, {"help", no_argument, 0, 'h'},
{0, 0, 0, 0} {0, 0, 0, 0}
...@@ -184,7 +193,7 @@ int parse_args(int argc, char *argv[], wssshc_config_t *config) { ...@@ -184,7 +193,7 @@ int parse_args(int argc, char *argv[], wssshc_config_t *config) {
int opt; int opt;
char *custom_config = NULL; char *custom_config = NULL;
while ((opt = getopt_long(argc, argv, "c:s:p:H:P:i:w:t:dh", long_options, NULL)) != -1) { while ((opt = getopt_long(argc, argv, "c:s:p:H:P:i:w:t:T:C:I:dh", long_options, NULL)) != -1) {
switch (opt) { switch (opt) {
case 'c': case 'c':
custom_config = optarg; custom_config = optarg;
...@@ -214,6 +223,18 @@ int parse_args(int argc, char *argv[], wssshc_config_t *config) { ...@@ -214,6 +223,18 @@ int parse_args(int argc, char *argv[], wssshc_config_t *config) {
case 't': case 't':
config->interval = atoi(optarg); config->interval = atoi(optarg);
break; break;
case 'T':
if (config->tunnel) free(config->tunnel);
config->tunnel = strdup(optarg);
break;
case 'C':
if (config->tunnel_control) free(config->tunnel_control);
config->tunnel_control = strdup(optarg);
break;
case 'I':
if (config->wssshd_private_ip) free(config->wssshd_private_ip);
config->wssshd_private_ip = strdup(optarg);
break;
case 'd': case 'd':
config->debug = 1; config->debug = 1;
break; break;
...@@ -363,12 +384,24 @@ int connect_to_server(const wssshc_config_t *config) { ...@@ -363,12 +384,24 @@ int connect_to_server(const wssshc_config_t *config) {
// WebSocket handshake and SSL connection are sufficient connectivity tests // WebSocket handshake and SSL connection are sufficient connectivity tests
// Skip additional ping test as server may not handle it properly // Skip additional ping test as server may not handle it properly
// Expand transport lists if "any" is specified
char *expanded_tunnel = expand_transport_list(config->tunnel, 0); // 0 for data channel
char *expanded_tunnel_control = expand_transport_list(config->tunnel_control, 1); // 1 for control channel
// Send registration message // Send registration message
if (config->debug) { if (config->debug) {
printf("[DEBUG] Sending registration message...\n"); printf("[DEBUG] Sending registration message...\n");
fflush(stdout); fflush(stdout);
} }
int reg_result = send_registration_message(ssl, config->client_id, config->password); int reg_result = send_registration_message(ssl, config->client_id, config->password, expanded_tunnel, expanded_tunnel_control, config->wssshd_private_ip);
// Free expanded transport lists
if (expanded_tunnel != config->tunnel) {
free(expanded_tunnel);
}
if (expanded_tunnel_control != config->tunnel_control) {
free(expanded_tunnel_control);
}
if (!reg_result) { if (!reg_result) {
fprintf(stderr, "Failed to send registration message\n"); fprintf(stderr, "Failed to send registration message\n");
SSL_free(ssl); SSL_free(ssl);
...@@ -904,7 +937,10 @@ int main(int argc, char *argv[]) { ...@@ -904,7 +937,10 @@ int main(int argc, char *argv[]) {
.client_id = NULL, .client_id = NULL,
.password = NULL, .password = NULL,
.interval = 30, .interval = 30,
.debug = 0 .debug = 0,
.tunnel = NULL,
.tunnel_control = NULL,
.wssshd_private_ip = NULL
}; };
// Initialize CPU affinity system for multi-core utilization // Initialize CPU affinity system for multi-core utilization
...@@ -931,6 +967,12 @@ int main(int argc, char *argv[]) { ...@@ -931,6 +967,12 @@ int main(int argc, char *argv[]) {
if (!config.ssh_host) { if (!config.ssh_host) {
config.ssh_host = strdup("127.0.0.1"); config.ssh_host = strdup("127.0.0.1");
} }
if (!config.tunnel) {
config.tunnel = strdup("any");
}
if (!config.tunnel_control) {
config.tunnel_control = strdup("any");
}
// Validate required arguments // Validate required arguments
if (!config.client_id || !config.password) { if (!config.client_id || !config.password) {
...@@ -1003,6 +1045,9 @@ int main(int argc, char *argv[]) { ...@@ -1003,6 +1045,9 @@ int main(int argc, char *argv[]) {
free(config.ssh_host); free(config.ssh_host);
free(config.client_id); free(config.client_id);
free(config.password); free(config.password);
free(config.tunnel);
free(config.tunnel_control);
free(config.wssshd_private_ip);
pthread_mutex_destroy(&tunnel_mutex); pthread_mutex_destroy(&tunnel_mutex);
return 0; return 0;
......
...@@ -54,13 +54,36 @@ char *read_config_value(const char *key) { ...@@ -54,13 +54,36 @@ char *read_config_value(const char *key) {
FILE *f = fopen(path, "r"); FILE *f = fopen(path, "r");
if (!f) return NULL; if (!f) return NULL;
char line[256]; char line[256];
char section[64] = "";
while (fgets(line, sizeof(line), f)) { while (fgets(line, sizeof(line), f)) {
if (strncmp(line, key, strlen(key)) == 0 && line[strlen(key)] == '=') { // Check for section headers
char *value = strdup(line + strlen(key) + 1); if (line[0] == '[') {
// remove newline sscanf(line, "[%63[^]]", section);
value[strcspn(value, "\n")] = 0; continue;
fclose(f); }
return value; // Skip comments and empty lines
if (line[0] == '#' || line[0] == ';' || line[0] == '\n') continue;
char *equals = strchr(line, '=');
if (equals) {
*equals = '\0';
char *config_key = line;
char *value = equals + 1;
// Trim whitespace
while (*config_key == ' ' || *config_key == '\t') config_key++;
char *end = config_key + strlen(config_key) - 1;
while (end > config_key && (*end == ' ' || *end == '\t')) *end-- = '\0';
while (*value == ' ' || *value == '\t') value++;
end = value + strlen(value) - 1;
while (end > value && (*end == ' ' || *end == '\t' || *end == '\n')) *end-- = '\0';
// Check if key matches (case-insensitive for section names)
if (strcmp(config_key, key) == 0) {
fclose(f);
return strdup(value);
}
} }
} }
fclose(f); fclose(f);
...@@ -213,6 +236,24 @@ static char *get_default_gateway_interface() { ...@@ -213,6 +236,24 @@ static char *get_default_gateway_interface() {
return interface; return interface;
} }
char *expand_transport_list(const char *transport_spec, int for_relay) {
if (!transport_spec) {
return NULL;
}
// If it's already "any", expand to the appropriate list
if (strcmp(transport_spec, "any") == 0) {
if (for_relay) {
return strdup(RELAY_TRANSPORTS);
} else {
return strdup(SUPPORTED_TRANSPORTS);
}
}
// If it's not "any", return as-is (could be a comma-separated list)
return strdup(transport_spec);
}
char *autodetect_local_ip() { char *autodetect_local_ip() {
struct ifaddrs *ifaddr, *ifa; struct ifaddrs *ifaddr, *ifa;
char *selected_ip = NULL; char *selected_ip = NULL;
......
...@@ -59,6 +59,9 @@ typedef struct { ...@@ -59,6 +59,9 @@ typedef struct {
int debug; int debug;
int interval; // Reconnection interval in seconds int interval; // Reconnection interval in seconds
int dev_tunnel; // Development mode - don't launch SSH, just setup tunnel int dev_tunnel; // Development mode - don't launch SSH, just setup tunnel
char *tunnel; // Transport types for data channel (comma-separated or "any")
char *tunnel_control; // Transport types for control channel (comma-separated or "any")
char *service; // Service type (default: "ssh")
} wsssh_config_t; } wsssh_config_t;
typedef struct { typedef struct {
...@@ -67,14 +70,22 @@ typedef struct { ...@@ -67,14 +70,22 @@ typedef struct {
int debug; int debug;
int interval; // Connection retry interval in seconds int interval; // Connection retry interval in seconds
int dev_tunnel; // Development mode - don't launch SCP, just setup tunnel int dev_tunnel; // Development mode - don't launch SCP, just setup tunnel
char *tunnel; // Transport types for data channel (comma-separated or "any")
char *tunnel_control; // Transport types for control channel (comma-separated or "any")
char *service; // Service type (default: "ssh")
} wsscp_config_t; } wsscp_config_t;
// tunnel_t is defined in tunnel.h // tunnel_t is defined in tunnel.h
// Thread arguments are defined in tunnel.h // Thread arguments are defined in tunnel.h
// Transport definitions
#define SUPPORTED_TRANSPORTS "websocket"
#define RELAY_TRANSPORTS "websocket"
// Function declarations // Function declarations
char *read_config_value(const char *key); char *read_config_value(const char *key);
char *expand_transport_list(const char *transport_spec, int for_relay);
void print_trans_flag(void); void print_trans_flag(void);
void print_palestinian_flag(void); void print_palestinian_flag(void);
int find_available_port(void); int find_available_port(void);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment