├── .dockerignore ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── docker.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── commands ├── encode.go ├── ns.go ├── root.go ├── uglify.go └── version.go ├── go.mod ├── go.sum ├── main.go └── pkg ├── cfg └── cfg.go ├── cli └── cli.go ├── handlers ├── cname │ └── handler.go ├── common.go ├── defaultip │ └── handler.go ├── errors.go ├── handler.go ├── ipv4 │ └── handler.go ├── ipv6 │ └── handler.go ├── limiter │ ├── count.go │ ├── limiter.go │ └── ttl.go ├── loop │ └── handler.go ├── notify │ └── handler.go ├── parser.go ├── parser │ ├── parser.go │ └── parser_test.go ├── proxy │ └── handler.go ├── random │ └── handler.go ├── slices │ └── slices.go └── sticky │ └── handler.go ├── hub ├── conn.go └── hub.go ├── iputil └── iputil.go ├── nssrv ├── handler.go ├── server.go └── server_test.go ├── obfustacor └── obfuscator.go ├── resolver ├── cache.go └── upstreams.go └── www ├── common.go ├── srv.go ├── static ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── pong.txt ├── session.html └── styles.css └── token.go /.dockerignore: -------------------------------------------------------------------------------- 1 | .github 2 | .git 3 | .idea -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | # Controls when the workflow will run 5 | on: 6 | push: 7 | tags: 8 | - v* 9 | branches: 10 | - master 11 | pull_request: 12 | branches: 13 | - master 14 | permissions: 15 | contents: read 16 | jobs: 17 | staticcheck: 18 | permissions: 19 | contents: read 20 | pull-requests: read 21 | runs-on: ubuntu-latest 22 | steps: 23 | # Get the repositery's code 24 | - name: Checkout 25 | uses: actions/checkout@v3 26 | 27 | - name: Set up Golang 28 | uses: actions/setup-go@v3 29 | with: 30 | go-version: '1.24.x' 31 | check-latest: true 32 | cache: true 33 | 34 | - run: "go test ./..." 35 | - run: "go vet ./..." 36 | - uses: dominikh/staticcheck-action@v1.3.1 37 | with: 38 | version: "2025.1" 39 | install-go: false 40 | 41 | test: 42 | strategy: 43 | fail-fast: false 44 | matrix: 45 | platform: 46 | - ubuntu 47 | go: 48 | - 24 49 | name: "tests on ${{ matrix.platform }} | 1.${{ matrix.go }}.x" 50 | runs-on: "${{ matrix.platform }}-latest" 51 | steps: 52 | # Get the repositery's code 53 | - name: Checkout 54 | uses: actions/checkout@v4 55 | 56 | - name: Set up Golang 57 | uses: actions/setup-go@v4 58 | with: 59 | go-version: "1.${{ matrix.go }}.x" 60 | cache: true 61 | 62 | - name: Run tests 63 | run: go clean -testcache && go test -race -cover -covermode=atomic ./... 64 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Docker Builds 3 | 4 | # Controls when the workflow will run 5 | on: 6 | workflow_dispatch: 7 | push: 8 | branches: 9 | - master 10 | tags: 11 | - 'v*.*.*' 12 | 13 | permissions: 14 | contents: read 15 | packages: write 16 | 17 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 18 | jobs: 19 | build-rip: 20 | runs-on: ubuntu-latest 21 | steps: 22 | # Get the repositery's code 23 | - name: Checkout 24 | uses: actions/checkout@v3 25 | 26 | # https://github.com/docker/setup-qemu-action 27 | - name: Set up QEMU 28 | uses: docker/setup-qemu-action@v2 29 | 30 | # https://github.com/docker/setup-buildx-action 31 | - name: Set up Docker Buildx 32 | id: buildx 33 | uses: docker/setup-buildx-action@v2 34 | 35 | - name: Login to GHCR 36 | if: github.event_name != 'pull_request' 37 | uses: docker/login-action@v2 38 | with: 39 | registry: ghcr.io 40 | username: ${{ github.repository_owner }} 41 | password: ${{ secrets.GITHUB_TOKEN }} 42 | 43 | - name: Docker meta 44 | id: meta_rip 45 | uses: docker/metadata-action@v4 46 | with: 47 | # list of Docker images to use as base name for tags 48 | images: | 49 | ghcr.io/buglloc/rip 50 | # generate Docker tags based on the following events/attributes 51 | tags: | 52 | type=schedule 53 | type=ref,event=branch 54 | type=semver,pattern={{version}} 55 | type=semver,pattern={{major}}.{{minor}} 56 | type=semver,pattern={{major}} 57 | type=sha 58 | 59 | - name: Build and push 60 | uses: docker/build-push-action@v3 61 | with: 62 | context: . 63 | platforms: linux/amd64,linux/arm64/v8 64 | push: ${{ github.event_name != 'pull_request' }} 65 | tags: ${{ steps.meta_rip.outputs.tags }} 66 | labels: ${{ steps.meta_rip.outputs.labels }} 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Goland project dir 15 | .idea 16 | .bin 17 | # our binary 18 | rip 19 | .gopath -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24 as build 2 | 3 | WORKDIR /go/src/app 4 | COPY . . 5 | 6 | RUN go mod download 7 | RUN go build -o /go/bin/rip 8 | 9 | FROM debian:bookworm-slim 10 | 11 | COPY --from=build /go/bin/rip /usr/sbin/rip 12 | 13 | ENTRYPOINT ["/usr/sbin/rip"] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Andrew Krasichkov 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 | # RIP 2 | A simple DNS server that extracts IP address from the requested domain name and sends it back in the response. 3 | 4 | # Usage 5 | 6 | 0. Install Go 1.16+ 7 | 1. Perform `go get -u github.com/buglloc/rip/v2` 8 | 2. Have fun ;) 9 | 10 | ## Encoding rules 11 | Since RIP extracts the response from the request, it's important to understand the encoding rules. 12 | RIP has three kinds of entities: 13 | - rr - something that generate response (e.g. IP, CNAME and so on): 14 | ``` 15 | - returns IP address (guesses IPv4/IPv6) 16 | .[4|v4] - strictly returns IPv4 address only 17 | .[6|v6] - strictly returns IPv6 address only 18 | .[c|cname] - return CNAME record with 19 | .[p|proxy] - resolve name and returns it 20 | ``` 21 | - container - something that holds rr's (or another container), picked one on each request and response with it: 22 | ``` 23 | ..[r|random] - pick random rr/container 24 | ..[l|loop] - iterate over rr/container 25 | ..[s|sticky] - alias for loop container: ..l 26 | ``` 27 | - limit modifier - something that limit this kind of responses: 28 | ``` 29 | cnt- - use rr requests. e.g.: 30 | * 1-1-1-1.v4-cnt-10 - returns 1.1.1.1 10 times 31 | ttl- - use rr duration: 32 | * 2-2-2-2.v4-ttl-20s - returns 2.2.2.2 20 seconds from first v4-rr response 33 | ``` 34 | 35 | Also, RIP allowing to use any prefixes (see examples below). 36 | 37 | ## IP address format 38 | IP address can be presented in two variants - dash-delimited and base16-form. For example, ips `0a000001` and `10-0-0-1` are equal and points to `10.0.0.1` 39 | You can also use the built-in converter to encode IP address: 40 | ``` 41 | $ rip encode fe80::fa94:c2ff:fee5:3cf6 127.0.0.1 42 | fe80000000000000fa94c2fffee53cf6 7f000001 43 | ``` 44 | 45 | ## Examples 46 | Run NS server for zone `example.com` with default IP `77.88.55.70` and `2a02:6b8: a:: a`: 47 | ``` 48 | $ rip ns --zone=example.com --ipv4=77.88.55.70 --ipv6=2a02:6b8:a::a 49 | ``` 50 | 51 | When requesting it, we should get the following responses: 52 | ``` 53 | # IPv4 54 | 1-1-1-1.example.com -> 1.1.1.1 55 | 1-1-1-1.v4.example.com -> 1.1.1.1 56 | foo.1-1-1-1.v4.example.com -> 1.1.1.1 57 | bar.foo.1-1-1-1.v4.example.com -> 1.1.1.1 58 | 1010101.v4.example.com -> 1.1.1.1 59 | 60 | # IPv6 61 | 2a01-7e01--f03c-91ff-fe3b-c9ba.example.com -> 2a01:7e01::f03c:91ff:fe3b:c9ba 62 | 2a01-7e01--f03c-91ff-fe3b-c9ba.v6.example.com -> 2a01:7e01::f03c:91ff:fe3b:c9ba 63 | 2a017e0100000000f03c91fffe3bc9ba.v6.example.com -> 2a017e0100000000f03c91fffe3bc9ba 64 | foo.2a01-7e01--f03c-91ff-fe3b-c9ba.v6.example.com -> 2a01:7e01::f03c:91ff:fe3b:c9ba 65 | foo.--1.6.example.com -> ::1 66 | 67 | # Random 68 | 0a000002.0a000001.random.example.com -> random between 10.0.0.1 and 10.0.0.2 69 | 0a000003.0a000002.0a000001.random.example.com -> random between 10.0.0.1 and 10.0.0.2 70 | 71 | # Loop 72 | 8ba299a7.8ba299a8.loop.example.com -> loop over 139.162.153.168 and 139.162.153.167 73 | 8ba299a7.v4-ttl-5s.8ba299a8.v4-cnt-5.loop.example.com -> 139.162.153.168 (first 5 requests), then 139.162.153.167 (next 5s), then 139.162.153.168 (next 5 requests), and so on 74 | 8ba299a7.v4-ttl-5s.b32-onxw2zlunbuw4zzomnxw63bnmnxs44tv.c-cnt-5.loop.example.com -> CNAME "something.cool.co.ru." (first 5 requests), then 139.162.153.167 (next 5s), CNAME "something.cool.co.ru." (first 5 requests), and so on 75 | 8ba299a6.v4.8ba299a7.v4.loop-ttl-5s.8ba299a8.v4-cnt-5.loop.example.com -> 139.162.153.168 (first 5 requests), then 139.162.153.167/139.162.153.166 (next 5s), then 139.162.153.168 (next 5 requests) and so on 76 | 77 | # Sticky 78 | 8ba299a7.8ba299a8.s.example.com -> 139.162.153.168 (first A request) then 139.162.153.167 (30s), then 139.162.153.168 (next A request) and so on 79 | 80 | # Cname 81 | ya.ru.c.example.com -> canonical name ya.ru 82 | google.com.c.example.com -> canonical name google.com 83 | b32-onxw2zlunbuw4zzomnxw63bnmnxs44tv.c.example.com -> canonical name something.cool.co.ru 84 | 85 | # Proxy 86 | ya.ru.p.example.com -> 87.250.250.242 and 2a02:6b8::2:242 87 | google.com.p.example.com -> 64.233.164.102 and 2a00:1450:4010:c07::64 88 | ``` 89 | -------------------------------------------------------------------------------- /commands/encode.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "strings" 8 | 9 | "github.com/spf13/cobra" 10 | 11 | "github.com/buglloc/rip/v2/pkg/iputil" 12 | ) 13 | 14 | var ip2Hex = &cobra.Command{ 15 | Use: "encode [IP] IP", 16 | Short: "Encode IPs", 17 | RunE: func(_ *cobra.Command, args []string) error { 18 | if len(args) < 1 { 19 | return errors.New("please provide IP") 20 | } 21 | 22 | results := make([]string, len(args)) 23 | for i, ip := range args { 24 | if strings.Contains(ip, ":") { 25 | results[i] = iputil.EncodeIP6(net.ParseIP(ip)) 26 | } else { 27 | results[i] = iputil.EncodeIP4(net.ParseIP(ip)) 28 | } 29 | } 30 | 31 | fmt.Println(strings.Join(results, "\t")) 32 | return nil 33 | }, 34 | } 35 | 36 | func init() { 37 | RootCmd.AddCommand(ip2Hex) 38 | } 39 | -------------------------------------------------------------------------------- /commands/ns.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | 12 | log "github.com/buglloc/simplelog" 13 | "github.com/google/uuid" 14 | "github.com/spf13/cobra" 15 | "github.com/spf13/viper" 16 | 17 | "github.com/buglloc/rip/v2/pkg/cfg" 18 | "github.com/buglloc/rip/v2/pkg/cli" 19 | "github.com/buglloc/rip/v2/pkg/nssrv" 20 | ) 21 | 22 | var nsServerCmd = &cobra.Command{ 23 | Use: "ns --zone=example.com --zone=example1.com", 24 | Short: "Start RIP NS server", 25 | PreRunE: parseServerConfig, 26 | RunE: runServerCmd, 27 | } 28 | 29 | func init() { 30 | flags := nsServerCmd.PersistentFlags() 31 | flags.String("addr", ":53", 32 | "address to listen on") 33 | flags.StringSlice("zone", []string{"."}, 34 | "your zone name (e.g. 'buglloc.com')") 35 | flags.String("ipv4", "127.0.0.1", 36 | "default ipv4 address") 37 | flags.Uint32("ttl", cfg.TTL, 38 | "DNS records TTL") 39 | flags.Uint32("sticky-ttl", 30, 40 | "sticky record TTL in seconds") 41 | flags.String("ipv6", "::1", 42 | "default ipv6 address") 43 | flags.String("upstream", "77.88.8.8:53", 44 | "upstream DNS server") 45 | flags.Bool("use-default", false, 46 | "return default IPs for not supported requests") 47 | flags.Bool("no-proxy", false, 48 | "disable proxy mode") 49 | flags.String("http-addr", ":9000", 50 | "http address to listen") 51 | flags.String("hub-sign", uuid.NewString(), 52 | "hub signing key") 53 | flags.Bool("with-hub", false, 54 | "enable http server") 55 | _ = cli.BindPFlags(flags) 56 | RootCmd.AddCommand(nsServerCmd) 57 | } 58 | 59 | func runServerCmd(_ *cobra.Command, _ []string) error { 60 | srv, err := nssrv.NewSrv() 61 | if err != nil { 62 | return err 63 | } 64 | 65 | doneChan := make(chan error) 66 | go func() { 67 | defer close(doneChan) 68 | 69 | err := srv.ListenAndServe() 70 | if err != nil { 71 | doneChan <- err 72 | } 73 | }() 74 | 75 | stopChan := make(chan os.Signal, 1) 76 | signal.Notify(stopChan, syscall.SIGINT, syscall.SIGTERM) 77 | select { 78 | case <-stopChan: 79 | log.Info("shutting down...") 80 | 81 | if err := srv.Shutdown(context.TODO()); err != nil { 82 | log.Error("shutdown failed", "err", err) 83 | } 84 | case err := <-doneChan: 85 | return err 86 | } 87 | 88 | return nil 89 | } 90 | 91 | func parseServerConfig(_ *cobra.Command, _ []string) error { 92 | cfg.Zones = viper.GetStringSlice("Zone") 93 | if len(cfg.Zones) == 0 { 94 | return errors.New("empty zone list, please provide at leas one") 95 | } 96 | 97 | cfg.Addr = viper.GetString("Addr") 98 | cfg.IPv4 = net.ParseIP(viper.GetString("Ipv4")) 99 | cfg.IPv6 = net.ParseIP(viper.GetString("Ipv6")) 100 | cfg.AllowProxy = !viper.GetBool("NoProxy") 101 | cfg.UseDefault = viper.GetBool("UseDefault") 102 | cfg.Upstream = viper.GetString("Upstream") 103 | cfg.TTL = uint32(viper.GetInt("Ttl")) 104 | cfg.StickyTTL = time.Duration(viper.GetInt("StickyTtl")) * time.Second 105 | cfg.HttpAddr = viper.GetString("HttpAddr") 106 | cfg.HubSign = viper.GetString("HubSign") 107 | cfg.HubEnabled = viper.GetBool("WithHub") 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /commands/root.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | log "github.com/buglloc/simplelog" 8 | "github.com/mitchellh/go-homedir" 9 | "github.com/spf13/cobra" 10 | "github.com/spf13/viper" 11 | 12 | "github.com/buglloc/rip/v2/pkg/cli" 13 | ) 14 | 15 | var RootCmd = &cobra.Command{ 16 | Use: "rip", 17 | Short: "Wildcard DNS", 18 | SilenceUsage: true, 19 | PreRunE: parseRootConfig, 20 | } 21 | 22 | func init() { 23 | cobra.OnInitialize(initConfig) 24 | flags := RootCmd.PersistentFlags() 25 | flags.String("config", "", 26 | "config file (default is $HOME/.rip.toml)") 27 | flags.Bool("verbose", false, 28 | "verbose output") 29 | 30 | viper.AutomaticEnv() 31 | _ = cli.BindPFlags(flags) 32 | } 33 | 34 | func Execute() { 35 | if err := RootCmd.Execute(); err != nil { 36 | os.Exit(1) 37 | } 38 | } 39 | 40 | func initConfig() { 41 | if cfgFile := viper.GetString("config"); cfgFile != "" { 42 | viper.SetConfigFile(cfgFile) 43 | } else { 44 | home, err := homedir.Dir() 45 | if err != nil { 46 | fmt.Println(err) 47 | os.Exit(1) 48 | } 49 | 50 | viper.AddConfigPath(home) 51 | viper.AddConfigPath(".") 52 | viper.SetConfigName(".rip") 53 | } 54 | 55 | if err := viper.ReadInConfig(); err == nil { 56 | fmt.Println("Using config file:", viper.ConfigFileUsed()) 57 | } 58 | } 59 | 60 | func parseRootConfig(cmd *cobra.Command, args []string) error { 61 | if viper.GetBool("Verbose") { 62 | log.SetLevel(log.DebugLevel) 63 | } 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /commands/uglify.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/spf13/cobra" 8 | 9 | obfuscator "github.com/buglloc/rip/v2/pkg/obfustacor" 10 | ) 11 | 12 | var uglify = &cobra.Command{ 13 | Use: "uglify IP", 14 | Short: "Uglify (obfuscate) IP", 15 | RunE: runUglifyCmd, 16 | } 17 | 18 | func init() { 19 | RootCmd.AddCommand(uglify) 20 | } 21 | 22 | func runUglifyCmd(cmd *cobra.Command, args []string) error { 23 | if len(args) < 1 { 24 | return errors.New("please provide IP") 25 | } 26 | 27 | obfuscated := obfuscator.IPv4(args[0]) 28 | for _, r := range obfuscated { 29 | fmt.Printf("http://%s\n", r) 30 | } 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /commands/version.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | 8 | "github.com/buglloc/rip/v2/pkg/cfg" 9 | ) 10 | 11 | var version = &cobra.Command{ 12 | Use: "version", 13 | Short: "Print rip version", 14 | RunE: func(_ *cobra.Command, _ []string) error { 15 | fmt.Printf("RIP v%s\n", cfg.Version) 16 | return nil 17 | }, 18 | } 19 | 20 | func init() { 21 | RootCmd.AddCommand(version) 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/buglloc/rip/v2 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/buglloc/simplelog v0.0.0-20190311170333-2fbd6fd42b73 7 | github.com/go-chi/chi/v5 v5.2.1 8 | github.com/google/uuid v1.6.0 9 | github.com/gorilla/websocket v1.5.3 10 | github.com/karlseguin/ccache/v3 v3.0.6 11 | github.com/lestrrat-go/jwx v1.2.31 12 | github.com/miekg/dns v1.1.66 13 | github.com/mitchellh/go-homedir v1.1.0 14 | github.com/spf13/cobra v1.9.1 15 | github.com/spf13/pflag v1.0.6 16 | github.com/spf13/viper v1.20.1 17 | github.com/stretchr/testify v1.10.0 18 | golang.org/x/sync v0.14.0 19 | ) 20 | 21 | require ( 22 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 23 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect 24 | github.com/fsnotify/fsnotify v1.9.0 // indirect 25 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect 26 | github.com/goccy/go-json v0.10.5 // indirect 27 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 28 | github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect 29 | github.com/lestrrat-go/blackmagic v1.0.3 // indirect 30 | github.com/lestrrat-go/httpcc v1.0.1 // indirect 31 | github.com/lestrrat-go/iter v1.0.2 // indirect 32 | github.com/lestrrat-go/option v1.0.1 // indirect 33 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 34 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 35 | github.com/pkg/errors v0.9.1 // indirect 36 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 37 | github.com/sagikazarmark/locafero v0.9.0 // indirect 38 | github.com/sourcegraph/conc v0.3.0 // indirect 39 | github.com/spf13/afero v1.14.0 // indirect 40 | github.com/spf13/cast v1.7.1 // indirect 41 | github.com/subosito/gotenv v1.6.0 // indirect 42 | go.uber.org/multierr v1.11.0 // indirect 43 | golang.org/x/crypto v0.37.0 // indirect 44 | golang.org/x/mod v0.24.0 // indirect 45 | golang.org/x/net v0.39.0 // indirect 46 | golang.org/x/sys v0.32.0 // indirect 47 | golang.org/x/term v0.31.0 // indirect 48 | golang.org/x/text v0.24.0 // indirect 49 | golang.org/x/tools v0.32.0 // indirect 50 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 51 | gopkg.in/yaml.v3 v3.0.1 // indirect 52 | ) 53 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/buglloc/simplelog v0.0.0-20190311170333-2fbd6fd42b73 h1:kMytUqYS7b6BIizpXLuvBa1VDesJ5OI/uUd3/+SQqP0= 2 | github.com/buglloc/simplelog v0.0.0-20190311170333-2fbd6fd42b73/go.mod h1:MnBMAur3tQuporD52/oyWPB5IeYR5EMq/zjKXo5NP+s= 3 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 6 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= 8 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= 9 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 10 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 11 | github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= 12 | github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 13 | github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= 14 | github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 15 | github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= 16 | github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 17 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 18 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 19 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 20 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 21 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 22 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 23 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 24 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 25 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 26 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 27 | github.com/karlseguin/ccache/v3 v3.0.6 h1:6wC04CXSdptebuSUBgsQixNrrRMUdimtwmjlJUpCf/4= 28 | github.com/karlseguin/ccache/v3 v3.0.6/go.mod h1:b0qfdUOHl4vJgKFQN41paXIdBb3acAtyX2uWrBAZs1w= 29 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 30 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 31 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 32 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 33 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 34 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 35 | github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= 36 | github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= 37 | github.com/lestrrat-go/blackmagic v1.0.3 h1:94HXkVLxkZO9vJI/w2u1T0DAoprShFd13xtnSINtDWs= 38 | github.com/lestrrat-go/blackmagic v1.0.3/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= 39 | github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= 40 | github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= 41 | github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= 42 | github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= 43 | github.com/lestrrat-go/jwx v1.2.31 h1:/OM9oNl/fzyldpv5HKZ9m7bTywa7COUfg8gujd9nJ54= 44 | github.com/lestrrat-go/jwx v1.2.31/go.mod h1:eQJKoRwWcLg4PfD5CFA5gIZGxhPgoPYq9pZISdxLf0c= 45 | github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 46 | github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= 47 | github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 48 | github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= 49 | github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= 50 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 51 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 52 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 53 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 54 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 55 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 56 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 57 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 58 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 59 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 60 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 61 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 62 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 63 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 64 | github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= 65 | github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= 66 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= 67 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 68 | github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= 69 | github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= 70 | github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= 71 | github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 72 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 73 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 74 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 75 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 76 | github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= 77 | github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= 78 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 79 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 80 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 81 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 82 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 83 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 84 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 85 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 86 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 87 | golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= 88 | golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 89 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 90 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 91 | golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 92 | golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 93 | golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= 94 | golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 95 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 96 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 97 | golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= 98 | golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 99 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 100 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 101 | golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= 102 | golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= 103 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 104 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 105 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 106 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 107 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 108 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 109 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/buglloc/rip/v2/commands" 5 | ) 6 | 7 | func main() { 8 | commands.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /pkg/cfg/cfg.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | const Version = "2.0.3" 9 | 10 | var ( 11 | // Addr is address to listen on, ":dns" if empty. 12 | Addr string 13 | // Zones - list of acceptable zones names 14 | Zones []string 15 | // IPv4 is default IPv4 address 16 | IPv4 net.IP 17 | // IPv6 is default IPv6 address 18 | IPv6 net.IP 19 | // Upstream DNS server for proxying 20 | Upstream = "1.1.1.1:53" 21 | // UseDefault enables "strict" mode 22 | UseDefault bool 23 | HttpAddr string 24 | HubSign string 25 | HubSignTTL = 24 * time.Hour 26 | HubEnabled bool 27 | AllowProxy bool 28 | CacheSize int64 = 4096 29 | CacheTTL = 10 * time.Minute 30 | TTL uint32 = 0 31 | StickyTTL time.Duration = 30 32 | ) 33 | -------------------------------------------------------------------------------- /pkg/cli/cli.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "unicode" 5 | 6 | "github.com/spf13/pflag" 7 | "github.com/spf13/viper" 8 | ) 9 | 10 | func BindPFlags(flags *pflag.FlagSet) (err error) { 11 | flags.VisitAll(func(flag *pflag.Flag) { 12 | if err = viper.BindPFlag(transformFlagName(flag.Name), flag); err != nil { 13 | return 14 | } 15 | }) 16 | return 17 | } 18 | 19 | func transformFlagName(name string) string { 20 | runes := []rune(name) 21 | length := len(runes) 22 | 23 | var out []rune 24 | nextUpper := true 25 | for i := 0; i < length; i++ { 26 | switch { 27 | case nextUpper: 28 | out = append(out, unicode.ToUpper(runes[i])) 29 | nextUpper = false 30 | case runes[i] == '-': 31 | nextUpper = true 32 | default: 33 | out = append(out, runes[i]) 34 | } 35 | } 36 | 37 | return string(out) 38 | } 39 | -------------------------------------------------------------------------------- /pkg/handlers/cname/handler.go: -------------------------------------------------------------------------------- 1 | package cname 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/miekg/dns" 7 | 8 | "github.com/buglloc/rip/v2/pkg/cfg" 9 | "github.com/buglloc/rip/v2/pkg/handlers" 10 | "github.com/buglloc/rip/v2/pkg/handlers/limiter" 11 | "github.com/buglloc/rip/v2/pkg/handlers/slices" 12 | ) 13 | 14 | const ShortName = "c" 15 | const Name = "cname" 16 | 17 | var _ handlers.Handler = (*Handler)(nil) 18 | 19 | type Handler struct { 20 | handlers.BaseHandler 21 | TargetFQDN string 22 | } 23 | 24 | func NewHandler(modifiers ...limiter.Limiter) *Handler { 25 | return &Handler{ 26 | BaseHandler: handlers.BaseHandler{ 27 | Limiters: modifiers, 28 | }, 29 | } 30 | } 31 | 32 | func (h *Handler) Name() string { 33 | return Name 34 | } 35 | 36 | func (h *Handler) Init(p handlers.Parser) error { 37 | parts, _ := p.RestValues() 38 | if len(parts) == 0 { 39 | return handlers.ErrUnexpectedEOF 40 | } 41 | 42 | slices.StringsReverse(parts) 43 | h.TargetFQDN = dns.Fqdn(strings.Join(parts, ".")) 44 | return nil 45 | } 46 | 47 | func (h *Handler) Handle(question dns.Question) ([]dns.RR, bool, error) { 48 | rr := []dns.RR{&dns.CNAME{ 49 | Hdr: dns.RR_Header{ 50 | Name: question.Name, 51 | Rrtype: dns.TypeCNAME, 52 | Class: dns.ClassINET, 53 | Ttl: cfg.TTL, 54 | }, 55 | Target: h.TargetFQDN, 56 | }} 57 | 58 | h.Limiters.Use() 59 | return rr, h.Limiters.MoveOn(), nil 60 | } 61 | -------------------------------------------------------------------------------- /pkg/handlers/common.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | 7 | "github.com/miekg/dns" 8 | 9 | "github.com/buglloc/rip/v2/pkg/cfg" 10 | "github.com/buglloc/rip/v2/pkg/iputil" 11 | ) 12 | 13 | func PartToIP(part string) net.IP { 14 | dotCounts := strings.Count(part, "-") 15 | 16 | switch dotCounts { 17 | case 0: 18 | ip, _ := iputil.DecodeIp(part) 19 | return ip 20 | case 3: 21 | return net.ParseIP(strings.ReplaceAll(part, "-", ".")).To4() 22 | default: 23 | return net.ParseIP(strings.ReplaceAll(part, "-", ":")).To16() 24 | } 25 | } 26 | 27 | func DefaultIp(reqType uint16) net.IP { 28 | if reqType == dns.TypeA { 29 | return cfg.IPv4 30 | } 31 | return cfg.IPv6 32 | } 33 | 34 | func IPsToRR(question dns.Question, ips ...net.IP) (result []dns.RR) { 35 | result = make([]dns.RR, len(ips)) 36 | for i, ip := range ips { 37 | result[i] = createIpRR(question, ip) 38 | } 39 | 40 | return 41 | } 42 | 43 | func createIpRR(question dns.Question, ip net.IP) (rr dns.RR) { 44 | head := dns.RR_Header{ 45 | Name: question.Name, 46 | Rrtype: question.Qtype, 47 | Class: dns.ClassINET, 48 | Ttl: cfg.TTL, 49 | } 50 | 51 | if question.Qtype == dns.TypeA { 52 | rr = &dns.A{ 53 | Hdr: head, 54 | A: ip, 55 | } 56 | } else { 57 | rr = &dns.AAAA{ 58 | Hdr: head, 59 | AAAA: ip, 60 | } 61 | } 62 | return 63 | } 64 | -------------------------------------------------------------------------------- /pkg/handlers/defaultip/handler.go: -------------------------------------------------------------------------------- 1 | package defaultip 2 | 3 | import ( 4 | "github.com/miekg/dns" 5 | 6 | "github.com/buglloc/rip/v2/pkg/handlers" 7 | "github.com/buglloc/rip/v2/pkg/handlers/limiter" 8 | ) 9 | 10 | const ShortName = "d" 11 | const Name = "default" 12 | 13 | var _ handlers.Handler = (*Handler)(nil) 14 | 15 | type Handler struct { 16 | handlers.BaseHandler 17 | } 18 | 19 | func NewHandler(modifiers ...limiter.Limiter) *Handler { 20 | return &Handler{ 21 | BaseHandler: handlers.BaseHandler{ 22 | Limiters: modifiers, 23 | }, 24 | } 25 | } 26 | 27 | func (h *Handler) Name() string { 28 | return Name 29 | } 30 | 31 | func (h *Handler) Init(_ handlers.Parser) error { 32 | return nil 33 | } 34 | 35 | func (h *Handler) Handle(question dns.Question) ([]dns.RR, bool, error) { 36 | h.Limiters.Use() 37 | return handlers.IPsToRR(question, handlers.DefaultIp(question.Qtype)), h.Limiters.MoveOn(), nil 38 | } 39 | -------------------------------------------------------------------------------- /pkg/handlers/errors.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import "errors" 4 | 5 | var ErrEOF = errors.New("EOF") 6 | var ErrUnexpectedEOF = errors.New("unexpected EOF, value required") 7 | var ErrNotAllowed = errors.New("not alowed") 8 | var ErrMoveOn = errors.New("move on") 9 | -------------------------------------------------------------------------------- /pkg/handlers/handler.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/miekg/dns" 5 | 6 | "github.com/buglloc/rip/v2/pkg/handlers/limiter" 7 | ) 8 | 9 | type Handler interface { 10 | Name() string 11 | Init(p Parser) error 12 | SetDefaultLimiters(modifiers ...limiter.Limiter) 13 | Handle(question dns.Question) (rrs []dns.RR, moveOn bool, err error) 14 | } 15 | 16 | type BaseHandler struct { 17 | Limiters limiter.Limiters 18 | } 19 | 20 | func (h *BaseHandler) SetDefaultLimiters(modifiers ...limiter.Limiter) { 21 | if len(h.Limiters) == 0 { 22 | h.Limiters = modifiers 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pkg/handlers/ipv4/handler.go: -------------------------------------------------------------------------------- 1 | package ipv4 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/miekg/dns" 8 | 9 | "github.com/buglloc/rip/v2/pkg/handlers" 10 | "github.com/buglloc/rip/v2/pkg/handlers/limiter" 11 | ) 12 | 13 | const ShortName = "4" 14 | const Name = "v4" 15 | 16 | var _ handlers.Handler = (*Handler)(nil) 17 | 18 | type Handler struct { 19 | handlers.BaseHandler 20 | IP net.IP 21 | } 22 | 23 | func NewHandler(modifiers ...limiter.Limiter) *Handler { 24 | return &Handler{ 25 | BaseHandler: handlers.BaseHandler{ 26 | Limiters: modifiers, 27 | }, 28 | } 29 | } 30 | 31 | func (h *Handler) Name() string { 32 | return Name 33 | } 34 | 35 | func (h *Handler) Init(p handlers.Parser) error { 36 | if len(h.IP) > 0 { 37 | return nil 38 | } 39 | 40 | ip, _ := p.NextRaw() 41 | if ip == "" { 42 | return handlers.ErrUnexpectedEOF 43 | } 44 | 45 | targetIP := handlers.PartToIP(ip) 46 | if len(targetIP) != net.IPv4len { 47 | return fmt.Errorf("not IPv4 address: %s", ip) 48 | } 49 | 50 | h.IP = targetIP 51 | return nil 52 | } 53 | 54 | func (h *Handler) Handle(question dns.Question) ([]dns.RR, bool, error) { 55 | if question.Qtype != dns.TypeA { 56 | return nil, false, nil 57 | } 58 | 59 | h.Limiters.Use() 60 | return handlers.IPsToRR(question, h.IP), h.Limiters.MoveOn(), nil 61 | } 62 | -------------------------------------------------------------------------------- /pkg/handlers/ipv6/handler.go: -------------------------------------------------------------------------------- 1 | package ipv6 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/miekg/dns" 8 | 9 | "github.com/buglloc/rip/v2/pkg/handlers" 10 | "github.com/buglloc/rip/v2/pkg/handlers/limiter" 11 | ) 12 | 13 | const ShortName = "6" 14 | const Name = "v6" 15 | 16 | var _ handlers.Handler = (*Handler)(nil) 17 | 18 | type Handler struct { 19 | handlers.BaseHandler 20 | IP net.IP 21 | } 22 | 23 | func NewHandler(modifiers ...limiter.Limiter) *Handler { 24 | return &Handler{ 25 | BaseHandler: handlers.BaseHandler{ 26 | Limiters: modifiers, 27 | }, 28 | } 29 | } 30 | 31 | func (h *Handler) Name() string { 32 | return Name 33 | } 34 | 35 | func (h *Handler) Init(p handlers.Parser) error { 36 | if len(h.IP) > 0 { 37 | return nil 38 | } 39 | 40 | ip, _ := p.NextRaw() 41 | if ip == "" { 42 | return handlers.ErrUnexpectedEOF 43 | } 44 | 45 | targetIP := handlers.PartToIP(ip) 46 | if len(targetIP) != net.IPv6len { 47 | return fmt.Errorf("not IPv6 address: %s", ip) 48 | } 49 | 50 | h.IP = targetIP 51 | return nil 52 | } 53 | 54 | func (h *Handler) Handle(question dns.Question) ([]dns.RR, bool, error) { 55 | if question.Qtype != dns.TypeAAAA { 56 | return nil, false, nil 57 | } 58 | 59 | h.Limiters.Use() 60 | return handlers.IPsToRR(question, h.IP), h.Limiters.MoveOn(), nil 61 | } 62 | -------------------------------------------------------------------------------- /pkg/handlers/limiter/count.go: -------------------------------------------------------------------------------- 1 | package limiter 2 | 3 | var _ Limiter = (*Count)(nil) 4 | 5 | type Count struct { 6 | Cur int 7 | Max int 8 | } 9 | 10 | func (m *Count) Use() { 11 | if m.Cur < 0 { 12 | m.Cur = 0 13 | } 14 | m.Cur++ 15 | } 16 | 17 | func (m *Count) MoveOn() bool { 18 | expired := m.Cur >= m.Max 19 | if expired { 20 | m.Cur = -1 21 | } 22 | return expired 23 | } 24 | -------------------------------------------------------------------------------- /pkg/handlers/limiter/limiter.go: -------------------------------------------------------------------------------- 1 | package limiter 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "time" 7 | ) 8 | 9 | type Limiter interface { 10 | Use() 11 | MoveOn() bool 12 | } 13 | 14 | type Limiters []Limiter 15 | 16 | func (m Limiters) Use() { 17 | for _, mod := range m { 18 | mod.Use() 19 | } 20 | } 21 | 22 | func (m Limiters) MoveOn() bool { 23 | if len(m) == 0 { 24 | return true 25 | } 26 | 27 | moveOn := false 28 | for _, mod := range m { 29 | if mod.MoveOn() { 30 | moveOn = true 31 | } 32 | } 33 | 34 | return moveOn 35 | } 36 | 37 | func ParseLimiters(opts map[string]string) ([]Limiter, error) { 38 | out := make([]Limiter, 0, len(opts)) 39 | for k, v := range opts { 40 | switch k { 41 | case "ttl": 42 | ttl, err := time.ParseDuration(v) 43 | if err != nil { 44 | return nil, fmt.Errorf("failed to parse 'ttl' limiter (%q): %w", v, err) 45 | } 46 | out = append(out, &TTL{ 47 | TTL: ttl, 48 | }) 49 | case "cnt": 50 | cnt, err := strconv.Atoi(v) 51 | if err != nil { 52 | return nil, fmt.Errorf("failed to parse 'cnt' limiter (%q): %w", v, err) 53 | } 54 | 55 | out = append(out, &Count{ 56 | Max: cnt, 57 | }) 58 | default: 59 | return nil, fmt.Errorf("unexpected limiter: %s", k) 60 | } 61 | } 62 | 63 | return out, nil 64 | } 65 | -------------------------------------------------------------------------------- /pkg/handlers/limiter/ttl.go: -------------------------------------------------------------------------------- 1 | package limiter 2 | 3 | import "time" 4 | 5 | var _ Limiter = (*Count)(nil) 6 | 7 | type TTL struct { 8 | TTL time.Duration 9 | Until time.Time 10 | } 11 | 12 | func (m *TTL) Use() { 13 | if m.Until.Second() == 0 { 14 | m.Until = time.Now().Add(m.TTL) 15 | } 16 | } 17 | 18 | func (m *TTL) MoveOn() bool { 19 | if time.Now().After(m.Until) { 20 | m.Until = time.Unix(0, 0) 21 | return true 22 | } 23 | 24 | return false 25 | } 26 | -------------------------------------------------------------------------------- /pkg/handlers/loop/handler.go: -------------------------------------------------------------------------------- 1 | package loop 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/miekg/dns" 7 | 8 | "github.com/buglloc/rip/v2/pkg/handlers" 9 | "github.com/buglloc/rip/v2/pkg/handlers/limiter" 10 | ) 11 | 12 | const ShortName = "l" 13 | const Name = "loop" 14 | 15 | var _ handlers.Handler = (*Handler)(nil) 16 | 17 | type Handler struct { 18 | handlers.BaseHandler 19 | Nested [2]handlers.Handler 20 | Cur uint64 21 | } 22 | 23 | func NewHandler(modifiers ...limiter.Limiter) *Handler { 24 | return &Handler{ 25 | BaseHandler: handlers.BaseHandler{ 26 | Limiters: modifiers, 27 | }, 28 | } 29 | } 30 | 31 | func (h *Handler) Name() string { 32 | return Name 33 | } 34 | 35 | func (h *Handler) Init(p handlers.Parser) error { 36 | var err error 37 | h.Nested[0], err = p.NextHandler() 38 | if err != nil { 39 | return err 40 | } 41 | 42 | h.Nested[1], err = p.NextHandler() 43 | if err != nil { 44 | return err 45 | } 46 | return nil 47 | } 48 | 49 | func (h *Handler) Handle(question dns.Question) ([]dns.RR, bool, error) { 50 | rr, moveOn, err := h.Nested[h.Cur].Handle(question) 51 | if err != nil { 52 | return nil, false, fmt.Errorf("loop: %w", err) 53 | } 54 | 55 | if moveOn { 56 | h.Cur = (h.Cur + 1) % 2 57 | } 58 | 59 | if len(rr) > 0 { 60 | h.Limiters.Use() 61 | } 62 | 63 | return rr, h.Limiters.MoveOn(), nil 64 | } 65 | -------------------------------------------------------------------------------- /pkg/handlers/notify/handler.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/miekg/dns" 9 | 10 | "github.com/buglloc/rip/v2/pkg/cfg" 11 | "github.com/buglloc/rip/v2/pkg/handlers" 12 | "github.com/buglloc/rip/v2/pkg/hub" 13 | ) 14 | 15 | const ShortName = "n" 16 | const Name = "notify" 17 | 18 | var _ handlers.Handler = (*Handler)(nil) 19 | 20 | type Handler struct { 21 | handlers.BaseHandler 22 | channel string 23 | Nested handlers.Handler 24 | } 25 | 26 | func NewHandler() *Handler { 27 | return &Handler{} 28 | } 29 | 30 | func (h *Handler) Name() string { 31 | return Name 32 | } 33 | 34 | func (h *Handler) Init(p handlers.Parser) error { 35 | init := func() error { 36 | var err error 37 | h.channel, err = p.NextRaw() 38 | if err != nil { 39 | return err 40 | } 41 | 42 | h.Nested, err = p.NextHandler() 43 | if err != nil { 44 | return err 45 | } 46 | 47 | return nil 48 | } 49 | 50 | err := init() 51 | if err != nil { 52 | h.reportErr(dns.Question{Name: p.FQDN()}, fmt.Sprintf("can't parse request: %v", err)) 53 | } 54 | return err 55 | } 56 | 57 | func (h *Handler) Handle(question dns.Question) ([]dns.RR, bool, error) { 58 | rr, moveOn, err := h.Nested.Handle(question) 59 | if cfg.HubEnabled { 60 | if err != nil { 61 | h.reportErr(question, err.Error()) 62 | } else { 63 | h.reportRR(question, rr) 64 | } 65 | } 66 | 67 | return rr, moveOn, err 68 | } 69 | 70 | func (h *Handler) reportRR(question dns.Question, rr []dns.RR) { 71 | if h.channel == "" { 72 | return 73 | } 74 | 75 | now := time.Now() 76 | if len(rr) == 0 { 77 | hub.Send(h.channel, hub.Message{ 78 | Time: now, 79 | Name: question.Name, 80 | QType: dns.Type(question.Qtype).String(), 81 | RR: "", 82 | Ok: true, 83 | }) 84 | return 85 | } 86 | 87 | for _, r := range rr { 88 | hub.Send(h.channel, hub.Message{ 89 | Time: now, 90 | Name: question.Name, 91 | QType: dns.Type(question.Qtype).String(), 92 | RR: strings.TrimPrefix(r.String(), r.Header().String()), 93 | Ok: true, 94 | }) 95 | } 96 | } 97 | 98 | func (h *Handler) reportErr(question dns.Question, err string) { 99 | if h.channel == "" { 100 | return 101 | } 102 | 103 | qType := "n/a" 104 | if question.Qtype != dns.TypeNone { 105 | qType = dns.Type(question.Qtype).String() 106 | } 107 | 108 | hub.Send(h.channel, hub.Message{ 109 | Time: time.Now(), 110 | Name: question.Name, 111 | QType: qType, 112 | RR: err, 113 | Ok: false, 114 | }) 115 | } 116 | -------------------------------------------------------------------------------- /pkg/handlers/parser.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | type Parser interface { 4 | RestHandlers() ([]Handler, error) 5 | NextHandler() (Handler, error) 6 | NextValue() (string, error) 7 | RestValues() ([]string, error) 8 | NextRaw() (string, error) 9 | FQDN() string 10 | } 11 | -------------------------------------------------------------------------------- /pkg/handlers/parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "encoding/base32" 5 | "fmt" 6 | "net" 7 | "strings" 8 | 9 | log "github.com/buglloc/simplelog" 10 | 11 | "github.com/buglloc/rip/v2/pkg/handlers" 12 | "github.com/buglloc/rip/v2/pkg/handlers/cname" 13 | "github.com/buglloc/rip/v2/pkg/handlers/defaultip" 14 | "github.com/buglloc/rip/v2/pkg/handlers/ipv4" 15 | "github.com/buglloc/rip/v2/pkg/handlers/ipv6" 16 | "github.com/buglloc/rip/v2/pkg/handlers/limiter" 17 | "github.com/buglloc/rip/v2/pkg/handlers/loop" 18 | "github.com/buglloc/rip/v2/pkg/handlers/notify" 19 | "github.com/buglloc/rip/v2/pkg/handlers/proxy" 20 | "github.com/buglloc/rip/v2/pkg/handlers/random" 21 | "github.com/buglloc/rip/v2/pkg/handlers/slices" 22 | "github.com/buglloc/rip/v2/pkg/handlers/sticky" 23 | ) 24 | 25 | var _ handlers.Parser = (*Parser)(nil) 26 | 27 | type Parser struct { 28 | cur int 29 | maxLabel int 30 | labels []string 31 | fqdn string 32 | } 33 | 34 | func NewParser(fqdn, zone string) *Parser { 35 | ripReq := fqdn 36 | if len(zone) > 0 { 37 | ripReq = fqdn[:len(fqdn)-len(zone)-1] 38 | } 39 | 40 | labels := strings.Split(strings.ToLower(ripReq), ".") 41 | slices.StringsReverse(labels) 42 | return &Parser{ 43 | cur: 0, 44 | maxLabel: len(labels), 45 | labels: labels, 46 | fqdn: fqdn, 47 | } 48 | } 49 | 50 | func (p *Parser) FQDN() string { 51 | return p.fqdn 52 | } 53 | 54 | func (p *Parser) NextHandler() (handlers.Handler, error) { 55 | if p.cur >= p.maxLabel { 56 | return nil, handlers.ErrEOF 57 | } 58 | 59 | part := p.labels[p.cur] 60 | h := parseHandler(part) 61 | if h == nil { 62 | return nil, handlers.ErrEOF 63 | } 64 | 65 | p.cur++ 66 | err := h.Init(p) 67 | if err != nil { 68 | return nil, fmt.Errorf("can't parse handler %s: %w", h.Name(), err) 69 | } 70 | 71 | return h, nil 72 | } 73 | 74 | func (p *Parser) NextRaw() (string, error) { 75 | if p.cur >= p.maxLabel { 76 | return "", handlers.ErrEOF 77 | } 78 | 79 | ret := p.labels[p.cur] 80 | if strings.HasPrefix(ret, "b32-") { 81 | decoded, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(strings.ToUpper(ret[4:])) 82 | if err == nil { 83 | ret = string(decoded) 84 | } 85 | } 86 | p.cur++ 87 | return ret, nil 88 | } 89 | 90 | func (p *Parser) RestValues() ([]string, error) { 91 | var out []string 92 | for { 93 | v, err := p.NextValue() 94 | if v == "" { 95 | return out, nil 96 | } 97 | 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | out = append(out, v) 103 | } 104 | } 105 | 106 | func (p *Parser) RestHandlers() ([]handlers.Handler, error) { 107 | var out []handlers.Handler 108 | for { 109 | h, err := p.NextHandler() 110 | if h == nil { 111 | return out, nil 112 | } 113 | 114 | if err != nil { 115 | return nil, err 116 | } 117 | 118 | out = append(out, h) 119 | } 120 | } 121 | 122 | func (p *Parser) NextValue() (string, error) { 123 | if p.cur >= p.maxLabel { 124 | return "", handlers.ErrEOF 125 | } 126 | 127 | label := p.labels[p.cur] 128 | handlerName := label 129 | if indx := strings.IndexByte(handlerName, '-'); indx > 0 { 130 | handlerName = handlerName[:indx] 131 | } 132 | 133 | switch handlerName { 134 | case ipv4.ShortName, ipv4.Name: 135 | fallthrough 136 | case ipv6.ShortName, ipv6.Name: 137 | fallthrough 138 | case cname.ShortName, cname.Name: 139 | fallthrough 140 | case proxy.ShortName, proxy.Name: 141 | fallthrough 142 | case random.ShortName, random.Name: 143 | fallthrough 144 | case loop.ShortName, loop.Name: 145 | fallthrough 146 | case sticky.ShortName, sticky.Name: 147 | fallthrough 148 | case notify.ShortName, notify.Name: 149 | fallthrough 150 | case defaultip.ShortName, defaultip.Name: 151 | return "", handlers.ErrEOF 152 | } 153 | 154 | if strings.HasPrefix(label, "b32-") { 155 | decoded, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(strings.ToUpper(label[4:])) 156 | if err == nil { 157 | label = string(decoded) 158 | } 159 | } 160 | p.cur++ 161 | return label, nil 162 | } 163 | 164 | func parseHandler(label string) handlers.Handler { 165 | if len(label) == 0 { 166 | return nil 167 | } 168 | 169 | parts := strings.Split(label, "-") 170 | parseLimiters := func() []limiter.Limiter { 171 | if len(parts) <= 1 { 172 | return nil 173 | } 174 | 175 | opts := make(map[string]string, len(parts)/2) 176 | for i := 1; i < len(parts)-1; i += 2 { 177 | opts[parts[i]] = parts[i+1] 178 | } 179 | 180 | ret, err := limiter.ParseLimiters(opts) 181 | if err != nil { 182 | log.Error("can't parse limiter", "label", label, "err", err) 183 | return nil 184 | } 185 | 186 | return ret 187 | } 188 | 189 | switch parts[0] { 190 | case ipv4.ShortName, ipv4.Name: 191 | return ipv4.NewHandler(parseLimiters()...) 192 | case ipv6.ShortName, ipv6.Name: 193 | return ipv6.NewHandler(parseLimiters()...) 194 | case cname.ShortName, cname.Name: 195 | return cname.NewHandler(parseLimiters()...) 196 | case proxy.ShortName, proxy.Name: 197 | return proxy.NewHandler(parseLimiters()...) 198 | case random.ShortName, random.Name: 199 | return random.NewHandler(parseLimiters()...) 200 | case loop.ShortName, loop.Name: 201 | return loop.NewHandler(parseLimiters()...) 202 | case sticky.ShortName, sticky.Name: 203 | return sticky.NewHandler(parseLimiters()...) 204 | case notify.ShortName, notify.Name: 205 | return notify.NewHandler() 206 | case defaultip.ShortName, defaultip.Name: 207 | return defaultip.NewHandler(parseLimiters()...) 208 | default: 209 | return parseIPHandler(label) 210 | } 211 | } 212 | 213 | func parseIPHandler(part string) handlers.Handler { 214 | ip := handlers.PartToIP(part) 215 | switch len(ip) { 216 | case net.IPv4len: 217 | return &ipv4.Handler{IP: ip} 218 | case net.IPv6len: 219 | return &ipv6.Handler{IP: ip} 220 | default: 221 | return nil 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /pkg/handlers/parser/parser_test.go: -------------------------------------------------------------------------------- 1 | package parser_test 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/require" 9 | 10 | "github.com/buglloc/rip/v2/pkg/cfg" 11 | "github.com/buglloc/rip/v2/pkg/handlers" 12 | "github.com/buglloc/rip/v2/pkg/handlers/cname" 13 | "github.com/buglloc/rip/v2/pkg/handlers/defaultip" 14 | "github.com/buglloc/rip/v2/pkg/handlers/ipv4" 15 | "github.com/buglloc/rip/v2/pkg/handlers/ipv6" 16 | "github.com/buglloc/rip/v2/pkg/handlers/limiter" 17 | "github.com/buglloc/rip/v2/pkg/handlers/loop" 18 | "github.com/buglloc/rip/v2/pkg/handlers/parser" 19 | "github.com/buglloc/rip/v2/pkg/handlers/proxy" 20 | "github.com/buglloc/rip/v2/pkg/handlers/sticky" 21 | ) 22 | 23 | func init() { 24 | // TODO(buglloc): so ugly 25 | cfg.AllowProxy = true 26 | } 27 | 28 | func TestParser(t *testing.T) { 29 | cases := []struct { 30 | in string 31 | handlers []handlers.Handler 32 | }{ 33 | { 34 | in: "", 35 | }, 36 | { 37 | in: "lalala", 38 | }, 39 | { 40 | in: "1-1-1-1", 41 | handlers: []handlers.Handler{ 42 | &ipv4.Handler{IP: net.ParseIP("1.1.1.1").To4()}, 43 | }, 44 | }, 45 | { 46 | in: "1010101", 47 | handlers: []handlers.Handler{ 48 | &ipv4.Handler{IP: net.ParseIP("1.1.1.1").To4()}, 49 | }, 50 | }, 51 | { 52 | in: "1-1-1-1.4", 53 | handlers: []handlers.Handler{ 54 | &ipv4.Handler{IP: net.ParseIP("1.1.1.1").To4()}, 55 | }, 56 | }, 57 | { 58 | in: "1010101.4", 59 | handlers: []handlers.Handler{ 60 | &ipv4.Handler{IP: net.ParseIP("1.1.1.1").To4()}, 61 | }, 62 | }, 63 | { 64 | in: "fe80--fa94-c2ff-fee5-3cf6", 65 | handlers: []handlers.Handler{ 66 | &ipv6.Handler{IP: net.ParseIP("fe80::fa94:c2ff:fee5:3cf6").To16()}, 67 | }, 68 | }, 69 | { 70 | in: "fe80000000000000fa94c2fffee53cf6", 71 | handlers: []handlers.Handler{ 72 | &ipv6.Handler{IP: net.ParseIP("fe80::fa94:c2ff:fee5:3cf6").To16()}, 73 | }, 74 | }, 75 | { 76 | in: "fe80--fa94-c2ff-fee5-3cf6.6", 77 | handlers: []handlers.Handler{ 78 | &ipv6.Handler{IP: net.ParseIP("fe80::fa94:c2ff:fee5:3cf6").To16()}, 79 | }, 80 | }, 81 | { 82 | in: "fe80000000000000fa94c2fffee53cf6.6", 83 | handlers: []handlers.Handler{ 84 | &ipv6.Handler{IP: net.ParseIP("fe80::fa94:c2ff:fee5:3cf6").To16()}, 85 | }, 86 | }, 87 | { 88 | in: "lalala.d.d.d", 89 | handlers: []handlers.Handler{ 90 | &defaultip.Handler{}, 91 | &defaultip.Handler{}, 92 | &defaultip.Handler{}, 93 | }, 94 | }, 95 | { 96 | in: "lalala.example.com.c", 97 | handlers: []handlers.Handler{ 98 | &cname.Handler{TargetFQDN: "lalala.example.com."}, 99 | }, 100 | }, 101 | { 102 | in: "1-1-1-1.v4.example.com.c", 103 | handlers: []handlers.Handler{ 104 | &cname.Handler{TargetFQDN: "example.com."}, 105 | &ipv4.Handler{IP: net.ParseIP("1.1.1.1").To4()}, 106 | }, 107 | }, 108 | { 109 | in: "lalala.example.com.p", 110 | handlers: []handlers.Handler{ 111 | &proxy.Handler{TargetFQDN: "lalala.example.com."}, 112 | }, 113 | }, 114 | { 115 | in: "lalala.d.lala.com.p.d.example.com.c.d", 116 | handlers: []handlers.Handler{ 117 | &defaultip.Handler{}, 118 | &cname.Handler{TargetFQDN: "example.com."}, 119 | &defaultip.Handler{}, 120 | &proxy.Handler{TargetFQDN: "lala.com."}, 121 | &defaultip.Handler{}, 122 | }, 123 | }, 124 | { 125 | in: "2-2-2-2.4.3-3-3-3.4.l", 126 | handlers: []handlers.Handler{ 127 | &loop.Handler{ 128 | Nested: [2]handlers.Handler{ 129 | &ipv4.Handler{IP: net.ParseIP("3.3.3.3").To4()}, 130 | &ipv4.Handler{IP: net.ParseIP("2.2.2.2").To4()}, 131 | }, 132 | }, 133 | }, 134 | }, 135 | { 136 | in: "1-1-1-1.4-ttl-10s.2-2-2-2.4.loop-cnt-1.3-3-3-3.4-cnt-2.l", 137 | handlers: []handlers.Handler{ 138 | &loop.Handler{ 139 | Nested: [2]handlers.Handler{ 140 | &ipv4.Handler{ 141 | IP: net.ParseIP("3.3.3.3").To4(), 142 | BaseHandler: handlers.BaseHandler{ 143 | Limiters: limiter.Limiters{ 144 | &limiter.Count{ 145 | Max: 2, 146 | }, 147 | }, 148 | }, 149 | }, 150 | &loop.Handler{ 151 | Nested: [2]handlers.Handler{ 152 | &ipv4.Handler{ 153 | IP: net.ParseIP("2.2.2.2").To4(), 154 | }, 155 | &ipv4.Handler{ 156 | IP: net.ParseIP("1.1.1.1").To4(), 157 | BaseHandler: handlers.BaseHandler{ 158 | Limiters: limiter.Limiters{ 159 | &limiter.TTL{ 160 | TTL: 10 * time.Second, 161 | }, 162 | }, 163 | }, 164 | }, 165 | }, 166 | BaseHandler: handlers.BaseHandler{ 167 | Limiters: limiter.Limiters{ 168 | &limiter.Count{ 169 | Max: 1, 170 | }, 171 | }, 172 | }, 173 | }, 174 | }, 175 | }, 176 | }, 177 | }, 178 | { 179 | in: "2-2-2-2.4-ttl-10s.3-3-3-3.s", 180 | handlers: []handlers.Handler{ 181 | &sticky.Handler{ 182 | Nested: [2]handlers.Handler{ 183 | &ipv4.Handler{ 184 | IP: net.ParseIP("3.3.3.3").To4(), 185 | }, 186 | &ipv4.Handler{ 187 | IP: net.ParseIP("2.2.2.2").To4(), 188 | BaseHandler: handlers.BaseHandler{ 189 | Limiters: limiter.Limiters{ 190 | &limiter.TTL{ 191 | TTL: 10 * time.Second, 192 | }, 193 | }, 194 | }, 195 | }, 196 | }, 197 | }, 198 | }, 199 | }, 200 | { 201 | in: "2-2-2-2.v4.3-3-3-3.v4.s", 202 | handlers: []handlers.Handler{ 203 | &sticky.Handler{ 204 | Nested: [2]handlers.Handler{ 205 | &ipv4.Handler{ 206 | IP: net.ParseIP("3.3.3.3").To4(), 207 | }, 208 | &ipv4.Handler{ 209 | IP: net.ParseIP("2.2.2.2").To4(), 210 | BaseHandler: handlers.BaseHandler{ 211 | Limiters: limiter.Limiters{ 212 | &limiter.TTL{ 213 | TTL: cfg.StickyTTL, 214 | }, 215 | }, 216 | }, 217 | }, 218 | }, 219 | }, 220 | }, 221 | }, 222 | } 223 | 224 | for _, tc := range cases { 225 | t.Run(tc.in, func(t *testing.T) { 226 | hndlrs, err := parser.NewParser(tc.in, "").RestHandlers() 227 | require.NoError(t, err) 228 | require.EqualValues(t, tc.handlers, hndlrs) 229 | }) 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /pkg/handlers/proxy/handler.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/miekg/dns" 8 | 9 | "github.com/buglloc/rip/v2/pkg/cfg" 10 | "github.com/buglloc/rip/v2/pkg/handlers" 11 | "github.com/buglloc/rip/v2/pkg/handlers/limiter" 12 | "github.com/buglloc/rip/v2/pkg/handlers/slices" 13 | "github.com/buglloc/rip/v2/pkg/resolver" 14 | ) 15 | 16 | const ShortName = "p" 17 | const Name = "proxy" 18 | 19 | var _ handlers.Handler = (*Handler)(nil) 20 | 21 | type Handler struct { 22 | handlers.BaseHandler 23 | TargetFQDN string 24 | } 25 | 26 | func NewHandler(modifiers ...limiter.Limiter) *Handler { 27 | return &Handler{ 28 | BaseHandler: handlers.BaseHandler{ 29 | Limiters: modifiers, 30 | }, 31 | } 32 | } 33 | 34 | func (h *Handler) Name() string { 35 | return Name 36 | } 37 | 38 | func (h *Handler) Init(p handlers.Parser) error { 39 | if !cfg.AllowProxy { 40 | return handlers.ErrNotAllowed 41 | } 42 | 43 | parts, _ := p.RestValues() 44 | if len(parts) == 0 { 45 | return handlers.ErrUnexpectedEOF 46 | } 47 | 48 | slices.StringsReverse(parts) 49 | h.TargetFQDN = dns.Fqdn(strings.Join(parts, ".")) 50 | return nil 51 | } 52 | 53 | func (h *Handler) Handle(question dns.Question) ([]dns.RR, bool, error) { 54 | ips, err := resolver.ResolveIp(question.Qtype, h.TargetFQDN) 55 | if err != nil { 56 | return nil, false, fmt.Errorf("proxy: %w", err) 57 | } 58 | 59 | h.Limiters.Use() 60 | return handlers.IPsToRR(question, ips...), h.Limiters.MoveOn(), nil 61 | } 62 | -------------------------------------------------------------------------------- /pkg/handlers/random/handler.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | 7 | "github.com/miekg/dns" 8 | 9 | "github.com/buglloc/rip/v2/pkg/handlers" 10 | "github.com/buglloc/rip/v2/pkg/handlers/limiter" 11 | ) 12 | 13 | const ShortName = "r" 14 | const Name = "random" 15 | 16 | var _ handlers.Handler = (*Handler)(nil) 17 | 18 | type Handler struct { 19 | handlers.BaseHandler 20 | Nested [2]handlers.Handler 21 | Cur int 22 | } 23 | 24 | func NewHandler(modifiers ...limiter.Limiter) *Handler { 25 | return &Handler{ 26 | BaseHandler: handlers.BaseHandler{ 27 | Limiters: modifiers, 28 | }, 29 | } 30 | } 31 | 32 | func (h *Handler) Name() string { 33 | return Name 34 | } 35 | 36 | func (h *Handler) Init(p handlers.Parser) error { 37 | var err error 38 | h.Nested[0], err = p.NextHandler() 39 | if err != nil { 40 | return err 41 | } 42 | 43 | h.Nested[1], err = p.NextHandler() 44 | if err != nil { 45 | return err 46 | } 47 | 48 | h.Cur = randCur() 49 | return nil 50 | } 51 | 52 | func (h *Handler) Handle(question dns.Question) ([]dns.RR, bool, error) { 53 | rr, moveOn, err := h.Nested[h.Cur].Handle(question) 54 | if err != nil { 55 | return nil, false, fmt.Errorf("random: %w", err) 56 | } 57 | 58 | if moveOn { 59 | h.Cur = randCur() 60 | } 61 | 62 | if len(rr) > 0 { 63 | h.Limiters.Use() 64 | } 65 | 66 | return rr, h.Limiters.MoveOn(), nil 67 | } 68 | 69 | func randCur() int { 70 | if rand.Intn(50) > 50 { 71 | return 1 72 | } 73 | 74 | return 0 75 | } 76 | -------------------------------------------------------------------------------- /pkg/handlers/slices/slices.go: -------------------------------------------------------------------------------- 1 | package slices 2 | 3 | func StringsReverse(ss []string) { 4 | last := len(ss) - 1 5 | for i := 0; i < len(ss)/2; i++ { 6 | ss[i], ss[last-i] = ss[last-i], ss[i] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /pkg/handlers/sticky/handler.go: -------------------------------------------------------------------------------- 1 | package sticky 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/miekg/dns" 7 | 8 | "github.com/buglloc/rip/v2/pkg/cfg" 9 | "github.com/buglloc/rip/v2/pkg/handlers" 10 | "github.com/buglloc/rip/v2/pkg/handlers/limiter" 11 | ) 12 | 13 | const ShortName = "s" 14 | const Name = "sticky" 15 | 16 | var _ handlers.Handler = (*Handler)(nil) 17 | 18 | type Handler struct { 19 | handlers.BaseHandler 20 | Nested [2]handlers.Handler 21 | Cur int 22 | } 23 | 24 | func NewHandler(modifiers ...limiter.Limiter) *Handler { 25 | return &Handler{ 26 | BaseHandler: handlers.BaseHandler{ 27 | Limiters: modifiers, 28 | }, 29 | } 30 | } 31 | func (h *Handler) Name() string { 32 | return Name 33 | } 34 | 35 | func (h *Handler) Init(p handlers.Parser) error { 36 | var err error 37 | h.Nested[0], err = p.NextHandler() 38 | if err != nil { 39 | return err 40 | } 41 | 42 | h.Nested[1], err = p.NextHandler() 43 | if err != nil { 44 | return err 45 | } 46 | 47 | h.Nested[1].SetDefaultLimiters(&limiter.TTL{ 48 | TTL: cfg.StickyTTL, 49 | }) 50 | 51 | return nil 52 | } 53 | 54 | func (h *Handler) Handle(question dns.Question) ([]dns.RR, bool, error) { 55 | rr, moveOn, err := h.Nested[h.Cur].Handle(question) 56 | if err != nil { 57 | return nil, false, fmt.Errorf("sticky: %w", err) 58 | } 59 | 60 | if moveOn { 61 | h.Cur = (h.Cur + 1) % 2 62 | } 63 | 64 | if len(rr) > 0 { 65 | h.Limiters.Use() 66 | } 67 | 68 | return rr, h.Limiters.MoveOn(), nil 69 | } 70 | -------------------------------------------------------------------------------- /pkg/hub/conn.go: -------------------------------------------------------------------------------- 1 | package hub 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gorilla/websocket" 7 | ) 8 | 9 | const ( 10 | // Time allowed to write a message to the peer. 11 | writeWait = 10 * time.Second 12 | 13 | // Time allowed to read the next pong message from the peer. 14 | pongWait = 60 * time.Second 15 | 16 | // Send pings to peer with this period. Must be less than pongWait. 17 | pingPeriod = (pongWait * 9) / 10 18 | 19 | // Maximum message size allowed from peer. 20 | maxMessageSize = 8 21 | ) 22 | 23 | // connection is an middleman between the websocket connection and the hub. 24 | type connection struct { 25 | // The websocket connection. 26 | ws *websocket.Conn 27 | 28 | // Buffered channel of outbound messages. 29 | send chan []byte 30 | } 31 | 32 | // readPump pumps messages from the websocket connection to the hub. 33 | func (s subscription) readPump() { 34 | c := s.conn 35 | defer func() { 36 | h.unregister <- s 37 | _ = c.ws.Close() 38 | }() 39 | 40 | c.ws.SetReadLimit(maxMessageSize) 41 | _ = c.ws.SetReadDeadline(time.Now().Add(pongWait)) 42 | c.ws.SetPongHandler(func(string) error { 43 | return c.ws.SetReadDeadline(time.Now().Add(pongWait)) 44 | }) 45 | 46 | for { 47 | _, _, err := c.ws.ReadMessage() 48 | if err != nil { 49 | break 50 | } 51 | } 52 | } 53 | 54 | // write writes a message with the given message type and payload. 55 | func (c *connection) write(mt int, payload []byte) error { 56 | _ = c.ws.SetWriteDeadline(time.Now().Add(writeWait)) 57 | return c.ws.WriteMessage(mt, payload) 58 | } 59 | 60 | // writePump pumps messages from the hub to the websocket connection. 61 | func (s *subscription) writePump() { 62 | c := s.conn 63 | ticker := time.NewTicker(pingPeriod) 64 | defer func() { 65 | ticker.Stop() 66 | _ = c.ws.Close() 67 | }() 68 | 69 | for { 70 | select { 71 | case message, ok := <-c.send: 72 | if !ok { 73 | _ = c.write(websocket.CloseMessage, []byte{}) 74 | return 75 | } 76 | if err := c.write(websocket.TextMessage, message); err != nil { 77 | return 78 | } 79 | case <-ticker.C: 80 | if err := c.write(websocket.PingMessage, []byte{}); err != nil { 81 | return 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /pkg/hub/hub.go: -------------------------------------------------------------------------------- 1 | package hub 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | 7 | log "github.com/buglloc/simplelog" 8 | "github.com/gorilla/websocket" 9 | ) 10 | 11 | type Message struct { 12 | Time time.Time `json:"time"` 13 | QType string `json:"type"` 14 | Name string `json:"name"` 15 | RR string `json:"rr"` 16 | Ok bool `json:"ok"` 17 | } 18 | 19 | type channelMessage struct { 20 | msg Message 21 | channel string 22 | } 23 | 24 | type subscription struct { 25 | conn *connection 26 | id string 27 | } 28 | 29 | // hub maintains the set of active connections and broadcasts messages to the 30 | // connections. 31 | type hub struct { 32 | // Registered connections. 33 | channels map[string]map[*connection]struct{} 34 | 35 | // Inbound messages from the connections. 36 | broadcast chan channelMessage 37 | 38 | // Register requests from the connections. 39 | register chan subscription 40 | 41 | // Unregister requests from connections. 42 | unregister chan subscription 43 | } 44 | 45 | var h = hub{ 46 | broadcast: make(chan channelMessage), 47 | register: make(chan subscription), 48 | unregister: make(chan subscription), 49 | channels: make(map[string]map[*connection]struct{}), 50 | } 51 | 52 | // TODO(buglloc): bullshit 53 | func init() { 54 | go h.run() 55 | } 56 | 57 | func (h *hub) run() { 58 | for { 59 | select { 60 | case s := <-h.register: 61 | connections := h.channels[s.id] 62 | if connections == nil { 63 | connections = make(map[*connection]struct{}) 64 | h.channels[s.id] = connections 65 | } 66 | h.channels[s.id][s.conn] = struct{}{} 67 | case s := <-h.unregister: 68 | connections := h.channels[s.id] 69 | if connections != nil { 70 | if _, ok := connections[s.conn]; ok { 71 | delete(connections, s.conn) 72 | close(s.conn.send) 73 | if len(connections) == 0 { 74 | delete(h.channels, s.id) 75 | } 76 | } 77 | } 78 | case m := <-h.broadcast: 79 | data, err := json.Marshal(m.msg) 80 | if err != nil { 81 | log.Error("can't marshal hub message", "name", m.msg.Name, "err", err) 82 | continue 83 | } 84 | 85 | connections := h.channels[m.channel] 86 | for c := range connections { 87 | select { 88 | case c.send <- data: 89 | default: 90 | close(c.send) 91 | delete(connections, c) 92 | if len(connections) == 0 { 93 | delete(h.channels, m.channel) 94 | } 95 | } 96 | } 97 | } 98 | } 99 | } 100 | 101 | func Register(conn *websocket.Conn, id string) { 102 | s := subscription{ 103 | conn: &connection{ 104 | send: make(chan []byte, 256), 105 | ws: conn, 106 | }, 107 | id: id, 108 | } 109 | 110 | h.register <- s 111 | go s.writePump() 112 | s.readPump() 113 | } 114 | 115 | func Send(channel string, msg Message) { 116 | h.broadcast <- channelMessage{ 117 | msg: msg, 118 | channel: channel, 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /pkg/iputil/iputil.go: -------------------------------------------------------------------------------- 1 | package iputil 2 | 3 | import ( 4 | "math/big" 5 | "net" 6 | ) 7 | 8 | func DecodeIp(hex string) (net.IP, bool) { 9 | ip, ok := big.NewInt(0).SetString(hex, 16) 10 | if !ok { 11 | return nil, false 12 | } 13 | return ip.Bytes(), true 14 | } 15 | 16 | func EncodeIP4(ip net.IP) string { 17 | ipInt := big.NewInt(0) 18 | ipInt.SetBytes(ip.To4()) 19 | return ipInt.Text(16) 20 | } 21 | 22 | func EncodeIP6(ip net.IP) string { 23 | ipInt := big.NewInt(0) 24 | ipInt.SetBytes(ip.To16()) 25 | return ipInt.Text(16) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/nssrv/handler.go: -------------------------------------------------------------------------------- 1 | package nssrv 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | log "github.com/buglloc/simplelog" 8 | "github.com/miekg/dns" 9 | 10 | "github.com/buglloc/rip/v2/pkg/cfg" 11 | "github.com/buglloc/rip/v2/pkg/handlers" 12 | "github.com/buglloc/rip/v2/pkg/handlers/defaultip" 13 | "github.com/buglloc/rip/v2/pkg/handlers/parser" 14 | ) 15 | 16 | var defaultHandler = &defaultip.Handler{} 17 | 18 | type cachedHandler struct { 19 | handlers.Handler 20 | mu sync.Mutex 21 | } 22 | 23 | func (h *cachedHandler) Handle(question dns.Question) ([]dns.RR, error) { 24 | h.mu.Lock() 25 | defer h.mu.Unlock() 26 | ret, _, err := h.Handler.Handle(question) 27 | return ret, err 28 | } 29 | 30 | func (s *NSSrv) handleRequest(zone string, req *dns.Msg, logger *log.Logger) *dns.Msg { 31 | out := &dns.Msg{} 32 | out.SetReply(req) 33 | 34 | realHandler := func(question dns.Question, zone string) (*cachedHandler, error) { 35 | if len(question.Name)-len(zone) <= 3 { 36 | // fast exit 37 | return &cachedHandler{ 38 | Handler: defaultHandler, 39 | }, nil 40 | } 41 | 42 | //if item != nil && 43 | item := s.cache.Get(question.Name) 44 | if item != nil { 45 | if item.Expired() { 46 | item.Extend(cfg.CacheTTL) 47 | } 48 | return item.Value(), nil 49 | } 50 | 51 | h, err := parser.NewParser(question.Name, zone).NextHandler() 52 | if err != nil { 53 | if err != handlers.ErrEOF { 54 | return nil, err 55 | } 56 | 57 | if !cfg.UseDefault { 58 | return nil, fmt.Errorf("no handlers for request %q available", question.Name) 59 | } 60 | 61 | h = defaultHandler 62 | } 63 | 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | ret := &cachedHandler{ 69 | Handler: h, 70 | } 71 | s.cache.Set(question.Name, ret, cfg.CacheTTL) 72 | return ret, nil 73 | } 74 | 75 | for _, question := range req.Question { 76 | switch question.Qtype { 77 | case dns.TypeA, dns.TypeAAAA: 78 | l := logger.Child("type", dns.Type(question.Qtype), "name", question.Name) 79 | handler, err := realHandler(question, zone) 80 | if err != nil { 81 | l.Error("failed to parse request", "err", err) 82 | continue 83 | } 84 | 85 | answers, err := handler.Handle(question) 86 | if err != nil { 87 | l.Error("failed to handle request", "err", err) 88 | continue 89 | } 90 | 91 | l.Info("cooking response", "answers", fmt.Sprint(answers)) 92 | if len(answers) == 0 { 93 | continue 94 | } 95 | 96 | out.Answer = append(out.Answer, answers...) 97 | } 98 | } 99 | 100 | return out 101 | } 102 | -------------------------------------------------------------------------------- /pkg/nssrv/server.go: -------------------------------------------------------------------------------- 1 | package nssrv 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | log "github.com/buglloc/simplelog" 9 | "github.com/karlseguin/ccache/v3" 10 | "github.com/miekg/dns" 11 | "golang.org/x/sync/errgroup" 12 | 13 | "github.com/buglloc/rip/v2/pkg/cfg" 14 | "github.com/buglloc/rip/v2/pkg/www" 15 | ) 16 | 17 | type NSSrv struct { 18 | tcpServer *dns.Server 19 | udpServer *dns.Server 20 | wwwServer *www.HttpSrv 21 | cache *ccache.Cache[*cachedHandler] 22 | } 23 | 24 | func NewSrv() (*NSSrv, error) { 25 | srv := &NSSrv{ 26 | tcpServer: &dns.Server{ 27 | Addr: cfg.Addr, 28 | Net: "tcp", 29 | ReadTimeout: 5 * time.Second, 30 | WriteTimeout: 5 * time.Second, 31 | }, 32 | udpServer: &dns.Server{ 33 | Addr: cfg.Addr, 34 | Net: "udp", 35 | UDPSize: 65535, 36 | ReadTimeout: 5 * time.Second, 37 | WriteTimeout: 5 * time.Second, 38 | }, 39 | cache: ccache.New(ccache.Configure[*cachedHandler]().MaxSize(cfg.CacheSize)), 40 | } 41 | 42 | srv.udpServer.Handler = srv.newDNSRouter() 43 | srv.tcpServer.Handler = srv.newDNSRouter() 44 | if cfg.HubEnabled { 45 | srv.wwwServer = www.NewHttpSrv() 46 | } 47 | 48 | return srv, nil 49 | } 50 | 51 | func (s *NSSrv) ListenAndServe() error { 52 | var g errgroup.Group 53 | g.Go(func() error { 54 | log.Info("starting TCP-server", "addr", s.tcpServer.Addr) 55 | err := s.tcpServer.ListenAndServe() 56 | if err != nil { 57 | log.Error("can't start TCP-server", "err", err) 58 | } 59 | return err 60 | }) 61 | 62 | g.Go(func() error { 63 | log.Info("starting UDP-server", "addr", s.tcpServer.Addr) 64 | err := s.udpServer.ListenAndServe() 65 | if err != nil { 66 | log.Error("can't start UDP-server", "err", err) 67 | } 68 | return err 69 | }) 70 | 71 | if s.wwwServer != nil { 72 | g.Go(func() error { 73 | log.Info("starting HTTP-server", "addr", s.wwwServer.Addr()) 74 | err := s.wwwServer.ListenAndServe() 75 | if err != nil { 76 | log.Error("can't start HTTP-server", "err", err) 77 | } 78 | return err 79 | }) 80 | } 81 | 82 | return g.Wait() 83 | } 84 | 85 | func (s *NSSrv) Shutdown(ctx context.Context) error { 86 | var g errgroup.Group 87 | g.Go(func() error { 88 | return s.tcpServer.ShutdownContext(ctx) 89 | }) 90 | 91 | g.Go(func() error { 92 | return s.udpServer.ShutdownContext(ctx) 93 | }) 94 | 95 | if s.wwwServer != nil { 96 | g.Go(func() error { 97 | return s.wwwServer.Shutdown(ctx) 98 | }) 99 | } 100 | 101 | done := make(chan error) 102 | go func() { 103 | defer close(done) 104 | done <- g.Wait() 105 | }() 106 | 107 | select { 108 | case <-ctx.Done(): 109 | return ctx.Err() 110 | case err := <-done: 111 | return err 112 | } 113 | } 114 | 115 | func (s *NSSrv) newDNSRouter() *dns.ServeMux { 116 | out := dns.NewServeMux() 117 | for _, zone := range cfg.Zones { 118 | if !strings.HasSuffix(zone, ".") { 119 | zone += "." 120 | } 121 | 122 | out.HandleFunc(zone, func(zone string) func(w dns.ResponseWriter, req *dns.Msg) { 123 | return func(w dns.ResponseWriter, req *dns.Msg) { 124 | defer func() { _ = w.Close() }() 125 | 126 | l := log.Child("client", w.RemoteAddr().String()) 127 | msg := s.handleRequest(zone, req, &l) 128 | _ = w.WriteMsg(msg) 129 | } 130 | }(zone)) 131 | } 132 | return out 133 | } 134 | -------------------------------------------------------------------------------- /pkg/nssrv/server_test.go: -------------------------------------------------------------------------------- 1 | package nssrv_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "testing" 8 | "time" 9 | 10 | "github.com/miekg/dns" 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/buglloc/rip/v2/pkg/cfg" 14 | "github.com/buglloc/rip/v2/pkg/nssrv" 15 | ) 16 | 17 | func getFreePort() (int, error) { 18 | addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 19 | if err != nil { 20 | return 0, err 21 | } 22 | 23 | l, err := net.ListenTCP("tcp", addr) 24 | if err != nil { 25 | return 0, err 26 | } 27 | defer func() { _ = l.Close() }() 28 | 29 | return l.Addr().(*net.TCPAddr).Port, nil 30 | } 31 | 32 | func newSRV(t *testing.T) *nssrv.NSSrv { 33 | port, err := getFreePort() 34 | require.NoError(t, err) 35 | 36 | cfg.Zones = []string{"tst"} 37 | cfg.Addr = fmt.Sprintf("localhost:%d", port) 38 | srv, err := nssrv.NewSrv() 39 | require.NoError(t, err) 40 | 41 | go func() { 42 | err = srv.ListenAndServe() 43 | }() 44 | 45 | // TODO((buglloc): too ugly 46 | time.Sleep(1 * time.Second) 47 | if err != nil { 48 | _ = srv.Shutdown(context.Background()) 49 | require.NoError(t, err) 50 | } 51 | 52 | return srv 53 | } 54 | 55 | func resolve(t *testing.T, client *dns.Client, msg *dns.Msg) net.IP { 56 | res, _, err := client.Exchange(msg, cfg.Addr) 57 | require.NoError(t, err) 58 | require.NotEmpty(t, res.Answer) 59 | 60 | var ip net.IP 61 | switch v := res.Answer[0].(type) { 62 | case *dns.A: 63 | ip = v.A.To4() 64 | case *dns.AAAA: 65 | ip = v.AAAA.To16() 66 | } 67 | 68 | return ip 69 | } 70 | 71 | func TestServer_simple(t *testing.T) { 72 | cases := []struct { 73 | in string 74 | reqType uint16 75 | ip net.IP 76 | }{ 77 | { 78 | in: "1-1-1-1.4.tst", 79 | reqType: dns.TypeA, 80 | ip: net.ParseIP("1.1.1.1").To4(), 81 | }, 82 | { 83 | in: "1-1-1-1.v4.tst", 84 | reqType: dns.TypeA, 85 | ip: net.ParseIP("1.1.1.1").To4(), 86 | }, 87 | { 88 | in: "1-1-1-1.v4.tst", 89 | reqType: dns.TypeA, 90 | ip: net.ParseIP("1.1.1.1").To4(), 91 | }, 92 | { 93 | in: "fe80--fa94-c2ff-fee5-3cf6.6.tst", 94 | reqType: dns.TypeAAAA, 95 | ip: net.ParseIP("fe80::fa94:c2ff:fee5:3cf6").To16(), 96 | }, 97 | { 98 | in: "fe80000000000000fa94c2fffee53cf6.v6.tst", 99 | reqType: dns.TypeAAAA, 100 | ip: net.ParseIP("fe80::fa94:c2ff:fee5:3cf6").To16(), 101 | }, 102 | { 103 | in: "2-2-2-2.3-3-3-3.4.l.tst", 104 | reqType: dns.TypeA, 105 | ip: net.ParseIP("3.3.3.3").To4(), 106 | }, 107 | { 108 | in: "2-2-2-2.3-3-3-3.4.s.tst", 109 | reqType: dns.TypeA, 110 | ip: net.ParseIP("3.3.3.3").To4(), 111 | }, 112 | } 113 | 114 | srv := newSRV(t) 115 | defer func() { _ = srv.Shutdown(context.Background()) }() 116 | 117 | client := &dns.Client{ 118 | Net: "tcp", 119 | ReadTimeout: time.Second * 1, 120 | WriteTimeout: time.Second * 1, 121 | } 122 | for _, tc := range cases { 123 | t.Run(tc.in, func(t *testing.T) { 124 | msg := &dns.Msg{} 125 | msg.SetQuestion(dns.Fqdn(tc.in), tc.reqType) 126 | ip := resolve(t, client, msg) 127 | require.Equal(t, tc.ip, ip) 128 | }) 129 | } 130 | } 131 | 132 | func TestServer_loop(t *testing.T) { 133 | srv := newSRV(t) 134 | defer func() { _ = srv.Shutdown(context.Background()) }() 135 | 136 | client := &dns.Client{ 137 | Net: "tcp", 138 | ReadTimeout: time.Second * 1, 139 | WriteTimeout: time.Second * 1, 140 | } 141 | 142 | msg := &dns.Msg{} 143 | msg.SetQuestion(dns.Fqdn("1-1-1-1.v4.2-2-2-2.v4.loop.tst"), dns.TypeA) 144 | ip := resolve(t, client, msg) 145 | require.Equal(t, net.ParseIP("2.2.2.2").To4(), ip) 146 | ip = resolve(t, client, msg) 147 | require.Equal(t, net.ParseIP("1.1.1.1").To4(), ip) 148 | ip = resolve(t, client, msg) 149 | require.Equal(t, net.ParseIP("2.2.2.2").To4(), ip) 150 | } 151 | 152 | func TestServer_multiLoop(t *testing.T) { 153 | srv := newSRV(t) 154 | defer func() { _ = srv.Shutdown(context.Background()) }() 155 | 156 | client := &dns.Client{ 157 | Net: "tcp", 158 | ReadTimeout: time.Second * 1, 159 | WriteTimeout: time.Second * 1, 160 | } 161 | 162 | msg := &dns.Msg{} 163 | msg.SetQuestion(dns.Fqdn("1-1-1-1.v4.2-2-2-2.v4.loop-cnt-2.3-3-3-3.v4.loop.tst"), dns.TypeA) 164 | ip := resolve(t, client, msg) 165 | require.Equal(t, net.ParseIP("3.3.3.3").To4(), ip) 166 | ip = resolve(t, client, msg) 167 | require.Equal(t, net.ParseIP("2.2.2.2").To4(), ip) 168 | ip = resolve(t, client, msg) 169 | require.Equal(t, net.ParseIP("1.1.1.1").To4(), ip) 170 | ip = resolve(t, client, msg) 171 | require.Equal(t, net.ParseIP("3.3.3.3").To4(), ip) 172 | } 173 | 174 | func TestServer_multiLoopWithTTL(t *testing.T) { 175 | srv := newSRV(t) 176 | defer func() { _ = srv.Shutdown(context.Background()) }() 177 | 178 | client := &dns.Client{ 179 | Net: "tcp", 180 | ReadTimeout: time.Second * 1, 181 | WriteTimeout: time.Second * 1, 182 | } 183 | 184 | msg := &dns.Msg{} 185 | msg.SetQuestion(dns.Fqdn("1-1-1-1.v4.2-2-2-2.v4.loop-ttl-20s.3-3-3-3.v4.loop.tst"), dns.TypeA) 186 | ip := resolve(t, client, msg) 187 | require.Equal(t, net.ParseIP("3.3.3.3").To4(), ip) 188 | ip = resolve(t, client, msg) 189 | require.Equal(t, net.ParseIP("2.2.2.2").To4(), ip) 190 | ip = resolve(t, client, msg) 191 | require.Equal(t, net.ParseIP("1.1.1.1").To4(), ip) 192 | ip = resolve(t, client, msg) 193 | require.Equal(t, net.ParseIP("2.2.2.2").To4(), ip) 194 | ip = resolve(t, client, msg) 195 | require.Equal(t, net.ParseIP("1.1.1.1").To4(), ip) 196 | } 197 | -------------------------------------------------------------------------------- /pkg/obfustacor/obfuscator.go: -------------------------------------------------------------------------------- 1 | package obfuscator 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | // Based on https://github.com/OsandaMalith/IPObfuscator 9 | 10 | func IPv4(ipStr string) []string { 11 | ip := net.ParseIP(ipStr).To4() 12 | result := make([]string, 0) 13 | result = append(result, fmt.Sprintf( 14 | "%d", 15 | (int(ip[0])<<24)|(int(ip[1])<<16)|(int(ip[2])<<8)|int(ip[3]), 16 | )) 17 | result = append(result, fmt.Sprintf( 18 | "%#x.%#x.%#x.%#x", 19 | ip[0], ip[1], ip[2], ip[3], 20 | )) 21 | result = append(result, fmt.Sprintf( 22 | "%#X.%#X.%#X.%#X", 23 | ip[0], ip[1], ip[2], ip[3], 24 | )) 25 | result = append(result, fmt.Sprintf( 26 | "%#X.%#x.%#X.%#x", 27 | ip[0], ip[1], ip[2], ip[3], 28 | )) 29 | result = append(result, fmt.Sprintf( 30 | "%#05X.%#04X.%#03X.%#X", 31 | ip[0], ip[1], ip[2], ip[3], 32 | )) 33 | result = append(result, fmt.Sprintf( 34 | "%010o.%010o.%010o.%010o", 35 | ip[0], ip[1], ip[2], ip[3], 36 | )) 37 | result = append(result, fmt.Sprintf( 38 | "%010o.%010o.%010o.%d", 39 | ip[0], ip[1], ip[2], ip[3], 40 | )) 41 | result = append(result, fmt.Sprintf( 42 | "%010o.%010o.%010o.%d", 43 | ip[0], ip[1], ip[2], ip[3], 44 | )) 45 | result = append(result, fmt.Sprintf( 46 | "%010o.%010o.%d.%d", 47 | ip[0], ip[1], ip[2], ip[3], 48 | )) 49 | result = append(result, fmt.Sprintf( 50 | "%010o.%d.%d.%d", 51 | ip[0], ip[1], ip[2], ip[3], 52 | )) 53 | result = append(result, fmt.Sprintf( 54 | "%010o.%#x.%#X.%d", 55 | ip[0], ip[1], ip[2], ip[3], 56 | )) 57 | 58 | decSuffix := (int(ip[1]) << 16) | (int(ip[2]) << 8) | int(ip[3]) 59 | result = append(result, fmt.Sprintf( 60 | "%010o.%d", 61 | ip[0], decSuffix, 62 | )) 63 | result = append(result, fmt.Sprintf( 64 | "%#x.%d", 65 | ip[0], decSuffix, 66 | )) 67 | 68 | return result 69 | } 70 | -------------------------------------------------------------------------------- /pkg/resolver/cache.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/karlseguin/ccache/v3" 9 | ) 10 | 11 | type Cache struct { 12 | lruCache *ccache.Cache[[]net.IP] 13 | } 14 | 15 | func NewCache() *Cache { 16 | return &Cache{ 17 | lruCache: ccache.New(ccache.Configure[[]net.IP]()), 18 | } 19 | } 20 | 21 | func (c *Cache) Get(reqType uint16, domain string) []net.IP { 22 | if item := c.lruCache.Get(makeKey(reqType, domain)); item != nil && !item.Expired() && item.Value() != nil { 23 | return item.Value() 24 | } 25 | return nil 26 | } 27 | 28 | func (c *Cache) Set(reqType uint16, domain string, ttl time.Duration, ip []net.IP) { 29 | key := makeKey(reqType, domain) 30 | if ip == nil { 31 | c.lruCache.Delete(key) 32 | return 33 | } 34 | 35 | c.lruCache.Set(key, ip, ttl) 36 | } 37 | 38 | func makeKey(reqType uint16, domain string) string { 39 | return strconv.Itoa(int(reqType)) + domain 40 | } 41 | -------------------------------------------------------------------------------- /pkg/resolver/upstreams.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/miekg/dns" 8 | 9 | "github.com/buglloc/rip/v2/pkg/cfg" 10 | ) 11 | 12 | var ( 13 | dnsClient = &dns.Client{ 14 | Net: "tcp", 15 | ReadTimeout: time.Second * 1, 16 | WriteTimeout: time.Second * 1, 17 | } 18 | dnsCache = NewCache() 19 | ) 20 | 21 | func ResolveIp(reqType uint16, name string) ([]net.IP, error) { 22 | if ips := dnsCache.Get(reqType, name); ips != nil { 23 | return ips, nil 24 | } 25 | 26 | msg := &dns.Msg{} 27 | msg.SetQuestion(dns.Fqdn(name), reqType) 28 | res, _, err := dnsClient.Exchange(msg, cfg.Upstream) 29 | if err != nil || len(res.Answer) == 0 { 30 | return nil, err 31 | } 32 | 33 | var ipv4 []net.IP 34 | var ipv6 []net.IP 35 | for _, rr := range res.Answer { 36 | switch v := rr.(type) { 37 | case *dns.A: 38 | ipv4 = append(ipv4, v.A) 39 | case *dns.AAAA: 40 | ipv6 = append(ipv6, v.AAAA) 41 | } 42 | } 43 | 44 | ttl := time.Duration(res.Answer[0].Header().Ttl) * time.Second 45 | if reqType == dns.TypeA { 46 | dnsCache.Set(dns.TypeA, name, ttl, ipv4) 47 | return ipv4, nil 48 | } 49 | 50 | dnsCache.Set(dns.TypeAAAA, name, ttl, ipv6) 51 | return ipv6, nil 52 | } 53 | -------------------------------------------------------------------------------- /pkg/www/common.go: -------------------------------------------------------------------------------- 1 | package www 2 | 3 | import ( 4 | "io" 5 | "mime" 6 | "net/http" 7 | "path/filepath" 8 | ) 9 | 10 | func serveStatic(fs http.FileSystem, path string) http.HandlerFunc { 11 | return func(w http.ResponseWriter, r *http.Request) { 12 | f, err := fs.Open(path) 13 | if err != nil { 14 | http.NotFound(w, r) 15 | return 16 | } 17 | defer func() { _ = f.Close() }() 18 | 19 | ctype := mime.TypeByExtension(filepath.Ext(path)) 20 | if ctype != "" { 21 | w.Header().Set("Content-Type", ctype) 22 | } 23 | 24 | _, _ = io.Copy(w, f) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pkg/www/srv.go: -------------------------------------------------------------------------------- 1 | package www 2 | 3 | import ( 4 | "context" 5 | "embed" 6 | "fmt" 7 | "net/http" 8 | 9 | log "github.com/buglloc/simplelog" 10 | "github.com/go-chi/chi/v5" 11 | "github.com/go-chi/chi/v5/middleware" 12 | "github.com/gorilla/websocket" 13 | 14 | "github.com/buglloc/rip/v2/pkg/cfg" 15 | "github.com/buglloc/rip/v2/pkg/hub" 16 | ) 17 | 18 | //go:embed static/* 19 | var staticFiles embed.FS 20 | var upgrader = websocket.Upgrader{ 21 | ReadBufferSize: 1024, 22 | WriteBufferSize: 1024, 23 | } 24 | 25 | type HttpSrv struct { 26 | https http.Server 27 | tokens *tokenManager 28 | inShutdown bool 29 | } 30 | 31 | func NewHttpSrv() *HttpSrv { 32 | srv := &HttpSrv{ 33 | tokens: NewTokenManager(), 34 | } 35 | 36 | srv.https = http.Server{ 37 | Addr: cfg.HttpAddr, 38 | Handler: srv.router(), 39 | } 40 | 41 | return srv 42 | } 43 | 44 | func (s *HttpSrv) Addr() string { 45 | return s.https.Addr 46 | } 47 | 48 | func (s *HttpSrv) ListenAndServe() error { 49 | err := s.https.ListenAndServe() 50 | if s.inShutdown { 51 | return nil 52 | } 53 | 54 | return err 55 | } 56 | 57 | func (s *HttpSrv) Shutdown(ctx context.Context) error { 58 | s.inShutdown = true 59 | return s.https.Shutdown(ctx) 60 | } 61 | 62 | func (s *HttpSrv) router() http.Handler { 63 | var staticFS = http.FS(staticFiles) 64 | fs := http.FileServer(staticFS) 65 | 66 | r := chi.NewRouter() 67 | r.Use(middleware.Recoverer) 68 | 69 | r.Get("/", serveStatic(staticFS, "/static/index.html")) 70 | r.Get("/session", serveStatic(staticFS, "/static/session.html")) 71 | r.Get("/ping", serveStatic(staticFS, "/static/pong.txt")) 72 | 73 | r.Get("/start", func(w http.ResponseWriter, r *http.Request) { 74 | token, err := s.tokens.NewToken() 75 | if err != nil { 76 | log.Error("can't create new token", "err", err) 77 | http.Error(w, "can't start new session", http.StatusInternalServerError) 78 | return 79 | } 80 | 81 | http.Redirect(w, r, fmt.Sprintf("/session?token=%s", token), http.StatusTemporaryRedirect) 82 | }) 83 | 84 | r.Get("/ws", func(w http.ResponseWriter, r *http.Request) { 85 | token := r.URL.Query().Get("token") 86 | if token == "" { 87 | http.NotFound(w, r) 88 | return 89 | } 90 | 91 | channelID, err := s.tokens.ParseToken(token) 92 | if err != nil { 93 | http.NotFound(w, r) 94 | return 95 | } 96 | 97 | ws, err := upgrader.Upgrade(w, r, nil) 98 | if err != nil { 99 | http.Error(w, fmt.Sprintf("can't upgrade request: %v", err), http.StatusInternalServerError) 100 | return 101 | } 102 | 103 | hub.Register(ws, channelID) 104 | }) 105 | 106 | r.Mount("/static/", fs) 107 | return r 108 | } 109 | -------------------------------------------------------------------------------- /pkg/www/static/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /pkg/www/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buglloc/rip/19fcb39253a86b12dd3fcd6efbff7aec8a0d4c20/pkg/www/static/favicon-16x16.png -------------------------------------------------------------------------------- /pkg/www/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buglloc/rip/19fcb39253a86b12dd3fcd6efbff7aec8a0d4c20/pkg/www/static/favicon-32x32.png -------------------------------------------------------------------------------- /pkg/www/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buglloc/rip/19fcb39253a86b12dd3fcd6efbff7aec8a0d4c20/pkg/www/static/favicon.ico -------------------------------------------------------------------------------- /pkg/www/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RIP 8 | 9 | 10 | 11 | 12 | 13 | 23 | 24 | 25 | 26 |
27 |
28 | 29 | RIP 30 | RIP 31 | 32 |
33 | 34 |
35 |

