如何重用监听器/连接?Golang

huangapple go评论95阅读模式
英文:

How to reuse listener/connection ? Golang

问题

我正在尝试通过第三方服务器(也称为反向连接)将位于NAT后面的计算机与互联网连接起来。我在两个端口上进行监听。一个端口(dstNet)连接的是位于NAT后面的机器,另一个端口连接的是互联网客户端。

问题是我不知道如何处理位于NAT后面的机器的断开连接。即使机器重新连接,流量也不再被处理发送/写入...我得到的是[DEBUG] socks: Copied 0 bytes to client,这当然是我的警告。下面是代码。它相当长,但我找不到要删除的部分。

// 在dstNet上建立一个桥接,通常dstNet位于NAT后面,srcNet是客户端,希望通过NAT机器路由流量。
package main

import (
	"bufio"
	"errors"
	log "github.com/golang/glog"
	"io"
	"net"
	"time"
)

const (
	// 在dstNet上监听,以便我们可以与NAT客户端建立连接
	dstNet = "0.0.0.0:9000"
	// 在srcNet上监听,以便我们可以获取要转发到dstNet的流量
	srcNet = "0.0.0.0:9001"
)

var errCh = make(chan error, 1)

// 创建一个通道来发送反向连接
var lrCh = make(chan net.Conn, 1)

func listenDst() {
	// 在dstNet上监听
	lr, err := net.Listen("tcp", dstNet)
	if err != nil {
		log.Error(err)
		errCh <- err
		return
	}
	// 接受连接
	for {
		lrConn, err := lr.Accept()
		if err != nil {
			log.Error(err)
			errCh <- err
			return
		}
		log.Errorf("sent connection")
		// lrConn.SetReadDeadline(time.Now().Add(10 * time.Second))
		lrCh <- lrConn
	}
}

func main() {

	go func() {
		for err := range errCh {
			if err != nil {
				panic(err)
			}
		}
	}()
	// 监听NAT服务器
	go listenDst()

	// 监听客户端连接
	l, err := net.Listen("tcp", srcNet)
	if err != nil {
		log.Error(err)
		panic(err)
	}
	// 接受连接
	for {
		conn, err := l.Accept()
		if err != nil {
			log.Error(err)
			panic(err)
		}
		// 处理连接
		go func(conn net.Conn) {
			defer conn.Close()
			bufConn := bufio.NewReader(conn)
			dst := <-lrCh
			defer dst.Close()

			// 开始代理
			errCh2 := make(chan error, 2)
			go proxy("target", dst, bufConn, errCh2)
			go proxy("client", conn, dst, errCh2)

			// 等待
			var ei int
			for err = range errCh2 {
				switch {
				case err != nil && err.Error() == "no byte":
					log.Error(err)
				case err != nil && err.Error() == "use of closed network connection":
					// 如果连接关闭,我们重新启动它。
					log.Error(err)
					// BUG() 尝试再次写入字节
				case err != nil:
					log.Error(err)
					errCh <- err
				}
				if ei == 1 {
					log.Errorf("done with errors")
					close(errCh2)
				}
				ei++
			}
		}(conn)
	}
}

// 代理用于将数据从源传输到目标,并将错误发送到专用通道
func proxy(name string, dst io.Writer, src io.Reader, errCh2 chan error) {
	n, err := io.Copy(dst, src)
	// 记录并休眠。这是一个巧妙的方法,允许另一端完成挂起的复制
	log.Errorf("[DEBUG] socks: Copied %d bytes to %s", n, name)
	time.Sleep(10 * time.Millisecond)
	// 发送任何错误
	switch {
	case err != nil:
		log.Error(err)
		errCh2 <- err
	case n < 1:
		errCh2 <- errors.New("no byte")
	default:
		errCh2 <- nil
	}
	return
}

希望这可以帮助到你!如果你有任何其他问题,请随时问我。

英文:

I'm trying to connect a computer behind NAT with the internet through a 3rd party server(aka reverse connection). I'm listening on two ports. On one port (dstNet) is connecting the machine behind NAT and on the other port are connecting the internet clients.
The issue is that I don't know how to handle the disconnection of the machine behind NAT. Even if the machine is connecting again the the traffic is not handled sent/written anymore... I get [DEBUG] socks: Copied 0 bytes to client which is my warning of course. Below is the code. It's quite long but I can't find what to trim.

