鏡像自
				https://github.com/therootcompany/sclient
				synced 2025-10-31 13:12:49 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			168 行
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			168 行
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package sclient
 | |
| 
 | |
| import (
 | |
| 	"crypto/tls"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"net"
 | |
| 	"os"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // Tunnel specifies which remote encrypted connection to make available as a plain connection locally.
 | |
| type Tunnel struct {
 | |
| 	RemoteAddress      string
 | |
| 	RemotePort         int
 | |
| 	LocalAddress       string
 | |
| 	LocalPort          int
 | |
| 	InsecureSkipVerify bool
 | |
| 	ServerName         string
 | |
| 	Silent             bool
 | |
| }
 | |
| 
 | |
| // DialAndListen will create a test TLS connection to the remote address and then
 | |
| // begin listening locally. Each local connection will result in a separate remote connection.
 | |
| func (t *Tunnel) DialAndListen() error {
 | |
| 	remote := t.RemoteAddress + ":" + strconv.Itoa(t.RemotePort)
 | |
| 	conn, err := tls.Dial("tcp", remote,
 | |
| 		&tls.Config{
 | |
| 			ServerName:         t.ServerName,
 | |
| 			InsecureSkipVerify: t.InsecureSkipVerify,
 | |
| 		})
 | |
| 
 | |
| 	if err != nil {
 | |
| 		fmt.Fprintf(os.Stderr, "[warn] '%s' may not be accepting connections: %s\n", remote, err)
 | |
| 	} else {
 | |
| 		conn.Close()
 | |
| 	}
 | |
| 
 | |
| 	// use stdin/stdout
 | |
| 	if "-" == t.LocalAddress || "|" == t.LocalAddress {
 | |
| 		var name string
 | |
| 		network := "stdio"
 | |
| 		if "|" == t.LocalAddress {
 | |
| 			name = "pipe"
 | |
| 		} else {
 | |
| 			name = "stdin"
 | |
| 		}
 | |
| 		conn := &stdnet{os.Stdin, os.Stdout, &stdaddr{net.UnixAddr{Name: name, Net: network}}}
 | |
| 		t.handleConnection(remote, conn)
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// use net.Conn
 | |
| 	local := t.LocalAddress + ":" + strconv.Itoa(t.LocalPort)
 | |
| 	ln, err := net.Listen("tcp", local)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if !t.Silent {
 | |
| 		fmt.Fprintf(os.Stdout, "[listening] %s:%d <= %s:%d\n",
 | |
| 			t.RemoteAddress, t.RemotePort, t.LocalAddress, t.LocalPort)
 | |
| 	}
 | |
| 
 | |
| 	for {
 | |
| 		conn, err := ln.Accept()
 | |
| 		if nil != err {
 | |
| 			fmt.Fprintf(os.Stderr, "[error] %s\n", err)
 | |
| 			continue
 | |
| 		}
 | |
| 		go t.handleConnection(remote, conn)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // I wonder if I can get this to exactly mirror UnixAddr without passing it in
 | |
| type stdaddr struct {
 | |
| 	net.UnixAddr
 | |
| }
 | |
| 
 | |
| type stdnet struct {
 | |
| 	in   *os.File // os.Stdin
 | |
| 	out  *os.File // os.Stdout
 | |
| 	addr *stdaddr
 | |
| }
 | |
| 
 | |
| func (rw *stdnet) Read(buf []byte) (n int, err error) {
 | |
| 	return rw.in.Read(buf)
 | |
| }
 | |
| func (rw *stdnet) Write(buf []byte) (n int, err error) {
 | |
| 	return rw.out.Write(buf)
 | |
| }
 | |
| func (rw *stdnet) Close() error {
 | |
| 	return rw.in.Close()
 | |
| }
 | |
| func (rw *stdnet) RemoteAddr() net.Addr {
 | |
| 	return rw.addr
 | |
| }
 | |
| 
 | |
| // not all of net.Conn, just RWC and RemoteAddr()
 | |
| type netReadWriteCloser interface {
 | |
| 	io.ReadWriteCloser
 | |
| 	RemoteAddr() net.Addr
 | |
| }
 | |
| 
 | |
| func pipe(r netReadWriteCloser, w netReadWriteCloser, t string) {
 | |
| 	buffer := make([]byte, 2048)
 | |
| 	for {
 | |
| 		done := false
 | |
| 		// NOTE: count may be > 0 even if there's an err
 | |
| 		//fmt.Fprintf(os.Stdout, "[debug] (%s) reading\n", t)
 | |
| 		count, err := r.Read(buffer)
 | |
| 		if nil != err {
 | |
| 			//fmt.Fprintf(os.Stdout, "[debug] (%s:%d) error reading %s\n", t, count, err)
 | |
| 			if io.EOF != err {
 | |
| 				fmt.Fprintf(os.Stderr, "[read error] (%s:%d) %s\n", t, count, err)
 | |
| 			}
 | |
| 			r.Close()
 | |
| 			//w.Close()
 | |
| 			done = true
 | |
| 		}
 | |
| 		if 0 == count {
 | |
| 			break
 | |
| 		}
 | |
| 		_, err = w.Write(buffer[:count])
 | |
| 		if nil != err {
 | |
| 			//fmt.Fprintf(os.Stdout, "[debug] %s error writing\n", t)
 | |
| 			if io.EOF != err {
 | |
| 				fmt.Fprintf(os.Stderr, "[write error] (%s) %s\n", t, err)
 | |
| 			}
 | |
| 			// TODO handle error closing?
 | |
| 			r.Close()
 | |
| 			//w.Close()
 | |
| 			done = true
 | |
| 		}
 | |
| 		if done {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (t *Tunnel) handleConnection(remote string, conn netReadWriteCloser) {
 | |
| 	sclient, err := tls.Dial("tcp", remote,
 | |
| 		&tls.Config{
 | |
| 			ServerName:         t.ServerName,
 | |
| 			InsecureSkipVerify: t.InsecureSkipVerify,
 | |
| 		})
 | |
| 
 | |
| 	if err != nil {
 | |
| 		fmt.Fprintf(os.Stderr, "[error] (remote) %s\n", err)
 | |
| 		conn.Close()
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if !t.Silent {
 | |
| 		if "stdio" == conn.RemoteAddr().Network() {
 | |
| 			fmt.Fprintf(os.Stdout, "(connected to %s:%d and reading from %s)\n",
 | |
| 				t.RemoteAddress, t.RemotePort, conn.RemoteAddr().String())
 | |
| 		} else {
 | |
| 			fmt.Fprintf(os.Stdout, "[connect] %s => %s:%d\n",
 | |
| 				strings.Replace(conn.RemoteAddr().String(), "[::1]:", "localhost:", 1), t.RemoteAddress, t.RemotePort)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	go pipe(conn, sclient, "local")
 | |
| 	pipe(sclient, conn, "remote")
 | |
| }
 |