RIP is a simple NS server that extracts IP address from the requested name and sends it back in the response.

36 | 37 |
38 | Start session 39 |
40 | 41 |
42 | 43 |
44 |
45 |

Available rr's

46 |

(something that generate response)

47 |
    48 |
  • <IP> - returns IP address (guesses IPv4/IPv6)
  • 49 |
  • <IPv4>.[4|v4] - strictly returns IPv4 address only
  • 50 |
  • <IPv6>.[6|v6] - strictly returns IPv6 address only
  • 51 |
  • <cname>.[c|cname] - return CNAME record with <cname>
  • 52 |
  • <target>.[p|proxy] - resolve <target> name and returns it
  • 53 |
54 |
55 | 56 |
57 |

Available containers

58 |

(something that holds rr or another container)

59 |
    60 |
  • <rr>.<container>.[r|random] - pick random rr/container
  • 61 |
  • <rr>.<container>.[l|loop] - iterate over rr/container
  • 62 |
  • <rr1>.<rr0>.[s|sticky] - alias for loop (<rr1-ttl-30>.<rr0>.l)
  • 63 |
64 |
65 | 66 |
67 |

Available limiters

68 |

(something that limit this kind of responses:)

69 |
    70 |
  • cnt-<num> - use rr <num> requests
  • 71 |
  • ttl-<duration> - use rr <duration> duration
  • 72 |