// Make a bridge between dstNet which is
// usually behind NAT and srcNet which is usually a client
// which wants to route the traffic though the NAT machine.
package main
import (
&quot;bufio&quot;
&quot;errors&quot;
log &quot;github.com/golang/glog&quot;
&quot;io&quot;
&quot;net&quot;
&quot;time&quot;
)
const (
// listen on the dstNet so that we can
// create a connection with the NAT client
dstNet = &quot;0.0.0.0:9000&quot;
// listen on srcNet so that we can get traffic
// to forward to dstNet
srcNet = &quot;0.0.0.0:9001&quot;
)
var errCh = make(chan error, 1)
// make a channel to send the reverse connections
var lrCh = make(chan net.Conn, 1)
func listenDst() {
// Listen on the dstNet
lr, err := net.Listen(&quot;tcp&quot;, dstNet)
if err != nil {
log.Error(err)
errCh &lt;- err
return
}
// accept the connection
for {
lrConn, err := lr.Accept()
if err != nil {
log.Error(err)
errCh &lt;- err
return
}
log.Errorf(&quot;sent connection&quot;)
//	lrConn.SetReadDeadline(time.Now().Add(10 * time.Second))
lrCh &lt;- lrConn
}
}
func main() {
go func() {
for err := range errCh {
if err != nil {
panic(err)
}
}
}()
// listen for the nat server
go listenDst()
// listen for clients to connect
l, err := net.Listen(&quot;tcp&quot;, srcNet)
if err != nil {
log.Error(err)
panic(err)
}
// accept the connection
for {
conn, err := l.Accept()
if err != nil {
log.Error(err)
panic(err)
}
// serve the connection
go func(conn net.Conn) {
defer conn.Close()
bufConn := bufio.NewReader(conn)
dst := &lt;-lrCh
defer dst.Close()
// Start proxying
errCh2 := make(chan error, 2)
go proxy(&quot;target&quot;, dst, bufConn, errCh2)
go proxy(&quot;client&quot;, conn, dst, errCh2)
// Wait
var ei int
for err = range errCh2 {
switch {
case err != nil &amp;&amp; err.Error() == &quot;no byte&quot;:
log.Error(err)
case err != nil &amp;&amp; err.Error() == &quot;use of closed network connection&quot;:
// if the connection is closed we restart it.
log.Error(err)
// BUG() attempt to write again the bytes
case err != nil:
log.Error(err)
errCh &lt;- err
}
if ei == 1 {
log.Errorf(&quot;done with errors&quot;)
close(errCh2)
}
ei++
}
}(conn)
}
}
// proxy is used to suffle data from src to destination, and sends errors
// down a dedicated channel
func proxy(name string, dst io.Writer, src io.Reader, errCh2 chan error) {
n, err := io.Copy(dst, src)
// Log, and sleep. This is jank but allows the otherside
// to finish a pending copy
log.Errorf(&quot;[DEBUG] socks: Copied %d bytes to %s&quot;, n, name)
time.Sleep(10 * time.Millisecond)
// Send any errors
switch {
case err != nil:
log.Error(err)
errCh2 &lt;- err
case n &lt; 1:
errCh2 &lt;- errors.New(&quot;no byte&quot;)
default:
errCh2 &lt;- nil
}
return
}

答案1

得分: 3

你只能在错误是临时条件时重新使用连接。

if err, ok := err.(net.Error); ok && err.Temporary() {
}

如果你试图代理一个TCP连接,并且出现任何其他错误(检查Temporary可能并不那么有用),你需要放弃整个连接并重新开始。你不知道远程服务器的状态如何,有多少数据包正在传输或丢失,而且这只会导致越来越难以解决的错误。提示:不要用sleep来隐藏并发或时间问题。这只会使问题变得更加困难。

这是一个更简单的Go代理模式,供你参考:
https://gist.github.com/jbardin/821d08cb64c01c84b81a

func Proxy(srvConn, cliConn *net.TCPConn) {
    // 等待每个连接关闭的通道
    serverClosed := make(chan struct{}, 1)
    clientClosed := make(chan struct{}, 1)

    go broker(srvConn, cliConn, clientClosed)
    go broker(cliConn, srvConn, serverClosed)

    // 等待代理的一半退出,然后通过调用CloseRead()触发另一半的关闭。
    // 这将中断broker中的读取循环,并允许我们完全关闭连接,避免出现“use of closed network connection”错误。
    var waitFor chan struct{}
    select {
    case <-clientClosed:
        // 客户端先关闭,服务器的更多数据包没有用,所以我们可以选择在这里设置SetLinger(0)以更快地回收端口。
        srvConn.SetLinger(0)
        srvConn.CloseRead()
        waitFor = serverClosed
    case <-serverClosed:
        cliConn.CloseRead()
        waitFor = clientClosed
    }

    // 等待另一个连接关闭。
    // 这种“waitFor”模式不是必需的,但它给我们一种跟踪连接并确保所有副本正确终止的方法;我们可以在此函数的入口和延迟退出时触发统计信息。
    <-waitFor
}

