-
-
Save AndiHappy/925e953578056165cde5a07ce60eeaa3 to your computer and use it in GitHub Desktop.
proxy server
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "bufio" | |
| "bytes" | |
| "errors" | |
| "flag" | |
| "fmt" | |
| "io" | |
| "log" | |
| "net" | |
| "net/url" | |
| "strconv" | |
| "sync" | |
| ) | |
| func main() { | |
| addr := flag.String("s", ":1080", "proxy server address") | |
| flag.Parse() | |
| err := proxy(*addr) | |
| if err != nil { | |
| panic(err) | |
| } | |
| } | |
| const netTCP = "tcp" | |
| func proxy(addr string) error { | |
| lc, err := net.Listen(netTCP, addr) | |
| if err != nil { | |
| return err | |
| } | |
| //goland:noinspection GoUnhandledErrorResult | |
| defer lc.Close() | |
| for { | |
| ac, ea := lc.Accept() | |
| if ea == nil { | |
| go func() { | |
| if ea = handleProxy(ac); ea != nil { | |
| log.Printf("%+v", ea) | |
| } | |
| }() | |
| } | |
| } | |
| } | |
| type poolBuf struct { | |
| buf0, buf1 []byte | |
| buf *bufio.Reader | |
| } | |
| func (pb *poolBuf) ReadLine() ([]byte, error) { | |
| line := pb.buf0[:0] | |
| for { | |
| l, more, err := pb.buf.ReadLine() | |
| if err != nil { | |
| return nil, err | |
| } | |
| if len(line) == 0 && !more { | |
| return l, nil | |
| } | |
| if line = append(line, l...); !more { | |
| return line, nil | |
| } | |
| } | |
| } | |
| var bytePool = &sync.Pool{New: func() any { | |
| const leakyBufSize = 64 << 10 | |
| return &poolBuf{ | |
| buf0: make([]byte, leakyBufSize), | |
| buf1: make([]byte, leakyBufSize), | |
| buf: new(bufio.Reader), | |
| } | |
| }} | |
| //goland:noinspection GoUnhandledErrorResult | |
| func handleProxy(c net.Conn) error { | |
| defer c.Close() | |
| bp := bytePool.Get().(*poolBuf) | |
| defer bytePool.Put(bp) | |
| bp.buf.Reset(c) | |
| tp, err := bp.buf.Peek(2) | |
| if err != nil { | |
| return err | |
| } | |
| var rc net.Conn | |
| switch { | |
| case tp[0] == socksVer5: | |
| rc, err = dialSocks5(c, bp.buf, bp.buf0, int(tp[1])) | |
| if err != nil { | |
| return err | |
| } | |
| defer rc.Close() | |
| _, err = c.Write(socks5Established) | |
| if err != nil { | |
| return err | |
| } | |
| case tp[0] >= 'A' && tp[0] <= 'Z': | |
| line, err := bp.ReadLine() | |
| if err != nil { | |
| return err | |
| } | |
| head := bytes.SplitN(line, httpSep, 3) | |
| if len(head) != 3 { | |
| return errors.New("head error") | |
| } | |
| address := string(head[1]) | |
| if bytes.Equal(head[0], httpConnect) { | |
| for { | |
| line, err = bp.ReadLine() | |
| if err != nil { | |
| return err | |
| } | |
| if len(line) == 0 { | |
| break // read all heads | |
| } | |
| } | |
| rc, err = net.Dial(netTCP, address) | |
| if err != nil { | |
| return err | |
| } | |
| defer rc.Close() | |
| _, err = c.Write(httpEstablished) | |
| if err != nil { | |
| return err | |
| } | |
| } else { | |
| u, err := url.Parse(address) | |
| if err != nil { | |
| return err | |
| } | |
| if port := u.Port(); port == "" { | |
| if u.Scheme == "https" { | |
| port = "443" | |
| } else { | |
| port = "80" | |
| } | |
| u.Host = net.JoinHostPort(u.Host, port) | |
| } | |
| rc, err = net.Dial(netTCP, u.Host) | |
| if err != nil { | |
| return err | |
| } | |
| defer rc.Close() | |
| // forward first line | |
| _, err = rc.Write(append(line, '\r', '\n')) | |
| if err != nil { | |
| return err | |
| } | |
| } | |
| default: | |
| return errors.New("unsupported protocol version") | |
| } | |
| ret := make(chan struct{}) | |
| go func() { | |
| copyBuffer(c, rc, bp.buf0) | |
| ret <- struct{}{} | |
| }() | |
| copyBuffer(rc, bp.buf, bp.buf1) | |
| <-ret | |
| return nil | |
| } | |
| func copyBuffer(dst io.Writer, src io.Reader, buf []byte) { | |
| var ( | |
| nr, nw int | |
| er, ew error | |
| ) | |
| for er == nil { | |
| nr, er = src.Read(buf) | |
| if nr > 0 { | |
| nw, ew = dst.Write(buf[:nr]) | |
| if nw < 0 || nr < nw || ew != nil || nr != nw { | |
| return // Refer io.CopyBuffer | |
| } | |
| } | |
| } | |
| } | |
| var ( | |
| httpConnect = []byte("CONNECT") | |
| httpSep = []byte(" ") | |
| httpEstablished = []byte("HTTP/1.1 200 Connection Established\r\n\r\n") | |
| socks5Established = []byte{socksVer5, 0, 0, 1, 0, 0, 0, 0, 0, 0} | |
| ) | |
| const ( | |
| socksVer5 = 5 | |
| aTypIPV4 = 1 | |
| aTypDomain = 3 | |
| aTypIPV6 = 4 | |
| ) | |
| func dialSocks5(w io.Writer, r io.Reader, buf []byte, n int) (net.Conn, error) { | |
| _, err := io.ReadFull(r, buf[:n+2]) | |
| if err != nil { | |
| return nil, err | |
| } | |
| _, err = w.Write(socks5Established[:2]) | |
| if err != nil { | |
| return nil, err | |
| } | |
| _, err = io.ReadFull(r, buf[:5]) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if buf[0] != socksVer5 { | |
| return nil, fmt.Errorf("invalid ver %d", buf[0]) | |
| } | |
| if buf[1] != 1 { // cmd connect = 1 | |
| return nil, fmt.Errorf("invalid cmd %d", buf[1]) | |
| } | |
| switch buf[3] { | |
| case aTypDomain: | |
| // VER CMD RSV TYPE LEN DST.ADDR DST.PORT | |
| // 1 1 1 1 1 LEN 2 | |
| n = int(buf[4]) + 7 | |
| case aTypIPV4: | |
| // VER CMD RSV TYPE DST.ADDR DST.PORT | |
| // 1 1 1 1 ipv4Len 2 | |
| n = net.IPv4len + 6 | |
| case aTypIPV6: | |
| // VER CMD RSV TYPE DST.ADDR DST.PORT | |
| // 1 1 1 1 ipv6Len 2 | |
| n = net.IPv6len + 6 | |
| default: | |
| return nil, errors.New("socks addr type not supported") | |
| } | |
| _, err = io.ReadFull(r, buf[5:n]) | |
| if err != nil { | |
| return nil, err | |
| } | |
| var addr string | |
| switch buf[3] { | |
| case aTypDomain: | |
| n = 5 + int(buf[4]) | |
| addr = string(buf[5:n]) | |
| case aTypIPV4: | |
| n = 4 + net.IPv4len | |
| addr = net.IP(buf[4:n]).String() | |
| case aTypIPV6: | |
| n = 4 + net.IPv6len | |
| addr = net.IP(buf[4:n]).String() | |
| } | |
| addr = net.JoinHostPort(addr, strconv.FormatUint(uint64(buf[n])<<8|uint64(buf[n+1]), 10)) | |
| return net.Dial(netTCP, addr) | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
这里有个 go 代理服务器,同一个 tcp 端口同时支持 http 和 socks5 协议,我估计 Java 代码量会多一些。