summaryrefslogtreecommitdiff
path: root/listener.go
blob: 6b0442dc96d73b0b79165b402d1392308abf95e1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tcpproxy

import (
	"io"
	"net"
	"sync"
)

// TargetListener implements both net.Listener and Target.
// Matched Targets become accepted connections.
type TargetListener struct {
	Address string // Address is the string reported by TargetListener.Addr().String().

	mu       sync.Mutex
	cond     *sync.Cond
	closed   bool
	nextConn net.Conn
}

var (
	_ net.Listener = (*TargetListener)(nil)
	_ Target       = (*TargetListener)(nil)
)

func (tl *TargetListener) lock() {
	tl.mu.Lock()
	if tl.cond == nil {
		tl.cond = sync.NewCond(&tl.mu)
	}
}

type tcpAddr string

func (a tcpAddr) Network() string { return "tcp" }
func (a tcpAddr) String() string  { return string(a) }

func (tl *TargetListener) Addr() net.Addr { return tcpAddr(tl.Address) }

func (tl *TargetListener) Close() error {
	tl.lock()
	if tl.closed {
		tl.mu.Unlock()
		return nil
	}
	tl.closed = true
	tl.mu.Unlock()
	tl.cond.Broadcast()
	return nil
}

// HandleConn implements the Target interface. It blocks until tl is
// closed or another goroutine has called Accept and received c.
func (tl *TargetListener) HandleConn(c net.Conn) {
	tl.lock()
	defer tl.mu.Unlock()
	for tl.nextConn != nil && !tl.closed {
		tl.cond.Wait()
	}
	if tl.closed {
		c.Close()
		return
	}
	tl.nextConn = c
	tl.cond.Broadcast() // Signal might be sufficient; verify.
	for tl.nextConn == c && !tl.closed {
		tl.cond.Wait()
	}
	if tl.closed {
		c.Close()
		return
	}
}

func (tl *TargetListener) Accept() (net.Conn, error) {
	tl.lock()
	for tl.nextConn == nil && !tl.closed {
		tl.cond.Wait()
	}
	if tl.closed {
		tl.mu.Unlock()
		return nil, io.EOF
	}
	c := tl.nextConn
	tl.nextConn = nil
	tl.mu.Unlock()
	tl.cond.Broadcast() // Signal might be sufficient; verify.

	return c, nil
}