我的要求是从我的组织网络连接到外部 SQL Server 数据库。我的 .NET Core 应用程序托管在 Openshift 容器中,它必须通过内部 Bluecoat Web 代理 -> 防火墙到达外部数据库。
现在我的方法是创建一个到这个外部数据库的 TCP 隧道,然后使用下面的连接字符串连接到数据库。
var connectionString = "Data Source=127.0.0.1,1433;Initial Catalog=myDatabase;Integrated Security=false;User ID=username;Password=sqlPassword;TrustServerCertificate=true;";
TCP 隧道创建成功,但当我尝试连接到数据库时,在建立与 SQL Server 的连接时出现网络相关或特定于实例的错误。找不到服务器或无法访问服务器。 (更多的是一般错误)
当我检查双方的日志时,我可以看到 TCP 隧道创建成功,但从外部来看,我随后看到 [RST,ACK](重置确认)。
我想知道我当前尝试的方法是否正确,或者是否有其他方法可以更好地做到这一点。
这是我的 TCP 隧道创建和 SQL 连接代码:
// Connect to the proxy server
var tcpClient = new TcpClient(proxyIP, proxyPort);
NetworkStream networkStream = tcpClient.GetStream();
// Create the proxy authentication header
string authInfo = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{proxyUsername}:{proxyPassword}"));
string connectRequest = $"CONNECT {targetHost}:{targetPort} HTTP/1.1\r\n" +
$"Host: {targetHost}:{targetPort}\r\n" +
$"Proxy-Authorization: Basic {authInfo}\r\n" +
"Proxy-Connection: Keep-Alive\r\n\r\n";
// Send the CONNECT request to the proxy
byte[] requestBytes = Encoding.ASCII.GetBytes(connectRequest);
await networkStream.WriteAsync(requestBytes, 0, requestBytes.Length);
// Read the response from the proxy
StreamReader reader = new StreamReader(networkStream);
string responseLine = await reader.ReadLineAsync();
if (!responseLine.Contains("200 Connection established"))
{
Console.WriteLine("Failed to establish a connection with the target server.");
return;
}
// At this point, the tunnel is established
Console.WriteLine("Tunnel established");
// from here I'm connecting to my database
var con = new SqlConnection(connectionString);
// This line throws the error
con.Open();
我还尝试了下面的代码从 SQL 服务器读取。但我没有看到任何回应。这是正确的做法吗?
那么,对于数据读取,我可以使用以下内容吗?我尝试了,但没有看到任何反应。
byte[] sqlQueryBytes = Encoding.ASCII.GetBytes("SELECT COUNT(*) FROM tblTest");
networkStream.Write(sqlQueryBytes, 0, sqlQueryBytes.Length);
byte[] responseBuffer = new byte[4096];
int bytesRead = networkStream.Read(responseBuffer, 0, responseBuffer.Length);
string response = Encoding.ASCII.GetString(responseBuffer, 0, bytesRead);
Console.WriteLine(response);
通过创建TcpListener解决了这个问题,监听器将创建TCP隧道。因此,从代码中,我们访问 localhost:(listnerPort)。下面是对我有用的代码示例,
为监听器创建BackgroundService,
public class TcpTunnelService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
string proxyAddress = "11.22.22.11";
int proxyPort = 8081;
string sqlAddress = "11.22.33.44";
int sqlPort = 1433;
var listener = new TcpListener(IPAddress.Any, 8888);
listener.Start();
while (!stoppingToken.IsCancellationRequested)
{
var client = await listener.AcceptTcpClientAsync();
_ = HandleClientAsync(client, proxyAddress, proxyPort, sqlAddress, sqlPort);
}
}
private async Task HandleClientAsync(TcpClient client, string proxyAddress, int proxyPort, string sqlAddress, int sqlPort)
{
using(client)
{
TcpClient proxyClient = new TcpClent();
await proxyClient.ConnectAsync(proxyAddress, proxyPort);
using(proxyClent)
using(NetworkStream cs = clent.Stream())
using(NetworkStream ps = proxyClent.GetStream())
{
string proxyCredentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{yourProxyUserName}:{yourProxyPassword}"));
StreamWriter w = new StreamWriter(ps);
w.WriteLine($"CONNECT {sqlAddress}:{sqlPort} HTTP/1.1");
w.WriteLine($"Host: {sqlAddress}:{sqlPort}");
w.WriteLine($"Proxy-Authorization: Basic {proxyCredentials}");
w.WriteLine("Connection: keep-alive");
w.WriteLine();
w.Flush();
StreamReader sr = new StreamReader(ps);
string res = await sr.ReadLineAsync();
if(!res.Contains("200 Connection established"))
{
//errror
return;
}
else
{
//success
}
Task cToProxy = RelayDataAsync(cs, ps);
Task pToClient = RelayDataAsync(ps, cs);
await Task.WhenAny(cToProxy, pToClient);
}
}
}
private async Task RelayDataAsync(NetworkStream source, NetworkStream dest)
{
byte[] buf= new byte[8192];
int bytesRead;
while(bytesRead = await source.ReadAsync(buf, 0, buffer.Length)) > 0)
{
await dest.WriteAsync(buf, 0, bytesRead);
}
}
}
然后像下面一样注册服务,
services.AddHostedService<TcpListenerService>();
连接字符串必须如下所示,
"Data Source=localhost,8888; Initial Catelog= ....."