11import { loadOrCreateIdentity } from "../crypto/identity" ;
2+ import net from "node:net" ;
23import { releaseManifest } from "../node/manifest" ;
34import { loadConfig } from "../node/state" ;
45import { capabilitiesRecord } from "../runtime/capabilities" ;
@@ -34,6 +35,7 @@ export async function startControlClient(options: ControlClientOptions) {
3435 const heartbeatIntervalMs = options . heartbeatIntervalMs ?? 30_000 ;
3536 let activeRequests = 0 ;
3637 const activeStreams = new Set < string > ( ) ;
38+ const rawStreams = new Map < string , net . Socket > ( ) ;
3739 let preparedUpdate : {
3840 updateId : string ;
3941 manifest : ReleaseManifest ;
@@ -82,17 +84,69 @@ export async function startControlClient(options: ControlClientOptions) {
8284 }
8385
8486 if ( message . type === MESSAGE_TYPE . STREAM_OPEN ) {
85- if ( message . target !== "proxy-session" ) {
87+ if ( message . target === "proxy-session" ) {
88+ activeStreams . add ( message . stream_id ) ;
89+ return ;
90+ }
91+
92+ const target = parseRawTunnelTarget ( message . target ) ;
93+ if ( ! target ) {
8694 await client . send ( createErrorMessage ( {
8795 code : "unsupported_stream_target" ,
8896 message : `Unsupported stream target: ${ message . target ?? "" } ` ,
8997 } ) ) ;
98+ return ;
9099 }
100+
91101 activeStreams . add ( message . stream_id ) ;
102+ const socket = net . createConnection ( { host : target . host , port : target . port } ) ;
103+ rawStreams . set ( message . stream_id , socket ) ;
104+
105+ socket . on ( "data" , ( data ) => {
106+ void client . send ( {
107+ type : MESSAGE_TYPE . STREAM_DATA ,
108+ timestamp : nowSeconds ( ) ,
109+ stream_id : message . stream_id ,
110+ data : data . toString ( "base64" ) ,
111+ encoding : "base64" ,
112+ } ) . catch ( ( ) => undefined ) ;
113+ } ) ;
114+
115+ socket . on ( "close" , ( ) => {
116+ rawStreams . delete ( message . stream_id ) ;
117+ activeStreams . delete ( message . stream_id ) ;
118+ void client . send ( {
119+ type : MESSAGE_TYPE . STREAM_CLOSE ,
120+ timestamp : nowSeconds ( ) ,
121+ stream_id : message . stream_id ,
122+ reason : "target closed" ,
123+ } ) . catch ( ( ) => undefined ) ;
124+ } ) ;
125+
126+ socket . on ( "error" , ( error ) => {
127+ rawStreams . delete ( message . stream_id ) ;
128+ activeStreams . delete ( message . stream_id ) ;
129+ void client . send ( createErrorMessage ( {
130+ code : "raw_tunnel_failed" ,
131+ message : error . message ,
132+ } ) ) . catch ( ( ) => undefined ) ;
133+ void client . send ( {
134+ type : MESSAGE_TYPE . STREAM_CLOSE ,
135+ timestamp : nowSeconds ( ) ,
136+ stream_id : message . stream_id ,
137+ reason : error . message ,
138+ } ) . catch ( ( ) => undefined ) ;
139+ } ) ;
92140 return ;
93141 }
94142
95143 if ( message . type === MESSAGE_TYPE . STREAM_DATA ) {
144+ const rawSocket = rawStreams . get ( message . stream_id ) ;
145+ if ( rawSocket ) {
146+ rawSocket . write ( Buffer . from ( message . data , "base64" ) ) ;
147+ return ;
148+ }
149+
96150 activeRequests += 1 ;
97151 try {
98152 const output = await executeProxySessionMessage ( Buffer . from ( message . data , "base64" ) ) ;
@@ -110,6 +164,11 @@ export async function startControlClient(options: ControlClientOptions) {
110164 }
111165
112166 if ( message . type === MESSAGE_TYPE . STREAM_CLOSE ) {
167+ const rawSocket = rawStreams . get ( message . stream_id ) ;
168+ if ( rawSocket ) {
169+ rawStreams . delete ( message . stream_id ) ;
170+ rawSocket . destroy ( ) ;
171+ }
113172 activeStreams . delete ( message . stream_id ) ;
114173 return ;
115174 }
@@ -207,3 +266,17 @@ export async function startControlClient(options: ControlClientOptions) {
207266 } ,
208267 } ;
209268}
269+
270+ function parseRawTunnelTarget ( value : string | undefined ) : { host : string ; port : number } | null {
271+ if ( ! value ) return null ;
272+ try {
273+ const parsed = JSON . parse ( value ) as { kind ?: string ; host ?: string ; port ?: number } ;
274+ if ( parsed . kind !== "raw-tunnel" || ! parsed . host ) return null ;
275+ if ( typeof parsed . port !== "number" || ! Number . isInteger ( parsed . port ) || parsed . port < 1 || parsed . port > 65535 ) {
276+ return null ;
277+ }
278+ return { host : parsed . host , port : parsed . port } ;
279+ } catch {
280+ return null ;
281+ }
282+ }
0 commit comments