Skip to main content

netget/server/socks5/
actions.rs

1//! SOCKS5 protocol actions implementation
2
3use crate::llm::actions::{
4    protocol_trait::{ActionResult, Protocol, Server},
5    ActionDefinition, Parameter,
6};
7use crate::protocol::log_template::LogTemplate;
8use crate::protocol::EventType;
9use crate::state::app_state::AppState;
10use anyhow::{Context, Result};
11use serde_json::json;
12use std::sync::LazyLock;
13use tracing::debug;
14
15/// SOCKS5 protocol action handler
16pub struct Socks5Protocol {}
17
18impl Socks5Protocol {
19    pub fn new() -> Self {
20        Self {}
21    }
22}
23
24// Implement Protocol trait (common functionality)
25impl Protocol for Socks5Protocol {
26    fn get_async_actions(&self, _state: &AppState) -> Vec<ActionDefinition> {
27        // Future: Could add actions like:
28        // - close_socks5_connection(connection_id)
29        // - set_socks5_filter(patterns)
30        // - list_socks5_connections()
31        Vec::new()
32    }
33    fn get_sync_actions(&self) -> Vec<ActionDefinition> {
34        vec![
35            allow_socks5_connect_action(),
36            deny_socks5_connect_action(),
37            allow_socks5_auth_action(),
38            deny_socks5_auth_action(),
39            forward_socks5_data_action(),
40            modify_socks5_data_action(),
41            close_connection_action(),
42        ]
43    }
44    fn protocol_name(&self) -> &'static str {
45        "SOCKS5"
46    }
47    fn get_event_types(&self) -> Vec<EventType> {
48        get_socks5_event_types()
49    }
50    fn stack_name(&self) -> &'static str {
51        "ETH>IP>TCP>SOCKS5"
52    }
53    fn keywords(&self) -> Vec<&'static str> {
54        vec!["socks", "socks5"]
55    }
56    fn metadata(&self) -> crate::protocol::metadata::ProtocolMetadataV2 {
57        use crate::protocol::metadata::{DevelopmentState, ProtocolMetadataV2};
58
59        ProtocolMetadataV2::builder()
60            .state(DevelopmentState::Experimental)
61            .well_known_port(1080)
62            .implementation("Manual SOCKS5 protocol (RFC 1928)")
63            .llm_control("Auth allow/deny, connection allow/deny, MITM data forward/modify/close")
64            // Not "curl --socks5", which this claimed and which no test does. The peer in
65            // tests/server/socks5/e2e_test.rs is a SOCKS5 client hand-written inside the test
66            // over a raw TcpStream -- an independent reading of RFC 1928, not an independent
67            // implementation, so it does not clear the bar for Beta.
68            .e2e_testing(
69                "A SOCKS5 client hand-written in tests/server/socks5/e2e_test.rs (raw TcpStream, \
70                 RFC 1928/1929 by hand): no-auth and username/password handshakes, CONNECT by \
71                 IPv4 and by domain, a refusal, and an HTTP exchange through a MITM tunnel. No \
72                 third-party SOCKS5 client has been run against it.",
73            )
74            .notes(
75                "OPEN RELAY BY DESIGN, AND UNRESTRICTED: the destination of every connection is \
76                 chosen by the peer, and there is no allow-list, deny-list or network \
77                 restriction of any kind. Loopback, link-local (including 169.254.169.254, the \
78                 cloud instance-metadata endpoint), and every RFC 1918 range are all reachable, \
79                 so anyone who can reach this port can reach whatever this host can -- an SSRF \
80                 pivot into the operator's private network, and a relay someone else's traffic \
81                 can be laundered through. The only gate is the model: `filter_mode` decides \
82                 whether it is consulted at all, and `allow_all` or a `selective` filter whose \
83                 patterns miss (with `default_action: allow`) connects with no consultation \
84                 whatsoever. `target_host_patterns` and `target_port_ranges` select what the \
85                 model is ASKED about; they do not restrict anything on their own. Do not \
86                 expose this to an untrusted network. \
87                 CONNECT only (no BIND/UDP ASSOCIATE); a connect/auth event with no explicit \
88                 allow action is denied; MITM inspection costs one LLM call per data chunk in \
89                 each direction; the handshake reads time out after 30s but the number of \
90                 concurrent connections is unbounded.",
91            )
92            .build()
93    }
94    fn description(&self) -> &'static str {
95        "SOCKS5 proxy server"
96    }
97    fn example_prompt(&self) -> &'static str {
98        "Start a SOCKS5 proxy on port 1080 that asks before connecting"
99    }
100    fn get_startup_parameters(&self) -> Vec<crate::llm::actions::ParameterDefinition> {
101        use crate::llm::actions::ParameterDefinition;
102        vec![
103                ParameterDefinition {
104                    name: "auth_methods".to_string(),
105                    type_hint: "array".to_string(),
106                    description: "Array of allowed authentication methods: 'none' (no auth) or 'username_password' (RFC 1929)".to_string(),
107                    required: false,
108                    example: json!(["none", "username_password"]),
109                    default: None,
110                },
111                ParameterDefinition {
112                    name: "default_action".to_string(),
113                    type_hint: "string".to_string(),
114                    description: "Default action when no filter matches: 'allow' or 'deny'".to_string(),
115                    required: false,
116                    example: json!("allow"),
117                    default: None,
118                },
119                ParameterDefinition {
120                    name: "filter_mode".to_string(),
121                    type_hint: "string".to_string(),
122                    description: "Filter mode: 'allow_all', 'deny_all', 'ask_llm', or 'selective'".to_string(),
123                    required: false,
124                    example: json!("selective"),
125                    default: None,
126                },
127                ParameterDefinition {
128                    name: "filter".to_string(),
129                    type_hint: "object".to_string(),
130                    description: "Filter configuration object with 'target_host_patterns' (array of regex) and 'target_port_ranges' (array of [start, end])".to_string(),
131                    required: false,
132                    example: json!({
133                        "target_host_patterns": [".*\\.example\\.com"],
134                        "target_port_ranges": [[80, 80], [443, 443]]
135                    }),
136                    default: None,
137                },
138                ParameterDefinition {
139                    name: "mitm_by_default".to_string(),
140                    type_hint: "boolean".to_string(),
141                    description: "Enable Man-in-the-Middle inspection for all allowed connections by default".to_string(),
142                    required: false,
143                    example: json!(false),
144                    default: None,
145                },
146                ParameterDefinition {
147                    name: "idle_timeout_secs".to_string(),
148                    type_hint: "number".to_string(),
149                    description: "Seconds an established tunnel may move no data in either \
150                                  direction before the proxy closes it (default 3600). Traffic \
151                                  either way keeps it open. Raise it for long-idle protocols \
152                                  such as SSH without keepalives."
153                        .to_string(),
154                    required: false,
155                    example: json!(3600),
156                    default: Some(serde_json::json!(super::RELAY_IDLE_TIMEOUT.as_secs())),
157                },
158            ]
159    }
160    fn group_name(&self) -> &'static str {
161        "Proxy & Network"
162    }
163
164    fn get_startup_examples(&self) -> crate::llm::actions::StartupExamples {
165        use crate::llm::actions::StartupExamples;
166
167        // Deterministic: allow every auth attempt and every CONNECT (no MITM),
168        // no LLM call. One script handles both events.
169        let script = r#"import json, sys
170data = json.load(sys.stdin)
171event = data["event"]
172et = data["event_type_id"]
173if et == "socks5_connect_request":
174    actions = [{"type": "allow_socks5_connect", "mitm": False}]
175elif et == "socks5_auth_request":
176    actions = [{"type": "allow_socks5_auth"}]
177else:
178    actions = []
179print(json.dumps({"actions": actions}))"#;
180
181        StartupExamples::new(
182            // LLM mode
183            json!({
184                "type": "open_server",
185                "port": 1080,
186                "base_stack": "socks5",
187                "instruction": "SOCKS5 proxy server. Accept all connections without authentication. Allow all CONNECT requests to pass through."
188            }),
189            // Script mode
190            json!({
191                "type": "open_server",
192                "port": 1080,
193                "base_stack": "socks5",
194                "event_handlers": [{
195                    "event_pattern": "socks5_connect_request",
196                    "handler": {
197                        "type": "script",
198                        "language": "python",
199                        "code": script
200                    }
201                }, {
202                    "event_pattern": "socks5_auth_request",
203                    "handler": {
204                        "type": "script",
205                        "language": "python",
206                        "code": script
207                    }
208                }]
209            }),
210            // Static mode
211            json!({
212                "type": "open_server",
213                "port": 1080,
214                "base_stack": "socks5",
215                "event_handlers": [{
216                    "event_pattern": "socks5_connect_request",
217                    "handler": {
218                        "type": "static",
219                        "actions": [{
220                            "type": "allow_socks5_connect",
221                            "mitm": false
222                        }]
223                    }
224                }]
225            }),
226        )
227    }
228}
229
230// Implement Server trait (server-specific functionality)
231impl Server for Socks5Protocol {
232    fn spawn(
233        &self,
234        ctx: crate::protocol::SpawnContext,
235    ) -> std::pin::Pin<
236        Box<dyn std::future::Future<Output = anyhow::Result<std::net::SocketAddr>> + Send>,
237    > {
238        Box::pin(async move {
239            use crate::server::socks5::Socks5Server;
240            Socks5Server::spawn_with_llm_actions(
241                ctx.legacy_listen_addr(),
242                ctx.llm_client,
243                ctx.state,
244                ctx.status_tx,
245                ctx.server_id,
246                ctx.startup_params,
247            )
248            .await
249        })
250    }
251    fn execute_action(&self, action: serde_json::Value) -> Result<ActionResult> {
252        let action_type = action
253            .get("type")
254            .and_then(|v| v.as_str())
255            .context("Missing 'type' field in action")?;
256
257        match action_type {
258            "allow_socks5_connect" => self.execute_allow_connect(action),
259            "deny_socks5_connect" => self.execute_deny_connect(action),
260            "allow_socks5_auth" => self.execute_allow_auth(action),
261            "deny_socks5_auth" => self.execute_deny_auth(action),
262            "forward_socks5_data" => self.execute_forward_data(action),
263            "modify_socks5_data" => self.execute_modify_data(action),
264            "close_connection" => self.execute_close_connection(action),
265            _ => Err(anyhow::anyhow!("Unknown SOCKS5 action: {}", action_type)),
266        }
267    }
268}
269
270impl Socks5Protocol {
271    fn execute_allow_connect(&self, action: serde_json::Value) -> Result<ActionResult> {
272        let _mitm = action
273            .get("mitm")
274            .and_then(|v| v.as_bool())
275            .unwrap_or(false);
276
277        debug!("SOCKS5 allowing connection (MITM: {})", _mitm);
278
279        // Return NoAction to signal connection should proceed
280        Ok(ActionResult::NoAction)
281    }
282
283    fn execute_deny_connect(&self, action: serde_json::Value) -> Result<ActionResult> {
284        let reason = action
285            .get("reason")
286            .and_then(|v| v.as_str())
287            .unwrap_or("Connection denied by policy");
288
289        debug!("SOCKS5 denying connection: {}", reason);
290
291        // Return CloseConnection to deny
292        Ok(ActionResult::CloseConnection)
293    }
294
295    fn execute_allow_auth(&self, _action: serde_json::Value) -> Result<ActionResult> {
296        debug!("SOCKS5 allowing authentication");
297        Ok(ActionResult::NoAction)
298    }
299
300    fn execute_deny_auth(&self, action: serde_json::Value) -> Result<ActionResult> {
301        let reason = action
302            .get("reason")
303            .and_then(|v| v.as_str())
304            .unwrap_or("Authentication failed");
305
306        debug!("SOCKS5 denying authentication: {}", reason);
307        Ok(ActionResult::CloseConnection)
308    }
309
310    fn execute_forward_data(&self, _action: serde_json::Value) -> Result<ActionResult> {
311        debug!("SOCKS5 forwarding data as-is");
312        // Return NoAction to signal data should be forwarded unchanged
313        Ok(ActionResult::NoAction)
314    }
315
316    fn execute_modify_data(&self, action: serde_json::Value) -> Result<ActionResult> {
317        let modified_data = action
318            .get("data")
319            .and_then(|v| v.as_str())
320            .context("Missing 'data' field for modify_socks5_data action")?;
321
322        // The `data` field used to be documented as "base64 or UTF-8" while the
323        // executor only ever took `.as_bytes()`, so a base64 payload was relayed
324        // as its literal base64 text. Encoding is now explicit and honoured.
325        let bytes = decode_outbound_data(modified_data, &action)?;
326
327        debug!("SOCKS5 modifying data (new length: {} bytes)", bytes.len());
328
329        // Return Output with the modified data
330        Ok(ActionResult::Output(bytes))
331    }
332
333    fn execute_close_connection(&self, action: serde_json::Value) -> Result<ActionResult> {
334        let reason = action
335            .get("reason")
336            .and_then(|v| v.as_str())
337            .unwrap_or("Connection closed by policy");
338
339        debug!("SOCKS5 closing connection: {}", reason);
340        Ok(ActionResult::CloseConnection)
341    }
342}
343
344/// Turn the `data` field of a MITM action into the exact bytes to relay,
345/// honouring the action's optional `encoding` field.
346///
347/// - `encoding` absent or `"utf8"`: the string's UTF-8 bytes are relayed verbatim.
348/// - `encoding` = `"hex"`: `data` is decoded as hex.
349///
350/// There is deliberately no auto-detection: `"48656c6c6f"` is both valid text and
351/// valid hex, so the caller must say which it means.
352fn decode_outbound_data(data: &str, action: &serde_json::Value) -> Result<Vec<u8>> {
353    let encoding = action
354        .get("encoding")
355        .and_then(|v| v.as_str())
356        .unwrap_or("utf8");
357
358    match encoding {
359        "utf8" => Ok(data.as_bytes().to_vec()),
360        "hex" => {
361            let cleaned: String = data
362                .chars()
363                .filter(|c| !c.is_ascii_whitespace() && *c != ':')
364                .collect();
365            let cleaned = cleaned.strip_prefix("0x").unwrap_or(&cleaned);
366
367            if cleaned.len() % 2 != 0 {
368                return Err(anyhow::anyhow!(
369                    "Invalid hex in 'data': expected an even number of hex digits, got {} \
370                     ({data:?}). Each byte is two hex digits, e.g. \"48656c6c6f\" = \"Hello\".",
371                    cleaned.len()
372                ));
373            }
374
375            hex::decode(cleaned).map_err(|e| {
376                anyhow::anyhow!(
377                    "Invalid hex in 'data' ({data:?}): {e}. Use only 0-9/a-f, two digits per \
378                     byte. To relay this string as literal text instead, omit 'encoding' or \
379                     set it to \"utf8\"."
380                )
381            })
382        }
383        other => Err(anyhow::anyhow!(
384            "Unknown 'encoding' value {other:?}. Valid values are \"utf8\" (default) and \"hex\"."
385        )),
386    }
387}
388
389// ============================================================================
390// SOCKS5 Action Definitions
391// ============================================================================
392
393fn allow_socks5_connect_action() -> ActionDefinition {
394    ActionDefinition {
395        name: "allow_socks5_connect".to_string(),
396        description: "Allow SOCKS5 CONNECT request to proceed".to_string(),
397        parameters: vec![Parameter {
398            name: "mitm".to_string(),
399            type_hint: "boolean".to_string(),
400            description: "Enable MITM inspection for this connection (default: false)".to_string(),
401            required: false,
402        }],
403        example: json!({
404            "type": "allow_socks5_connect",
405            "mitm": false
406        }),
407        log_template: Some(
408            LogTemplate::new()
409                .with_info("-> SOCKS5 allow (MITM={mitm})")
410                .with_debug("SOCKS5 allow_socks5_connect: mitm={mitm}"),
411        ),
412    }
413}
414
415fn deny_socks5_connect_action() -> ActionDefinition {
416    ActionDefinition {
417        name: "deny_socks5_connect".to_string(),
418        description: "Deny SOCKS5 CONNECT request".to_string(),
419        parameters: vec![Parameter {
420            name: "reason".to_string(),
421            type_hint: "string".to_string(),
422            description: "Reason for denial (for logging)".to_string(),
423            required: false,
424        }],
425        example: json!({
426            "type": "deny_socks5_connect",
427            "reason": "Blocked by security policy"
428        }),
429        log_template: Some(
430            LogTemplate::new()
431                .with_info("-> SOCKS5 deny: {reason}")
432                .with_debug("SOCKS5 deny_socks5_connect: reason={reason}"),
433        ),
434    }
435}
436
437fn allow_socks5_auth_action() -> ActionDefinition {
438    ActionDefinition {
439        name: "allow_socks5_auth".to_string(),
440        description: "Allow SOCKS5 username/password authentication".to_string(),
441        parameters: vec![],
442        example: json!({
443            "type": "allow_socks5_auth"
444        }),
445        log_template: Some(
446            LogTemplate::new()
447                .with_info("-> SOCKS5 auth allowed")
448                .with_debug("SOCKS5 allow_socks5_auth"),
449        ),
450    }
451}
452
453fn deny_socks5_auth_action() -> ActionDefinition {
454    ActionDefinition {
455        name: "deny_socks5_auth".to_string(),
456        description: "Deny SOCKS5 authentication".to_string(),
457        parameters: vec![Parameter {
458            name: "reason".to_string(),
459            type_hint: "string".to_string(),
460            description: "Reason for denial (for logging)".to_string(),
461            required: false,
462        }],
463        example: json!({
464            "type": "deny_socks5_auth",
465            "reason": "Invalid credentials"
466        }),
467        log_template: Some(
468            LogTemplate::new()
469                .with_info("-> SOCKS5 auth denied: {reason}")
470                .with_debug("SOCKS5 deny_socks5_auth: reason={reason}"),
471        ),
472    }
473}
474
475fn forward_socks5_data_action() -> ActionDefinition {
476    ActionDefinition {
477        name: "forward_socks5_data".to_string(),
478        description: "Forward SOCKS5 data without modification (MITM mode)".to_string(),
479        parameters: vec![],
480        example: json!({
481            "type": "forward_socks5_data"
482        }),
483        log_template: Some(
484            LogTemplate::new()
485                .with_info("-> SOCKS5 forward")
486                .with_debug("SOCKS5 forward_socks5_data"),
487        ),
488    }
489}
490
491fn modify_socks5_data_action() -> ActionDefinition {
492    ActionDefinition {
493        name: "modify_socks5_data".to_string(),
494        description: "Modify SOCKS5 data before forwarding (MITM mode)".to_string(),
495        parameters: vec![
496            Parameter {
497                name: "data".to_string(),
498                type_hint: "string".to_string(),
499                description: "Replacement payload. Interpreted according to 'encoding'.".to_string(),
500                required: true,
501            },
502            Parameter {
503                name: "encoding".to_string(),
504                type_hint: "string".to_string(),
505                description: "How to interpret 'data': \"utf8\" (default) relays the string's bytes verbatim; \"hex\" decodes it as hex first. Match the 'encoding' reported on the event to round-trip binary payloads.".to_string(),
506                required: false,
507            },
508        ],
509        example: json!({
510            "type": "modify_socks5_data",
511            "data": "Modified payload"
512        }),
513        log_template: Some(
514            LogTemplate::new()
515                .with_info("-> SOCKS5 modify data")
516                .with_debug("SOCKS5 modify_socks5_data"),
517        ),
518    }
519}
520
521fn close_connection_action() -> ActionDefinition {
522    ActionDefinition {
523        name: "close_connection".to_string(),
524        description: "Close the SOCKS5 connection (MITM mode)".to_string(),
525        parameters: vec![Parameter {
526            name: "reason".to_string(),
527            type_hint: "string".to_string(),
528            description: "Reason for closing (for logging)".to_string(),
529            required: false,
530        }],
531        example: json!({
532            "type": "close_connection",
533            "reason": "Suspicious data detected"
534        }),
535        log_template: Some(
536            LogTemplate::new()
537                .with_info("-> SOCKS5 close: {reason}")
538                .with_debug("SOCKS5 close_connection: reason={reason}"),
539        ),
540    }
541}
542
543// ============================================================================
544// SOCKS5 Event Type Constants
545// ============================================================================
546
547pub static SOCKS5_AUTH_REQUEST_EVENT: LazyLock<EventType> = LazyLock::new(|| {
548    EventType::new(
549        "socks5_auth_request",
550        "SOCKS5 client authentication request (username/password)",
551        json!({
552            "type": "allow_socks5_auth"
553        }),
554    )
555    .with_parameters(vec![
556        Parameter {
557            name: "username".to_string(),
558            type_hint: "string".to_string(),
559            description: "Username provided by client".to_string(),
560            required: true,
561        },
562        Parameter {
563            name: "password".to_string(),
564            type_hint: "string".to_string(),
565            description: "Password provided by client".to_string(),
566            required: true,
567        },
568    ])
569    .with_actions(vec![allow_socks5_auth_action(), deny_socks5_auth_action()])
570});
571
572pub static SOCKS5_CONNECT_REQUEST_EVENT: LazyLock<EventType> = LazyLock::new(|| {
573    EventType::new(
574        "socks5_connect_request",
575        "SOCKS5 CONNECT request to target address",
576        json!({
577            "type": "allow_socks5_connect",
578            "mitm": false
579        }),
580    )
581    .with_parameters(vec![
582        Parameter {
583            name: "target".to_string(),
584            type_hint: "string".to_string(),
585            description: "Target address (IP or domain:port)".to_string(),
586            required: true,
587        },
588        Parameter {
589            name: "username".to_string(),
590            type_hint: "string".to_string(),
591            description: "Authenticated username (if any)".to_string(),
592            required: false,
593        },
594    ])
595    .with_actions(vec![
596        allow_socks5_connect_action(),
597        deny_socks5_connect_action(),
598    ])
599    .with_log_template(
600        LogTemplate::new()
601            .with_info("SOCKS5 {client_ip} -> {target}")
602            .with_debug("SOCKS5 connection from {client_ip}:{client_port} to {target}")
603            .with_trace("SOCKS5: {json_pretty(.)}"),
604    )
605});
606
607pub static SOCKS5_DATA_TO_TARGET_EVENT: LazyLock<EventType> = LazyLock::new(|| {
608    EventType::new(
609        "socks5_data_to_target",
610        "Data from client to target (MITM inspection mode)",
611        json!({
612            "type": "forward_socks5_data"
613        }),
614    )
615    .with_parameters(vec![
616        Parameter {
617            name: "data".to_string(),
618            type_hint: "string".to_string(),
619            description: "Data being sent from client to target. Printable payloads arrive as text, binary arrives hex-encoded; check 'encoding'.".to_string(),
620            required: true,
621        },
622        Parameter {
623            name: "encoding".to_string(),
624            type_hint: "string".to_string(),
625            description: "How 'data' is encoded: \"utf8\" or \"hex\". Pass the same value to modify_socks5_data when replacing the payload.".to_string(),
626            required: true,
627        },
628        Parameter {
629            name: "target".to_string(),
630            type_hint: "string".to_string(),
631            description: "Target address (IP or domain:port)".to_string(),
632            required: true,
633        },
634        Parameter {
635            name: "username".to_string(),
636            type_hint: "string".to_string(),
637            description: "Authenticated username (if any)".to_string(),
638            required: false,
639        },
640    ])
641    .with_actions(vec![
642        forward_socks5_data_action(),
643        modify_socks5_data_action(),
644        close_connection_action(),
645    ])
646});
647
648pub static SOCKS5_DATA_FROM_TARGET_EVENT: LazyLock<EventType> = LazyLock::new(|| {
649    EventType::new(
650        "socks5_data_from_target",
651        "Data from target to client (MITM inspection mode)",
652        json!({
653            "type": "forward_socks5_data"
654        }),
655    )
656    .with_parameters(vec![
657        Parameter {
658            name: "data".to_string(),
659            type_hint: "string".to_string(),
660            description: "Data being sent from target to client. Printable payloads arrive as text, binary arrives hex-encoded; check 'encoding'.".to_string(),
661            required: true,
662        },
663        Parameter {
664            name: "encoding".to_string(),
665            type_hint: "string".to_string(),
666            description: "How 'data' is encoded: \"utf8\" or \"hex\". Pass the same value to modify_socks5_data when replacing the payload.".to_string(),
667            required: true,
668        },
669        Parameter {
670            name: "target".to_string(),
671            type_hint: "string".to_string(),
672            description: "Target address (IP or domain:port)".to_string(),
673            required: true,
674        },
675        Parameter {
676            name: "username".to_string(),
677            type_hint: "string".to_string(),
678            description: "Authenticated username (if any)".to_string(),
679            required: false,
680        },
681    ])
682    .with_actions(vec![
683        forward_socks5_data_action(),
684        modify_socks5_data_action(),
685        close_connection_action(),
686    ])
687});
688
689pub fn get_socks5_event_types() -> Vec<EventType> {
690    vec![
691        SOCKS5_AUTH_REQUEST_EVENT.clone(),
692        SOCKS5_CONNECT_REQUEST_EVENT.clone(),
693        SOCKS5_DATA_TO_TARGET_EVENT.clone(),
694        SOCKS5_DATA_FROM_TARGET_EVENT.clone(),
695    ]
696}