diff --git a/README.md b/README.md index 1bf06bdbfa2e416d92276362832915dde2883add..24ee834ad8a6f0194b3e5a3022029cc21605f5a8 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,11 @@ Usage of ./go-mmproxy: Adress the proxy listens on (default "0.0.0.0:8443") -mark int The mark that will be set on outbound packets + -v int + 0 - no logging of individual connections + 1 - log errors occuring in individual connections + 2 - log all state changes of individual connections + ``` Example invocation: diff --git a/main.go b/main.go index de40383b5d39d23e12e919dcf0c858ebf7c081e5..187664b9eddce055e1e471061e297249f4ee86b3 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,8 @@ import ( "os" "strings" "syscall" + + "go.uber.org/zap" ) var listenAddr string @@ -23,15 +25,21 @@ var targetAddr4 string var targetAddr6 string var allowedSubnetsPath string var mark int +var verbose int var allowedSubnets []*net.IPNet +var logger *zap.Logger func init() { flag.StringVar(&listenAddr, "l", "0.0.0.0:8443", "Adress the proxy listens on") flag.StringVar(&targetAddr4, "4", "127.0.0.1:443", "Address to which IPv4 TCP traffic will be forwarded to") flag.StringVar(&targetAddr6, "6", "[::1]:443", "Address to which IPv6 TCP traffic will be forwarded to") flag.IntVar(&mark, "mark", 0, "The mark that will be set on outbound packets") - flag.StringVar(&allowedSubnetsPath, "allowed-subnets", "", "Path to a file that contains allowed subnets of the proxy servers") + flag.StringVar(&allowedSubnetsPath, "allowed-subnets", "", + "Path to a file that contains allowed subnets of the proxy servers") + flag.IntVar(&verbose, "v", 0, `0 - no logging of individual connections +1 - log errors occuring in individual connections +2 - log all state changes of individual connections`) } func readRemoteAddrPROXYv2(conn net.Conn, ctrlBuf []byte) (net.Addr, net.Addr, []byte, error) { @@ -73,10 +81,14 @@ func readRemoteAddrPROXYv2(conn net.Conn, ctrlBuf []byte) (net.Addr, net.Addr, [ if ctrlBuf[13] == 0x11 { // TCP over IPv4 srcIP := net.IPv4(ctrlBuf[16], ctrlBuf[17], ctrlBuf[18], ctrlBuf[19]) dstIP := net.IPv4(ctrlBuf[20], ctrlBuf[21], ctrlBuf[22], ctrlBuf[23]) - return &net.TCPAddr{IP: srcIP, Port: int(sport)}, &net.TCPAddr{IP: dstIP, Port: int(dport)}, ctrlBuf[16+dataLen:], nil + return &net.TCPAddr{IP: srcIP, Port: int(sport)}, + &net.TCPAddr{IP: dstIP, Port: int(dport)}, + ctrlBuf[16+dataLen:], nil } - return &net.TCPAddr{IP: ctrlBuf[16:32], Port: int(sport)}, &net.TCPAddr{IP: ctrlBuf[32:48], Port: int(dport)}, ctrlBuf[16+dataLen:], nil + return &net.TCPAddr{IP: ctrlBuf[16:32], Port: int(sport)}, + &net.TCPAddr{IP: ctrlBuf[32:48], Port: int(dport)}, + ctrlBuf[16+dataLen:], nil } func readRemoteAddrPROXYv1(conn net.Conn, ctrlBuf []byte) (net.Addr, net.Addr, []byte, error) { @@ -113,7 +125,9 @@ func readRemoteAddrPROXYv1(conn net.Conn, ctrlBuf []byte) (net.Addr, net.Addr, [ if dstIP == nil { return nil, nil, nil, fmt.Errorf("failed to parse destination IP address %s", dst) } - return &net.TCPAddr{IP: srcIP, Port: sport}, &net.TCPAddr{IP: dstIP, Port: dport}, ctrlBuf[idx+2:], nil + return &net.TCPAddr{IP: srcIP, Port: sport}, + &net.TCPAddr{IP: dstIP, Port: dport}, + ctrlBuf[idx+2:], nil } return nil, nil, nil, fmt.Errorf("did not find \\r\\n in first data segment") @@ -126,7 +140,8 @@ func readRemoteAddr(conn net.Conn) (net.Addr, net.Addr, []byte, error) { return nil, nil, nil, fmt.Errorf("failed to read header: %s", err.Error()) } - if n >= 16 && bytes.Equal(buf[:13], []byte{0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A}) { + if n >= 16 && bytes.Equal(buf[:12], + []byte{0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A}) { saddr, daddr, rest, err := readRemoteAddrPROXYv2(conn, buf[:n]) if err != nil { return nil, nil, nil, fmt.Errorf("failed to parse PROXY v2 header: %s", err.Error()) @@ -145,7 +160,7 @@ func readRemoteAddr(conn net.Conn) (net.Addr, net.Addr, []byte, error) { return nil, nil, nil, fmt.Errorf("PROXY header missing") } -func dialUpstreamControl(sport int) func(string, string, syscall.RawConn) error { +func dialUpstreamControl(sport int, connLog *zap.Logger) func(string, string, syscall.RawConn) error { return func(network, address string, c syscall.RawConn) error { var syscallErr error err := c.Control(func(fd uintptr) { @@ -169,7 +184,10 @@ func dialUpstreamControl(sport int) func(string, string, syscall.RawConn) error if sport == 0 { ipBindAddressNoPort := 24 - syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, ipBindAddressNoPort, 1) + err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, ipBindAddressNoPort, 1) + if err != nil && verbose > 1 { + connLog.Debug("Failed to set IP_BIND_ADDRESS_NO_PORT", zap.Error(err)) + } } if mark != 0 { @@ -217,15 +235,21 @@ func checkOriginAllowed(conn net.Conn) bool { func handleConnection(conn net.Conn) { defer conn.Close() + connLog := logger.With(zap.String("remoteAddr", conn.RemoteAddr().String()), + zap.String("localAddr", conn.LocalAddr().String())) if !checkOriginAllowed(conn) { - log.Printf("Disallowed connection from %s", conn.RemoteAddr().String()) + connLog.Debug("connection origin not in allowed subnets", zap.Bool("dropConnection", true)) return } + if verbose > 1 { + connLog.Debug("new connection") + } + saddr, _, restBytes, err := readRemoteAddr(conn) if err != nil { - log.Printf("Failed to parse PROXY data from %s: %s", conn.RemoteAddr().String(), err.Error()) + connLog.Debug("failed to parse PROXY header", zap.Error(err), zap.Bool("dropConnection", true)) return } @@ -234,31 +258,40 @@ func handleConnection(conn net.Conn) { targetAddr = targetAddr4 } - dialer := net.Dialer{LocalAddr: saddr, Control: dialUpstreamControl(saddr.(*net.TCPAddr).Port)} + connLog = connLog.With(zap.String("clientAddr", saddr.String()), zap.String("targetAddr", targetAddr)) + if verbose > 1 { + connLog.Debug("successfuly parsed PROXY header") + } + + dialer := net.Dialer{LocalAddr: saddr, Control: dialUpstreamControl(saddr.(*net.TCPAddr).Port, connLog)} upstreamConn, err := dialer.Dial("tcp", targetAddr) if err != nil { - log.Printf("Failed to establish upstream connection %s -> %s (PROXY %s -> %s): %s", - conn.RemoteAddr().String(), conn.LocalAddr().String(), saddr.String(), targetAddr, err.Error()) + connLog.Debug("failed to establish upstream connection", zap.Error(err), zap.Bool("dropConnection", true)) return } + defer upstreamConn.Close() + if verbose > 1 { + connLog.Debug("successfuly established upstream connection") + } + if err := conn.(*net.TCPConn).SetNoDelay(true); err != nil { - log.Printf("Failed to set nodelay on upstream connection %s -> %s (PROXY %s -> %s): %s", - conn.RemoteAddr().String(), conn.LocalAddr().String(), saddr.String(), targetAddr, err.Error()) + connLog.Debug("failed to set nodelay on downstream connection", zap.Error(err), zap.Bool("dropConnection", true)) + } else if verbose > 1 { + connLog.Debug("successfuly set NoDelay on downstream connection") } if err := upstreamConn.(*net.TCPConn).SetNoDelay(true); err != nil { - log.Printf("Failed to set nodelay on upstream connection %s -> %s (PROXY %s -> %s): %s", - conn.RemoteAddr().String(), conn.LocalAddr().String(), saddr.String(), targetAddr, err.Error()) + connLog.Debug("failed to set nodelay on upstream connection", zap.Error(err), zap.Bool("dropConnection", true)) + } else if verbose > 1 { + connLog.Debug("successfuly set NoDelay on upstream connection") } - defer upstreamConn.Close() - for len(restBytes) > 0 { n, err := conn.Write(restBytes) if err != nil { - log.Printf("Failed to write data to upstream connection %s -> %s (PROXY %s -> %s): %s", - conn.RemoteAddr().String(), conn.LocalAddr().String(), saddr.String(), targetAddr, err.Error()) + connLog.Debug("failed to write data to upstream connection", + zap.Error(err), zap.Bool("dropConnection", true)) return } restBytes = restBytes[n:] @@ -270,8 +303,9 @@ func handleConnection(conn net.Conn) { err = <-outErr if err != nil { - log.Printf("Connection %s -> %s (PROXY %s -> %s): %s", - conn.RemoteAddr().String(), conn.LocalAddr().String(), saddr.String(), targetAddr, err.Error()) + connLog.Debug("connection broken", zap.Error(err), zap.Bool("dropConnection", true)) + } else if verbose > 1 { + connLog.Debug("connection closing") } } @@ -290,29 +324,48 @@ func loadAllowedSubnets() error { return err } allowedSubnets = append(allowedSubnets, ipNet) + logger.Info("allowed subnet", zap.String("subnet", ipNet.String())) } return nil } +func initLogger() error { + logConfig := zap.NewProductionConfig() + if verbose > 0 { + logConfig.Level.SetLevel(zap.DebugLevel) + } + + l, err := logConfig.Build() + if err == nil { + logger = l + } + return err +} + func main() { flag.Parse() + if err := initLogger(); err != nil { + log.Fatalf("Failed to initialize logging: %s", err.Error()) + } + defer logger.Sync() + if allowedSubnetsPath != "" { if err := loadAllowedSubnets(); err != nil { - log.Fatalf("Failed to load allowed subnets file: %s", err.Error()) + logger.Fatal("failed to load allowed subnets file", zap.String("path", allowedSubnetsPath), zap.Error(err)) } } ln, err := net.Listen("tcp", listenAddr) if err != nil { - log.Fatalf("Failed to bind to %s: %s\n", listenAddr, err.Error()) + logger.Fatal("failed to bind listener", zap.String("listenAddr", listenAddr), zap.Error(err)) } for { conn, err := ln.Accept() if err != nil { - log.Fatalf("Failed to accept new connection: %s\n", err.Error()) + logger.Fatal("failed to accept new connection", zap.Error(err)) } go handleConnection(conn)