// 这个函数执行实际的数据传输。
// broker只关闭读取端。
func broker(dst, src net.Conn, srcClosed chan struct{}) {
    // 我们可以通过内联io.Copy来以更细粒度的方式处理错误(它很简单,并且我们不需要对net.Conn->net.Conn传输进行ReaderFrom或WriterTo检查,这是不需要的)。这还可以让我们调整缓冲区大小。
    _, err := io.Copy(dst, src)

    if err != nil {
        log.Printf("Copy error: %s", err)
    }
    if err := src.Close(); err != nil {
        log.Printf("Close error: %s", err)
    }
    srcClosed <- struct{}{}
}
英文:

The only time you can reuse a connection after an error is if is a temporary condition.

if err, ok := err.(net.Error); ok &amp;&amp; err.Temporary() {
}

If you are trying to proxy a TCPconnection, and there is any other error (checking for Temporary may not even be that useful), you need to drop the whole thing and start over. You have no idea what the state of the remote server is, how many packets are in flight or lost, and it's only going to cause more difficult bugs the harder you try. (tip: don't hide concurrency or timing problems with a sleep. It's just making it harder in the long run)

Here is a much simpler proxy pattern for go if you want to reference it:
https://gist.github.com/jbardin/821d08cb64c01c84b81a

func Proxy(srvConn, cliConn *net.TCPConn) {
// channels to wait on the close event for each connection
serverClosed := make(chan struct{}, 1)
clientClosed := make(chan struct{}, 1)
go broker(srvConn, cliConn, clientClosed)
go broker(cliConn, srvConn, serverClosed)
// wait for one half of the proxy to exit, then trigger a shutdown of the
// other half by calling CloseRead(). This will break the read loop in the
// broker and allow us to fully close the connection cleanly without a
// &quot;use of closed network connection&quot; error.
var waitFor chan struct{}
select {
case &lt;-clientClosed:
// the client closed first and any more packets from the server aren&#39;t
// useful, so we can optionally SetLinger(0) here to recycle the port
// faster.
srvConn.SetLinger(0)
srvConn.CloseRead()
waitFor = serverClosed
case &lt;-serverClosed:
cliConn.CloseRead()
waitFor = clientClosed
}
// Wait for the other connection to close.
// This &quot;waitFor&quot; pattern isn&#39;t required, but gives us a way to track the
// connection and ensure all copies terminate correctly; we can trigger
// stats on entry and deferred exit of this function.
&lt;-waitFor
}
// This does the actual data transfer.
// The broker only closes the Read side.
func broker(dst, src net.Conn, srcClosed chan struct{}) {
// We can handle errors in a finer-grained manner by inlining io.Copy (it&#39;s
// simple, and we drop the ReaderFrom or WriterTo checks for
// net.Conn-&gt;net.Conn transfers, which aren&#39;t needed). This would also let
// us adjust buffersize.
_, err := io.Copy(dst, src)
if err != nil {
log.Printf(&quot;Copy error: %s&quot;, err)
}
if err := src.Close(); err != nil {
log.Printf(&quot;Close error: %s&quot;, err)
}
srcClosed &lt;- struct{}{}
}

答案2

得分: 0

原来我不仅需要重新启动监听器来关闭连接。我修改了代理函数,如果无法向源地址写入数据(即写入0字节),则重置destNet监听器。我仍然不确定这是否是正确的方法(在多连接场景中关闭监听器似乎是不好的,因为我猜测会重置所有拨号到该地址的客户端连接),但到目前为止,这是我能做的最好的修复方法。

以下是所有的代码。所有的功劳归功于@JimB。

// Make a bridge between dstNet which is
// usually behind NAT and srcNet which is usually a client
// which wants to route the traffic though the NAT machine.
package main

import (
	log "github.com/golang/glog"
	"io"
	"net"
)

// listen on the dstNet so that we can
// create a connection with the NAT client
var dstNet *net.TCPAddr = &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 9000}

// listen on srcNet so that we can get traffic
// to forward to dstNet
var srcNet *net.TCPAddr = &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 9001}

var errCh = make(chan error, 1)

// make a channel to send the reverse connections
var lrCh = make(chan *net.TCPConn, 1)
var lrNewCh = make(chan int, 1)

