diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a08425 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/sandbox +/dist +/bin +/cert diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7f322eb --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +export ROOT=$(realpath $(dir $(firstword $(MAKEFILE_LIST)))) +export BIN=$(ROOT)/bin +export GOBIN?=$(BIN) +export GO=$(shell which go) +export CGO_ENABLED=1 +export PROTO_OUT=$(ROOT) +export GOX=$(BIN)/gox + +proto: + @protoc \ + -Iprotofiles \ + --go_out=${PROTO_OUT} \ + --go-grpc_out=${PROTO_OUT} \ + protofiles/*.proto +.PHONY: proto + +install-gox: + @$(GO) install github.com/mitchellh/gox@v1.0.1 + +.PHONY: build-linux +build-linux: install-gox + @$(GOX) --arch=amd64 --os=linux --output="dist/utg_{{.OS}}_{{.Arch}}" + +.PHONY: build-macOS +build-macOS: install-gox + @$(GOX) --arch=amd64 --os=darwin --output="dist/utg_{{.OS}}_{{.Arch}}" + +.PHONY: build-windows +build-windows: install-gox + @$(GOX) -ldflags ${GO_LDFLAGS} --arch=amd64 --os=windows --output="dist/utg_{{.OS}}_{{.Arch}}" + +.PHONY: build-artifacts +build-artifacts: + @$(MAKE) build-linux && \ + $(MAKE) build-macOS && \ + $(MAKE) build-windows + +cert: + @openssl genrsa -out cert/ca.key 2048 + @openssl req -new -x509 -days 365 -key cert/ca.key -subj "/C=CN/ST=GD/L=SZ/O=Acme, Inc./CN=Acme Root CA" -out cert/ca.crt + @openssl req -newkey rsa:2048 -nodes -keyout cert/server.key -subj "/C=CN/ST=GD/L=SZ/O=Acme, Inc./CN=*.mrjosh.net" -out cert/server.csr + @openssl x509 -req -extfile <(printf "subjectAltName=DNS:mrjosh.net,DNS:u2g.mrjosh.net") -days 365 -in cert/server.csr -CA cert/ca.crt -CAkey cert/ca.key -CAcreateserial -out cert/server.crt diff --git a/cmds/client.go b/cmds/client.go new file mode 100644 index 0000000..4bd3d9e --- /dev/null +++ b/cmds/client.go @@ -0,0 +1,93 @@ +package cmds + +import ( + "context" + "fmt" + "log" + + "github.com/mrjosh/udp2grpc/internal/client" + "github.com/mrjosh/udp2grpc/proto" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +type NewClientFlags struct { + localaddr, remoteaddr string + localport, remoteport int + insecure bool + certFile, serverNameOverride string +} + +func NewClientCommand() *cobra.Command { + + log.SetFlags(log.Lshortfile) + cFlags := new(NewClientFlags) + + cmd := &cobra.Command{ + Use: "client", + Short: "Start a udp2grpc tcp/client", + RunE: func(cmd *cobra.Command, args []string) error { + + if cFlags.remoteaddr == "" { + return errors.New("server remote address is required. try 'utg client --address domain.tld'") + } + + opts := []grpc.DialOption{} + if cFlags.insecure { + opts = append(opts, grpc.WithInsecure()) + } + + if !cFlags.insecure { + + if cFlags.certFile == "" { + return errors.New("--tls-cert-file flag is required in tls mode. turn off tls mode with --insecure flag") + } + + creds, err := credentials.NewClientTLSFromFile(cFlags.certFile, cFlags.serverNameOverride) + if err != nil { + log.Fatalln(err) + } + opts = append(opts, grpc.WithTransportCredentials(creds)) + } + + addr := fmt.Sprintf("%s:%d", cFlags.remoteaddr, cFlags.remoteport) + + conn, err := grpc.Dial(addr, opts...) + if err != nil { + return fmt.Errorf("did not connect: %v", err) + } + + c := proto.NewVPNServiceClient(conn) + + log.Println(fmt.Sprintf("Connecting to tcp:%s", addr)) + + callOpts := grpc.EmptyCallOption{} + stream, err := c.Connect(context.Background(), callOpts) + if err != nil { + return err + } + + log.Println(fmt.Sprintf("Connected to tcp:%s", addr)) + + ic, err := client.NewClient(fmt.Sprintf("%s:%d", cFlags.localaddr, cFlags.localport), stream) + if err != nil { + return err + } + defer ic.Close() + + return ic.Listen() + }, + } + + cmd.SuggestionsMinimumDistance = 1 + cmd.Flags().StringVarP(&cFlags.remoteaddr, "remote-address", "r", "", "Server remote address") + cmd.Flags().IntVarP(&cFlags.remoteport, "port", "p", 52935, "Server tcp port") + cmd.Flags().StringVarP(&cFlags.localaddr, "local-address", "l", "", "Local server address") + cmd.Flags().IntVarP(&cFlags.localport, "local-port", "P", 52935, "Local server port") + cmd.Flags().StringVarP(&cFlags.certFile, "tls-cert-file", "c", "", "Server TLS certificate file") + cmd.Flags().StringVarP(&cFlags.serverNameOverride, "tls-server-name", "o", "", "TLS server name override") + cmd.Flags().BoolVarP(&cFlags.insecure, "insecure", "k", false, "Connect to server without tls") + return cmd +} diff --git a/cmds/server.go b/cmds/server.go new file mode 100644 index 0000000..301e4fc --- /dev/null +++ b/cmds/server.go @@ -0,0 +1,146 @@ +package cmds + +import ( + "crypto/tls" + "fmt" + "log" + "net" + "strconv" + "strings" + "time" + + "github.com/mrjosh/udp2grpc/internal/service" + "github.com/mrjosh/udp2grpc/proto" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/keepalive" + "google.golang.org/grpc/reflection" +) + +type NewServerFlags struct { + localaddr, remoteaddr string + port int + insecure bool + certFile, keyFile string +} + +func NewServerCommand() *cobra.Command { + + log.SetFlags(log.Lshortfile) + cFlags := new(NewServerFlags) + + cmd := &cobra.Command{ + Use: "server", + Short: "Start a udp2grpc tcp/server", + RunE: func(cmd *cobra.Command, args []string) error { + + if cFlags.remoteaddr == "" { + return fmt.Errorf("Server remote address is required. try with flag 'utg server --remote-address=\"127.0.0.1\" '") + } + + listener, err := net.Listen("tcp4", fmt.Sprintf("%s:%d", cFlags.localaddr, cFlags.port)) + if err != nil { + return fmt.Errorf("could not create tcp listener: %v", err) + } + + kaParams := keepalive.ServerParameters{ + Time: 10 * time.Second, + Timeout: 5 * time.Second, + } + + enforcement := keepalive.EnforcementPolicy{ + MinTime: 3 * time.Second, + PermitWithoutStream: true, + } + + opts := []grpc.ServerOption{ + grpc.KeepaliveParams(kaParams), + grpc.KeepaliveEnforcementPolicy(enforcement), + } + + if !cFlags.insecure { + + if cFlags.certFile == "" { + return errors.New("--tls-cert-file flag is required in tls mode. turn off tls mode with --insecure flag") + } + + if cFlags.keyFile == "" { + return errors.New("--tls-key-file flag is required in tls mode. turn off tls mode with --insecure flag") + } + + tlsCredentials, err := loadTLSCredentials(cFlags.certFile, cFlags.keyFile) + if err != nil { + return err + } + + opts = append(opts, grpc.Creds(credentials.NewServerTLSFromCert(tlsCredentials))) + } + + server := grpc.NewServer(opts...) + + remoteConn, err := createRemoteConnection(cFlags.remoteaddr) + if err != nil { + return err + } + + // Register binance services + svc := service.NewVPNService(remoteConn) + defer svc.Close() + + proto.RegisterVPNServiceServer(server, svc) + + reflection.Register(server) + + log.Println(fmt.Sprintf("Server running in tcp:%s:%d", cFlags.localaddr, cFlags.port)) + if err := server.Serve(listener); err != nil { + return fmt.Errorf("could not serve grpc.tcp.listener: %v", err) + } + + return nil + }, + } + cmd.SuggestionsMinimumDistance = 1 + cmd.Flags().StringVarP(&cFlags.localaddr, "local-address", "l", "0.0.0.0", "Local server address") + cmd.Flags().StringVarP(&cFlags.remoteaddr, "remote-address", "r", "", "Remote address") + cmd.Flags().IntVarP(&cFlags.port, "port", "p", 52935, "Local server port") + cmd.Flags().StringVarP(&cFlags.certFile, "tls-cert-file", "c", "", "Server TLS certificate file") + cmd.Flags().StringVarP(&cFlags.keyFile, "tls-key-file", "s", "", "Server TLS key file") + cmd.Flags().BoolVarP(&cFlags.insecure, "insecure", "k", false, "Start the server without tls") + return cmd +} + +func createRemoteConnection(address string) (*net.UDPConn, error) { + + remote := strings.Split(address, ":") + rport, err := strconv.Atoi(remote[1]) + if err != nil { + log.Fatal(errors.New("listen flag should contains ip:port")) + } + + remoteConn, err := net.DialUDP( + "udp", + &net.UDPAddr{}, + &net.UDPAddr{ + IP: net.ParseIP(remote[0]), + Port: rport, + }, + ) + + if err != nil { + return nil, err + } + + return remoteConn, nil +} + +func loadTLSCredentials(certFile, keyFile string) (*tls.Certificate, error) { + // Load server's certificate and private key + serverCert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return nil, err + } + + return &serverCert, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..01031c1 --- /dev/null +++ b/go.mod @@ -0,0 +1,20 @@ +module github.com/mrjosh/udp2grpc + +go 1.19 + +require ( + github.com/pkg/errors v0.9.1 + github.com/spf13/cobra v1.6.1 + google.golang.org/grpc v1.50.1 + google.golang.org/protobuf v1.28.1 +) + +require ( + github.com/golang/protobuf v1.5.2 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect + golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect + golang.org/x/text v0.3.3 // indirect + google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b0f6962 --- /dev/null +++ b/go.sum @@ -0,0 +1,95 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/client/client.go b/internal/client/client.go new file mode 100644 index 0000000..be9973e --- /dev/null +++ b/internal/client/client.go @@ -0,0 +1,131 @@ +package client + +import ( + "fmt" + "log" + "net" + "strconv" + "strings" + + "github.com/mrjosh/udp2grpc/internal/service" + "github.com/mrjosh/udp2grpc/proto" + "github.com/pkg/errors" +) + +type Client struct { + localConn *net.UDPConn + remoteStream proto.VPNService_ConnectClient + localChan, remoteChan chan *proto.Packet + localConnAddr net.Addr +} + +func (c *Client) Close() error { + return c.remoteStream.CloseSend() +} + +func NewClient(localAddress string, remoteStream proto.VPNService_ConnectClient) (*Client, error) { + log.Println(fmt.Sprintf("Create a new local connection on udp:%s", localAddress)) + localConn, err := createNewLocalUDPListener(localAddress) + if err != nil { + return nil, err + } + c := &Client{ + remoteStream: remoteStream, + localConn: localConn, + localChan: make(chan *proto.Packet), + remoteChan: make(chan *proto.Packet), + } + go c.handleLocalConn() + return c, nil +} + +func (c *Client) Listen() error { + + go func() { + + for { + p := <-c.localChan + if p.Body != nil { + if err := c.remoteStream.Send(p); err != nil { + log.Println(err) + } + } + } + + }() + + for { + + req, err := c.remoteStream.Recv() + if err != nil { + return errors.Wrapf(err, "can't receive message") + } + + log.Println(fmt.Sprintf("new packet: len[%d]", len(req.Body))) + c.remoteChan <- req + + } + +} + +func createNewLocalUDPListener(address string) (*net.UDPConn, error) { + + local := strings.Split(address, ":") + if len(local) < 2 { + log.Fatal(errors.New("listen flag should contains ip:port")) + } + + rport, err := strconv.Atoi(local[1]) + if err != nil { + log.Fatal(errors.New("listen flag should contains ip:port")) + } + + localConn, err := net.ListenUDP( + "udp4", + &net.UDPAddr{ + IP: net.ParseIP(local[0]), + Port: rport, + }, + ) + + if err != nil { + return nil, err + } + + return localConn, nil +} + +func (c *Client) handleLocalConn() error { + + var laddr *net.Addr + + go func() { + + for { + + p := <-c.remoteChan + if p.Body != nil && laddr != nil { + if _, err := c.localConn.WriteTo(p.Body[:], *laddr); err != nil { + log.Println(err) + } + } + + } + + }() + + for { + + buf := make([]byte, service.MaxSegmentSize) + n, localAddr, err := c.localConn.ReadFrom(buf[:]) + if err != nil { + log.Fatal(err) + } + laddr = &localAddr + + c.localChan <- &proto.Packet{ + Body: buf[:n], + } + + } +} diff --git a/internal/service/service.go b/internal/service/service.go new file mode 100644 index 0000000..4d475a9 --- /dev/null +++ b/internal/service/service.go @@ -0,0 +1,94 @@ +package service + +import ( + "fmt" + "log" + "net" + + "github.com/mrjosh/udp2grpc/proto" + "github.com/pkg/errors" +) + +const MaxSegmentSize = (1 << 16) - 1 // largest possible UDP datagram + +type VPNService struct { + remoteConn *net.UDPConn + localChan, remoteChan chan *proto.Packet + proto.UnimplementedVPNServiceServer +} + +func NewVPNService(remoteConn *net.UDPConn) *VPNService { + svc := &VPNService{ + remoteChan: make(chan *proto.Packet), + localChan: make(chan *proto.Packet), + remoteConn: remoteConn, + } + go svc.handleRemoteConn() + return svc +} + +func (v *VPNService) handleRemoteConn() error { + + go func() { + + for { + p := <-v.localChan + if p.Body != nil { + if _, err := v.remoteConn.Write(p.Body); err != nil { + log.Println(err) + } + } + } + + }() + + for { + + buf := make([]byte, MaxSegmentSize) + n, err := v.remoteConn.Read(buf[:]) + if err != nil { + log.Fatal(err) + } + + v.remoteChan <- &proto.Packet{ + Body: buf[:n], + } + + } + +} + +func (v *VPNService) Close() error { + return v.remoteConn.Close() +} + +func (v *VPNService) Connect(stream proto.VPNService_ConnectServer) error { + + log.Println("new connection: server_ready") + + go func() { + + for { + p := <-v.remoteChan + if p.Body != nil { + if err := stream.Send(p); err != nil { + log.Println(err) + } + } + } + + }() + + for { + + req, err := stream.Recv() + if err != nil { + return errors.Wrapf(err, "can't receive message") + } + + log.Println(fmt.Sprintf("new packet: len[%d]", len(req.Body))) + v.localChan <- req + + } + +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..ddaf6f8 --- /dev/null +++ b/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "log" + "os" + + "github.com/mrjosh/udp2grpc/cmds" + "github.com/spf13/cobra" +) + +func main() { + + log.SetFlags(log.Lshortfile) + + rootCmd := &cobra.Command{ + Use: "utg", + Long: ` + __ ______ ____ ___ ____ ____ ______ + / / / / __ \/ __ \__ \ ____ / __ \/ __ \/ ____/ + / / / / / / / /_/ /_/ // __ \/ /_/ / /_/ / / +/ /_/ / /_/ / ____/ __// /_/ / _, _/ ____/ /___ +\____/_____/_/ /____/\__, /_/ |_/_/ \____/ + /____/`, + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, + } + + rootCmd.SetArgs(os.Args[1:]) + rootCmd.AddCommand(cmds.NewServerCommand()) + rootCmd.AddCommand(cmds.NewClientCommand()) + + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + +} diff --git a/proto/conn.pb.go b/proto/conn.pb.go new file mode 100644 index 0000000..b49e691 --- /dev/null +++ b/proto/conn.pb.go @@ -0,0 +1,146 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.18.0 +// source: conn.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Packet struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Body []byte `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` +} + +func (x *Packet) Reset() { + *x = Packet{} + if protoimpl.UnsafeEnabled { + mi := &file_conn_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Packet) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Packet) ProtoMessage() {} + +func (x *Packet) ProtoReflect() protoreflect.Message { + mi := &file_conn_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Packet.ProtoReflect.Descriptor instead. +func (*Packet) Descriptor() ([]byte, []int) { + return file_conn_proto_rawDescGZIP(), []int{0} +} + +func (x *Packet) GetBody() []byte { + if x != nil { + return x.Body + } + return nil +} + +var File_conn_proto protoreflect.FileDescriptor + +var file_conn_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x1c, 0x0a, 0x06, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, + 0x79, 0x32, 0x39, 0x0a, 0x0a, 0x56, 0x50, 0x4e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x2b, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x12, 0x0d, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x1a, 0x0d, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_conn_proto_rawDescOnce sync.Once + file_conn_proto_rawDescData = file_conn_proto_rawDesc +) + +func file_conn_proto_rawDescGZIP() []byte { + file_conn_proto_rawDescOnce.Do(func() { + file_conn_proto_rawDescData = protoimpl.X.CompressGZIP(file_conn_proto_rawDescData) + }) + return file_conn_proto_rawDescData +} + +var file_conn_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_conn_proto_goTypes = []interface{}{ + (*Packet)(nil), // 0: proto.Packet +} +var file_conn_proto_depIdxs = []int32{ + 0, // 0: proto.VPNService.Connect:input_type -> proto.Packet + 0, // 1: proto.VPNService.Connect:output_type -> proto.Packet + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_conn_proto_init() } +func file_conn_proto_init() { + if File_conn_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_conn_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Packet); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_conn_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_conn_proto_goTypes, + DependencyIndexes: file_conn_proto_depIdxs, + MessageInfos: file_conn_proto_msgTypes, + }.Build() + File_conn_proto = out.File + file_conn_proto_rawDesc = nil + file_conn_proto_goTypes = nil + file_conn_proto_depIdxs = nil +} diff --git a/proto/conn_grpc.pb.go b/proto/conn_grpc.pb.go new file mode 100644 index 0000000..32b5fb9 --- /dev/null +++ b/proto/conn_grpc.pb.go @@ -0,0 +1,133 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package proto + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// VPNServiceClient is the client API for VPNService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type VPNServiceClient interface { + Connect(ctx context.Context, opts ...grpc.CallOption) (VPNService_ConnectClient, error) +} + +type vPNServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewVPNServiceClient(cc grpc.ClientConnInterface) VPNServiceClient { + return &vPNServiceClient{cc} +} + +func (c *vPNServiceClient) Connect(ctx context.Context, opts ...grpc.CallOption) (VPNService_ConnectClient, error) { + stream, err := c.cc.NewStream(ctx, &VPNService_ServiceDesc.Streams[0], "/proto.VPNService/Connect", opts...) + if err != nil { + return nil, err + } + x := &vPNServiceConnectClient{stream} + return x, nil +} + +type VPNService_ConnectClient interface { + Send(*Packet) error + Recv() (*Packet, error) + grpc.ClientStream +} + +type vPNServiceConnectClient struct { + grpc.ClientStream +} + +func (x *vPNServiceConnectClient) Send(m *Packet) error { + return x.ClientStream.SendMsg(m) +} + +func (x *vPNServiceConnectClient) Recv() (*Packet, error) { + m := new(Packet) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// VPNServiceServer is the server API for VPNService service. +// All implementations must embed UnimplementedVPNServiceServer +// for forward compatibility +type VPNServiceServer interface { + Connect(VPNService_ConnectServer) error + mustEmbedUnimplementedVPNServiceServer() +} + +// UnimplementedVPNServiceServer must be embedded to have forward compatible implementations. +type UnimplementedVPNServiceServer struct { +} + +func (UnimplementedVPNServiceServer) Connect(VPNService_ConnectServer) error { + return status.Errorf(codes.Unimplemented, "method Connect not implemented") +} +func (UnimplementedVPNServiceServer) mustEmbedUnimplementedVPNServiceServer() {} + +// UnsafeVPNServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to VPNServiceServer will +// result in compilation errors. +type UnsafeVPNServiceServer interface { + mustEmbedUnimplementedVPNServiceServer() +} + +func RegisterVPNServiceServer(s grpc.ServiceRegistrar, srv VPNServiceServer) { + s.RegisterService(&VPNService_ServiceDesc, srv) +} + +func _VPNService_Connect_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(VPNServiceServer).Connect(&vPNServiceConnectServer{stream}) +} + +type VPNService_ConnectServer interface { + Send(*Packet) error + Recv() (*Packet, error) + grpc.ServerStream +} + +type vPNServiceConnectServer struct { + grpc.ServerStream +} + +func (x *vPNServiceConnectServer) Send(m *Packet) error { + return x.ServerStream.SendMsg(m) +} + +func (x *vPNServiceConnectServer) Recv() (*Packet, error) { + m := new(Packet) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// VPNService_ServiceDesc is the grpc.ServiceDesc for VPNService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var VPNService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "proto.VPNService", + HandlerType: (*VPNServiceServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "Connect", + Handler: _VPNService_Connect_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "conn.proto", +} diff --git a/protofiles/conn.proto b/protofiles/conn.proto new file mode 100644 index 0000000..c27d833 --- /dev/null +++ b/protofiles/conn.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; +package proto; + +option go_package = "/proto"; + +message Packet { + bytes body = 1; +} + +service VPNService { + rpc Connect(stream Packet) returns (stream Packet); +}