├── .gitignore ├── Dockerfile ├── LICENSE.md ├── README.md ├── docker-compose.yml ├── down.sh ├── go.mod ├── go.sum ├── udp-clone.go └── up.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.18-bullseye AS build-img 2 | ENV CGO_ENABLED=0 3 | WORKDIR /go/src/app 4 | COPY go.mod go.sum ./ 5 | RUN go mod download 6 | COPY . . 7 | RUN go build 8 | 9 | FROM ubuntu:latest 10 | COPY --from=build-img /go/src/app/udp-clone /bin/udp-clone 11 | ENTRYPOINT ["udp-clone"] -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Path Network 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UDP Clone 2 | 3 | A tiny high performace UDP proxy that can foward any generic UDP traffic to one or more endpoints. 4 | 5 | ## Why? 6 | 7 | We needed a way to take a stream of NetFlow/IPFIX/sFlow and send it to multiple endpoints(Prod and Testing). 8 | 9 | ## Usage 10 | 11 | ### Docker Compose 12 | 13 | This project is meant to managed via single docker compose. Endpoints can easily be modified in the `docker-compose.yml` and safely reloaded by running `./up.sh` 14 | 15 | ``` 16 | justin@ops1:~/udp-clone$ ./up.sh 17 | [+] Building 15.5s (20/30) 18 | => [udp-clone_netflow5 internal] load build definition from Dockerfile 0.1s 19 | => => transferring dockerfile: 284B 0.0s 20 | => [udp-clone_netflow9 internal] load build definition from Dockerfile 0.1s 21 | => => transferring dockerfile: 284B 0.0s 22 | => [udp-clone_sflow internal] load build definition from Dockerfile 0.1s 23 | => => transferring dockerfile: 284B 0.0s 24 | => [udp-clone_netflow5 internal] load .dockerignore 0.1s 25 | => => transferring context: 2B 0.0s 26 | => [udp-clone_sflow internal] load .dockerignore 0.1s 27 | => => transferring context: 2B 0.0s 28 | => [udp-clone_netflow9 internal] load .dockerignore 0.1s 29 | => => transferring context: 2B 0.0s 30 | => [udp-clone_netflow9 internal] load metadata for docker.io/library/ubuntu:latest 0.0s 31 | => [udp-clone_netflow9 internal] load metadata for docker.io/library/golang:1.18-bullseye 5.0s 32 | => CACHED [udp-clone_netflow5 stage-1 1/2] FROM docker.io/library/ubuntu:latest 0.0s 33 | => [udp-clone_sflow build-img 1/6] FROM docker.io/library/golang:1.18-bullseye@sha256:db42e4bb1a7f32da1ec430906769dbbabe9f1868bd41 6.0s 34 | => => resolve docker.io/library/golang:1.18-bullseye@sha256:db42e4bb1a7f32da1ec430906769dbbabe9f1868bd4170751e4923f1b8948a45 0.1s 35 | => => sha256:e604223835ccf02d097187b5a58ca73e8598cadbb16a36202ca1943e97f56f1f 10.88MB / 10.88MB 0.5s 36 | => => sha256:db42e4bb1a7f32da1ec430906769dbbabe9f1868bd4170751e4923f1b8948a45 1.86kB / 1.86kB 0.0s 37 | => => sha256:bf168a6748997eb97b48cc86234b7ff7d8bc907645b9be99013158b3f146b272 5.16MB / 5.16MB 0.3s 38 | => => sha256:5417b4917fa7ed3ad2678a3ce6378a00c95bfd430c2ffa39936fce55130b5f2c 1.80kB / 1.80kB 0.0s 39 | => => sha256:76199a964a3fc66e31bda713381e92285f479fe8e3d4514a473f95ffc2062440 7.10kB / 7.10kB 0.0s 40 | => => sha256:e756f3fdd6a378aa16205b0f75d178b7532b110e86be7659004fc6a21183226c 55.01MB / 55.01MB 0.7s 41 | => => sha256:6d5c91c4cd86dde23108ab3af91e9eae838d0059a380ee7dfd4f370b6d985523 54.58MB / 54.58MB 0.8s 42 | => => sha256:93c221c34e03cb2bc3c5cb0e1fcf029b793cfe2c10362287dd05270d80333db9 85.87MB / 85.87MB 1.0s 43 | => => extracting sha256:e756f3fdd6a378aa16205b0f75d178b7532b110e86be7659004fc6a21183226c 0.6s 44 | => => sha256:399edca3a0ef467dadd57f6ed1ee48c7b64162ca25d1fae2940680b749c722a9 141.75MB / 141.75MB 1.4s 45 | => => sha256:00fc5c011105d0ac8b5453886bb3b836c81260e1d016938a1207d48da8f28718 155B / 155B 0.9s 46 | => => extracting sha256:bf168a6748997eb97b48cc86234b7ff7d8bc907645b9be99013158b3f146b272 0.1s 47 | => => extracting sha256:e604223835ccf02d097187b5a58ca73e8598cadbb16a36202ca1943e97f56f1f 0.1s 48 | => => extracting sha256:6d5c91c4cd86dde23108ab3af91e9eae838d0059a380ee7dfd4f370b6d985523 0.6s 49 | => => extracting sha256:93c221c34e03cb2bc3c5cb0e1fcf029b793cfe2c10362287dd05270d80333db9 0.8s 50 | => => extracting sha256:399edca3a0ef467dadd57f6ed1ee48c7b64162ca25d1fae2940680b749c722a9 1.7s 51 | => => extracting sha256:00fc5c011105d0ac8b5453886bb3b836c81260e1d016938a1207d48da8f28718 0.0s 52 | => [udp-clone_netflow9 internal] load build context 0.2s 53 | => => transferring context: 4.59MB 0.1s 54 | => [udp-clone_netflow5 internal] load build context 0.2s 55 | => => transferring context: 4.59MB 0.1s 56 | => [udp-clone_sflow internal] load build context 0.2s 57 | => => transferring context: 4.59MB 0.1s 58 | => [udp-clone_sflow build-img 2/6] WORKDIR /go/src/app 0.6s 59 | => [udp-clone_netflow9 build-img 3/6] COPY go.mod go.sum ./ 0.1s 60 | => [udp-clone_netflow9 build-img 4/6] RUN go mod download 0.7s 61 | => [udp-clone_netflow9 build-img 5/6] COPY . . 0.1s 62 | => [udp-clone_netflow9 build-img 6/6] RUN go build 2.4s 63 | => [udp-clone_netflow9 stage-1 2/2] COPY --from=build-img /go/src/app/udp-clone /bin/udp-clone 0.1s 64 | => [udp-clone_netflow5] exporting to image 0.3s 65 | => => exporting layers 0.1s 66 | => => writing image sha256:34530238bc121bc2c8a8daa2bbedc48ef36ccdca90f31a81cd4e510d27ad455f 0.0s 67 | => => naming to docker.io/library/udp-clone_netflow9 0.0s 68 | => => naming to docker.io/library/udp-clone_sflow 0.0s 69 | => => naming to docker.io/library/udp-clone_netflow5 0.0s 70 | 71 | Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them 72 | [+] Running 4/4 73 | _ Network udp-clone_default Created 0.1s 74 | _ Container udp-clone-netflow9 Started 0.9s 75 | _ Container udp-clone-sflow Started 0.8s 76 | _ Container udp-clone-netflow5 Started 0.7s 77 | 78 | ``` 79 | 80 | 81 | ### Direct 82 | 83 | To run this project directly, build it manually using `go build`. 84 | 85 | ``` 86 | ❯ ./udp-clone --listen-port=9500 --forward=192.0.2.1 --forward=198.51.100.5:9100 87 | 88 | {"level":"info","message":"Server started","ip":"0.0.0.0","port":9500} 89 | {"level":"info","message":"Forwarding target configured","num":1,"total":2,"addr":"192.0.2.1:9500"} 90 | {"level":"info","message":"Forwarding target configured","num":2,"total":2,"addr":"198.51.100.5:9100"} 91 | ``` 92 | 93 | The above command will: 94 | 95 | - Start a UDP server listening on `0.0.0.0` port `9500` 96 | - Add a forward target of `192.0.2.1:9500` (uses `listen-port` for destination port as not specified in configuration) 97 | - Add another forward target of `198.51.100.5:9100` 98 | 99 | The server will start listening on `0.0.0.0:9500`, any packet it receives will be replicated and sent to both `192.0.2.1:9500` and `198.51.100.5:9100` 100 | 101 | 102 | ## Configuration 103 | 104 | ``` 105 | usage: udp-clone [] 106 | 107 | Flags: 108 | --help Show context-sensitive help (also try --help-long and --help-man). 109 | --debug Enable debug mode 110 | --listen-ip=0.0.0.0 IP to listen in 111 | --listen-port=port Port to listen on 112 | --body-size=10000 Size of body to read 113 | --forward=ip:port ... ip:port to forward traffic to (port defaults to listen-port) 114 | --routines=10 Set the number of listeners per port 115 | ``` 116 | 117 | ## Copyright 118 | 119 | 2022 Path Network 120 | 121 | Written By: Justin Timperio 122 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | sflow: 5 | container_name: udp-clone-sflow 6 | hostname: udp-clone-sflow 7 | restart: unless-stopped 8 | build: 9 | dockerfile: Dockerfile 10 | 11 | command: > 12 | --listen-port=6343 13 | --forward=10.208.0.7:6343 14 | --forward=10.208.0.8:6343 15 | 16 | ports: 17 | - "6343:6343/udp" 18 | 19 | netflow5: 20 | container_name: udp-clone-netflow5 21 | hostname: udp-clone-netflow5 22 | restart: unless-stopped 23 | build: 24 | dockerfile: Dockerfile 25 | 26 | command: > 27 | --listen-port=2055 28 | --forward=10.208.0.7:2055 29 | --forward=10.208.0.8:2055 30 | 31 | ports: 32 | - "2055:2055/udp" 33 | 34 | netflow9: 35 | container_name: udp-clone-netflow9 36 | hostname: udp-clone-netflow9 37 | restart: unless-stopped 38 | build: 39 | dockerfile: Dockerfile 40 | 41 | command: > 42 | --listen-port=2057 43 | --forward=10.208.0.7:2057 44 | --forward=10.208.0.8:2057 45 | 46 | ports: 47 | - "2057:2057/udp" 48 | 49 | -------------------------------------------------------------------------------- /down.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sudo docker compose down -v 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module udp-clone 2 | 3 | go 1.18 4 | 5 | require ( 6 | go.uber.org/zap v1.21.0 7 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 8 | ) 9 | 10 | require ( 11 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect 12 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect 13 | go.uber.org/atomic v1.7.0 // indirect 14 | go.uber.org/multierr v1.6.0 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= 2 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 3 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= 4 | github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= 5 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 6 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 11 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 12 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 13 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 14 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 18 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 19 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 20 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 21 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 22 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 23 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= 24 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 25 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= 26 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 27 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= 28 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 29 | go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= 30 | go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= 31 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 32 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 33 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 34 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 35 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 36 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 37 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 38 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 39 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 40 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 41 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 42 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 44 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 46 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 47 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 48 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 49 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 50 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 51 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 52 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 53 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 54 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 55 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 56 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= 57 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 58 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 59 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 60 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 61 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 62 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 63 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 64 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 65 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 66 | -------------------------------------------------------------------------------- /udp-clone.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net" 7 | "os" 8 | "os/signal" 9 | "strings" 10 | "time" 11 | 12 | "go.uber.org/zap" 13 | "gopkg.in/alecthomas/kingpin.v2" 14 | ) 15 | 16 | var ( 17 | debug = kingpin.Flag("debug", "Enable debug mode").Envar("DEBUG").Bool() 18 | listenIP = kingpin.Flag("listen-ip", "IP to listen in").Default("0.0.0.0").Envar("LISTEN_IP").IP() 19 | listenPort = kingpin.Flag("listen-port", "Port to listen on").PlaceHolder("port").Envar("LISTEN_PORT").Int() 20 | bodySize = kingpin.Flag("body-size", "Size of body to read").Default("10000").Envar("BODY_SIZE").Int() 21 | routines = kingpin.Flag("routines", "Set the number of listeners per port.").Default("10").Envar("ROUTINES").Int() 22 | forwards = kingpin.Flag("forward", "ip:port to forward traffic to (port defaults to listen-port)").PlaceHolder("ip:port").Envar("FORWARD").Strings() 23 | 24 | targets []*net.UDPConn 25 | 26 | exit = make(chan bool, 1) 27 | ) 28 | 29 | func main() { 30 | // CLI 31 | kingpin.Parse() 32 | 33 | rawJSON := []byte(`{ 34 | "level": "info", 35 | "outputPaths": ["stdout"], 36 | "errorOutputPaths": ["stderr"], 37 | "encoding": "json", 38 | "encoderConfig": { 39 | "messageKey": "message", 40 | "levelKey": "level", 41 | "levelEncoder": "lowercase" 42 | } 43 | }`) 44 | 45 | // Set up Zap logger 46 | var cfg zap.Config 47 | if err := json.Unmarshal(rawJSON, &cfg); err != nil { 48 | panic(err) 49 | } 50 | logger, err := cfg.Build() 51 | if err != nil { 52 | panic(err) 53 | } 54 | defer logger.Sync() 55 | log := logger.Sugar() 56 | 57 | if *debug { 58 | cfg.Level.SetLevel(zap.ErrorLevel) 59 | } 60 | 61 | // Abort if no targets 62 | if len(*forwards) <= 0 { 63 | log.Fatal("Must specify at least one forward target") 64 | } 65 | 66 | // Create a list of vaild clients 67 | for _, forward := range *forwards { 68 | // Check for port 69 | if strings.Index(forward, ":") < 0 { 70 | forward = fmt.Sprintf("%s:%d", forward, *listenPort) 71 | } 72 | 73 | // Resolve 74 | addr, err := net.ResolveUDPAddr("udp", forward) 75 | if err != nil { 76 | log.Fatalf("Could not Resolve UDP Addr: %s (%s)", forward, err) 77 | } 78 | 79 | // Setup listenServer 80 | listenServer, err := net.DialUDP("udp", nil, addr) 81 | if err != nil { 82 | log.Fatalf("Could not Dial UDP: %+v (%s)", addr, err) 83 | } 84 | defer listenServer.Close() 85 | 86 | targets = append(targets, listenServer) 87 | } 88 | 89 | // Listen Server 90 | listenServer, err := net.ListenUDP("udp", &net.UDPAddr{ 91 | Port: *listenPort, 92 | IP: *listenIP, 93 | }) 94 | if err != nil { 95 | log.Fatal(err) 96 | } 97 | defer listenServer.Close() 98 | 99 | // Startup status 100 | log.Infow("Server started", 101 | "ip", *listenIP, 102 | "port", *listenPort, 103 | ) 104 | 105 | for i, target := range targets { 106 | log.Infow("Forwarding target configured", 107 | "num", i+1, 108 | "total", len(targets), 109 | "addr", target.RemoteAddr(), 110 | ) 111 | } 112 | 113 | // Launch listen routines 114 | for i := 0; i < *routines; i++ { 115 | go listenAndProxy(log, listenServer) 116 | } 117 | 118 | // Wait for exit signal 119 | c := make(chan os.Signal, 1) 120 | signal.Notify(c, os.Interrupt) 121 | for sig := range c { 122 | log.Infow("Exiting Gracefully from Interrupt Signal", "signal", sig) 123 | os.Exit(1) 124 | } 125 | } 126 | 127 | func listenAndProxy(log *zap.SugaredLogger, listenServer *net.UDPConn) { 128 | 129 | // Buffer for Packets 130 | buffer := make([]byte, *bodySize) 131 | 132 | for { 133 | // Read Packet 134 | n, source, err := listenServer.ReadFromUDP(buffer) 135 | if err != nil { 136 | log.Errorw("Failed to read from UDP", 137 | "error", err, 138 | ) 139 | continue 140 | } 141 | 142 | // Proxy Packet 143 | for _, target := range targets { 144 | go proxyUDPpacket(log, target, source, buffer, n) 145 | } 146 | } 147 | } 148 | 149 | func proxyUDPpacket(log *zap.SugaredLogger, target *net.UDPConn, source *net.UDPAddr, buffer []byte, n int) { 150 | _, err := target.Write(buffer[:n]) 151 | if err != nil { 152 | log.Warnw("Could not forward packet", 153 | "source", source.String(), 154 | "target", target.RemoteAddr(), 155 | "error", err, 156 | "time", time.Now().UTC().String(), 157 | ) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /up.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sudo docker compose build --no-cache 4 | sudo docker compose up -d 5 | --------------------------------------------------------------------------------