mirror of
https://github.com/tailscale/tailscale.git
synced 2026-02-15 10:22:05 -05:00
This file was never truly necessary and has never actually been used in the history of Tailscale's open source releases. A Brief History of AUTHORS files --- The AUTHORS file was a pattern developed at Google, originally for Chromium, then adopted by Go and a bunch of other projects. The problem was that Chromium originally had a copyright line only recognizing Google as the copyright holder. Because Google (and most open source projects) do not require copyright assignemnt for contributions, each contributor maintains their copyright. Some large corporate contributors then tried to add their own name to the copyright line in the LICENSE file or in file headers. This quickly becomes unwieldy, and puts a tremendous burden on anyone building on top of Chromium, since the license requires that they keep all copyright lines intact. The compromise was to create an AUTHORS file that would list all of the copyright holders. The LICENSE file and source file headers would then include that list by reference, listing the copyright holder as "The Chromium Authors". This also become cumbersome to simply keep the file up to date with a high rate of new contributors. Plus it's not always obvious who the copyright holder is. Sometimes it is the individual making the contribution, but many times it may be their employer. There is no way for the proejct maintainer to know. Eventually, Google changed their policy to no longer recommend trying to keep the AUTHORS file up to date proactively, and instead to only add to it when requested: https://opensource.google/docs/releasing/authors. They are also clear that: > Adding contributors to the AUTHORS file is entirely within the > project's discretion and has no implications for copyright ownership. It was primarily added to appease a small number of large contributors that insisted that they be recognized as copyright holders (which was entirely their right to do). But it's not truly necessary, and not even the most accurate way of identifying contributors and/or copyright holders. In practice, we've never added anyone to our AUTHORS file. It only lists Tailscale, so it's not really serving any purpose. It also causes confusion because Tailscalars put the "Tailscale Inc & AUTHORS" header in other open source repos which don't actually have an AUTHORS file, so it's ambiguous what that means. Instead, we just acknowledge that the contributors to Tailscale (whoever they are) are copyright holders for their individual contributions. We also have the benefit of using the DCO (developercertificate.org) which provides some additional certification of their right to make the contribution. The source file changes were purely mechanical with: git ls-files | xargs sed -i -e 's/\(Tailscale Inc &\) AUTHORS/\1 contributors/g' Updates #cleanup Change-Id: Ia101a4a3005adb9118051b3416f5a64a4a45987d Signed-off-by: Will Norris <will@tailscale.com>
731 lines
18 KiB
Go
731 lines
18 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// Package socks5 is a SOCKS5 server implementation.
|
|
//
|
|
// This is used for userspace networking in Tailscale. Specifically,
|
|
// this is used for dialing out of the machine to other nodes, without
|
|
// the host kernel's involvement, so it doesn't proper routing tables,
|
|
// TUN, IPv6, etc. This package is meant to only handle the SOCKS5 protocol
|
|
// details and not any integration with Tailscale internals itself.
|
|
//
|
|
// The glue between this package and Tailscale is in net/socks5/tssocks.
|
|
package socks5
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"strconv"
|
|
"time"
|
|
|
|
"tailscale.com/types/logger"
|
|
)
|
|
|
|
// Authentication METHODs described in RFC 1928, section 3.
|
|
const (
|
|
noAuthRequired byte = 0
|
|
passwordAuth byte = 2
|
|
noAcceptableAuth byte = 255
|
|
)
|
|
|
|
// passwordAuthVersion is the auth version byte described in RFC 1929.
|
|
const passwordAuthVersion = 1
|
|
|
|
// socks5Version is the byte that represents the SOCKS version
|
|
// in requests.
|
|
const socks5Version byte = 5
|
|
|
|
// commandType are the bytes sent in SOCKS5 packets
|
|
// that represent the kind of connection the client needs.
|
|
type commandType byte
|
|
|
|
// The set of valid SOCKS5 commands as described in RFC 1928.
|
|
const (
|
|
connect commandType = 1
|
|
bind commandType = 2
|
|
udpAssociate commandType = 3
|
|
)
|
|
|
|
// addrType are the bytes sent in SOCKS5 packets
|
|
// that represent particular address types.
|
|
type addrType byte
|
|
|
|
// The set of valid SOCKS5 address types as defined in RFC 1928.
|
|
const (
|
|
ipv4 addrType = 1
|
|
domainName addrType = 3
|
|
ipv6 addrType = 4
|
|
)
|
|
|
|
// replyCode are the bytes sent in SOCKS5 packets
|
|
// that represent replies from the server to a client
|
|
// request.
|
|
type replyCode byte
|
|
|
|
// The set of valid SOCKS5 reply types as per the RFC 1928.
|
|
const (
|
|
success replyCode = 0
|
|
generalFailure replyCode = 1
|
|
connectionNotAllowed replyCode = 2
|
|
networkUnreachable replyCode = 3
|
|
hostUnreachable replyCode = 4
|
|
connectionRefused replyCode = 5
|
|
ttlExpired replyCode = 6
|
|
commandNotSupported replyCode = 7
|
|
addrTypeNotSupported replyCode = 8
|
|
)
|
|
|
|
// UDP conn default buffer size and read timeout.
|
|
const (
|
|
bufferSize = 8 * 1024
|
|
readTimeout = 5 * time.Second
|
|
)
|
|
|
|
// Server is a SOCKS5 proxy server.
|
|
type Server struct {
|
|
// Logf optionally specifies the logger to use.
|
|
// If nil, the standard logger is used.
|
|
Logf logger.Logf
|
|
|
|
// Dialer optionally specifies the dialer to use for outgoing connections.
|
|
// If nil, the net package's standard dialer is used.
|
|
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
|
|
|
|
// Username and Password, if set, are the credential clients must provide.
|
|
Username string
|
|
Password string
|
|
}
|
|
|
|
func (s *Server) dial(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
dial := s.Dialer
|
|
if dial == nil {
|
|
dialer := &net.Dialer{}
|
|
dial = dialer.DialContext
|
|
}
|
|
return dial(ctx, network, addr)
|
|
}
|
|
|
|
func (s *Server) logf(format string, args ...any) {
|
|
logf := s.Logf
|
|
if logf == nil {
|
|
logf = log.Printf
|
|
}
|
|
logf(format, args...)
|
|
}
|
|
|
|
// Serve accepts and handles incoming connections on the given listener.
|
|
func (s *Server) Serve(ln net.Listener) error {
|
|
defer ln.Close()
|
|
for {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
go func() {
|
|
defer c.Close()
|
|
conn := &Conn{logf: s.Logf, clientConn: c, srv: s}
|
|
err := conn.Run()
|
|
if err != nil {
|
|
s.logf("client connection failed: %v", err)
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
// Conn is a SOCKS5 connection for client to reach
|
|
// server.
|
|
type Conn struct {
|
|
// The struct is filled by each of the internal
|
|
// methods in turn as the transaction progresses.
|
|
|
|
logf logger.Logf
|
|
srv *Server
|
|
clientConn net.Conn
|
|
request *request
|
|
|
|
udpClientAddr net.Addr
|
|
udpTargetConns map[socksAddr]net.Conn
|
|
}
|
|
|
|
// Run starts the new connection.
|
|
func (c *Conn) Run() error {
|
|
needAuth := c.srv.Username != "" || c.srv.Password != ""
|
|
authMethod := noAuthRequired
|
|
if needAuth {
|
|
authMethod = passwordAuth
|
|
}
|
|
|
|
err := parseClientGreeting(c.clientConn, authMethod)
|
|
if err != nil {
|
|
c.clientConn.Write([]byte{socks5Version, noAcceptableAuth})
|
|
return err
|
|
}
|
|
c.clientConn.Write([]byte{socks5Version, authMethod})
|
|
if !needAuth {
|
|
return c.handleRequest()
|
|
}
|
|
|
|
user, pwd, err := parseClientAuth(c.clientConn)
|
|
if err != nil || user != c.srv.Username || pwd != c.srv.Password {
|
|
c.clientConn.Write([]byte{1, 1}) // auth error
|
|
return err
|
|
}
|
|
c.clientConn.Write([]byte{1, 0}) // auth success
|
|
|
|
return c.handleRequest()
|
|
}
|
|
|
|
func (c *Conn) handleRequest() error {
|
|
req, err := parseClientRequest(c.clientConn)
|
|
if err != nil {
|
|
res := errorResponse(generalFailure)
|
|
buf, _ := res.marshal()
|
|
c.clientConn.Write(buf)
|
|
return err
|
|
}
|
|
|
|
c.request = req
|
|
switch req.command {
|
|
case connect:
|
|
return c.handleTCP()
|
|
case udpAssociate:
|
|
return c.handleUDP()
|
|
default:
|
|
res := errorResponse(commandNotSupported)
|
|
buf, _ := res.marshal()
|
|
c.clientConn.Write(buf)
|
|
return fmt.Errorf("unsupported command %v", req.command)
|
|
}
|
|
}
|
|
|
|
func (c *Conn) handleTCP() error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
srv, err := c.srv.dial(
|
|
ctx,
|
|
"tcp",
|
|
c.request.destination.hostPort(),
|
|
)
|
|
if err != nil {
|
|
res := errorResponse(generalFailure)
|
|
buf, _ := res.marshal()
|
|
c.clientConn.Write(buf)
|
|
return err
|
|
}
|
|
defer srv.Close()
|
|
|
|
localAddr := srv.LocalAddr().String()
|
|
serverAddr, serverPort, err := splitHostPort(localAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
res := &response{
|
|
reply: success,
|
|
bindAddr: socksAddr{
|
|
addrType: getAddrType(serverAddr),
|
|
addr: serverAddr,
|
|
port: serverPort,
|
|
},
|
|
}
|
|
buf, err := res.marshal()
|
|
if err != nil {
|
|
res = errorResponse(generalFailure)
|
|
buf, _ = res.marshal()
|
|
}
|
|
c.clientConn.Write(buf)
|
|
|
|
errc := make(chan error, 2)
|
|
go func() {
|
|
_, err := io.Copy(c.clientConn, srv)
|
|
if err != nil {
|
|
err = fmt.Errorf("from backend to client: %w", err)
|
|
}
|
|
errc <- err
|
|
}()
|
|
go func() {
|
|
_, err := io.Copy(srv, c.clientConn)
|
|
if err != nil {
|
|
err = fmt.Errorf("from client to backend: %w", err)
|
|
}
|
|
errc <- err
|
|
}()
|
|
return <-errc
|
|
}
|
|
|
|
func (c *Conn) handleUDP() error {
|
|
// The DST.ADDR and DST.PORT fields contain the address and port that
|
|
// the client expects to use to send UDP datagrams on for the
|
|
// association. The server MAY use this information to limit access
|
|
// to the association.
|
|
// @see Page 6, https://datatracker.ietf.org/doc/html/rfc1928.
|
|
//
|
|
// We do NOT limit the access from the client currently in this implementation.
|
|
_ = c.request.destination
|
|
|
|
addr := c.clientConn.LocalAddr()
|
|
host, _, err := net.SplitHostPort(addr.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
clientUDPConn, err := net.ListenPacket("udp", net.JoinHostPort(host, "0"))
|
|
if err != nil {
|
|
res := errorResponse(generalFailure)
|
|
buf, _ := res.marshal()
|
|
c.clientConn.Write(buf)
|
|
return err
|
|
}
|
|
defer clientUDPConn.Close()
|
|
|
|
bindAddr, bindPort, err := splitHostPort(clientUDPConn.LocalAddr().String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
res := &response{
|
|
reply: success,
|
|
bindAddr: socksAddr{
|
|
addrType: getAddrType(bindAddr),
|
|
addr: bindAddr,
|
|
port: bindPort,
|
|
},
|
|
}
|
|
buf, err := res.marshal()
|
|
if err != nil {
|
|
res = errorResponse(generalFailure)
|
|
buf, _ = res.marshal()
|
|
}
|
|
c.clientConn.Write(buf)
|
|
|
|
return c.transferUDP(c.clientConn, clientUDPConn)
|
|
}
|
|
|
|
func (c *Conn) transferUDP(associatedTCP net.Conn, clientConn net.PacketConn) error {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// client -> target
|
|
go func() {
|
|
defer cancel()
|
|
|
|
c.udpTargetConns = make(map[socksAddr]net.Conn)
|
|
// close all target udp connections when the client connection is closed
|
|
defer func() {
|
|
for _, conn := range c.udpTargetConns {
|
|
_ = conn.Close()
|
|
}
|
|
}()
|
|
|
|
buf := make([]byte, bufferSize)
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
err := c.handleUDPRequest(ctx, clientConn, buf)
|
|
if err != nil {
|
|
if isTimeout(err) {
|
|
continue
|
|
}
|
|
if errors.Is(err, net.ErrClosed) {
|
|
return
|
|
}
|
|
c.logf("udp transfer: handle udp request fail: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
// A UDP association terminates when the TCP connection that the UDP
|
|
// ASSOCIATE request arrived on terminates. RFC1928
|
|
_, err := io.Copy(io.Discard, associatedTCP)
|
|
if err != nil {
|
|
err = fmt.Errorf("udp associated tcp conn: %w", err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *Conn) getOrDialTargetConn(
|
|
ctx context.Context,
|
|
clientConn net.PacketConn,
|
|
targetAddr socksAddr,
|
|
) (net.Conn, error) {
|
|
conn, exist := c.udpTargetConns[targetAddr]
|
|
if exist {
|
|
return conn, nil
|
|
}
|
|
conn, err := c.srv.dial(ctx, "udp", targetAddr.hostPort())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.udpTargetConns[targetAddr] = conn
|
|
|
|
// target -> client
|
|
go func() {
|
|
buf := make([]byte, bufferSize)
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
err := c.handleUDPResponse(clientConn, targetAddr, conn, buf)
|
|
if err != nil {
|
|
if isTimeout(err) {
|
|
continue
|
|
}
|
|
if errors.Is(err, net.ErrClosed) || errors.Is(err, io.EOF) {
|
|
return
|
|
}
|
|
c.logf("udp transfer: handle udp response fail: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
func (c *Conn) handleUDPRequest(
|
|
ctx context.Context,
|
|
clientConn net.PacketConn,
|
|
buf []byte,
|
|
) error {
|
|
// add a deadline for the read to avoid blocking forever
|
|
_ = clientConn.SetReadDeadline(time.Now().Add(readTimeout))
|
|
n, addr, err := clientConn.ReadFrom(buf)
|
|
if err != nil {
|
|
return fmt.Errorf("read from client: %w", err)
|
|
}
|
|
c.udpClientAddr = addr
|
|
req, data, err := parseUDPRequest(buf[:n])
|
|
if err != nil {
|
|
return fmt.Errorf("parse udp request: %w", err)
|
|
}
|
|
|
|
targetConn, err := c.getOrDialTargetConn(ctx, clientConn, req.addr)
|
|
if err != nil {
|
|
return fmt.Errorf("dial target %s fail: %w", req.addr, err)
|
|
}
|
|
|
|
nn, err := targetConn.Write(data)
|
|
if err != nil {
|
|
return fmt.Errorf("write to target %s fail: %w", req.addr, err)
|
|
}
|
|
if nn != len(data) {
|
|
return fmt.Errorf("write to target %s fail: %w", req.addr, io.ErrShortWrite)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Conn) handleUDPResponse(
|
|
clientConn net.PacketConn,
|
|
targetAddr socksAddr,
|
|
targetConn net.Conn,
|
|
buf []byte,
|
|
) error {
|
|
// add a deadline for the read to avoid blocking forever
|
|
_ = targetConn.SetReadDeadline(time.Now().Add(readTimeout))
|
|
n, err := targetConn.Read(buf)
|
|
if err != nil {
|
|
return fmt.Errorf("read from target: %w", err)
|
|
}
|
|
hdr := udpRequest{addr: targetAddr}
|
|
pkt, err := hdr.marshal()
|
|
if err != nil {
|
|
return fmt.Errorf("marshal udp request: %w", err)
|
|
}
|
|
data := append(pkt, buf[:n]...)
|
|
// use addr from client to send back
|
|
nn, err := clientConn.WriteTo(data, c.udpClientAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("write to client: %w", err)
|
|
}
|
|
if nn != len(data) {
|
|
return fmt.Errorf("write to client: %w", io.ErrShortWrite)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isTimeout(err error) bool {
|
|
terr, ok := errors.Unwrap(err).(interface{ Timeout() bool })
|
|
return ok && terr.Timeout()
|
|
}
|
|
|
|
func splitHostPort(hostport string) (host string, port uint16, err error) {
|
|
host, portStr, err := net.SplitHostPort(hostport)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
portInt, err := strconv.Atoi(portStr)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
if portInt < 0 || portInt > 65535 {
|
|
return "", 0, fmt.Errorf("invalid port number %d", portInt)
|
|
}
|
|
return host, uint16(portInt), nil
|
|
}
|
|
|
|
// parseClientGreeting parses a request initiation packet.
|
|
func parseClientGreeting(r io.Reader, authMethod byte) error {
|
|
var hdr [2]byte
|
|
_, err := io.ReadFull(r, hdr[:])
|
|
if err != nil {
|
|
return fmt.Errorf("could not read packet header")
|
|
}
|
|
if hdr[0] != socks5Version {
|
|
return fmt.Errorf("incompatible SOCKS version")
|
|
}
|
|
count := int(hdr[1])
|
|
methods := make([]byte, count)
|
|
_, err = io.ReadFull(r, methods)
|
|
if err != nil {
|
|
return fmt.Errorf("could not read methods")
|
|
}
|
|
for _, m := range methods {
|
|
if m == authMethod {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("no acceptable auth methods")
|
|
}
|
|
|
|
func parseClientAuth(r io.Reader) (usr, pwd string, err error) {
|
|
var hdr [2]byte
|
|
if _, err := io.ReadFull(r, hdr[:]); err != nil {
|
|
return "", "", fmt.Errorf("could not read auth packet header")
|
|
}
|
|
if hdr[0] != passwordAuthVersion {
|
|
return "", "", fmt.Errorf("bad SOCKS auth version")
|
|
}
|
|
usrLen := int(hdr[1])
|
|
usrBytes := make([]byte, usrLen)
|
|
if _, err := io.ReadFull(r, usrBytes); err != nil {
|
|
return "", "", fmt.Errorf("could not read auth packet username")
|
|
}
|
|
var hdrPwd [1]byte
|
|
if _, err := io.ReadFull(r, hdrPwd[:]); err != nil {
|
|
return "", "", fmt.Errorf("could not read auth packet password length")
|
|
}
|
|
pwdLen := int(hdrPwd[0])
|
|
pwdBytes := make([]byte, pwdLen)
|
|
if _, err := io.ReadFull(r, pwdBytes); err != nil {
|
|
return "", "", fmt.Errorf("could not read auth packet password")
|
|
}
|
|
return string(usrBytes), string(pwdBytes), nil
|
|
}
|
|
|
|
func getAddrType(addr string) addrType {
|
|
if ip := net.ParseIP(addr); ip != nil {
|
|
if ip.To4() != nil {
|
|
return ipv4
|
|
}
|
|
return ipv6
|
|
}
|
|
return domainName
|
|
}
|
|
|
|
// request represents data contained within a SOCKS5
|
|
// connection request packet.
|
|
type request struct {
|
|
command commandType
|
|
destination socksAddr
|
|
}
|
|
|
|
// parseClientRequest converts raw packet bytes into a
|
|
// SOCKS5Request struct.
|
|
func parseClientRequest(r io.Reader) (*request, error) {
|
|
var hdr [3]byte
|
|
_, err := io.ReadFull(r, hdr[:])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not read packet header")
|
|
}
|
|
cmd := hdr[1]
|
|
|
|
destination, err := parseSocksAddr(r)
|
|
return &request{
|
|
command: commandType(cmd),
|
|
destination: destination,
|
|
}, err
|
|
}
|
|
|
|
type socksAddr struct {
|
|
addrType addrType
|
|
addr string
|
|
port uint16
|
|
}
|
|
|
|
var zeroSocksAddr = socksAddr{addrType: ipv4, addr: "0.0.0.0", port: 0}
|
|
|
|
func parseSocksAddr(r io.Reader) (addr socksAddr, err error) {
|
|
var addrTypeData [1]byte
|
|
_, err = io.ReadFull(r, addrTypeData[:])
|
|
if err != nil {
|
|
return socksAddr{}, fmt.Errorf("could not read address type")
|
|
}
|
|
|
|
dstAddrType := addrType(addrTypeData[0])
|
|
var destination string
|
|
switch dstAddrType {
|
|
case ipv4:
|
|
var ip [4]byte
|
|
_, err = io.ReadFull(r, ip[:])
|
|
if err != nil {
|
|
return socksAddr{}, fmt.Errorf("could not read IPv4 address")
|
|
}
|
|
destination = net.IP(ip[:]).String()
|
|
case domainName:
|
|
var dstSizeByte [1]byte
|
|
_, err = io.ReadFull(r, dstSizeByte[:])
|
|
if err != nil {
|
|
return socksAddr{}, fmt.Errorf("could not read domain name size")
|
|
}
|
|
dstSize := int(dstSizeByte[0])
|
|
domainName := make([]byte, dstSize)
|
|
_, err = io.ReadFull(r, domainName)
|
|
if err != nil {
|
|
return socksAddr{}, fmt.Errorf("could not read domain name")
|
|
}
|
|
destination = string(domainName)
|
|
case ipv6:
|
|
var ip [16]byte
|
|
_, err = io.ReadFull(r, ip[:])
|
|
if err != nil {
|
|
return socksAddr{}, fmt.Errorf("could not read IPv6 address")
|
|
}
|
|
destination = net.IP(ip[:]).String()
|
|
default:
|
|
return socksAddr{}, fmt.Errorf("unsupported address type")
|
|
}
|
|
var portBytes [2]byte
|
|
_, err = io.ReadFull(r, portBytes[:])
|
|
if err != nil {
|
|
return socksAddr{}, fmt.Errorf("could not read port")
|
|
}
|
|
port := binary.BigEndian.Uint16(portBytes[:])
|
|
return socksAddr{
|
|
addrType: dstAddrType,
|
|
addr: destination,
|
|
port: port,
|
|
}, nil
|
|
}
|
|
|
|
func (s socksAddr) marshal() ([]byte, error) {
|
|
var addr []byte
|
|
switch s.addrType {
|
|
case ipv4:
|
|
addr = net.ParseIP(s.addr).To4()
|
|
if addr == nil {
|
|
return nil, fmt.Errorf("invalid IPv4 address for binding")
|
|
}
|
|
case domainName:
|
|
if len(s.addr) > 255 {
|
|
return nil, fmt.Errorf("invalid domain name for binding")
|
|
}
|
|
addr = make([]byte, 0, len(s.addr)+1)
|
|
addr = append(addr, byte(len(s.addr)))
|
|
addr = append(addr, []byte(s.addr)...)
|
|
case ipv6:
|
|
addr = net.ParseIP(s.addr).To16()
|
|
if addr == nil {
|
|
return nil, fmt.Errorf("invalid IPv6 address for binding")
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unsupported address type")
|
|
}
|
|
|
|
pkt := []byte{byte(s.addrType)}
|
|
pkt = append(pkt, addr...)
|
|
pkt = binary.BigEndian.AppendUint16(pkt, s.port)
|
|
return pkt, nil
|
|
}
|
|
|
|
func (s socksAddr) hostPort() string {
|
|
return net.JoinHostPort(s.addr, strconv.Itoa(int(s.port)))
|
|
}
|
|
|
|
func (s socksAddr) String() string {
|
|
return s.hostPort()
|
|
}
|
|
|
|
// response contains the contents of
|
|
// a response packet sent from the proxy
|
|
// to the client.
|
|
type response struct {
|
|
reply replyCode
|
|
bindAddr socksAddr
|
|
}
|
|
|
|
func errorResponse(code replyCode) *response {
|
|
return &response{reply: code, bindAddr: zeroSocksAddr}
|
|
}
|
|
|
|
// marshal converts a SOCKS5Response struct into
|
|
// a packet. If res.reply == Success, it may throw an error on
|
|
// receiving an invalid bind address. Otherwise, it will not throw.
|
|
func (res *response) marshal() ([]byte, error) {
|
|
pkt := make([]byte, 3)
|
|
pkt[0] = socks5Version
|
|
pkt[1] = byte(res.reply)
|
|
pkt[2] = 0 // null reserved byte
|
|
|
|
addrPkt, err := res.bindAddr.marshal()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return append(pkt, addrPkt...), nil
|
|
}
|
|
|
|
type udpRequest struct {
|
|
frag byte
|
|
addr socksAddr
|
|
}
|
|
|
|
// +----+------+------+----------+----------+----------+
|
|
// |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
|
|
// +----+------+------+----------+----------+----------+
|
|
// | 2 | 1 | 1 | Variable | 2 | Variable |
|
|
// +----+------+------+----------+----------+----------+
|
|
func parseUDPRequest(data []byte) (*udpRequest, []byte, error) {
|
|
if len(data) < 4 {
|
|
return nil, nil, fmt.Errorf("invalid packet length")
|
|
}
|
|
|
|
// reserved bytes
|
|
if !(data[0] == 0 && data[1] == 0) {
|
|
return nil, nil, fmt.Errorf("invalid udp request header")
|
|
}
|
|
|
|
frag := data[2]
|
|
|
|
reader := bytes.NewReader(data[3:])
|
|
addr, err := parseSocksAddr(reader)
|
|
bodyLen := reader.Len() // (*bytes.Reader).Len() return unread data length
|
|
body := data[len(data)-bodyLen:]
|
|
return &udpRequest{
|
|
frag: frag,
|
|
addr: addr,
|
|
}, body, err
|
|
}
|
|
|
|
func (u *udpRequest) marshal() ([]byte, error) {
|
|
pkt := make([]byte, 3)
|
|
pkt[0] = 0
|
|
pkt[1] = 0
|
|
pkt[2] = u.frag
|
|
|
|
addrPkt, err := u.addr.marshal()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return append(pkt, addrPkt...), nil
|
|
}
|