mirror of
https://github.com/fiddyschmitt/File-Tunnel.git
synced 2026-11-01 11:21:35 +00:00
Adds ft equivalents of SSH's dynamic forwarding: -D [bind:]port opens a local SOCKS proxy (the remote side dials each destination), and a bare -R [bind:]port (no destination) opens one on the remote side (the local side dials). Supports the same surface modern OpenSSH implements: SOCKS4, SOCKS4a and SOCKS5; CONNECT only; no authentication; no BIND or UDP ASSOCIATE. A new SocksServer performs the per-connection handshake and produces a tcp://host:port destination that flows through the existing Connect/relay path unchanged - so hostnames (SOCKS4a/5) and IPv6 resolve on the exit side, exactly like SSH. -R rides the existing CreateListener command via a "socks" protocol sentinel and a listen-spec partition in ProvideRemoteForwardsToCounterpart. SOCKS replies carry accurate result codes. Since ft had no positive per-connection dial signal, a new ConnectResult command (id 12) reports the outcome (SocketError mapped to a SOCKS reply code) from the dialing side back to the SOCKS host, which blocks until it arrives before answering its client. The reply is written before any relaying so it is the first thing the client sees. No existing wire command changed; -L/-U behaviour is untouched. Tested hermetically (SOCKS4/4a/5 negotiator byte-level tests, listen-spec parsing, ConnectResult serialization, and in-process end-to-end -D/-R transfers incl. far-side resolution and an accurate-failure-reply case) and verified with a real curl client in both directions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SjajJ7d3BhSZGfsMrGRS5S
85 lines
2.4 KiB
C#
85 lines
2.4 KiB
C#
using ft.Streams;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace ft.Commands
|
|
{
|
|
public abstract class Command
|
|
{
|
|
public Command()
|
|
{
|
|
|
|
}
|
|
|
|
public abstract byte CommandId { get; }
|
|
public ulong PacketNumber { get; set; } = (ulong)Random.Shared.NextInt64();
|
|
public uint CRC { get; set; }
|
|
|
|
protected abstract void Deserialize(BinaryReader reader);
|
|
protected abstract void Serialize(BinaryWriter writer);
|
|
|
|
public void Serialise(BinaryWriter writer)
|
|
{
|
|
if (writer.BaseStream is not HashingStream hashingStream) return;
|
|
hashingStream.StartHashing();
|
|
|
|
writer.Write(CommandId);
|
|
|
|
//PacketNumber = SentPacketCount++;
|
|
writer.Write(PacketNumber);
|
|
|
|
Serialize(writer);
|
|
|
|
hashingStream.StopHashing();
|
|
|
|
CRC = hashingStream.GetCrc32();
|
|
writer.Write(CRC);
|
|
|
|
//Don't flush here. This way, we can write multiple commands to the file in one go.
|
|
//writer.Flush();
|
|
}
|
|
|
|
public static Command? Deserialise(BinaryReader reader)
|
|
{
|
|
if (reader.BaseStream is not HashingStream hashingStream) return null;
|
|
hashingStream.StartHashing();
|
|
|
|
var commandId = reader.ReadByte();
|
|
|
|
Command? result = commandId switch
|
|
{
|
|
Connect.COMMAND_ID => new Connect(),
|
|
ConnectResult.COMMAND_ID => new ConnectResult(),
|
|
CreateListener.COMMAND_ID => new CreateListener(),
|
|
Forward.COMMAND_ID => new Forward(),
|
|
Purge.COMMAND_ID => new Purge(),
|
|
TearDown.COMMAND_ID => new TearDown(),
|
|
Ping.COMMAND_ID => new Ping(),
|
|
_ => null
|
|
};
|
|
|
|
if (result != null)
|
|
{
|
|
result.PacketNumber = reader.ReadUInt64();
|
|
result.Deserialize(reader);
|
|
|
|
hashingStream.StopHashing();
|
|
|
|
//check the crc
|
|
var actualCrc = hashingStream.GetCrc32();
|
|
result.CRC = reader.ReadUInt32();
|
|
|
|
if (actualCrc != result.CRC)
|
|
{
|
|
throw new InvalidDataException("Command is invalid (CRC mismatch).");
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
}
|