From c56e17fff9691d19b81b47bba3c81ae5ea5fb8a2 Mon Sep 17 00:00:00 2001
From: V2Ray <admin@v2ray.com>
Date: Fri, 30 Oct 2015 15:56:46 +0100
Subject: [PATCH] Dokodemo proxy

---
 common/net/json/network.go         |  32 +++++++++
 common/net/network.go              |  12 ++++
 common/net/timed_io.go             |  10 ++-
 proxy/dokodemo/config/json/json.go |  23 +++++++
 proxy/dokodemo/dokodemo.go         | 102 +++++++++++++++++++++++++++++
 proxy/dokodemo/dokodemo_factory.go |  19 ++++++
 release/server/main.go             |   2 +
 7 files changed, 197 insertions(+), 3 deletions(-)
 create mode 100644 common/net/json/network.go
 create mode 100644 common/net/network.go
 create mode 100644 proxy/dokodemo/config/json/json.go
 create mode 100644 proxy/dokodemo/dokodemo.go
 create mode 100644 proxy/dokodemo/dokodemo_factory.go

diff --git a/common/net/json/network.go b/common/net/json/network.go
new file mode 100644
index 00000000..ce8e5357
--- /dev/null
+++ b/common/net/json/network.go
@@ -0,0 +1,32 @@
+package json
+
+import (
+	"encoding/json"
+	"strings"
+
+	v2net "github.com/v2ray/v2ray-core/common/net"
+)
+
+type NetworkList []string
+
+func (this *NetworkList) UnmarshalJSON(data []byte) error {
+	var strList []string
+	err := json.Unmarshal(data, &strList)
+	if err != nil {
+		return err
+	}
+	*this = make([]string, len(strList))
+	for idx, str := range strList {
+		(*this)[idx] = strings.ToLower(str)
+	}
+	return nil
+}
+
+func (this *NetworkList) HasNetwork(network v2net.Network) bool {
+	for _, value := range *this {
+		if value == string(network) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/common/net/network.go b/common/net/network.go
new file mode 100644
index 00000000..bfc57e6d
--- /dev/null
+++ b/common/net/network.go
@@ -0,0 +1,12 @@
+package net
+
+const (
+	TCPNetwork = Network("tcp")
+	UDPNetwork = Network("udp")
+)
+
+type Network string
+
+type NetworkList interface {
+	HasNetwork(Network) bool
+}
diff --git a/common/net/timed_io.go b/common/net/timed_io.go
index 795165ca..a5603463 100644
--- a/common/net/timed_io.go
+++ b/common/net/timed_io.go
@@ -22,10 +22,14 @@ func NewTimeOutReader(timeout int, connection net.Conn) *TimeOutReader {
 }
 
 func (reader *TimeOutReader) Read(p []byte) (n int, err error) {
-	deadline := time.Duration(reader.timeout) * time.Second
-	reader.connection.SetReadDeadline(time.Now().Add(deadline))
+	if reader.timeout > 0 {
+		deadline := time.Duration(reader.timeout) * time.Second
+		reader.connection.SetReadDeadline(time.Now().Add(deadline))
+	}
 	n, err = reader.connection.Read(p)
-	reader.connection.SetReadDeadline(emptyTime)
+	if reader.timeout > 0 {
+		reader.connection.SetReadDeadline(emptyTime)
+	}
 	return
 }
 
diff --git a/proxy/dokodemo/config/json/json.go b/proxy/dokodemo/config/json/json.go
new file mode 100644
index 00000000..ccc814dc
--- /dev/null
+++ b/proxy/dokodemo/config/json/json.go
@@ -0,0 +1,23 @@
+package json
+
+import (
+	v2net "github.com/v2ray/v2ray-core/common/net"
+	v2netjson "github.com/v2ray/v2ray-core/common/net/json"
+	"github.com/v2ray/v2ray-core/config"
+	"github.com/v2ray/v2ray-core/config/json"
+)
+
+type DokodemoConfig struct {
+	Host    string                 `json:"address"`
+	Port    int                    `json:"port"`
+	Network *v2netjson.NetworkList `json:"network"`
+	Timeout int                    `json:"timeout"`
+
+	address v2net.Address
+}
+
+func init() {
+	json.RegisterConfigType("dokodemo-door", config.TypeInbound, func() interface{} {
+		return new(DokodemoConfig)
+	})
+}
diff --git a/proxy/dokodemo/dokodemo.go b/proxy/dokodemo/dokodemo.go
new file mode 100644
index 00000000..98dcca07
--- /dev/null
+++ b/proxy/dokodemo/dokodemo.go
@@ -0,0 +1,102 @@
+package dokodemo
+
+import (
+	"io"
+	"net"
+	"sync"
+
+	"github.com/v2ray/v2ray-core/app"
+	"github.com/v2ray/v2ray-core/common/alloc"
+	"github.com/v2ray/v2ray-core/common/log"
+	v2net "github.com/v2ray/v2ray-core/common/net"
+	"github.com/v2ray/v2ray-core/common/retry"
+	"github.com/v2ray/v2ray-core/proxy/dokodemo/config/json"
+)
+
+type DokodemoDoor struct {
+	config     *json.DokodemoConfig
+	accepting  bool
+	address    v2net.Address
+	dispatcher app.PacketDispatcher
+}
+
+func NewDokodemoDoor(dispatcher app.PacketDispatcher, config *json.DokodemoConfig) *DokodemoDoor {
+	d := &DokodemoDoor{
+		config:     config,
+		dispatcher: dispatcher,
+	}
+	ip := net.ParseIP(config.Host)
+	if ip != nil {
+		d.address = v2net.IPAddress(ip, uint16(config.Port))
+	} else {
+		d.address = v2net.DomainAddress(config.Host, uint16(config.Port))
+	}
+	return d
+}
+
+func (this *DokodemoDoor) Listen(port uint16) error {
+	if this.config.Network.HasNetwork(v2net.TCPNetwork) {
+		err := this.ListenTCP(port)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (this *DokodemoDoor) ListenTCP(port uint16) error {
+	tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{
+		IP:   []byte{0, 0, 0, 0},
+		Port: int(port),
+		Zone: "",
+	})
+	if err != nil {
+		log.Error("Dokodemo failed to listen on port %d: %v", port, err)
+		return err
+	}
+	this.accepting = true
+	go this.AcceptTCPConnections(tcpListener)
+	return nil
+}
+
+func (this *DokodemoDoor) AcceptTCPConnections(tcpListener *net.TCPListener) {
+	for this.accepting {
+		retry.Timed(100, 100).On(func() error {
+			connection, err := tcpListener.AcceptTCP()
+			if err != nil {
+				log.Error("Dokodemo failed to accept new connections: %v", err)
+				return err
+			}
+			go this.HandleTCPConnection(connection)
+			return nil
+		})
+	}
+}
+
+func (this *DokodemoDoor) HandleTCPConnection(conn *net.TCPConn) {
+	defer conn.Close()
+
+	packet := v2net.NewPacket(v2net.NewTCPDestination(this.address), nil, true)
+	ray := this.dispatcher.DispatchToOutbound(packet)
+
+	var inputFinish, outputFinish sync.Mutex
+	inputFinish.Lock()
+	outputFinish.Lock()
+
+	reader := v2net.NewTimeOutReader(this.config.Timeout, conn)
+	go dumpInput(reader, ray.InboundInput(), &inputFinish)
+	go dumpOutput(conn, ray.InboundOutput(), &outputFinish)
+
+	outputFinish.Lock()
+}
+
+func dumpInput(reader io.Reader, input chan<- *alloc.Buffer, finish *sync.Mutex) {
+	v2net.ReaderToChan(input, reader)
+	finish.Unlock()
+	close(input)
+}
+
+func dumpOutput(writer io.Writer, output <-chan *alloc.Buffer, finish *sync.Mutex) {
+	v2net.ChanToWriter(writer, output)
+	finish.Unlock()
+}
diff --git a/proxy/dokodemo/dokodemo_factory.go b/proxy/dokodemo/dokodemo_factory.go
new file mode 100644
index 00000000..b8452153
--- /dev/null
+++ b/proxy/dokodemo/dokodemo_factory.go
@@ -0,0 +1,19 @@
+package dokodemo
+
+import (
+	"github.com/v2ray/v2ray-core/app"
+	"github.com/v2ray/v2ray-core/proxy/common/connhandler"
+	"github.com/v2ray/v2ray-core/proxy/dokodemo/config/json"
+)
+
+type DokodemoDoorFactory struct {
+}
+
+func (this DokodemoDoorFactory) Create(dispatcher app.PacketDispatcher, rawConfig interface{}) (connhandler.InboundConnectionHandler, error) {
+	config := rawConfig.(*json.DokodemoConfig)
+	return NewDokodemoDoor(dispatcher, config), nil
+}
+
+func init() {
+	connhandler.RegisterInboundConnectionHandlerFactory("dokodemo-door", DokodemoDoorFactory{})
+}
diff --git a/release/server/main.go b/release/server/main.go
index 2aab8780..fb39d896 100644
--- a/release/server/main.go
+++ b/release/server/main.go
@@ -12,6 +12,8 @@ import (
 	jsonconf "github.com/v2ray/v2ray-core/config/json"
 
 	// The following are neccesary as they register handlers in their init functions.
+	_ "github.com/v2ray/v2ray-core/proxy/dokodemo"
+	_ "github.com/v2ray/v2ray-core/proxy/dokodemo/config/json"
 	_ "github.com/v2ray/v2ray-core/proxy/freedom"
 	_ "github.com/v2ray/v2ray-core/proxy/freedom/config/json"
 	_ "github.com/v2ray/v2ray-core/proxy/socks"