73 |
74 |
75 | 76 |
77 | 78 |
79 |

Examples

80 |
81 |
82 |
IPv4
83 |

84 |

    85 |
  • 1-1-1-1.{{zone}}
    => returns 1.1.1.1
  • 86 |
  • 1-1-1-1.v4.{{zone}}
    => returns 1.1.1.1
  • 87 |
  • foo.1-1-1-1.v4.{{zone}}
    => returns 1.1.1.1
  • 88 |
  • bar.foo.1-1-1-1.v4.{{zone}}
    => returns 1.1.1.1
  • 89 |
  • 1010101.v4.{{zone}}
    => returns 1.1.1.1
  • 90 |
91 |

92 |
93 | 94 |
95 |
IPv6
96 |

97 |

    98 |
  • 2a01-7e01--f03c-91ff-fe3b-c9ba.{{zone}}
    => returns 2a01:7e01::f03c:91ff:fe3b:c9ba
  • 99 |
  • 2a01-7e01--f03c-91ff-fe3b-c9ba.v6.{{zone}}
    => returns 2a01:7e01::f03c:91ff:fe3b:c9ba
  • 100 |
  • 2a017e0100000000f03c91fffe3bc9ba.v6.{{zone}}
    => returns 2a01:7e01::f03c:91ff:fe3b:c9ba
  • 101 |
  • foo.2a01-7e01--f03c-91ff-fe3b-c9ba.v6.{{zone}}
    => returns 2a01:7e01::f03c:91ff:fe3b:c9ba
  • 102 |