func listenDst() {
	// Listen on the dstNet
	lr, err := net.ListenTCP("tcp", dstNet)
	if err != nil {
		log.Error(err)
		errCh <- err
		return
	}
	// accept the connection
	for {
		lrConn, err := lr.AcceptTCP()
		if err != nil {
			log.Error(err)
			//errCh <- err
			//return
		}
		status := <-lrNewCh
		log.Errorf("status request is %v", status)
		if status == 1 {
			log.Errorf("we close and restart the listener and the connection")
			if err = lrConn.Close(); err != nil {
				log.Error(err)
			}
			if err = lr.Close(); err != nil {
				log.Error(err)
			}
			lr, err = net.ListenTCP("tcp", dstNet)
			if err != nil {
				log.Error(err)
				errCh <- err
				return
			}
			lrConn, err = lr.AcceptTCP()
			if err != nil {
				log.Error(err)
				errCh <- err
			}
		} else {
			log.Errorf("new connection on its way")
			lrCh <- lrConn
		}
		//	default:
		// log.Errorf("accepting new connections")

	}

}

func main() {

	go func() {
		for err := range errCh {
			if err != nil {
				panic(err)
			}
		}
	}()
	// listen for the nat server
	go listenDst()

	// listen for clients to connect
	l, err := net.ListenTCP("tcp", srcNet)
	if err != nil {
		log.Error(err)
		panic(err)
	}
	// accept the connection
	for {
		conn, err := l.AcceptTCP()
		if err != nil {
			log.Error(err)
			panic(err)
		}
		// serve the connection
		go func(conn *net.TCPConn) {
			defer conn.Close()
			lrNewCh <- 0
			dst := <-lrCh
			defer dst.Close()
			proxy(dst, conn)
		}(conn)

	}
}

func proxy(srvConn, cliConn *net.TCPConn) {
	// channels to wait on the close event for each connection
	serverClosed := make(chan struct{}, 1)
	clientClosed := make(chan struct{}, 1)

	go broker(srvConn, cliConn, clientClosed)
	go broker(cliConn, srvConn, serverClosed)

	// wait for one half of the proxy to exit, then trigger a shutdown of the
	// other half by calling CloseRead(). This will break the read loop in the
	// broker and allow us to fully close the connection cleanly without a
	// "use of closed network connection" error.
	var waitFor chan struct{}
	select {
	case <-clientClosed:
		// the client closed first and any more packets from the server aren't
		// useful, so we can optionally SetLinger(0) here to recycle the port
		// faster.
		srvConn.SetLinger(0)
		srvConn.CloseRead()
		waitFor = serverClosed
	case <-serverClosed:
		cliConn.CloseRead()
		waitFor = clientClosed
	}

	// Wait for the other connection to close.
	// This "waitFor" pattern isn't required, but gives us a way to track the
	// connection and ensure all copies terminate correctly; we can trigger
	// stats on entry and deferred exit of this function.
	<-waitFor
}

// This does the actual data transfer.
// The broker only closes the Read side.
func broker(dst, src net.Conn, srcClosed chan struct{}) {
	// We can handle errors in a finer-grained manner by inlining io.Copy (it's
	// simple, and we drop the ReaderFrom or WriterTo checks for
	// net.Conn->net.Conn transfers, which aren't needed). This would also let
	// us adjust buffersize.
	n, err := io.Copy(dst, src)
	log.Errorf(" %v bytes copied", n)
	if err != nil {
		log.Errorf("Copy error: %s", err)
		// errCh <- err
	}
	if err := src.Close(); err != nil {
		log.Errorf("Close error: %s", err)
		errCh <- err
	}
	if n == 0 {
		lrNewCh <- 1
	}
	srcClosed <- struct{}{}
}

希望对你有帮助!

英文:

It turned out that I had to restart the listener not only to close the connection. I've modified the broker function to reset the destNet listener if it can't write (i.e. writes 0 bytes) to src. I'm still not sure if this is the right way to do it (i.e. closing the listener seems bad in a multi-connections scenario as I guess I reset all the client connections dialing on that address) but so far this is the best I could do to fix it.

 if n == 0 {
lrNewCh &lt;- 1
}

Here is all the code. All the credit goes to @JimB

