@@ -1158,6 +1158,33 @@ public void runTcp(String host, int port, long idleTimeoutMs) throws IOException
11581158 });
11591159 }
11601160
1161+ /**
1162+ * Serve the identity-preserving raw upstream consumed by
1163+ * {@code vgi-iroh-bridge}. The upstream is loopback-only and requires the
1164+ * bridge's EndpointId-bearing PROXY-v2 preamble on every connection.
1165+ *
1166+ * @param host loopback bind host
1167+ * @param port bind port; {@code 0} selects a free port
1168+ * @param idleTimeoutMs idle watchdog in milliseconds; {@code <= 0} disables it
1169+ * @param bridge trusted bridge identity configuration
1170+ * @throws IOException if the socket cannot be bound or served
1171+ */
1172+ public void runIrohTcpUpstream (
1173+ String host , int port , long idleTimeoutMs , IrohBridgeOptions bridge ) throws IOException {
1174+ requireLoopback (host , "Iroh raw bridge upstream" );
1175+ if (bridge == null ) throw new IllegalArgumentException ("Iroh bridge options are required" );
1176+ TcpSocketTransport .serveForever (
1177+ host ,
1178+ port ,
1179+ buildServer (false ),
1180+ idleTimeoutMs ,
1181+ (boundHost , boundPort ) -> {
1182+ System .out .println ("TCP:" + boundHost + ":" + boundPort );
1183+ System .out .flush ();
1184+ },
1185+ bridge .tcpServerOptions ());
1186+ }
1187+
11611188 /**
11621189 * Parsed {@code [HOST:]PORT} TCP bind spec. Host defaults to loopback.
11631190 *
@@ -1195,13 +1222,32 @@ public void runHttp(String host, int port) throws Exception {
11951222 runHttp (HttpServer .Config .builder ().host (host ).port (port ).build ());
11961223 }
11971224
1225+ /**
1226+ * Run the ordinary VGI HTTP server behind {@code vgi-iroh-bridge}, retaining
1227+ * HTTP limits, continuations, externalized batches, and the authenticated
1228+ * client EndpointId.
1229+ *
1230+ * @param host loopback bind host
1231+ * @param port bind port; {@code 0} selects a free port
1232+ * @param bridge trusted bridge identity configuration
1233+ * @throws Exception if the server fails to start or serve
1234+ */
1235+ public void runHttp (String host , int port , IrohBridgeOptions bridge ) throws Exception {
1236+ requireLoopback (host , "Iroh HTTP bridge upstream" );
1237+ if (bridge == null ) throw new IllegalArgumentException ("Iroh bridge options are required" );
1238+ runHttp (bridge .apply (HttpServer .Config .builder ().host (host ).port (port )).build ());
1239+ }
1240+
11981241 /**
11991242 * Canonical CLI dispatcher used by worker {@code main} methods. Parses
1200- * the four flags every VGI worker accepts and runs the matching transport:
1243+ * the transport flags every VGI worker accepts and runs the matching transport:
12011244 * <ul>
12021245 * <li>{@code --unix <path>}: AF_UNIX socket (launcher protocol)
12031246 * <li>{@code --tcp [<host>:]<port>}: TCP socket (launcher protocol)
12041247 * <li>{@code --http} with optional {@code --host}, {@code --port}: HTTP
1248+ * <li>{@code --iroh-raw-upstream [<host>:]<port>}: trusted raw bridge upstream
1249+ * <li>{@code --iroh-issuer}, repeated {@code --iroh-trusted-proxy}, and
1250+ * {@code --iroh-observe}: Iroh bridge trust and authentication mode
12051251 * <li>{@code --idle-timeout <seconds>}: passed to {@code runUnixSocket} / {@code runTcp}
12061252 * <li>(default): stdio
12071253 * </ul>
@@ -1231,6 +1277,10 @@ public void runFromArgs(String[] args,
12311277 int port = 0 ;
12321278 String unixSocket = null ;
12331279 String tcpAddr = null ;
1280+ String irohRawUpstream = null ;
1281+ String irohIssuer = null ;
1282+ List <String > irohTrustedProxies = new ArrayList <>();
1283+ boolean irohObserve = false ;
12341284 long idleTimeoutMs = 0 ;
12351285 for (int i = 0 ; i < args .length ; i ++) {
12361286 switch (args [i ]) {
@@ -1239,20 +1289,55 @@ public void runFromArgs(String[] args,
12391289 case "--port" -> port = Integer .parseInt (args [++i ]);
12401290 case "--unix" -> unixSocket = args [++i ];
12411291 case "--tcp" -> tcpAddr = args [++i ];
1292+ case "--iroh-raw-upstream" -> irohRawUpstream = args [++i ];
1293+ case "--iroh-issuer" -> irohIssuer = args [++i ];
1294+ case "--iroh-trusted-proxy" -> irohTrustedProxies .add (args [++i ]);
1295+ case "--iroh-observe" -> irohObserve = true ;
12421296 case "--idle-timeout" -> idleTimeoutMs =
12431297 (long ) (Double .parseDouble (args [++i ]) * 1000.0 );
12441298 default -> { System .err .println ("unknown arg: " + args [i ]); System .exit (2 ); }
12451299 }
12461300 }
1301+ int selectedTransports = (http ? 1 : 0 )
1302+ + (unixSocket != null ? 1 : 0 )
1303+ + (tcpAddr != null ? 1 : 0 )
1304+ + (irohRawUpstream != null ? 1 : 0 );
1305+ if (selectedTransports > 1 ) {
1306+ throw new IllegalArgumentException (
1307+ "--http, --unix, --tcp, and --iroh-raw-upstream are mutually exclusive" );
1308+ }
1309+ if ((irohIssuer != null || !irohTrustedProxies .isEmpty () || irohObserve )
1310+ && irohRawUpstream == null && !http ) {
1311+ throw new IllegalArgumentException (
1312+ "Iroh bridge options require --http or --iroh-raw-upstream" );
1313+ }
1314+ if ((irohRawUpstream != null || !irohTrustedProxies .isEmpty () || irohObserve )
1315+ && irohIssuer == null ) {
1316+ throw new IllegalArgumentException ("Iroh bridge options require --iroh-issuer" );
1317+ }
12471318 try {
12481319 if (unixSocket != null ) {
12491320 runUnixSocket (Path .of (unixSocket ), idleTimeoutMs );
12501321 } else if (tcpAddr != null ) {
12511322 TcpAddr a = parseTcpAddr (tcpAddr );
12521323 runTcp (a .host (), a .port (), idleTimeoutMs );
1324+ } else if (irohRawUpstream != null ) {
1325+ if (irohIssuer == null ) {
1326+ throw new IllegalArgumentException (
1327+ "--iroh-raw-upstream requires --iroh-issuer" );
1328+ }
1329+ TcpAddr a = parseTcpAddr (irohRawUpstream );
1330+ runIrohTcpUpstream (a .host (), a .port (), idleTimeoutMs ,
1331+ IrohBridgeOptions .fromArgs (
1332+ irohIssuer , irohTrustedProxies , !irohObserve ));
12531333 } else if (http ) {
12541334 HttpServer .Config .Builder b = HttpServer .Config .builder ().host (host ).port (port );
12551335 if (httpCustomizer != null ) b = httpCustomizer .apply (b );
1336+ if (irohIssuer != null ) {
1337+ requireLoopback (host , "Iroh HTTP bridge upstream" );
1338+ b = IrohBridgeOptions .fromArgs (
1339+ irohIssuer , irohTrustedProxies , !irohObserve ).apply (b );
1340+ }
12561341 runHttp (b .build ());
12571342 } else {
12581343 runStdio ();
@@ -1309,4 +1394,10 @@ public void runHttp(HttpServer.Config config) throws Exception {
13091394 }, "vgi-http-shutdown" ));
13101395 http .join ();
13111396 }
1397+
1398+ private static void requireLoopback (String host , String label ) {
1399+ if (!("127.0.0.1" .equals (host ) || "::1" .equals (host ) || "localhost" .equals (host ))) {
1400+ throw new IllegalArgumentException (label + " must bind loopback, got " + host );
1401+ }
1402+ }
13121403}
0 commit comments