github.com/bepass-org/wireguard-go@v1.0.4-rc2.0.20240304192354-ebce6572bc24/app/app.go (about)

     1  package app
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"log"
     8  	"net"
     9  	"net/netip"
    10  	"os"
    11  	"path/filepath"
    12  	"time"
    13  
    14  	"github.com/bepass-org/wireguard-go/psiphon"
    15  	"github.com/bepass-org/wireguard-go/warp"
    16  	"github.com/bepass-org/wireguard-go/wiresocks"
    17  )
    18  
    19  type WarpOptions struct {
    20  	LogLevel string
    21  	Bind     netip.AddrPort
    22  	Endpoint string
    23  	License  string
    24  	Psiphon  *PsiphonOptions
    25  	Gool     bool
    26  	Scan     *ScanOptions
    27  }
    28  
    29  type PsiphonOptions struct {
    30  	Country string
    31  }
    32  
    33  type ScanOptions struct {
    34  	MaxRTT time.Duration
    35  }
    36  
    37  func RunWarp(ctx context.Context, opts WarpOptions) error {
    38  	if opts.Psiphon != nil && opts.Gool {
    39  		return errors.New("can't use psiphon and gool at the same time")
    40  	}
    41  
    42  	if opts.Psiphon != nil && opts.Psiphon.Country == "" {
    43  		return errors.New("must provide country for psiphon")
    44  	}
    45  
    46  	//create necessary file structures
    47  	if err := makeDirs(); err != nil {
    48  		return err
    49  	}
    50  	log.Println("'primary' and 'secondary' directories are ready")
    51  
    52  	// Change the current working directory to 'stuff'
    53  	if err := os.Chdir("stuff"); err != nil {
    54  		return fmt.Errorf("error changing to 'stuff' directory: %w", err)
    55  	}
    56  	log.Println("Changed working directory to 'stuff'")
    57  
    58  	//create identities
    59  	if err := createPrimaryAndSecondaryIdentities(opts.License); err != nil {
    60  		return err
    61  	}
    62  
    63  	//Decide Working Scenario
    64  	endpoints := []string{opts.Endpoint, opts.Endpoint}
    65  
    66  	if opts.Scan != nil {
    67  		var err error
    68  		endpoints, err = wiresocks.RunScan(ctx, opts.Scan.MaxRTT)
    69  		if err != nil {
    70  			return err
    71  		}
    72  	}
    73  
    74  	var warpErr error
    75  	switch {
    76  	case opts.Psiphon != nil:
    77  		// run primary warp on a random tcp port and run psiphon on bind address
    78  		warpErr = runWarpWithPsiphon(ctx, opts.Bind, endpoints, opts.Psiphon.Country, opts.LogLevel == "debug")
    79  	case opts.Gool:
    80  		// run warp in warp
    81  		warpErr = runWarpInWarp(ctx, opts.Bind, endpoints, opts.LogLevel == "debug")
    82  	default:
    83  		// just run primary warp on bindAddress
    84  		_, _, warpErr = runWarp(ctx, opts.Bind, endpoints, "./primary/wgcf-profile.ini", opts.LogLevel == "debug", true, true)
    85  	}
    86  
    87  	return warpErr
    88  }
    89  
    90  func runWarp(ctx context.Context, bind netip.AddrPort, endpoints []string, confPath string, verbose, startProxy bool, trick bool) (*wiresocks.VirtualTun, int, error) {
    91  	conf, err := wiresocks.ParseConfig(confPath, endpoints[0])
    92  	if err != nil {
    93  		log.Println(err)
    94  		return nil, 0, err
    95  	}
    96  
    97  	if trick {
    98  		conf.Device.Trick = trick
    99  	}
   100  
   101  	tnet, err := wiresocks.StartWireguard(ctx, conf.Device, verbose)
   102  	if err != nil {
   103  		log.Println(err)
   104  		return nil, 0, err
   105  	}
   106  
   107  	if startProxy {
   108  		tnet.StartProxy(bind)
   109  	}
   110  
   111  	return tnet, conf.Device.MTU, nil
   112  }
   113  
   114  func runWarpWithPsiphon(ctx context.Context, bind netip.AddrPort, endpoints []string, country string, verbose bool) error {
   115  	// make a random bind address for warp
   116  	warpBindAddress, err := findFreePort("tcp")
   117  	if err != nil {
   118  		log.Println("There are no free tcp ports on Device!")
   119  		return err
   120  	}
   121  
   122  	_, _, err = runWarp(ctx, warpBindAddress, endpoints, "./primary/wgcf-profile.ini", verbose, true, true)
   123  	if err != nil {
   124  		return err
   125  	}
   126  
   127  	// run psiphon
   128  	err = psiphon.RunPsiphon(warpBindAddress.String(), bind.String(), country, ctx)
   129  	if err != nil {
   130  		log.Printf("unable to run psiphon %v", err)
   131  		return fmt.Errorf("unable to run psiphon %v", err)
   132  	}
   133  
   134  	log.Printf("Serving on %s", bind)
   135  
   136  	return nil
   137  }
   138  
   139  func runWarpInWarp(ctx context.Context, bind netip.AddrPort, endpoints []string, verbose bool) error {
   140  	// run secondary warp
   141  	vTUN, mtu, err := runWarp(ctx, netip.AddrPort{}, endpoints, "./secondary/wgcf-profile.ini", verbose, false, true)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	// run virtual endpoint
   147  	virtualEndpointBindAddress, err := findFreePort("udp")
   148  	if err != nil {
   149  		log.Println("There are no free udp ports on Device!")
   150  		return err
   151  	}
   152  	addr := endpoints[1]
   153  	err = wiresocks.NewVtunUDPForwarder(virtualEndpointBindAddress.String(), addr, vTUN, mtu+100, ctx)
   154  	if err != nil {
   155  		log.Println(err)
   156  		return err
   157  	}
   158  
   159  	// run primary warp
   160  	_, _, err = runWarp(ctx, bind, []string{virtualEndpointBindAddress.String()}, "./primary/wgcf-profile.ini", verbose, true, false)
   161  	if err != nil {
   162  		return err
   163  	}
   164  	return nil
   165  }
   166  
   167  func findFreePort(network string) (netip.AddrPort, error) {
   168  	if network == "udp" {
   169  		addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
   170  		if err != nil {
   171  			return netip.AddrPort{}, err
   172  		}
   173  
   174  		conn, err := net.ListenUDP("udp", addr)
   175  		if err != nil {
   176  			return netip.AddrPort{}, err
   177  		}
   178  		defer conn.Close()
   179  
   180  		return netip.MustParseAddrPort(conn.LocalAddr().String()), nil
   181  	}
   182  	// Listen on TCP port 0, which tells the OS to pick a free port.
   183  	listener, err := net.Listen(network, "127.0.0.1:0")
   184  	if err != nil {
   185  		return netip.AddrPort{}, err // Return error if unable to listen on a port
   186  	}
   187  	defer listener.Close() // Ensure the listener is closed when the function returns
   188  
   189  	// Get the port from the listener's address
   190  	return netip.MustParseAddrPort(listener.Addr().String()), nil
   191  }
   192  
   193  func createPrimaryAndSecondaryIdentities(license string) error {
   194  	// make primary identity
   195  	warp.UpdatePath("./primary")
   196  	if !warp.CheckProfileExists(license) {
   197  		err := warp.LoadOrCreateIdentity(license)
   198  		if err != nil {
   199  			log.Printf("error: %v", err)
   200  			return fmt.Errorf("error: %v", err)
   201  		}
   202  	}
   203  	// make secondary
   204  	warp.UpdatePath("./secondary")
   205  	if !warp.CheckProfileExists(license) {
   206  		err := warp.LoadOrCreateIdentity(license)
   207  		if err != nil {
   208  			log.Printf("error: %v", err)
   209  			return fmt.Errorf("error: %v", err)
   210  		}
   211  	}
   212  	return nil
   213  }
   214  
   215  func makeDirs() error {
   216  	stuffDir := "stuff"
   217  	primaryDir := "primary"
   218  	secondaryDir := "secondary"
   219  
   220  	// Check if 'stuff' directory exists, if not create it
   221  	if _, err := os.Stat(stuffDir); os.IsNotExist(err) {
   222  		if err := os.Mkdir(stuffDir, 0755); err != nil {
   223  			return fmt.Errorf("error creating 'stuff' directory: %w", err)
   224  		}
   225  	}
   226  
   227  	// Create 'primary' and 'secondary' directories if they don't exist
   228  	for _, dir := range []string{primaryDir, secondaryDir} {
   229  		if _, err := os.Stat(filepath.Join(stuffDir, dir)); os.IsNotExist(err) {
   230  			if err := os.Mkdir(filepath.Join(stuffDir, dir), 0755); err != nil {
   231  				return fmt.Errorf("error creating '%s' directory: %w", dir, err)
   232  			}
   233  		}
   234  	}
   235  
   236  	return nil
   237  }