// Make a bridge between dstNet which is
// usually behind NAT and srcNet which is usually a client
// which wants to route the traffic though the NAT machine.
package main
import (
log &quot;github.com/golang/glog&quot;
&quot;io&quot;
&quot;net&quot;
)
// listen on the dstNet so that we can
// create a connection with the NAT client
var dstNet *net.TCPAddr = &amp;net.TCPAddr{IP: net.ParseIP(&quot;0.0.0.0&quot;), Port: 9000}
// listen on srcNet so that we can get traffic
// to forward to dstNet
var srcNet *net.TCPAddr = &amp;net.TCPAddr{IP: net.ParseIP(&quot;0.0.0.0&quot;), Port: 9001}
var errCh = make(chan error, 1)
// make a channel to send the reverse connections
var lrCh = make(chan *net.TCPConn, 1)
var lrNewCh = make(chan int, 1)
func listenDst() {
// Listen on the dstNet
lr, err := net.ListenTCP(&quot;tcp&quot;, dstNet)
if err != nil {
log.Error(err)
errCh &lt;- err
return
}
// accept the connection
for {
lrConn, err := lr.AcceptTCP()
if err != nil {
log.Error(err)
//errCh &lt;- err
//return
}
status := &lt;-lrNewCh
log.Errorf(&quot;status request is %v&quot;, status)
if status == 1{
log.Errorf(&quot;we close and restart the listener and the connection&quot;)
if err =  lrConn.Close(); err !=nil{
log.Error(err)
}
if err =  lr.Close(); err !=nil{
log.Error(err)
}
lr, err = net.ListenTCP(&quot;tcp&quot;, dstNet)
if err != nil {
log.Error(err)
errCh &lt;- err
return
}
lrConn, err = lr.AcceptTCP()
if err !=nil{
log.Error(err)
errCh &lt;- err
}
}else{
log.Errorf(&quot;new connection on its way&quot;)
lrCh &lt;- lrConn
}
//	default:
// log.Errorf(&quot;accepting new connections&quot;)
}
}
func main() {
go func() {
for err := range errCh {
if err != nil {
panic(err)
}
}
}()
// listen for the nat server
go listenDst()
// listen for clients to connect
l, err := net.ListenTCP(&quot;tcp&quot;, srcNet)
if err != nil {
log.Error(err)
panic(err)
}
// accept the connection
for {
conn, err := l.AcceptTCP()
if err != nil {
log.Error(err)
panic(err)
}
// serve the connection
go func(conn *net.TCPConn) {
defer conn.Close()
lrNewCh &lt;- 0
dst := &lt;-lrCh
defer dst.Close()
proxy(dst, conn)
}(conn)
}
}
func proxy(srvConn, cliConn *net.TCPConn) {
// channels to wait on the close event for each connection
serverClosed := make(chan struct{}, 1)
clientClosed := make(chan struct{}, 1)
go broker(srvConn, cliConn, clientClosed)
go broker(cliConn, srvConn, serverClosed)
// wait for one half of the proxy to exit, then trigger a shutdown of the
// other half by calling CloseRead(). This will break the read loop in the
// broker and allow us to fully close the connection cleanly without a
// &quot;use of closed network connection&quot; error.
var waitFor chan struct{}
select {
case &lt;-clientClosed:
// the client closed first and any more packets from the server aren&#39;t
// useful, so we can optionally SetLinger(0) here to recycle the port
// faster.
srvConn.SetLinger(0)
srvConn.CloseRead()
waitFor = serverClosed
case &lt;-serverClosed:
cliConn.CloseRead()
waitFor = clientClosed
}
// Wait for the other connection to close.
// This &quot;waitFor&quot; pattern isn&#39;t required, but gives us a way to track the
// connection and ensure all copies terminate correctly; we can trigger
// stats on entry and deferred exit of this function.
&lt;-waitFor
}
// This does the actual data transfer.
// The broker only closes the Read side.
func broker(dst, src net.Conn, srcClosed chan struct{}) {
// We can handle errors in a finer-grained manner by inlining io.Copy (it&#39;s
// simple, and we drop the ReaderFrom or WriterTo checks for
// net.Conn-&gt;net.Conn transfers, which aren&#39;t needed). This would also let
// us adjust buffersize.
n, err := io.Copy(dst, src)
log.Errorf(&quot; %v bytes copied&quot;, n)
if err != nil {
log.Errorf(&quot;Copy error: %s&quot;, err)
// errCh &lt;- err
}
if err := src.Close(); err != nil {
log.Errorf(&quot;Close error: %s&quot;, err)
errCh &lt;- err
}
if n == 0 {
lrNewCh &lt;- 1
}
srcClosed &lt;- struct{}{}
}

huangapple
  • 本文由 发表于 2014年12月12日 15:00:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/27438486.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定