From 21b7ece45364c8147ea82ac405bf085d2a2b0c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Assun=C3=A7=C3=A3o?= Date: Thu, 2 Oct 2025 16:37:35 +0100 Subject: [PATCH 1/2] SOCKS5 support --- lib/tds/protocol.ex | 79 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/lib/tds/protocol.ex b/lib/tds/protocol.ex index 44c1057..edc70eb 100644 --- a/lib/tds/protocol.ex +++ b/lib/tds/protocol.ex @@ -363,8 +363,32 @@ defmodule Tds.Protocol do s = %{s | opts: opts} + # SOCKS5 proxy support + proxy_host = Keyword.get(opts, :proxy_host) + # Default SOCKS5 port, but Oxylabs may use 7777 + proxy_port = Keyword.get(opts, :proxy_port, 1080) + proxy_username = Keyword.get(opts, :proxy_username) + proxy_password = Keyword.get(opts, :proxy_password) + # tcp_opts = [:binary, packet: :raw, active: false] ++ Keyword.get(opts, :socket_options, []) + + connection_result = + if proxy_host do + socks_connect( + proxy_host, + proxy_port, + host, + port, + proxy_username, + proxy_password, + sock_opts, + timeout + ) + else + :gen_tcp.connect(host, port, sock_opts, timeout) + end + # Initalize TCP connection with the SQL Server - with {:ok, sock} <- :gen_tcp.connect(host, port, sock_opts, timeout), + with {:ok, sock} <- connection_result, {:ok, buffers} <- :inet.getopts(sock, [:sndbuf, :recbuf, :buffer]), :ok <- :inet.setopts(sock, buffer: max_buf_size(buffers)) do # Send Prelogin message to SQL Server @@ -382,6 +406,59 @@ defmodule Tds.Protocol do end end + defp socks_connect( + proxy_host, + proxy_port, + target_host, + target_port, + username, + password, + sock_opts, + timeout + ) do + with {:ok, sock} <- + :gen_tcp.connect(String.to_charlist(proxy_host), proxy_port, sock_opts, timeout) do + # SOCKS5 greeting: version 5, support no-auth (0x00) and user/pass (0x02) + :gen_tcp.send(sock, <<5, 2, 0, 2>>) + {:ok, <<5, method>>} = :gen_tcp.recv(sock, 2) + + case method do + # User/pass auth + 2 -> + user_bin = :erlang.iolist_to_binary(username || "") + pass_bin = :erlang.iolist_to_binary(password || "") + + :gen_tcp.send( + sock, + <<1, byte_size(user_bin)>> <> user_bin <> <> <> pass_bin + ) + + {:ok, <<1, status>>} = :gen_tcp.recv(sock, 2) + if status != 0, do: raise("SOCKS5 auth failed") + + # No auth, proceed + 0 -> + :ok + + _ -> + raise "SOCKS5 unsupported method" + end + + # Connect request: ver 5, cmd 1 (connect), rsv 0, addr type 3 (domain), addr len + addr, port + host_bin = :erlang.iolist_to_binary(target_host) + + :gen_tcp.send( + sock, + <<5, 1, 0, 3, byte_size(host_bin)>> <> + host_bin <> <> + ) + + # Read full reply + {:ok, <<5, rep, 0, _atype, _rest::binary>>} = :gen_tcp.recv(sock, 0) + if rep == 0, do: {:ok, sock}, else: raise("SOCKS5 connect failed: #{rep}") + end + end + defp parse_udp( {_, 1434, <<_head::binary-3, data::binary>>}, %{opts: opts, usock: sock} = s From 116c6b06a88794fe25130761017ac50ea1ab59fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Assun=C3=A7=C3=A3o?= Date: Thu, 2 Oct 2025 16:49:54 +0100 Subject: [PATCH 2/2] Cleanup --- lib/tds/protocol.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/tds/protocol.ex b/lib/tds/protocol.ex index edc70eb..dfd8e6e 100644 --- a/lib/tds/protocol.ex +++ b/lib/tds/protocol.ex @@ -365,11 +365,9 @@ defmodule Tds.Protocol do # SOCKS5 proxy support proxy_host = Keyword.get(opts, :proxy_host) - # Default SOCKS5 port, but Oxylabs may use 7777 proxy_port = Keyword.get(opts, :proxy_port, 1080) proxy_username = Keyword.get(opts, :proxy_username) proxy_password = Keyword.get(opts, :proxy_password) - # tcp_opts = [:binary, packet: :raw, active: false] ++ Keyword.get(opts, :socket_options, []) connection_result = if proxy_host do