Skip to content

Commit

Permalink
Merge pull request #71 from SenseUnit/refactoring
Browse files Browse the repository at this point in the history
Split packages
  • Loading branch information
Snawoot authored Oct 24, 2024
2 parents aa2af64 + 248112e commit b28c8eb
Show file tree
Hide file tree
Showing 10 changed files with 442 additions and 421 deletions.
28 changes: 23 additions & 5 deletions auth.go → auth/auth.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package auth

import (
"bytes"
Expand All @@ -8,13 +8,16 @@ import (
"fmt"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"
"time"

"github.com/tg123/go-htpasswd"
"golang.org/x/crypto/bcrypt"

clog "github.com/SenseUnit/dumbproxy/log"
)

const AUTH_REQUIRED_MSG = "Proxy authentication required.\n"
Expand All @@ -27,7 +30,7 @@ type Auth interface {
Stop()
}

func NewAuth(paramstr string, logger *CondLogger) (Auth, error) {
func NewAuth(paramstr string, logger *clog.CondLogger) (Auth, error) {
url, err := url.Parse(paramstr)
if err != nil {
return nil, err
Expand All @@ -47,7 +50,7 @@ func NewAuth(paramstr string, logger *CondLogger) (Auth, error) {
}
}

func NewStaticAuth(param_url *url.URL, logger *CondLogger) (*BasicAuth, error) {
func NewStaticAuth(param_url *url.URL, logger *clog.CondLogger) (*BasicAuth, error) {
values, err := url.ParseQuery(param_url.RawQuery)
if err != nil {
return nil, err
Expand Down Expand Up @@ -100,14 +103,14 @@ type BasicAuth struct {
pwFilename string
pwFile *htpasswd.File
pwMux sync.RWMutex
logger *CondLogger
logger *clog.CondLogger
hiddenDomain string
stopOnce sync.Once
stopChan chan struct{}
lastReloaded time.Time
}

func NewBasicFileAuth(param_url *url.URL, logger *CondLogger) (*BasicAuth, error) {
func NewBasicFileAuth(param_url *url.URL, logger *clog.CondLogger) (*BasicAuth, error) {
values, err := url.ParseQuery(param_url.RawQuery)
if err != nil {
return nil, err
Expand Down Expand Up @@ -268,3 +271,18 @@ func (_ CertAuth) Validate(wr http.ResponseWriter, req *http.Request) (string, b
}

func (_ CertAuth) Stop() {}

func fileModTime(filename string) (time.Time, error) {
f, err := os.Open(filename)
if err != nil {
return time.Time{}, fmt.Errorf("fileModTime(): can't open file %q: %w", filename, err)
}
defer f.Close()

fi, err := f.Stat()
if err != nil {
return time.Time{}, fmt.Errorf("fileModTime(): can't stat file %q: %w", filename, err)
}

return fi.ModTime(), nil
}
85 changes: 85 additions & 0 deletions dialer/dialer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package dialer

import (
"context"
"fmt"
"net"
"net/url"
"strings"
"sync"

xproxy "golang.org/x/net/proxy"
)

type Dialer = xproxy.Dialer
type ContextDialer = xproxy.ContextDialer

var registerDialerTypesOnce sync.Once

func ProxyDialerFromURL(proxyURL string, forward Dialer) (Dialer, error) {
registerDialerTypesOnce.Do(func() {
xproxy.RegisterDialerType("http", HTTPProxyDialerFromURL)
xproxy.RegisterDialerType("https", HTTPProxyDialerFromURL)
})
parsedURL, err := url.Parse(proxyURL)
if err != nil {
return nil, fmt.Errorf("unable to parse proxy URL: %w", err)
}
d, err := xproxy.FromURL(parsedURL, forward)
if err != nil {
return nil, fmt.Errorf("unable to construct proxy dialer from URL %q: %w", proxyURL, err)
}
return d, nil
}

type wrappedDialer struct {
d Dialer
}

func (wd wrappedDialer) Dial(net, address string) (net.Conn, error) {
return wd.d.Dial(net, address)
}

func (wd wrappedDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
var (
conn net.Conn
done = make(chan struct{}, 1)
err error
)
go func() {
conn, err = wd.d.Dial(network, address)
close(done)
if conn != nil && ctx.Err() != nil {
conn.Close()
}
}()
select {
case <-ctx.Done():
err = ctx.Err()
case <-done:
}
return conn, err
}

func MaybeWrapWithContextDialer(d Dialer) ContextDialer {
if xd, ok := d.(ContextDialer); ok {
return xd
}
return wrappedDialer{d}
}

func parseIPList(list string) ([]net.IP, error) {
res := make([]net.IP, 0)
for _, elem := range strings.Split(list, ",") {
elem = strings.TrimSpace(elem)
if len(elem) == 0 {
continue
}
if parsed := net.ParseIP(elem); parsed == nil {
return nil, fmt.Errorf("unable to parse IP address %q", elem)
} else {
res = append(res, parsed)
}
}
return res, nil
}
2 changes: 1 addition & 1 deletion hintdialer.go → dialer/hintdialer.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package dialer

import (
"context"
Expand Down
6 changes: 3 additions & 3 deletions upstream.go → dialer/upstream.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package dialer

import (
"bufio"
Expand Down Expand Up @@ -29,7 +29,7 @@ func NewHTTPProxyDialer(address string, tls bool, userinfo *url.Userinfo, next D
return &HTTPProxyDialer{
address: address,
tls: tls,
next: maybeWrapWithContextDialer(next),
next: MaybeWrapWithContextDialer(next),
userinfo: userinfo,
}
}
Expand Down Expand Up @@ -106,7 +106,7 @@ func (d *HTTPProxyDialer) DialContext(ctx context.Context, network, address stri
if d.userinfo != nil {
fmt.Fprintf(&reqBuf, "Proxy-Authorization: %s\r\n", basicAuthHeader(d.userinfo))
}
fmt.Fprintf(&reqBuf, "User-Agent: dumbproxy/%s\r\n\r\n", version)
fmt.Fprintf(&reqBuf, "User-Agent: dumbproxy\r\n\r\n")
_, err = io.Copy(conn, &reqBuf)
if err != nil {
conn.Close()
Expand Down
20 changes: 12 additions & 8 deletions handler.go → handler/handler.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package handler

import (
"context"
Expand All @@ -8,6 +8,10 @@ import (
"strings"
"sync"
"time"

"github.com/SenseUnit/dumbproxy/auth"
"github.com/SenseUnit/dumbproxy/dialer"
clog "github.com/SenseUnit/dumbproxy/log"
)

const HintsHeaderName = "X-Src-IP-Hints"
Expand All @@ -18,17 +22,17 @@ type HandlerDialer interface {

type ProxyHandler struct {
timeout time.Duration
auth Auth
logger *CondLogger
auth auth.Auth
logger *clog.CondLogger
dialer HandlerDialer
httptransport http.RoundTripper
outbound map[string]string
outboundMux sync.RWMutex
userIPHints bool
}

func NewProxyHandler(timeout time.Duration, auth Auth, dialer HandlerDialer,
userIPHints bool, logger *CondLogger) *ProxyHandler {
func NewProxyHandler(timeout time.Duration, auth auth.Auth, dialer HandlerDialer,
userIPHints bool, logger *clog.CondLogger) *ProxyHandler {
httptransport := &http.Transport{
DialContext: dialer.DialContext,
DisableKeepAlives: true,
Expand Down Expand Up @@ -122,14 +126,14 @@ func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
if originator, isLoopback := s.isLoopback(req); isLoopback {
s.logger.Critical("Loopback tunnel detected: %s is an outbound "+
"address for another request from %s", req.RemoteAddr, originator)
http.Error(wr, BAD_REQ_MSG, http.StatusBadRequest)
http.Error(wr, auth.BAD_REQ_MSG, http.StatusBadRequest)
return
}

isConnect := strings.ToUpper(req.Method) == "CONNECT"
if (req.URL.Host == "" || req.URL.Scheme == "" && !isConnect) && req.ProtoMajor < 2 ||
req.Host == "" && req.ProtoMajor == 2 {
http.Error(wr, BAD_REQ_MSG, http.StatusBadRequest)
http.Error(wr, auth.BAD_REQ_MSG, http.StatusBadRequest)
return
}

Expand All @@ -149,7 +153,7 @@ func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
ipHints = &hintValues[0]
}
}
newCtx := context.WithValue(req.Context(), BoundDialerContextKey{}, BoundDialerContextValue{
newCtx := context.WithValue(req.Context(), dialer.BoundDialerContextKey{}, dialer.BoundDialerContextValue{
Hints: ipHints,
LocalAddr: trimAddrPort(localAddr),
})
Expand Down
140 changes: 140 additions & 0 deletions handler/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package handler

import (
"bufio"
"context"
"errors"
"io"
"net"
"net/http"
"sync"
"time"
)

const COPY_BUF = 128 * 1024

func proxy(ctx context.Context, left, right net.Conn) {
wg := sync.WaitGroup{}
cpy := func(dst, src net.Conn) {
defer wg.Done()
io.Copy(dst, src)
dst.Close()
}
wg.Add(2)
go cpy(left, right)
go cpy(right, left)
groupdone := make(chan struct{}, 1)
go func() {
wg.Wait()
groupdone <- struct{}{}
}()
select {
case <-ctx.Done():
left.Close()
right.Close()
case <-groupdone:
return
}
<-groupdone
return
}

func proxyh2(ctx context.Context, leftreader io.ReadCloser, leftwriter io.Writer, right net.Conn) {
wg := sync.WaitGroup{}
ltr := func(dst net.Conn, src io.Reader) {
defer wg.Done()
io.Copy(dst, src)
dst.Close()
}
rtl := func(dst io.Writer, src io.Reader) {
defer wg.Done()
copyBody(dst, src)
}
wg.Add(2)
go ltr(right, leftreader)
go rtl(leftwriter, right)
groupdone := make(chan struct{}, 1)
go func() {
wg.Wait()
groupdone <- struct{}{}
}()
select {
case <-ctx.Done():
leftreader.Close()
right.Close()
case <-groupdone:
return
}
<-groupdone
return
}

// Hop-by-hop headers. These are removed when sent to the backend.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
var hopHeaders = []string{
"Connection",
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Connection",
"Proxy-Authorization",
"Te", // canonicalized version of "TE"
"Trailers",
"Transfer-Encoding",
"Upgrade",
}

func copyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}

func delHopHeaders(header http.Header) {
for _, h := range hopHeaders {
header.Del(h)
}
}

func hijack(hijackable interface{}) (net.Conn, *bufio.ReadWriter, error) {
hj, ok := hijackable.(http.Hijacker)
if !ok {
return nil, nil, errors.New("Connection doesn't support hijacking")
}
conn, rw, err := hj.Hijack()
if err != nil {
return nil, nil, err
}
var emptytime time.Time
err = conn.SetDeadline(emptytime)
if err != nil {
conn.Close()
return nil, nil, err
}
return conn, rw, nil
}

func flush(flusher interface{}) bool {
f, ok := flusher.(http.Flusher)
if !ok {
return false
}
f.Flush()
return true
}

func copyBody(wr io.Writer, body io.Reader) {
buf := make([]byte, COPY_BUF)
for {
bread, read_err := body.Read(buf)
var write_err error
if bread > 0 {
_, write_err = wr.Write(buf[:bread])
flush(wr)
}
if read_err != nil || write_err != nil {
break
}
}
}
Loading

0 comments on commit b28c8eb

Please sign in to comment.