103 |

104 |
105 | 106 |
107 |
Loop
108 |

109 |

    110 |
  • 8ba299a7.8ba299a8.loop.{{zone}}
    => loop over 139.162.153.168 and 139.162.153.167
  • 111 |
  • 8ba299a7.v4-ttl-5s.8ba299a8.v4-cnt-5.loop.{{zone}}
    => returns 139.162.153.168 (5 times), then 139.162.153.167 (next 5s) and loop again
  • 112 |
  • 8ba299a7.v4-ttl-5s.b32-onxw2zlunbuw4zzomnxw63bnmnxs44tv.c-cnt-5.loop.{{zone}}
    => returns CNAME something.cool.co.ru (5 times), then 139.162.153.167 (next 5s) and loop again
  • 113 |
114 |

115 |
116 | 117 |
118 |
CName
119 |

120 |

    121 |
  • ya.ru.c.{{zone}}
    => returns CNAME ya.ru
  • 122 |
  • google.com.c.{{zone}}
    => returns CNAME google.com
  • 123 |
  • b32-onxw2zlunbuw4zzomnxw63bnmnxs44tv.c.{{zone}}
    => returns CNAME something.cool.co.ru
  • 124 |
125 |

126 |
127 | 128 |
129 |
130 |
131 |
132 | 133 | 134 | 135 | 143 | 144 | -------------------------------------------------------------------------------- /pkg/www/static/pong.txt: -------------------------------------------------------------------------------- 1 | pong -------------------------------------------------------------------------------- /pkg/www/static/session.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RIP | Session view 8 | 9 | 10 | 11 | 12 | 13 | 23 | 24 | 25 | 26 |
27 |
28 | 29 | RIP 30 | RIP | Session view 31 | 32 |
33 | 34 |
35 |
36 |

