Fix bridge mode to use SSL for WebSocket communication

- Bridge mode was incorrectly using raw socket operations over SSL connection
- Updated to use SSL_read/SSL_write for all WebSocket communication
- Fixed send_websocket_message to use send_websocket_frame with SSL
- Fixed pong frame sending to use SSL
- Fixed select() and FD_ISSET to monitor correct socket

This resolves the 'send_error' and connection closure issues in bridge mode tests.
parent 84781de4
...@@ -4,12 +4,17 @@ This document provides comprehensive testing tools and examples for the wsssht b ...@@ -4,12 +4,17 @@ This document provides comprehensive testing tools and examples for the wsssht b
## Overview ## Overview
Bridge mode allows programmatic control of wsssht tunnels through JSON commands sent via stdin/stdout. In bridge mode: Bridge mode provides a **pure transport layer** for WebSocket communication. Unlike other modes, bridge mode does NOT handle tunnel logic - it simply establishes a WebSocket connection and passes ALL messages through stdin/stdout with proper channel identification.
- **Tunnel control channel messages** are communicated through stdin/stdout (JSON protocol) In bridge mode:
- **Tunnel data channel messages** remain handled by wsssht (normal forwarding)
- External applications can control tunnel behavior via JSON commands - **Pure Transport Layer**: Establishes WebSocket connection to wssshd server
- Real-time status updates and responses are provided - **Message Passing**: Passes ALL messages from stdin to WebSocket (to server) with channel routing
- **Response Handling**: Passes ALL messages from WebSocket to stdout (from server) with channel identification
- **Channel Routing**: Automatically routes data/control messages to appropriate channels
- **No Tunnel Logic**: Does NOT open local ports or handle tunnel-specific logic
- **No Protocol Logic**: Does NOT send tunnel requests or handle tunnel responses
- **Client Responsibility**: All protocol logic must be implemented by the client application
## Test Scripts ## Test Scripts
...@@ -34,8 +39,12 @@ A comprehensive automated test script that demonstrates basic bridge mode functi ...@@ -34,8 +39,12 @@ A comprehensive automated test script that demonstrates basic bridge mode functi
#### Features: #### Features:
- ✅ Automatic wsssht startup in bridge mode - ✅ Automatic wsssht startup in bridge mode
- ✅ Sends test JSON commands (status, quit) - ✅ Sends tunnel_request to establish tunnel
- ✅ Monitors and displays JSON responses - ✅ Waits for tunnel_ack confirmation
- ✅ Sends data message with test string
- ✅ Receives and displays tunnel_response
- ✅ Sends tunnel_close to clean up
- ✅ Monitors and displays all JSON responses
- ✅ Colored output for easy reading - ✅ Colored output for easy reading
- ✅ Error handling and process management - ✅ Error handling and process management
- ✅ Configurable via environment variables - ✅ Configurable via environment variables
...@@ -75,36 +84,41 @@ An interactive testing tool that allows manual JSON command input and real-time ...@@ -75,36 +84,41 @@ An interactive testing tool that allows manual JSON command input and real-time
- ✅ JSON validation - ✅ JSON validation
- ✅ Process management and cleanup - ✅ Process management and cleanup
## JSON Command Examples ## Message Format Examples
### Basic Commands ### Messages Sent to Server (via stdin)
Any text string is forwarded to the server with automatic channel detection:
#### Status Check #### Control Channel Messages
```json
{"command":"status","timestamp":1640995200}
```
**Response:**
```json ```json
{"type":"tunnel_status","active":true,"timestamp":1640995200} {"type":"tunnel_request","client_id":"myclient","timestamp":1640995200}
``` ```
**Automatically detected as control channel and sent to control WebSocket channel**
#### Quit/Exit #### Data Channel Messages
```json ```json
{"command":"quit","timestamp":1640995200} {"type":"tunnel_data","data":"base64_encoded_data","timestamp":1640995200}
``` ```
**Response:** **Automatically detected as data channel and sent to data WebSocket channel**
### Messages Received from Server (via stdout)
All messages are wrapped with channel identification:
#### Control Channel Response
```json ```json
{"type":"command_received","command":"{\"command\":\"quit\",\"timestamp\":1640995200}","timestamp":1640995200} {"type":"message_received","channel":"control","message":"{\"type\":\"tunnel_response\",\"status\":\"success\"}","timestamp":1640995200}
{"type":"bridge_ended","timestamp":1640995200}
``` ```
#### Ping #### Data Channel Response
```json ```json
{"command":"ping","timestamp":1640995200} {"type":"message_received","channel":"data","message":"base64_encoded_response_data","timestamp":1640995200}
``` ```
**Response:**
#### WebSocket Events
```json ```json
{"type":"command_received","command":"{\"command\":\"ping\",\"timestamp\":1640995200}","timestamp":1640995200} {"type":"websocket_connected","socket":5,"timestamp":1640995200}
{"type":"ping_received","timestamp":1640995200}
{"type":"pong_received","timestamp":1640995200}
``` ```
### Tunnel Control Channel Messages ### Tunnel Control Channel Messages
...@@ -166,27 +180,36 @@ These are messages received from the wssshd server and forwarded as JSON through ...@@ -166,27 +180,36 @@ These are messages received from the wssshd server and forwarded as JSON through
### 2. Initial Responses ### 2. Initial Responses
Bridge mode sends initial status messages: Bridge mode sends initial status messages:
```json ```json
{"type":"status","message":"Bridge mode started","client_id":"myclient","host":"mbetter.nexlab.net","port":9898} {"type":"bridge_started","client_id":"myclient","host":"mbetter.nexlab.net","port":9898,"timestamp":1640995200}
{"type":"tunnel_established","listen_sock":5} {"type":"websocket_connected","socket":5,"timestamp":1640995200}
{"type":"connection_accepted","socket":6} {"type":"bridge_ready","message":"Pure transport layer active","timestamp":1640995200}
{"type":"ready","message":"Bridge mode active"}
``` ```
### 3. Command/Response Cycle ### 3. Message Exchange
Send JSON commands via stdin, receive responses via stdout: Send protocol messages via stdin, receive responses via stdout:
```bash ```bash
# Send command # Send tunnel request (control channel)
echo '{"command":"status","timestamp":1640995200}' | ./wssshtools/wsssht --bridge --clientid myclient --wssshd-host mbetter.nexlab.net echo '{"type":"tunnel_request","client_id":"myclient","request_id":"req123","tunnel":"any","tunnel_control":"any","service":"ssh","timestamp":1640995200}' | ./wssshtools/wsssht --bridge --clientid myclient --wssshd-host mbetter.nexlab.net
# Receive tunnel acknowledgment (control channel)
{"type":"message_received","channel":"control","message":"{\"type\":\"tunnel_ack\",\"request_id\":\"req123\"}","timestamp":1640995200}
# Send data (data channel) - hex-encoded
echo '{"type":"tunnel_data","request_id":"req123","data":"48656c6c6f","timestamp":1640995200}' | ./wssshtools/wsssht --bridge --clientid myclient --wssshd-host mbetter.nexlab.net
# Receive response # Receive data response (data channel)
{"type":"tunnel_status","active":true,"timestamp":1640995200} {"type":"message_received","channel":"data","message":"{\"type\":\"tunnel_response\",\"request_id\":\"req123\",\"data\":\"response_data\"}","timestamp":1640995200}
# Send tunnel close (control channel)
echo '{"type":"tunnel_close","request_id":"req123","timestamp":1640995200}' | ./wssshtools/wsssht --bridge --clientid myclient --wssshd-host mbetter.nexlab.net
``` ```
### 4. Tunnel Operation ### 4. Transport Layer Operation
While bridge mode is active: While bridge mode is active:
- **Data Channel**: wsssht handles actual tunnel data forwarding - **Pure Transport**: Only handles WebSocket connection and message routing
- **Control Channel**: JSON commands control tunnel behavior - **Channel Routing**: Automatically detects and routes messages to control/data channels
- **Status Updates**: Real-time status information via stdout - **No Protocol Logic**: Does NOT interpret or handle tunnel protocol
- **Client Responsibility**: Your application implements all protocol logic
## Testing Scenarios ## Testing Scenarios
...@@ -295,14 +318,14 @@ DEBUG=1 ./test_bridge_interactive.sh start ...@@ -295,14 +318,14 @@ DEBUG=1 ./test_bridge_interactive.sh start
## Integration Examples ## Integration Examples
### Python Integration ### Python Integration (Pure Transport Layer)
```python ```python
import subprocess import subprocess
import json import json
import time import time
def send_bridge_command(command): def send_protocol_message(message_type, **kwargs):
"""Send JSON command to wsssht bridge mode""" """Send protocol message to wsssht bridge mode (pure transport layer)"""
cmd = [ cmd = [
'./wssshtools/wsssht', './wssshtools/wsssht',
'--bridge', '--bridge',
...@@ -318,30 +341,43 @@ def send_bridge_command(command): ...@@ -318,30 +341,43 @@ def send_bridge_command(command):
text=True text=True
) )
# Send command # Create protocol message
json_cmd = json.dumps({ message = {
'command': command, 'type': message_type,
'timestamp': int(time.time()) 'timestamp': int(time.time()),
}) **kwargs
proc.stdin.write(json_cmd + '\n') }
json_msg = json.dumps(message)
proc.stdin.write(json_msg + '\n')
proc.stdin.flush() proc.stdin.flush()
# Read response # Read responses (your application handles the protocol)
while True:
response = proc.stdout.readline().strip() response = proc.stdout.readline().strip()
if response: if not response:
return json.loads(response) break
parsed = json.loads(response)
return None print(f"Received: {parsed}")
# Usage # Your application implements protocol logic here
response = send_bridge_command('status') if parsed['type'] == 'message_received':
print(f"Tunnel active: {response.get('active', False)}") server_msg = json.loads(parsed['message'])
if server_msg.get('type') == 'tunnel_response':
print("Tunnel established!")
return True
elif server_msg.get('type') == 'error':
print(f"Error: {server_msg.get('message')}")
return False
return False
# Usage - your application handles the protocol
success = send_protocol_message('tunnel_request', client_id='myclient')
``` ```
### Node.js Integration ### Node.js Integration (Pure Transport Layer)
```javascript ```javascript
const { spawn } = require('child_process'); const { spawn } = require('child_process');
const fs = require('fs');
function startBridgeMode() { function startBridgeMode() {
const wsssht = spawn('./wssshtools/wsssht', [ const wsssht = spawn('./wssshtools/wsssht', [
...@@ -350,18 +386,30 @@ function startBridgeMode() { ...@@ -350,18 +386,30 @@ function startBridgeMode() {
'--wssshd-host', 'mbetter.nexlab.net' '--wssshd-host', 'mbetter.nexlab.net'
]); ]);
// Send command // Send protocol message (your application handles the protocol)
const command = JSON.stringify({ const tunnelRequest = JSON.stringify({
command: 'status', type: 'tunnel_request',
client_id: 'myclient',
timestamp: Math.floor(Date.now() / 1000) timestamp: Math.floor(Date.now() / 1000)
}); });
wsssht.stdin.write(command + '\n'); wsssht.stdin.write(tunnelRequest + '\n');
// Handle responses // Handle responses (your application handles the protocol)
wsssht.stdout.on('data', (data) => { wsssht.stdout.on('data', (data) => {
const response = JSON.parse(data.toString().trim()); const response = JSON.parse(data.toString().trim());
console.log('Response:', response); console.log('Received:', response);
// Your application implements protocol logic here
if (response.type === 'message_received') {
const serverMessage = JSON.parse(response.message);
if (serverMessage.type === 'tunnel_response') {
console.log('Tunnel established!');
} else if (serverMessage.type === 'tunnel_data') {
// Handle tunnel data
console.log('Received data:', serverMessage.data);
}
}
}); });
wsssht.stderr.on('data', (data) => { wsssht.stderr.on('data', (data) => {
...@@ -371,7 +419,7 @@ function startBridgeMode() { ...@@ -371,7 +419,7 @@ function startBridgeMode() {
return wsssht; return wsssht;
} }
// Usage // Usage - your application handles the protocol
const proc = startBridgeMode(); const proc = startBridgeMode();
// Cleanup on exit // Cleanup on exit
...@@ -383,15 +431,15 @@ process.on('SIGINT', () => { ...@@ -383,15 +431,15 @@ process.on('SIGINT', () => {
## Best Practices ## Best Practices
### 1. Command Format ### 1. Message Format
- Always include `timestamp` field for tracking - Any text format is supported (JSON, plain text, custom protocols)
- Use valid JSON format - Handle responses asynchronously in your application
- Handle responses asynchronously - Implement proper message framing for your protocol
### 2. Error Handling ### 2. Error Handling
- Check process exit codes - Check process exit codes
- Monitor for timeout scenarios - Monitor for WebSocket connection status
- Validate JSON responses - Validate message formats in your application
### 3. Resource Management ### 3. Resource Management
- Properly terminate wsssht processes - Properly terminate wsssht processes
...@@ -399,9 +447,14 @@ process.on('SIGINT', () => { ...@@ -399,9 +447,14 @@ process.on('SIGINT', () => {
- Clean up file descriptors - Clean up file descriptors
### 4. Security ### 4. Security
- Validate input data - Validate input data in your application
- Use secure communication channels - Use secure WebSocket connections (wss://)
- Implement proper authentication - Implement proper authentication in your protocol
### 5. Channel Management
- Use appropriate message types for control vs data channels
- Handle channel-specific routing in your application
- Monitor both channels for complete protocol implementation
## Files Created ## Files Created
......
...@@ -106,20 +106,77 @@ test_bridge_mode() { ...@@ -106,20 +106,77 @@ test_bridge_mode() {
fi fi
done done
# Test 1: Send a status command # Generate a unique request ID for the tunnel
log_info "Test 1: Sending status command" REQUEST_ID="test_$(date +%s)_$$"
send_command '{"command":"status","timestamp":'$(date +%s)'}' >&${wsssht_in}
# Test 1: Send tunnel request
log_info "Test 1: Sending tunnel request"
send_command '{"type":"tunnel_request","client_id":"'$CLIENT_ID'","request_id":"'$REQUEST_ID'","tunnel":"any","tunnel_control":"any","service":"ssh","timestamp":'$(date +%s)'}' >&${wsssht_in}
# Wait for tunnel acknowledgment
log_info "Waiting for tunnel acknowledgment..."
local tunnel_ack_received=false
local timeout_counter=0
while [ $timeout_counter -lt 10 ]; do # 10 second timeout
if read -t 1 -u ${wsssht_out} line 2>/dev/null; then
if [ -n "$line" ]; then
log_info "Response: $line"
# Check if we received tunnel_ack
if echo "$line" | grep -q '"message".*"tunnel_ack"'; then
log_success "Tunnel acknowledgment received!"
tunnel_ack_received=true
break
fi
fi
fi
timeout_counter=$((timeout_counter + 1))
done
sleep 2 if [ "$tunnel_ack_received" = false ]; then
read_response log_error "Timeout waiting for tunnel acknowledgment"
# Send quit to clean up
send_command '{"command":"quit","timestamp":'$(date +%s)'}' >&${wsssht_in}
return 1
fi
# Test 2: Send data message with text string
log_info "Test 2: Sending data message with text string"
TEST_MESSAGE="Hello from bridge mode test!"
# Convert to hex for tunnel_data format
HEX_MESSAGE=$(echo -n "$TEST_MESSAGE" | xxd -p | tr -d '\n')
send_command '{"type":"tunnel_data","request_id":"'$REQUEST_ID'","data":"'$HEX_MESSAGE'","timestamp":'$(date +%s)'}' >&${wsssht_in}
# Wait for tunnel response
log_info "Waiting for tunnel response..."
local tunnel_response_received=false
timeout_counter=0
while [ $timeout_counter -lt 10 ]; do # 10 second timeout
if read -t 1 -u ${wsssht_out} line 2>/dev/null; then
if [ -n "$line" ]; then
log_info "Response: $line"
# Check if we received tunnel_response
if echo "$line" | grep -q '"message".*"tunnel_response"'; then
log_success "Tunnel response received!"
tunnel_response_received=true
break
fi
fi
fi
timeout_counter=$((timeout_counter + 1))
done
if [ "$tunnel_response_received" = false ]; then
log_warning "No tunnel response received within timeout"
fi
# Test 2: Monitor for tunnel control messages # Test 3: Send tunnel close
log_info "Test 2: Monitoring for tunnel control messages (5 seconds)" log_info "Test 3: Sending tunnel close"
log_info "Note: In a real scenario, you would see tunnel_data, tunnel_response, or tunnel_close messages here" send_command '{"type":"tunnel_close","request_id":"'$REQUEST_ID'","timestamp":'$(date +%s)'}' >&${wsssht_in}
sleep 5
sleep 2
# Test 3: Send a quit command # Test 4: Send quit command to end bridge mode
log_info "Test 3: Sending quit command" log_info "Test 4: Sending quit command to end bridge mode"
send_command '{"command":"quit","timestamp":'$(date +%s)'}' >&${wsssht_in} send_command '{"command":"quit","timestamp":'$(date +%s)'}' >&${wsssht_in}
sleep 2 sleep 2
......
...@@ -297,6 +297,16 @@ int run_bridge_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -297,6 +297,16 @@ int run_bridge_mode(wsssh_config_t *config, const char *client_id, const char *w
return 1; return 1;
} }
// Get the SSL connection for sending messages
SSL *ws_ssl = active_tunnel ? active_tunnel->ssl : NULL;
if (!ws_ssl) {
printf("{\"type\":\"error\",\"message\":\"Failed to get SSL connection\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
close(ws_sock);
if (ws_ctx) SSL_CTX_free(ws_ctx);
return 1;
}
// Send connection established message // Send connection established message
printf("{\"type\":\"websocket_connected\",\"socket\":%d,\"timestamp\":%ld}\n", ws_sock, time(NULL)); printf("{\"type\":\"websocket_connected\",\"socket\":%d,\"timestamp\":%ld}\n", ws_sock, time(NULL));
fflush(stdout); fflush(stdout);
...@@ -349,8 +359,8 @@ int run_bridge_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -349,8 +359,8 @@ int run_bridge_mode(wsssh_config_t *config, const char *client_id, const char *w
channel = "data"; channel = "data";
} }
// Send message to appropriate WebSocket channel // Send message to appropriate WebSocket channel using SSL
if (send_websocket_message(ws_sock, buffer, len, channel, config->debug) == 0) { if (send_websocket_frame(ws_ssl, buffer)) {
printf("{\"type\":\"message_sent\",\"channel\":\"%s\",\"message\":\"%s\",\"timestamp\":%ld}\n", printf("{\"type\":\"message_sent\",\"channel\":\"%s\",\"message\":\"%s\",\"timestamp\":%ld}\n",
channel, buffer, time(NULL)); channel, buffer, time(NULL));
fflush(stdout); fflush(stdout);
...@@ -364,8 +374,9 @@ int run_bridge_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -364,8 +374,9 @@ int run_bridge_mode(wsssh_config_t *config, const char *client_id, const char *w
// Handle WebSocket data (messages from server) // Handle WebSocket data (messages from server)
if (FD_ISSET(ws_sock, &readfds)) { if (FD_ISSET(ws_sock, &readfds)) {
if ((size_t)frame_buffer_used < sizeof(frame_buffer)) { if ((size_t)frame_buffer_used < sizeof(frame_buffer)) {
int bytes_read = recv(ws_sock, frame_buffer + frame_buffer_used, // Use SSL to read WebSocket data
sizeof(frame_buffer) - frame_buffer_used, 0); int bytes_read = SSL_read(ws_ssl, frame_buffer + frame_buffer_used,
sizeof(frame_buffer) - frame_buffer_used);
if (bytes_read > 0) { if (bytes_read > 0) {
frame_buffer_used += bytes_read; frame_buffer_used += bytes_read;
...@@ -406,7 +417,10 @@ int run_bridge_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -406,7 +417,10 @@ int run_bridge_mode(wsssh_config_t *config, const char *client_id, const char *w
printf("{\"type\":\"ping_received\",\"timestamp\":%ld}\n", time(NULL)); printf("{\"type\":\"ping_received\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout); fflush(stdout);
// Send pong // Send pong
send_pong_frame_ws(ws_sock, payload, payload_len); if (!send_pong_frame(ws_ssl, payload, payload_len)) {
printf("{\"type\":\"pong_send_error\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
}
} else if (frame_type == 0x8A) { // Pong frame } else if (frame_type == 0x8A) { // Pong frame
printf("{\"type\":\"pong_received\",\"timestamp\":%ld}\n", time(NULL)); printf("{\"type\":\"pong_received\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout); fflush(stdout);
......
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