initial code
This commit is contained in:
parent
8d4a8d4b63
commit
17326f3876
|
@ -0,0 +1 @@
|
||||||
|
bin/
|
|
@ -13,3 +13,5 @@
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
# Dependency directories (remove the comment below to include it)
|
||||||
# vendor/
|
# vendor/
|
||||||
|
bin/
|
||||||
|
*.snap
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
FROM golang AS build
|
||||||
|
|
||||||
|
WORKDIR /go/src/github.com/Snawoot/dumbproxy
|
||||||
|
COPY . .
|
||||||
|
RUN CGO_ENABLED=0 go build -a -tags netgo -ldflags '-s -w -extldflags "-static"'
|
||||||
|
ADD https://curl.haxx.se/ca/cacert.pem /certs.crt
|
||||||
|
RUN chmod 0644 /certs.crt
|
||||||
|
|
||||||
|
FROM scratch AS arrange
|
||||||
|
COPY --from=build /go/src/github.com/Snawoot/dumbproxy/dumbproxy /
|
||||||
|
COPY --from=build /certs.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=arrange / /
|
||||||
|
USER 9999:9999
|
||||||
|
EXPOSE 8080/tcp
|
||||||
|
ENTRYPOINT ["/dumbproxy", "-bind-address", ":8080"]
|
|
@ -0,0 +1,56 @@
|
||||||
|
PROGNAME = dumbproxy
|
||||||
|
OUTSUFFIX = bin/$(PROGNAME)
|
||||||
|
BUILDOPTS = -a -tags netgo
|
||||||
|
LDFLAGS = -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
src = $(wildcard *.go)
|
||||||
|
|
||||||
|
native: bin-native
|
||||||
|
all: bin-linux-amd64 bin-linux-386 bin-linux-arm \
|
||||||
|
bin-freebsd-amd64 bin-freebsd-386 bin-freebsd-arm \
|
||||||
|
bin-darwin-amd64 \
|
||||||
|
bin-windows-amd64 bin-windows-386
|
||||||
|
|
||||||
|
bin-native: $(OUTSUFFIX)
|
||||||
|
bin-linux-amd64: $(OUTSUFFIX).linux-amd64
|
||||||
|
bin-linux-386: $(OUTSUFFIX).linux-386
|
||||||
|
bin-linux-arm: $(OUTSUFFIX).linux-arm
|
||||||
|
bin-freebsd-amd64: $(OUTSUFFIX).freebsd-amd64
|
||||||
|
bin-freebsd-386: $(OUTSUFFIX).freebsd-386
|
||||||
|
bin-freebsd-arm: $(OUTSUFFIX).freebsd-arm
|
||||||
|
bin-darwin-amd64: $(OUTSUFFIX).darwin-amd64
|
||||||
|
bin-windows-amd64: $(OUTSUFFIX).windows-amd64.exe
|
||||||
|
bin-windows-386: $(OUTSUFFIX).windows-386.exe
|
||||||
|
|
||||||
|
$(OUTSUFFIX): $(src)
|
||||||
|
CGO_ENABLED=0 go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||||
|
|
||||||
|
$(OUTSUFFIX).linux-amd64: $(src)
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||||
|
|
||||||
|
$(OUTSUFFIX).linux-386: $(src)
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||||
|
|
||||||
|
$(OUTSUFFIX).linux-arm: $(src)
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||||
|
|
||||||
|
$(OUTSUFFIX).freebsd-amd64: $(src)
|
||||||
|
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||||
|
|
||||||
|
$(OUTSUFFIX).freebsd-386: $(src)
|
||||||
|
CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||||
|
|
||||||
|
$(OUTSUFFIX).freebsd-arm: $(src)
|
||||||
|
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||||
|
|
||||||
|
$(OUTSUFFIX).darwin-amd64: $(src)
|
||||||
|
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||||
|
|
||||||
|
$(OUTSUFFIX).windows-amd64.exe: $(src)
|
||||||
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||||
|
|
||||||
|
$(OUTSUFFIX).windows-386.exe: $(src)
|
||||||
|
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build $(BUILDOPTS) $(LDFLAGS) -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f bin/*
|
|
@ -0,0 +1,58 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CRITICAL = 50
|
||||||
|
ERROR = 40
|
||||||
|
WARNING = 30
|
||||||
|
INFO = 20
|
||||||
|
DEBUG = 10
|
||||||
|
NOTSET = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
type CondLogger struct {
|
||||||
|
logger *log.Logger
|
||||||
|
verbosity int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *CondLogger) Log(verb int, format string, v ...interface{}) error {
|
||||||
|
if verb >= cl.verbosity {
|
||||||
|
return cl.logger.Output(2, fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *CondLogger) log(verb int, format string, v ...interface{}) error {
|
||||||
|
if verb >= cl.verbosity {
|
||||||
|
return cl.logger.Output(3, fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *CondLogger) Critical(s string, v ...interface{}) error {
|
||||||
|
return cl.log(CRITICAL, "CRITICAL " + s, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *CondLogger) Error(s string, v ...interface{}) error {
|
||||||
|
return cl.log(ERROR, "ERROR " + s, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *CondLogger) Warning(s string, v ...interface{}) error {
|
||||||
|
return cl.log(WARNING, "WARNING " + s, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *CondLogger) Info(s string, v ...interface{}) error {
|
||||||
|
return cl.log(INFO, "INFO " + s, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *CondLogger) Debug(s string, v ...interface{}) error {
|
||||||
|
return cl.log(DEBUG, "DEBUG " + s, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCondLogger(logger *log.Logger, verbosity int) *CondLogger {
|
||||||
|
return &CondLogger{verbosity: verbosity, logger: logger}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProxyHandler struct {
|
||||||
|
logger *CondLogger
|
||||||
|
httptransport http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyHandler(logger *CondLogger) *ProxyHandler {
|
||||||
|
httptransport := &http.Transport{}
|
||||||
|
return &ProxyHandler{
|
||||||
|
logger: logger,
|
||||||
|
httptransport: httptransport,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProxyHandler) HandleTunnel(wr http.ResponseWriter, req *http.Request) {
|
||||||
|
conn, err := net.Dial("tcp", req.RequestURI)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Can't satisfy CONNECT request: %v", err)
|
||||||
|
http.Error(wr, "Can't satisfy CONNECT request", http.StatusBadGateway)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
|
||||||
|
// Upgrade client connection
|
||||||
|
localconn, _, err := hijack(wr)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Can't hijack client connection: %v", err)
|
||||||
|
http.Error(wr, "Can't hijack client connection", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer localconn.Close()
|
||||||
|
|
||||||
|
// Inform client connection is built
|
||||||
|
fmt.Fprintf(localconn, "HTTP/%d.%d 200 OK\r\n\r\n", req.ProtoMajor, req.ProtoMinor)
|
||||||
|
|
||||||
|
proxy(req.Context(), localconn, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProxyHandler) HandleRequest(wr http.ResponseWriter, req *http.Request) {
|
||||||
|
req.RequestURI = ""
|
||||||
|
resp, err := s.httptransport.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("HTTP fetch error: %v", err)
|
||||||
|
http.Error(wr, "Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
s.logger.Info("%v %v %v %v", req.RemoteAddr, req.Method, req.URL, resp.Status)
|
||||||
|
delHopHeaders(resp.Header)
|
||||||
|
copyHeader(wr.Header(), resp.Header)
|
||||||
|
wr.WriteHeader(resp.StatusCode)
|
||||||
|
io.Copy(wr, resp.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
|
||||||
|
s.logger.Info("Request: %v %v %v", req.RemoteAddr, req.Method, req.URL)
|
||||||
|
delHopHeaders(req.Header)
|
||||||
|
if strings.ToUpper(req.Method) == "CONNECT" {
|
||||||
|
s.HandleTunnel(wr, req)
|
||||||
|
} else {
|
||||||
|
s.HandleRequest(wr, req)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const MAX_LOG_QLEN = 128
|
||||||
|
const QUEUE_SHUTDOWN_TIMEOUT = 500 * time.Millisecond
|
||||||
|
|
||||||
|
type LogWriter struct {
|
||||||
|
writer io.Writer
|
||||||
|
ch chan []byte
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lw *LogWriter) Write(p []byte) (int, error) {
|
||||||
|
if p == nil {
|
||||||
|
return 0, errors.New("Can't write nil byte slice")
|
||||||
|
}
|
||||||
|
buf := make([]byte, len(p))
|
||||||
|
copy(buf, p)
|
||||||
|
select {
|
||||||
|
case lw.ch <- buf:
|
||||||
|
return len(p), nil
|
||||||
|
default:
|
||||||
|
return 0, errors.New("Writer queue overflow")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogWriter(writer io.Writer) *LogWriter {
|
||||||
|
lw := &LogWriter{writer,
|
||||||
|
make(chan []byte, MAX_LOG_QLEN),
|
||||||
|
make(chan struct{})}
|
||||||
|
go lw.loop()
|
||||||
|
return lw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lw *LogWriter) loop() {
|
||||||
|
for p := range lw.ch {
|
||||||
|
if p == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lw.writer.Write(p)
|
||||||
|
}
|
||||||
|
lw.done <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lw *LogWriter) Close() {
|
||||||
|
lw.ch <- nil
|
||||||
|
timer := time.After(QUEUE_SHUTDOWN_TIMEOUT)
|
||||||
|
select {
|
||||||
|
case <-timer:
|
||||||
|
case <-lw.done:
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"fmt"
|
||||||
|
"flag"
|
||||||
|
"time"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func perror(msg string) {
|
||||||
|
fmt.Fprintln(os.Stderr, "")
|
||||||
|
fmt.Fprintln(os.Stderr, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func arg_fail(msg string) {
|
||||||
|
perror(msg)
|
||||||
|
perror("Usage:")
|
||||||
|
flag.PrintDefaults()
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CLIArgs struct {
|
||||||
|
bind_address string
|
||||||
|
verbosity int
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func parse_args() CLIArgs {
|
||||||
|
var args CLIArgs
|
||||||
|
flag.StringVar(&args.bind_address, "bind-address", ":8080", "HTTP proxy listen address")
|
||||||
|
flag.IntVar(&args.verbosity, "verbosity", 20, "logging verbosity " +
|
||||||
|
"(10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical)")
|
||||||
|
flag.DurationVar(&args.timeout, "timeout", 10 * time.Second, "timeout for network operations")
|
||||||
|
flag.Parse()
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() int {
|
||||||
|
args := parse_args()
|
||||||
|
|
||||||
|
logWriter := NewLogWriter(os.Stderr)
|
||||||
|
defer logWriter.Close()
|
||||||
|
|
||||||
|
mainLogger := NewCondLogger(log.New(logWriter, "MAIN : ",
|
||||||
|
log.LstdFlags | log.Lshortfile),
|
||||||
|
args.verbosity)
|
||||||
|
proxyLogger := NewCondLogger(log.New(logWriter, "PROXY : ",
|
||||||
|
log.LstdFlags | log.Lshortfile),
|
||||||
|
args.verbosity)
|
||||||
|
mainLogger.Info("Starting proxy server...")
|
||||||
|
handler := NewProxyHandler(proxyLogger)
|
||||||
|
err := http.ListenAndServe(args.bind_address, handler)
|
||||||
|
mainLogger.Critical("Server terminated with a reason: %v", err)
|
||||||
|
mainLogger.Info("Shutting down...")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
os.Exit(run())
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"bufio"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
Loading…
Reference in New Issue