List of requests to *.{{zone}}

37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
timetypenamerr
{{rr.time | moment}}
{{rr.type}}
{{rr.name}}
{{rr.rr}}
56 |
57 |
58 |
59 |
60 | 61 | 62 | 63 | 135 | -------------------------------------------------------------------------------- /pkg/www/static/styles.css: -------------------------------------------------------------------------------- 1 | .github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}} -------------------------------------------------------------------------------- /pkg/www/token.go: -------------------------------------------------------------------------------- 1 | package www 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "fmt" 8 | "time" 9 | 10 | "github.com/lestrrat-go/jwx/jwa" 11 | "github.com/lestrrat-go/jwx/jwt" 12 | 13 | "github.com/buglloc/rip/v2/pkg/cfg" 14 | ) 15 | 16 | type tokenManager struct { 17 | signKey []byte 18 | ttl time.Duration 19 | } 20 | 21 | func NewTokenManager() *tokenManager { 22 | return &tokenManager{ 23 | signKey: []byte(cfg.HubSign), 24 | ttl: cfg.HubSignTTL, 25 | } 26 | } 27 | 28 | func (c *tokenManager) NewToken() (string, error) { 29 | channelID, err := newChannelID() 30 | if err != nil { 31 | return "", fmt.Errorf("can't create channel id: %w", err) 32 | } 33 | 34 | t := jwt.New() 35 | if err := t.Set(jwt.SubjectKey, channelID); err != nil { 36 | return "", fmt.Errorf("can't set 'sub' to JWT token: %w", err) 37 | } 38 | 39 | if err := t.Set(jwt.IssuedAtKey, time.Now()); err != nil { 40 | return "", fmt.Errorf("can't set 'iat' to JWT token: %w", err) 41 | } 42 | 43 | token, err := jwt.Sign(t, jwa.HS256, c.signKey) 44 | if err != nil { 45 | return "", fmt.Errorf("can't sign token: %w", err) 46 | } 47 | 48 | return string(token), nil 49 | } 50 | 51 | func (c *tokenManager) ParseToken(in string) (string, error) { 52 | token, err := jwt.Parse([]byte(in), jwt.WithVerify(jwa.HS256, c.signKey)) 53 | if err != nil { 54 | return "", err 55 | } 56 | 57 | iat := token.IssuedAt() 58 | if time.Since(iat) >= c.ttl { 59 | return "", fmt.Errorf("expired token") 60 | } 61 | 62 | return token.Subject(), nil 63 | } 64 | 65 | func newChannelID() (string, error) { 66 | bs := make([]byte, 6) 67 | if _, err := rand.Read(bs[:2]); err != nil { 68 | return "", fmt.Errorf("rand read: %w", err) 69 | } 70 | 71 | binary.BigEndian.PutUint32(bs[2:], uint32(time.Now().Unix())) 72 | return hex.EncodeToString(bs), nil 73 | } 74 | --------------------------------------------------------------------------------