Skip to content
Snippets Groups Projects
main.go 4.89 KiB
Newer Older
  • Learn to ignore specific revisions
  • // Copyright 2019 Path Network, Inc. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.
    
    package main
    
    import (
    
    	"bufio"
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	"time"
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    type options struct {
    	Protocol           string
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	ListenAddrStr      string
    	TargetAddr4Str     string
    	TargetAddr6Str     string
    	ListenAddr         netip.AddrPort
    	TargetAddr4        netip.AddrPort
    	TargetAddr6        netip.AddrPort
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	Mark               int
    	Verbose            int
    	allowedSubnetsPath string
    	AllowedSubnets     []*net.IPNet
    	Listeners          int
    
    	Logger             *slog.Logger
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	udpCloseAfter      int
    	UDPCloseAfter      time.Duration
    }
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    var Opts options
    
    func init() {
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	flag.StringVar(&Opts.Protocol, "p", "tcp", "Protocol that will be proxied: tcp, udp")
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	flag.StringVar(&Opts.ListenAddrStr, "l", "0.0.0.0:8443", "Address the proxy listens on")
    	flag.StringVar(&Opts.TargetAddr4Str, "4", "127.0.0.1:443", "Address to which IPv4 traffic will be forwarded to")
    	flag.StringVar(&Opts.TargetAddr6Str, "6", "[::1]:443", "Address to which IPv6 traffic will be forwarded to")
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	flag.IntVar(&Opts.Mark, "mark", 0, "The mark that will be set on outbound packets")
    	flag.IntVar(&Opts.Verbose, "v", 0, `0 - no logging of individual connections
    
    1 - log errors occurring in individual connections
    
    2 - log all state changes of individual connections`)
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	flag.StringVar(&Opts.allowedSubnetsPath, "allowed-subnets", "",
    		"Path to a file that contains allowed subnets of the proxy servers")
    	flag.IntVar(&Opts.Listeners, "listeners", 1,
    
    		"Number of listener sockets that will be opened for the listen address (Linux 3.9+)")
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	flag.IntVar(&Opts.udpCloseAfter, "close-after", 60, "Number of seconds after which UDP socket will be cleaned up")
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    func listen(listenerNum int, errors chan<- error) {
    
    	logger := Opts.Logger.With(slog.Int("listenerNum", listenerNum),
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    		slog.String("protocol", Opts.Protocol), slog.String("listenAdr", Opts.ListenAddr.String()))
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    
    	listenConfig := net.ListenConfig{}
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	if Opts.Listeners > 1 {
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    		listenConfig.Control = func(network, address string, c syscall.RawConn) error {
    			return c.Control(func(fd uintptr) {
    				soReusePort := 15
    				if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, soReusePort, 1); err != nil {
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    					logger.Warn("failed to set SO_REUSEPORT - only one listener setup will succeed")
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	if Opts.Protocol == "tcp" {
    		TCPListen(&listenConfig, logger, errors)
    	} else {
    		UDPListen(&listenConfig, logger, errors)
    
    func loadAllowedSubnets() error {
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	file, err := os.Open(Opts.allowedSubnetsPath)
    
    	if err != nil {
    		return err
    	}
    
    	defer file.Close()
    
    	scanner := bufio.NewScanner(file)
    	for scanner.Scan() {
    		_, ipNet, err := net.ParseCIDR(scanner.Text())
    		if err != nil {
    			return err
    		}
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    		Opts.AllowedSubnets = append(Opts.AllowedSubnets, ipNet)
    
    		Opts.Logger.Info("allowed subnet", slog.String("subnet", ipNet.String()))
    
    func main() {
    	flag.Parse()
    
    	lvl := slog.LevelInfo
    	if Opts.Verbose > 0 {
    		lvl = slog.LevelDebug
    
    	Opts.Logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: lvl}))
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	if Opts.allowedSubnetsPath != "" {
    
    		if err := loadAllowedSubnets(); err != nil {
    
    			Opts.Logger.Error("failed to load allowed subnets file", "path", Opts.allowedSubnetsPath, "error", err)
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	if Opts.Protocol != "tcp" && Opts.Protocol != "udp" {
    
    		Opts.Logger.Error("--protocol has to be one of udp, tcp", slog.String("protocol", Opts.Protocol))
    		os.Exit(1)
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	}
    
    	if Opts.Mark < 0 {
    
    		Opts.Logger.Error("--mark has to be >= 0", slog.Int("mark", Opts.Mark))
    		os.Exit(1)
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	}
    
    	if Opts.Verbose < 0 {
    
    		Opts.Logger.Error("-v has to be >= 0", slog.Int("verbose", Opts.Verbose))
    		os.Exit(1)
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	}
    
    	if Opts.Listeners < 1 {
    
    		Opts.Logger.Error("--listeners has to be >= 1")
    		os.Exit(1)
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	var err error
    	if Opts.ListenAddr, err = netip.ParseAddrPort(Opts.ListenAddrStr); err != nil {
    
    		Opts.Logger.Error("listen address is malformed", "error", err)
    		os.Exit(1)
    	}
    
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	if Opts.TargetAddr4, err = netip.ParseAddrPort(Opts.TargetAddr4Str); err != nil {
    
    		Opts.Logger.Error("ipv4 target address is malformed", "error", err)
    		os.Exit(1)
    	}
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	if !Opts.TargetAddr4.Addr().Is4() {
    		Opts.Logger.Error("ipv4 target address is not IPv4")
    		os.Exit(1)
    	}
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	if Opts.TargetAddr6, err = netip.ParseAddrPort(Opts.TargetAddr6Str); err != nil {
    
    		Opts.Logger.Error("ipv6 target address is malformed", "error", err)
    		os.Exit(1)
    	}
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	if !Opts.TargetAddr6.Addr().Is6() {
    		Opts.Logger.Error("ipv6 target address is not IPv6")
    		os.Exit(1)
    	}
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	if Opts.udpCloseAfter < 0 {
    
    		Opts.Logger.Error("--close-after has to be >= 0", slog.Int("close-after", Opts.udpCloseAfter))
    		os.Exit(1)
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	}
    	Opts.UDPCloseAfter = time.Duration(Opts.udpCloseAfter) * time.Second
    
    	listenErrors := make(chan error, Opts.Listeners)
    	for i := 0; i < Opts.Listeners; i++ {
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    		go listen(i, listenErrors)
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    	for i := 0; i < Opts.Listeners; i++ {
    
    Konrad Zemek's avatar
    Konrad Zemek committed
    		<-listenErrors