├── .dockerignore ├── Makefile ├── .gitignore ├── Dockerfile ├── libs ├── default_client.go ├── cidr.go ├── resolver.go ├── types_test.go ├── types.go ├── resolver_test.go ├── formatter.go ├── formatter_test.go ├── client_test.go └── client.go ├── runner ├── banner.go ├── output.go ├── runner_test.go ├── runner.go └── options.go ├── .github ├── workflows │ ├── lint-test.yml │ ├── build-test.yml │ ├── release-binary.yml │ └── dockerhub-push.yml └── dependabot.yml ├── .goreleaser.yml ├── cmd └── asnmap │ └── asnmap.go ├── examples └── simple.go ├── LICENSE ├── go.mod ├── README.md └── go.sum /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | *.idea 3 | **/.git 4 | *.gitignore 5 | *.sh 6 | Makefile 7 | LICENSE 8 | README.md 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Go parameters 2 | GOCMD=go 3 | GOBUILD=$(GOCMD) build 4 | GOMOD=$(GOCMD) mod 5 | GOTEST=$(GOCMD) test 6 | GOCLEAN=$(GOCMD) clean 7 | 8 | .PHONY: cli test tidy 9 | 10 | cli: 11 | $(GOBUILD) -v -ldflags="-extldflags=-static" -o "asnmap" ./cmd/asnmap/ 12 | test: 13 | $(GOCLEAN) -testcache 14 | $(GOTEST) -v ./... 15 | tidy: 16 | $(GOMOD) tidy 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | *.idea 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | dist/ 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Base 2 | FROM golang:1.20.1-alpine AS builder 3 | 4 | RUN apk add --no-cache git build-base gcc musl-dev 5 | WORKDIR /app 6 | COPY . /app 7 | RUN go mod download 8 | RUN go build ./cmd/asnmap 9 | 10 | FROM alpine:3.17.2 11 | RUN apk -U upgrade --no-cache \ 12 | && apk add --no-cache bind-tools ca-certificates 13 | COPY --from=builder /app/asnmap /usr/local/bin/ 14 | 15 | ENTRYPOINT ["asnmap"] 16 | -------------------------------------------------------------------------------- /libs/default_client.go: -------------------------------------------------------------------------------- 1 | package asnmap 2 | 3 | var DefaultClient *Client 4 | 5 | func init() { 6 | var err error 7 | DefaultClient, err = NewClient() 8 | if err != nil { 9 | // if we can't create the default client it makes sense to panic, as any other attempt will fail 10 | panic(err) 11 | } 12 | } 13 | 14 | func GetData(input string) ([]*Response, error) { 15 | return DefaultClient.GetData(input) 16 | } 17 | -------------------------------------------------------------------------------- /libs/cidr.go: -------------------------------------------------------------------------------- 1 | package asnmap 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/projectdiscovery/mapcidr" 7 | ) 8 | 9 | func GetCIDR(output []*Response) ([]*net.IPNet, error) { 10 | var cidrs []*net.IPNet 11 | for _, res := range output { 12 | cidr, err := mapcidr.GetCIDRFromIPRange(net.ParseIP(res.FirstIp), net.ParseIP(res.LastIp)) 13 | if err != nil { 14 | return nil, err 15 | } 16 | cidrs = append(cidrs, cidr...) 17 | } 18 | return cidrs, nil 19 | } 20 | -------------------------------------------------------------------------------- /runner/banner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import "github.com/projectdiscovery/gologger" 4 | 5 | const banner = ` 6 | ___ _____ __ 7 | / _ | / __/ |/ /_ _ ___ ____ 8 | / __ |_\ \/ / ' \/ _ / _ \ 9 | /_/ |_/___/_/|_/_/_/_/\_,_/ .__/ 10 | /_/ v1.0.2 11 | ` 12 | 13 | // Version is the current version of mapcidr 14 | const Version = `v1.0.2` 15 | 16 | // showBanner is used to show the banner to the user 17 | func showBanner() { 18 | gologger.Print().Msgf("%s\n", banner) 19 | gologger.Print().Msgf("\t\tprojectdiscovery.io\n\n") 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/lint-test.yml: -------------------------------------------------------------------------------- 1 | name: 🙏🏻 Lint Test 2 | 3 | on: 4 | pull_request: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | lint: 9 | name: Lint Test 10 | runs-on: ubuntu-latest-16-cores 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | 15 | - name: "Set up Go" 16 | uses: actions/setup-go@v3 17 | with: 18 | go-version: 1.19 19 | 20 | - name: Run golangci-lint 21 | uses: golangci/golangci-lint-action@v3.4.0 22 | with: 23 | version: latest 24 | args: --timeout 5m 25 | working-directory: . 26 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: 🔨 Build Test 2 | on: 3 | pull_request: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | name: Test Builds 9 | runs-on: ubuntu-latest-16-cores 10 | steps: 11 | - name: Set up Go 12 | uses: actions/setup-go@v3 13 | with: 14 | go-version: 1.19 15 | 16 | - name: Check out code 17 | uses: actions/checkout@v3 18 | 19 | - name: Build 20 | run: go build . 21 | working-directory: cmd/asnmap/ 22 | 23 | - name: Example Code Tests 24 | run: go build . 25 | working-directory: examples/ 26 | 27 | - name: Run test 28 | run: go test ./... -------------------------------------------------------------------------------- /libs/resolver.go: -------------------------------------------------------------------------------- 1 | package asnmap 2 | 3 | import ( 4 | "github.com/projectdiscovery/retryabledns" 5 | ) 6 | 7 | var resolvers = []string{"8.8.8.8:53", "8.8.4.4:53"} 8 | var max_retries = 2 9 | 10 | func ResolveDomain(domain string, customresolvers ...string) ([]string, error) { 11 | // it requires a list of resolvers 12 | if len(customresolvers) == 0 { 13 | customresolvers = resolvers 14 | } 15 | dnsClient, _ := retryabledns.New(customresolvers, max_retries) 16 | var list []string 17 | 18 | ips, err := dnsClient.A(domain) 19 | if err != nil { 20 | return nil, err 21 | } 22 | list = append(list, ips.A...) 23 | 24 | ipA4, err := dnsClient.AAAA(domain) 25 | if err != nil { 26 | return nil, err 27 | } 28 | list = append(list, ipA4.AAAA...) 29 | return list, nil 30 | } 31 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | 5 | builds: 6 | - binary: '{{ .ProjectName }}' 7 | main: cmd/asnmap/asnmap.go 8 | 9 | # release options (https://goreleaser.com/customization/release/) 10 | 11 | release: 12 | draft: false 13 | prerelease: auto 14 | 15 | archives: 16 | - format: zip 17 | replacements: 18 | darwin: macOS 19 | 20 | checksum: 21 | algorithm: sha256 22 | 23 | announce: 24 | slack: 25 | enabled: true 26 | channel: '#release' 27 | username: GoReleaser 28 | message_template: 'New Release: {{ .ProjectName }} {{.Tag}} is published! Check it out at {{ .ReleaseURL }}' 29 | 30 | discord: 31 | enabled: true 32 | message_template: '**New Release: {{ .ProjectName }} {{.Tag}}** is published! Check it out at {{ .ReleaseURL }}' -------------------------------------------------------------------------------- /cmd/asnmap/asnmap.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | 7 | "github.com/projectdiscovery/asnmap/runner" 8 | "github.com/projectdiscovery/gologger" 9 | ) 10 | 11 | func main() { 12 | // Parse the command line flags and read config files 13 | options := runner.ParseOptions() 14 | 15 | asnmapRunner, err := runner.New(options) 16 | if err != nil { 17 | gologger.Fatal().Msgf("Could not create runner: %s\n", err) 18 | } 19 | 20 | defer func() { 21 | _ = asnmapRunner.Close() 22 | }() 23 | 24 | // Setup graceful exits 25 | c := make(chan os.Signal, 1) 26 | signal.Notify(c, os.Interrupt) 27 | go func() { 28 | for range c { 29 | gologger.Info().Msgf("CTRL+C pressed: Exiting\n") 30 | // Close should be called explicitly as it doesn't honor 31 | _ = asnmapRunner.Close() 32 | os.Exit(1) 33 | } 34 | }() 35 | 36 | if err := asnmapRunner.Run(); err != nil { 37 | gologger.Fatal().Msgf("%s\n", err) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /libs/types_test.go: -------------------------------------------------------------------------------- 1 | package asnmap 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIdentifyInput(t *testing.T) { 10 | tt := []struct { 11 | name string 12 | input string 13 | expectedOutput InputType 14 | }{ 15 | {"IP", "10.101.101.10", IP}, 16 | {"ASN", "AS14421", ASN}, 17 | {"Org", "PPLINKNET", Org}, 18 | {"Org", "AS", Org}, 19 | {"Org", "AS-CHOOPA", Org}, 20 | {"Top level domain", "google.com", Domain}, 21 | {"Country level domain", "bbc.co.uk", Domain}, 22 | {"Second level domain", "cornell.edu", Domain}, 23 | {"Third level domain", "bigstuff.cornell.edu", Domain}, 24 | {"Fourth level domain", "www.bass.blm.gov", Domain}, 25 | {"Domain with number", "www.99acres.com", Domain}, 26 | } 27 | 28 | for _, tc := range tt { 29 | t.Run(tc.name, func(t *testing.T) { 30 | res := IdentifyInput(tc.input) 31 | require.Equal(t, res, tc.expectedOutput) 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/release-binary.yml: -------------------------------------------------------------------------------- 1 | name: 🎉 Release Binary 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest-16-cores 12 | steps: 13 | - name: "Check out code" 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: "Set up Go" 19 | uses: actions/setup-go@v3 20 | with: 21 | go-version: 1.19 22 | 23 | - name: "Create release on GitHub" 24 | uses: goreleaser/goreleaser-action@v4 25 | with: 26 | args: "release --rm-dist" 27 | version: latest 28 | workdir: . 29 | env: 30 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 31 | SLACK_WEBHOOK: "${{ secrets.RELEASE_SLACK_WEBHOOK }}" 32 | DISCORD_WEBHOOK_ID: "${{ secrets.DISCORD_WEBHOOK_ID }}" 33 | DISCORD_WEBHOOK_TOKEN: "${{ secrets.DISCORD_WEBHOOK_TOKEN }}" 34 | -------------------------------------------------------------------------------- /examples/simple.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | asnmap "github.com/projectdiscovery/asnmap/libs" 8 | ) 9 | 10 | func main() { 11 | client, err := asnmap.NewClient() 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | 16 | items := []string{ 17 | // Query based on ASN 18 | "14421", 19 | // Query based on IP 20 | "210.10.122.10", 21 | // Query based on Organization 22 | "pplinknet", 23 | } 24 | for _, item := range items { 25 | handleInput(client, item) 26 | } 27 | 28 | // Query based on domain 29 | domain := "hackerone.com" 30 | resolvedIps, err := asnmap.ResolveDomain(domain) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | for _, ip := range resolvedIps { 35 | handleInput(client, ip) 36 | } 37 | } 38 | 39 | func handleInput(client *asnmap.Client, item string) { 40 | results, err := client.GetData(item) 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | output, err := asnmap.GetFormattedDataInJson(results) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | if len(output) > 0 { 49 | log.Println(fmt.Sprintf("%s: %s", item, string(output))) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ProjectDiscovery 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 | -------------------------------------------------------------------------------- /.github/workflows/dockerhub-push.yml: -------------------------------------------------------------------------------- 1 | name: 🌥 Docker Push 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["🎉 Release Binary"] 6 | types: 7 | - completed 8 | workflow_dispatch: 9 | 10 | jobs: 11 | docker: 12 | runs-on: ubuntu-latest-16-cores 13 | steps: 14 | - name: Git Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Get Github tag 18 | id: meta 19 | run: | 20 | curl --silent "https://api.github.com/repos/projectdiscovery/asnmap/releases/latest" | jq -r .tag_name | xargs -I {} echo TAG={} >> $GITHUB_OUTPUT 21 | 22 | - name: Set up QEMU 23 | uses: docker/setup-qemu-action@v2 24 | 25 | - name: Set up Docker Buildx 26 | uses: docker/setup-buildx-action@v2 27 | 28 | - name: Login to DockerHub 29 | uses: docker/login-action@v2 30 | with: 31 | username: ${{ secrets.DOCKER_USERNAME }} 32 | password: ${{ secrets.DOCKER_TOKEN }} 33 | 34 | - name: Build and push 35 | uses: docker/build-push-action@v4 36 | with: 37 | context: . 38 | platforms: linux/amd64,linux/arm64 39 | push: true 40 | tags: projectdiscovery/asnmap:latest,projectdiscovery/asnmap:${{ steps.meta.outputs.TAG }} 41 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | 9 | # Maintain dependencies for GitHub Actions 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: "weekly" 14 | target-branch: "dev" 15 | commit-message: 16 | prefix: "chore" 17 | include: "scope" 18 | labels: 19 | - "Type: Maintenance" 20 | 21 | # Maintain dependencies for go modules 22 | - package-ecosystem: "gomod" 23 | directory: "/" 24 | schedule: 25 | interval: "weekly" 26 | target-branch: "dev" 27 | commit-message: 28 | prefix: "chore" 29 | include: "scope" 30 | labels: 31 | - "Type: Maintenance" 32 | 33 | # Maintain dependencies for docker 34 | - package-ecosystem: "docker" 35 | directory: "/" 36 | schedule: 37 | interval: "weekly" 38 | target-branch: "dev" 39 | commit-message: 40 | prefix: "chore" 41 | include: "scope" 42 | labels: 43 | - "Type: Maintenance" 44 | -------------------------------------------------------------------------------- /libs/types.go: -------------------------------------------------------------------------------- 1 | package asnmap 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/asaskevich/govalidator" 7 | iputil "github.com/projectdiscovery/utils/ip" 8 | stringsutil "github.com/projectdiscovery/utils/strings" 9 | ) 10 | 11 | type InputType uint8 12 | 13 | const ( 14 | ASN InputType = iota 15 | ASNID 16 | IP 17 | Org 18 | Domain 19 | Unknown 20 | ) 21 | 22 | var domainRegex = regexp.MustCompile(`^(?i)[a-z0-9-]+(\.[a-z0-9-]+)+\.?$`) 23 | 24 | // checkIfASN checks if the given input is ASN or not, 25 | // its possible to have an org name starting with AS/as prefix. 26 | func checkIfASN(input string) bool { 27 | if len(input) == 0 { 28 | return false 29 | } 30 | hasASNPrefix := stringsutil.HasPrefixI(input, "AS") 31 | if hasASNPrefix { 32 | input = input[2:] 33 | } 34 | return hasASNPrefix && checkIfASNId(input) 35 | } 36 | 37 | func checkIfASNId(input string) bool { 38 | if len(input) == 0 { 39 | return false 40 | } 41 | hasNumericId := input != "" && govalidator.IsNumeric(input) 42 | return hasNumericId 43 | } 44 | 45 | func IdentifyInput(input string) InputType { 46 | switch { 47 | case iputil.IsIP(input): 48 | return IP 49 | case checkIfASN(input): 50 | return ASN 51 | case checkIfASNId(input): 52 | return ASNID 53 | case domainRegex.MatchString(input): 54 | return Domain 55 | default: 56 | return Org 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /libs/resolver_test.go: -------------------------------------------------------------------------------- 1 | package asnmap 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/projectdiscovery/gologger" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestResolveDomain(t *testing.T) { 11 | tt := []struct { 12 | name string 13 | domain string 14 | customresolvers []string 15 | expectedOutput []string 16 | }{ 17 | {"Resolve google.com using default resolvers", "google.com", []string{}, []string{"142.250.183.110"}}, 18 | {"Resolve google.com using custom resolvers", "google.com", []string{"8.8.8.8"}, []string{"142.250.199.142"}}, 19 | {"Resolve random domain name using custom resolvers", "somerandomdomainnamethatisfake.com", []string{"8.8.8.8"}, []string{}}, 20 | } 21 | for _, tc := range tt { 22 | t.Run(tc.name, func(t *testing.T) { 23 | i, err := ResolveDomain(tc.domain, tc.customresolvers...) 24 | require.Nil(t, err) 25 | gologger.Info().Msgf("%v resolve to %v", tc.domain, i) 26 | 27 | // If we are unable to resolve the domain, then ResolveDomain() returns an empty list 28 | // So for some unregistered domain, we will get an empty list. 29 | // Here we are not comparing the exact response for domain as IPs might get change in future. 30 | // Instead we are checking whether we are able to resolve domain to some IP or not. 31 | require.Falsef(t, len(i) == 0 && tc.domain != "somerandomdomainnamethatisfake.com", "Failed to resolve domain for test case: %v", tc.name) 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /runner/output.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "encoding/csv" 5 | "fmt" 6 | "net" 7 | "strings" 8 | 9 | asnmap "github.com/projectdiscovery/asnmap/libs" 10 | iputil "github.com/projectdiscovery/utils/ip" 11 | ) 12 | 13 | var csvHeaders = [][]string{{"timestamp", "input", "as_number", "as_name", "as_country", "as_range"}} 14 | 15 | func (r *Runner) writeToCsv(records [][]string) error { 16 | w := csv.NewWriter(r.options.Output) 17 | w.Comma = '|' 18 | 19 | for _, record := range records { 20 | if err := w.Write(record); err != nil { 21 | return err 22 | } 23 | } 24 | 25 | w.Flush() 26 | 27 | return w.Error() 28 | } 29 | 30 | // filterIPv6 31 | // - DisplayIPv6==true => returns IPv6 + IPv4 32 | // - DisplayIPv6==false => returns IPv4 33 | func (r *Runner) filterIPv6(ipsnet []*net.IPNet) []*net.IPNet { 34 | if r.options.DisplayIPv6 { 35 | // ipv4 + ipv6 36 | return ipsnet 37 | } 38 | 39 | // only ipv4 40 | var filteredIpsNet []*net.IPNet 41 | for _, ipnet := range ipsnet { 42 | value := ipnet.String() 43 | // trim net suffix 44 | if idx := strings.Index(value, "/"); idx >= 0 { 45 | value = value[:idx] 46 | } 47 | if iputil.IsIPv4(value) { 48 | filteredIpsNet = append(filteredIpsNet, ipnet) 49 | } 50 | } 51 | return filteredIpsNet 52 | } 53 | 54 | // writeOutput either to file or to stdout 55 | func (r *Runner) writeOutput(output []*asnmap.Response) error { 56 | if r.options.OnResult != nil { 57 | r.options.OnResult(output) 58 | } 59 | // empty output is ignored 60 | if r.options.Output == nil { 61 | return nil 62 | } 63 | switch { 64 | case r.options.DisplayInJSON: 65 | result, err := asnmap.GetFormattedDataInJson(output) 66 | if err != nil { 67 | return err 68 | } 69 | _, err = fmt.Fprintf(r.options.Output, "%v\n", string(result)) 70 | return err 71 | case r.options.DisplayInCSV: 72 | results, err := asnmap.GetFormattedDataInCSV(output) 73 | if err != nil { 74 | return err 75 | } 76 | return r.writeToCsv(results) 77 | default: 78 | cidrs, err := asnmap.GetCIDR(output) 79 | if err != nil { 80 | return err 81 | } 82 | result := r.filterIPv6(cidrs) 83 | for _, cidr := range result { 84 | _, err := fmt.Fprintf(r.options.Output, "%v\n", cidr) 85 | if err != nil { 86 | return err 87 | } 88 | } 89 | return nil 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /libs/formatter.go: -------------------------------------------------------------------------------- 1 | package asnmap 2 | 3 | import ( 4 | "encoding/json" 5 | "net" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // To model json & csv formatted output 12 | type Result struct { 13 | Timestamp string `json:"timestamp,omitempty" csv:"timestamp"` 14 | Input string `json:"input" csv:"input"` 15 | ASN string `json:"as_number" csv:"as_number"` 16 | ASN_org string `json:"as_name" csv:"as_name"` 17 | AS_country string `json:"as_country" csv:"as_country"` 18 | AS_range []string `json:"as_range" csv:"as_range"` 19 | } 20 | 21 | // To model http response from server 22 | type Response struct { 23 | FirstIp string 24 | LastIp string 25 | Input string 26 | ASN int 27 | Country string 28 | Org string 29 | } 30 | 31 | // attachPrefix func attaches 'AS' prefix to ASN numbers 32 | func attachPrefix(input string) string { 33 | inp := input 34 | if _, err := strconv.Atoi(input); err == nil { 35 | inp = "AS" + input 36 | } 37 | return inp 38 | } 39 | 40 | func convertIPsToStringSlice(ips []*net.IPNet) []string { 41 | var res []string 42 | for _, ip := range ips { 43 | res = append(res, ip.String()) 44 | } 45 | return res 46 | } 47 | 48 | func intializeResult(resp *Response) (*Result, error) { 49 | result := &Result{} 50 | result.Timestamp = time.Now().Local().String() 51 | result.Input = attachPrefix(resp.Input) 52 | result.ASN = attachPrefix(strconv.Itoa(resp.ASN)) 53 | result.ASN_org = resp.Org 54 | result.AS_country = resp.Country 55 | cidrs, err := GetCIDR([]*Response{resp}) 56 | if err != nil { 57 | return nil, err 58 | } 59 | result.AS_range = convertIPsToStringSlice(cidrs) 60 | return result, nil 61 | } 62 | 63 | func prepareFormattedJSON(input *Response) ([]byte, error) { 64 | result, err := intializeResult(input) 65 | if err != nil { 66 | return nil, err 67 | } 68 | return json.Marshal(result) 69 | } 70 | 71 | func prepareFormattedCSV(input *Response) ([]string, error) { 72 | result, err := intializeResult(input) 73 | if err != nil { 74 | return nil, err 75 | } 76 | record := []string{result.Timestamp, result.Input, result.ASN, result.ASN_org, result.AS_country, strings.Join(result.AS_range, ",")} 77 | return record, nil 78 | } 79 | 80 | func GetFormattedDataInJson(output []*Response) ([]byte, error) { 81 | var jsonOutput []byte 82 | for _, res := range output { 83 | json, err := prepareFormattedJSON(res) 84 | if err != nil { 85 | return nil, err 86 | } 87 | jsonOutput = append(jsonOutput, json...) 88 | } 89 | return jsonOutput, nil 90 | } 91 | 92 | func GetFormattedDataInCSV(output []*Response) ([][]string, error) { 93 | records := [][]string{} 94 | for _, res := range output { 95 | record, err := prepareFormattedCSV(res) 96 | if err != nil { 97 | return nil, err 98 | } 99 | records = append(records, record) 100 | } 101 | 102 | return records, nil 103 | } 104 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/projectdiscovery/asnmap 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/projectdiscovery/goflags v0.1.8 7 | github.com/projectdiscovery/gologger v1.1.8 8 | github.com/projectdiscovery/hmap v0.0.10 9 | github.com/projectdiscovery/mapcidr v1.1.0 10 | github.com/projectdiscovery/retryabledns v1.0.21 11 | github.com/stretchr/testify v1.8.2 12 | golang.org/x/net v0.8.0 13 | ) 14 | 15 | require ( 16 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d 17 | github.com/aymerick/douceur v0.2.0 // indirect 18 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect 19 | github.com/davecgh/go-spew v1.1.1 // indirect 20 | github.com/gorilla/css v1.0.0 // indirect 21 | github.com/json-iterator/go v1.1.12 // indirect 22 | github.com/logrusorgru/aurora v2.0.3+incompatible // indirect 23 | github.com/microcosm-cc/bluemonday v1.0.21 // indirect 24 | github.com/miekg/dns v1.1.50 // indirect 25 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 26 | github.com/modern-go/reflect2 v1.0.2 // indirect 27 | github.com/pkg/errors v0.9.1 // indirect 28 | github.com/pmezard/go-difflib v1.0.0 // indirect 29 | github.com/projectdiscovery/blackrock v0.0.0-20221025011524-9e4efe804fb4 // indirect 30 | github.com/projectdiscovery/retryablehttp-go v1.0.10 // indirect 31 | github.com/projectdiscovery/utils v0.0.14 32 | github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect 33 | go.uber.org/atomic v1.10.0 // indirect 34 | go.uber.org/multierr v1.8.0 // indirect 35 | golang.org/x/mod v0.8.0 // indirect 36 | golang.org/x/sys v0.6.0 // indirect 37 | golang.org/x/text v0.8.0 // indirect 38 | golang.org/x/tools v0.6.0 // indirect 39 | gopkg.in/yaml.v3 v3.0.1 // indirect 40 | ) 41 | 42 | require ( 43 | github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect 44 | github.com/akrylysov/pogreb v0.10.1 // indirect 45 | github.com/dsnet/compress v0.0.1 // indirect 46 | github.com/golang/snappy v0.0.4 // indirect 47 | github.com/google/go-cmp v0.5.9 // indirect 48 | github.com/mholt/archiver v3.1.1+incompatible // indirect 49 | github.com/nwaples/rardecode v1.1.0 // indirect 50 | github.com/nxadm/tail v1.4.8 // indirect 51 | github.com/pierrec/lz4 v2.6.0+incompatible // indirect 52 | github.com/syndtr/goleveldb v1.0.0 // indirect 53 | github.com/tidwall/btree v1.4.3 // indirect 54 | github.com/tidwall/buntdb v1.2.10 // indirect 55 | github.com/tidwall/gjson v1.14.3 // indirect 56 | github.com/tidwall/grect v0.1.4 // indirect 57 | github.com/tidwall/match v1.1.1 // indirect 58 | github.com/tidwall/pretty v1.2.0 // indirect 59 | github.com/tidwall/rtred v0.1.2 // indirect 60 | github.com/tidwall/tinyqueue v0.1.1 // indirect 61 | github.com/ulikunitz/xz v0.5.8 // indirect 62 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect 63 | go.etcd.io/bbolt v1.3.7 // indirect 64 | golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect 65 | gopkg.in/djherbis/times.v1 v1.3.0 // indirect 66 | gopkg.in/yaml.v2 v2.4.0 // indirect 67 | ) 68 | -------------------------------------------------------------------------------- /libs/formatter_test.go: -------------------------------------------------------------------------------- 1 | package asnmap 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestGetFormattedDataInJson(t *testing.T) { 11 | tt := []struct { 12 | name string 13 | inputResponse []*Response 14 | expectedOutput *Result 15 | }{ 16 | { 17 | name: "ASN", 18 | inputResponse: []*Response{ 19 | { 20 | FirstIp: "216.101.17.0", 21 | LastIp: "216.101.17.255", 22 | Input: "AS14421", 23 | ASN: 14421, 24 | Country: "US", 25 | Org: "THERAVANCE"}, 26 | }, 27 | expectedOutput: &Result{ 28 | Timestamp: "", 29 | Input: "AS14421", 30 | ASN: "AS14421", 31 | ASN_org: "THERAVANCE", 32 | AS_country: "US", 33 | AS_range: []string{"216.101.17.0/24"}, 34 | }, 35 | }, 36 | } 37 | for _, tc := range tt { 38 | t.Run(tc.name, func(t *testing.T) { 39 | output, err := GetFormattedDataInJson(tc.inputResponse) 40 | require.Nil(t, err) 41 | var actualOutput *Result 42 | err = json.Unmarshal(output, &actualOutput) 43 | require.Nil(t, err) 44 | 45 | // Ignoring timestamp from acutal output 46 | actualOutput.Timestamp = "" 47 | require.Equal(t, actualOutput, tc.expectedOutput) 48 | }) 49 | } 50 | } 51 | 52 | func TestGetFormattedDataInCSV(t *testing.T) { 53 | tt := []struct { 54 | name string 55 | inputResponse []*Response 56 | expectedOutput [][]string 57 | }{ 58 | { 59 | name: "ASN", 60 | inputResponse: []*Response{ 61 | { 62 | FirstIp: "216.101.17.0", 63 | LastIp: "216.101.17.255", 64 | Input: "14421", 65 | ASN: 14421, 66 | Country: "US", 67 | Org: "THERAVANCE", 68 | }, 69 | }, 70 | expectedOutput: [][]string{ 71 | {"", "AS14421", "AS14421", "THERAVANCE", "US", "216.101.17.0/24"}, 72 | }, 73 | }, 74 | { 75 | name: "Org", 76 | inputResponse: []*Response{ 77 | { 78 | FirstIp: "45.239.52.0", 79 | LastIp: "45.239.55.255", 80 | Input: "pplinknet", 81 | ASN: 268353, 82 | Country: "BR", 83 | Org: "PPLINKNET SERVICOS DE COMUNICACAO LTDA - ME", 84 | }, 85 | { 86 | FirstIp: "2804:4fd8::", 87 | LastIp: "2804:4fd8:ffff:ffff:ffff:ffff:ffff:ffff", 88 | Input: "pplinknet", 89 | ASN: 268353, 90 | Country: "BR", 91 | Org: "PPLINKNET SERVICOS DE COMUNICACAO LTDA - ME", 92 | }, 93 | }, 94 | expectedOutput: [][]string{ 95 | {"", "pplinknet", "AS268353", "PPLINKNET SERVICOS DE COMUNICACAO LTDA - ME", "BR", "45.239.52.0/22"}, 96 | {"", "pplinknet", "AS268353", "PPLINKNET SERVICOS DE COMUNICACAO LTDA - ME", "BR", "2804:4fd8::/32"}, 97 | }, 98 | }, 99 | { 100 | name: "IP", 101 | inputResponse: []*Response{ 102 | { 103 | FirstIp: "104.16.0.0", 104 | LastIp: "104.21.127.255", 105 | Input: "104.16.99.52", 106 | ASN: 13335, 107 | Country: "US", 108 | Org: "CLOUDFLARENET", 109 | }, 110 | }, 111 | expectedOutput: [][]string{ 112 | {"", "104.16.99.52", "AS13335", "CLOUDFLARENET", "US", "104.16.0.0/14,104.20.0.0/16,104.21.0.0/17"}, 113 | }, 114 | }, 115 | } 116 | for _, tc := range tt { 117 | t.Run(tc.name, func(t *testing.T) { 118 | actualOutput, err := GetFormattedDataInCSV(tc.inputResponse) 119 | require.Nil(t, err) 120 | 121 | // Ignoring timestamp from acutal output 122 | for _, output := range actualOutput { 123 | output[0] = "" 124 | } 125 | require.Equal(t, actualOutput, tc.expectedOutput) 126 | }) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /runner/runner_test.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "testing" 5 | 6 | asnmap "github.com/projectdiscovery/asnmap/libs" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestRunner(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | options *Options 15 | expectedOutput []*asnmap.Response 16 | }{ 17 | { 18 | name: "IP", 19 | options: &Options{ 20 | Ip: []string{"104.16.99.52"}, 21 | }, 22 | expectedOutput: []*asnmap.Response{ 23 | { 24 | FirstIp: "104.16.0.0", 25 | LastIp: "104.21.127.255", 26 | Input: "104.16.99.52", 27 | ASN: 13335, 28 | Country: "US", 29 | Org: "CLOUDFLARENET"}, 30 | }, 31 | }, 32 | { 33 | name: "ASN", 34 | options: &Options{ 35 | Asn: []string{"AS14421"}, 36 | }, 37 | expectedOutput: []*asnmap.Response{ 38 | { 39 | FirstIp: "216.101.17.0", 40 | LastIp: "216.101.17.255", 41 | Input: "14421", 42 | ASN: 14421, 43 | Country: "US", 44 | Org: "THERAVANCE"}, 45 | }, 46 | }, 47 | { 48 | name: "Org", 49 | options: &Options{ 50 | Org: []string{"PPLINK"}, 51 | }, 52 | expectedOutput: []*asnmap.Response{ 53 | { 54 | FirstIp: "45.239.52.0", 55 | LastIp: "45.239.55.255", 56 | Input: "PPLINK", 57 | ASN: 268353, 58 | Country: "BR", 59 | Org: "PPLINKNET SERVICOS DE COMUNICACAO LTDA - ME"}, 60 | { 61 | FirstIp: "2804:4fd8::", 62 | LastIp: "2804:4fd8:ffff:ffff:ffff:ffff:ffff:ffff", 63 | Input: "PPLINK", 64 | ASN: 268353, 65 | Country: "BR", 66 | Org: "PPLINKNET SERVICOS DE COMUNICACAO LTDA - ME"}, 67 | }, 68 | }, 69 | } 70 | for _, tt := range tests { 71 | t.Run(tt.name, func(t *testing.T) { 72 | tt.options.OnResult = func(o []*asnmap.Response) { 73 | require.Equal(t, o, tt.expectedOutput) 74 | } 75 | r, err := New(tt.options) 76 | require.Nil(t, err) 77 | 78 | err = r.prepareInput() 79 | require.Nil(t, err) 80 | 81 | err = r.process() 82 | require.Nil(t, err) 83 | 84 | err = r.Close() 85 | require.Nil(t, err) 86 | }) 87 | } 88 | } 89 | 90 | func TestProcessForDomainInput(t *testing.T) { 91 | tests := []struct { 92 | name string 93 | inputchan chan interface{} 94 | outputchan chan []*asnmap.Response 95 | options *Options 96 | expectedOutput *asnmap.Response 97 | }{ 98 | { 99 | name: "Domain", 100 | inputchan: make(chan interface{}), 101 | outputchan: make(chan []*asnmap.Response), 102 | options: &Options{ 103 | Domain: []string{"google.com"}, 104 | }, 105 | expectedOutput: &asnmap.Response{ 106 | FirstIp: "142.250.0.0", 107 | LastIp: "142.250.82.255", 108 | Input: "google.com", 109 | ASN: 15169, 110 | Country: "US", 111 | Org: "GOOGLE", 112 | }, 113 | }, 114 | } 115 | 116 | for _, tt := range tests { 117 | t.Run(tt.name, func(t *testing.T) { 118 | tt.options.OnResult = func(o []*asnmap.Response) { 119 | x := compareResponse(o, tt.expectedOutput) 120 | // // Expecting true from comparision 121 | require.True(t, x) 122 | } 123 | 124 | r, err := New(tt.options) 125 | require.Nil(t, err) 126 | 127 | err = r.prepareInput() 128 | require.Nil(t, err) 129 | 130 | err = r.process() 131 | require.Nil(t, err) 132 | 133 | err = r.Close() 134 | require.Nil(t, err) 135 | }) 136 | } 137 | } 138 | 139 | // compareResponse compares ASN & ORG against given domain with expected output's ASN & ORG 140 | // Have excluded IPs for now as they might change in future. 141 | func compareResponse(respA []*asnmap.Response, respB *asnmap.Response) bool { 142 | for ind := range respA { 143 | if respA[ind].ASN == respB.ASN && respA[ind].Org == respB.Org { 144 | return true 145 | } 146 | } 147 | 148 | return false 149 | } 150 | -------------------------------------------------------------------------------- /runner/runner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "bufio" 5 | "encoding/csv" 6 | "fmt" 7 | "os" 8 | "strings" 9 | 10 | asnmap "github.com/projectdiscovery/asnmap/libs" 11 | "github.com/projectdiscovery/gologger" 12 | "github.com/projectdiscovery/hmap/store/hybrid" 13 | fileutil "github.com/projectdiscovery/utils/file" 14 | ) 15 | 16 | type Runner struct { 17 | options *Options 18 | hm *hybrid.HybridMap 19 | client *asnmap.Client 20 | } 21 | 22 | func New(options *Options) (*Runner, error) { 23 | client, err := asnmap.NewClient() 24 | if err != nil { 25 | return nil, err 26 | } 27 | return &Runner{options: options, client: client}, nil 28 | } 29 | 30 | func (r *Runner) Close() error { 31 | if r.hm != nil { 32 | err := r.hm.Close() 33 | if err != nil { 34 | return err 35 | } 36 | } 37 | 38 | return nil 39 | } 40 | 41 | func (r *Runner) Run() error { 42 | if len(r.options.Proxy) > 0 { 43 | if proxyURL, err := r.client.SetProxy(r.options.Proxy); err != nil { 44 | return fmt.Errorf("Could not set proxy: %s", err) 45 | } else { 46 | gologger.Info().Msgf("Using %s proxy %s", proxyURL.Scheme, proxyURL.String()) 47 | } 48 | } 49 | 50 | if r.options.DisplayInCSV { 51 | if r.options.OutputFile != "" { 52 | file, err := os.Create(r.options.OutputFile) 53 | if err != nil { 54 | return err 55 | } 56 | r.options.Output = file 57 | } 58 | w := csv.NewWriter(r.options.Output) 59 | w.Comma = '|' 60 | 61 | for _, record := range csvHeaders { 62 | if err := w.Write(record); err != nil { 63 | return err 64 | } 65 | } 66 | w.Flush() 67 | } 68 | 69 | if err := r.prepareInput(); err != nil { 70 | return err 71 | } 72 | 73 | return r.process() 74 | } 75 | 76 | // Process Function makes request to client returns response 77 | func (r *Runner) process() error { 78 | var errProcess error 79 | r.hm.Scan(func(key, _ []byte) error { 80 | item := string(key) 81 | switch asnmap.IdentifyInput(item) { 82 | case asnmap.Domain: 83 | resolvedIps, err := asnmap.ResolveDomain(item, r.options.Resolvers...) 84 | if err != nil { 85 | errProcess = err 86 | return err 87 | } 88 | 89 | if len(resolvedIps) == 0 { 90 | gologger.Verbose().Msgf("No records found for %v", item) 91 | return nil 92 | } 93 | 94 | for _, resolvedIp := range resolvedIps { 95 | ls, err := r.client.GetData(resolvedIp) 96 | if err != nil { 97 | errProcess = err 98 | return err 99 | } 100 | if err := r.writeOutput(ls); err != nil { 101 | errProcess = err 102 | return err 103 | } 104 | } 105 | 106 | default: 107 | ls, err := r.client.GetData(item) 108 | if err != nil { 109 | errProcess = err 110 | return err 111 | } 112 | if len(ls) == 0 { 113 | gologger.Verbose().Msgf("No records found for %v", item) 114 | return nil 115 | } 116 | if err := r.writeOutput(ls); err != nil { 117 | errProcess = err 118 | return err 119 | } 120 | } 121 | 122 | return nil 123 | }) 124 | return errProcess 125 | } 126 | 127 | func (r *Runner) setItem(v string) { 128 | item := strings.TrimSpace(v) 129 | if item != "" { 130 | _ = r.hm.Set(item, nil) 131 | } 132 | } 133 | 134 | func (r *Runner) prepareInput() error { 135 | var err error 136 | r.hm, err = hybrid.New(hybrid.DefaultDiskOptions) 137 | if err != nil { 138 | return err 139 | } 140 | if fileutil.HasStdin() { 141 | scanner := bufio.NewScanner(os.Stdin) 142 | for scanner.Scan() { 143 | r.setItem(scanner.Text()) 144 | } 145 | } 146 | 147 | for _, item := range r.options.FileInput { 148 | r.setItem(item) 149 | } 150 | 151 | for _, item := range r.options.Asn { 152 | r.setItem(item) 153 | } 154 | 155 | for _, item := range r.options.Ip { 156 | r.setItem(item) 157 | } 158 | 159 | for _, item := range r.options.Domain { 160 | r.setItem(item) 161 | } 162 | 163 | for _, item := range r.options.Org { 164 | r.setItem(item) 165 | } 166 | 167 | return nil 168 | } 169 | -------------------------------------------------------------------------------- /libs/client_test.go: -------------------------------------------------------------------------------- 1 | package asnmap 2 | 3 | import ( 4 | "github.com/stretchr/testify/require" 5 | 6 | "testing" 7 | ) 8 | 9 | func TestGetASNFromIP(t *testing.T) { 10 | client, err := NewClient() 11 | require.Nil(t, err) 12 | 13 | tt := []struct { 14 | name string 15 | ip string 16 | result []*Response 17 | }{ 18 | {"found", "100.19.12.21", []*Response{{FirstIp: "", LastIp: "", Input: "100.19.12.21", ASN: 701, Country: "US", Org: "UUNET"}}}, 19 | {"not found", "255.100.100.100", []*Response{}}, 20 | } 21 | 22 | for _, tc := range tt { 23 | t.Run(tc.name, func(t *testing.T) { 24 | i, err := client.GetData(tc.ip) 25 | require.Nil(t, err) 26 | // // Expecting true from comparision 27 | for _, result := range tc.result { 28 | x := compareResponse(i, result) 29 | require.True(t, x) 30 | } 31 | }) 32 | } 33 | } 34 | 35 | func TestGetIPFromASN(t *testing.T) { 36 | client, err := NewClient() 37 | require.Nil(t, err) 38 | 39 | tt := []struct { 40 | name string 41 | asn string 42 | result []*Response 43 | }{ 44 | { 45 | "zero match", "1123", []*Response{}, 46 | }, 47 | { 48 | "single match", "14421", []*Response{{ 49 | FirstIp: "216.101.17.0", 50 | LastIp: "216.101.17.255", 51 | Input: "14421", 52 | ASN: 14421, 53 | Country: "US", 54 | Org: "THERAVANCE", 55 | }}, 56 | }, 57 | { 58 | "multi match", "7712", []*Response{ 59 | { 60 | FirstIp: "118.67.200.0", 61 | LastIp: "118.67.202.255", 62 | Input: "7712", 63 | ASN: 7712, 64 | Country: "KH", 65 | Org: "SABAY Sabay Digital Cambodia", 66 | }, 67 | { 68 | FirstIp: "118.67.203.0", 69 | LastIp: "118.67.207.255", 70 | Input: "7712", 71 | ASN: 7712, 72 | Country: "KH", 73 | Org: "SABAY Sabay Digital Cambodia", 74 | }, 75 | { 76 | FirstIp: "2405:aa00::", 77 | LastIp: "2405:aa00:ffff:ffff:ffff:ffff:ffff:ffff", 78 | Input: "7712", 79 | ASN: 7712, 80 | Country: "KH", 81 | Org: "SABAY Sabay Digital Cambodia", 82 | }}, 83 | }, 84 | } 85 | 86 | for _, tc := range tt { 87 | t.Run(tc.name, func(t *testing.T) { 88 | i, err := client.GetData(tc.asn) 89 | require.Nil(t, err) 90 | for _, result := range tc.result { 91 | x := compareResponse(i, result) 92 | require.True(t, x) 93 | } 94 | }) 95 | } 96 | } 97 | 98 | func TestGetASNFromOrg(t *testing.T) { 99 | client, err := NewClient() 100 | require.Nil(t, err) 101 | 102 | tt := []struct { 103 | name string 104 | org string 105 | result []*Response 106 | }{ 107 | {"not found", "RANDOM_TEXT", []*Response{}}, 108 | {"regex match", "PPLINKNET*", []*Response{ 109 | { 110 | FirstIp: "45.239.52.0", 111 | LastIp: "45.239.55.255", 112 | Input: "PPLINKNET", 113 | ASN: 268353, 114 | Country: "BR", 115 | Org: "PPLINKNET SERVICOS DE COMUNICACAO LTDA - ME"}, 116 | { 117 | FirstIp: "2804:4fd8::", 118 | LastIp: "2804:4fd8:ffff:ffff:ffff:ffff:ffff:ffff", 119 | Input: "PPLINKNET", 120 | ASN: 268353, 121 | Country: "BR", 122 | Org: "PPLINKNET SERVICOS DE COMUNICACAO LTDA - ME"}, 123 | }}, 124 | {"exact match", "PPLINKNET", []*Response{}}, 125 | } 126 | 127 | for _, tc := range tt { 128 | t.Run(tc.name, func(t *testing.T) { 129 | i, err := client.GetData(tc.org) 130 | require.Nil(t, err) 131 | // // Expecting true from comparision 132 | for _, result := range tc.result { 133 | x := compareResponse(i, result) 134 | require.True(t, x) 135 | } 136 | }) 137 | } 138 | } 139 | 140 | // compareResponse compares ASN & ORG against given domain with expected output's ASN & ORG 141 | // Have excluded IPs for now as they might change in future. 142 | func compareResponse(respA []*Response, respB *Response) bool { 143 | compareResult := false 144 | 145 | for ind := range respA { 146 | if respA[ind].ASN == respB.ASN && respA[ind].Org == respB.Org { 147 | compareResult = true 148 | } 149 | } 150 | return compareResult 151 | } 152 | -------------------------------------------------------------------------------- /libs/client.go: -------------------------------------------------------------------------------- 1 | package asnmap 2 | 3 | import ( 4 | "bufio" 5 | "crypto/tls" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "net" 11 | "net/http" 12 | url "net/url" 13 | "os" 14 | "strings" 15 | "time" 16 | 17 | fileutil "github.com/projectdiscovery/utils/file" 18 | stringsutil "github.com/projectdiscovery/utils/strings" 19 | "golang.org/x/net/proxy" 20 | ) 21 | 22 | const serverURL = "https://api.asnmap.sh/" 23 | 24 | type Client struct { 25 | url *url.URL 26 | http http.Client 27 | } 28 | 29 | // generatefullURL creates the complete URL with path, scheme, and host 30 | func generateFullURL(host string) (*url.URL, error) { 31 | rawURL, err := url.Parse(host) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | if !stringsutil.EqualFoldAny(rawURL.Scheme, "http", "https") { 37 | return nil, errors.New("Host should start with http or https.") 38 | } 39 | 40 | rawURL.Path = "api/v1/asnmap" 41 | return rawURL, nil 42 | } 43 | 44 | // If SERVER_URL env provider use that else use serverURL constant 45 | func getURL() (*url.URL, error) { 46 | url := os.Getenv("SERVER_URL") 47 | if url == "" { 48 | url = serverURL 49 | } 50 | return generateFullURL(url) 51 | } 52 | 53 | func NewClient() (*Client, error) { 54 | URL, err := getURL() 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | // ignore expired SSL certificates 60 | transCfg := &http.Transport{ 61 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 62 | } 63 | 64 | client := Client{ 65 | url: URL, 66 | http: http.Client{Transport: transCfg}, 67 | } 68 | return &client, nil 69 | } 70 | 71 | // SetProxy adds a proxy to the client 72 | func (c *Client) SetProxy(proxyList []string) (*url.URL, error) { 73 | var ( 74 | proxyUrl *url.URL 75 | err error 76 | ) 77 | for _, p := range proxyList { 78 | switch { 79 | case fileutil.FileExists(p): 80 | proxyUrl, err = c.setProxyFromFile(p) 81 | default: 82 | proxyUrl, err = c.setProxy(p) 83 | } 84 | if err == nil && proxyUrl != nil { 85 | return proxyUrl, nil 86 | } 87 | } 88 | return nil, errors.New("no valid proxy found") 89 | } 90 | 91 | // setProxyFromFile reads the file contents and tries to set the proxy 92 | func (c *Client) setProxyFromFile(fileName string) (*url.URL, error) { 93 | file, err := os.Open(fileName) 94 | if err != nil { 95 | return nil, err 96 | } 97 | defer file.Close() 98 | scanner := bufio.NewScanner(file) 99 | for scanner.Scan() { 100 | proxy := strings.TrimSpace(scanner.Text()) 101 | if proxy == "" { 102 | continue 103 | } 104 | proxyUrl, err := c.setProxy(proxy) 105 | if err == nil && proxyUrl != nil { 106 | return proxyUrl, nil 107 | } 108 | } 109 | return nil, fmt.Errorf("no valid proxy found in file '%s'", fileName) 110 | } 111 | 112 | // setProxy sets a proxy to the client 113 | func (c *Client) setProxy(proxyString string) (*url.URL, error) { 114 | // parse the proxy url string 115 | proxyurl, err := url.Parse(proxyString) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | // try to connect to the proxy 121 | _, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%s", proxyurl.Hostname(), proxyurl.Port()), time.Second*5) 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | switch proxyurl.Scheme { 127 | case "http", "https": 128 | c.http.Transport = &http.Transport{ 129 | Proxy: http.ProxyURL(proxyurl), 130 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 131 | } 132 | return proxyurl, nil 133 | case "socks5": 134 | dialer, err := proxy.SOCKS5("tcp", proxyurl.Host, nil, proxy.Direct) 135 | if err != nil { 136 | return nil, err 137 | } 138 | c.http.Transport = &http.Transport{ 139 | Dial: dialer.Dial, 140 | } 141 | return proxyurl, nil 142 | default: 143 | return nil, fmt.Errorf("invalid proxy scheme: %s", proxyurl.Scheme) 144 | } 145 | } 146 | 147 | func generateRawQuery(query, value string) string { 148 | return query + "=" + value 149 | } 150 | 151 | func (c Client) makeRequest() ([]byte, error) { 152 | req, _ := http.NewRequest(http.MethodGet, c.url.String(), nil) 153 | res, err := c.http.Do(req) 154 | if err != nil { 155 | return nil, err 156 | } 157 | defer res.Body.Close() 158 | 159 | resBody, _ := io.ReadAll(res.Body) 160 | return resBody, nil 161 | } 162 | 163 | func (c Client) GetData(input string) ([]*Response, error) { 164 | inputToStore := input 165 | switch IdentifyInput(input) { 166 | case ASN: 167 | inputToStore = strings.TrimPrefix(strings.ToLower(input), "as") 168 | c.url.RawQuery = generateRawQuery("asn", inputToStore) 169 | case ASNID: 170 | c.url.RawQuery = generateRawQuery("asn", input) 171 | case IP: 172 | c.url.RawQuery = generateRawQuery("ip", input) 173 | case Org: 174 | c.url.RawQuery = generateRawQuery("org", input) 175 | case Unknown: 176 | return nil, errors.New("unknown type") 177 | } 178 | 179 | resp, err := c.makeRequest() 180 | if err != nil { 181 | return nil, err 182 | } 183 | 184 | results := []*Response{} 185 | err = json.Unmarshal(resp, &results) 186 | if err != nil { 187 | return nil, err 188 | } 189 | 190 | // insert original input in all responses 191 | for _, result := range results { 192 | result.Input = inputToStore 193 | } 194 | 195 | return results, nil 196 | } 197 | -------------------------------------------------------------------------------- /runner/options.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "os" 7 | "strings" 8 | 9 | asnmap "github.com/projectdiscovery/asnmap/libs" 10 | "github.com/projectdiscovery/goflags" 11 | "github.com/projectdiscovery/gologger" 12 | "github.com/projectdiscovery/gologger/levels" 13 | fileutil "github.com/projectdiscovery/utils/file" 14 | ) 15 | 16 | type OnResultCallback func([]*asnmap.Response) 17 | 18 | var cfgFile string 19 | 20 | type Options struct { 21 | FileInput goflags.StringSlice 22 | Resolvers goflags.StringSlice 23 | Asn goflags.StringSlice 24 | Domain goflags.StringSlice 25 | Ip goflags.StringSlice 26 | Org goflags.StringSlice 27 | Proxy goflags.StringSlice 28 | OutputFile string 29 | Output io.Writer 30 | DisplayInJSON bool 31 | DisplayInCSV bool 32 | Silent bool 33 | Verbose bool 34 | Version bool 35 | DisplayIPv6 bool 36 | OnResult OnResultCallback 37 | } 38 | 39 | // configureOutput configures the output on the screen 40 | func (options *Options) configureOutput() { 41 | if options.Silent { 42 | gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent) 43 | } else if options.Verbose { 44 | gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose) 45 | } 46 | } 47 | 48 | // validateOptions validates different command line option combinations 49 | func (options *Options) validateOptions() error { 50 | if options.Verbose && options.Silent { 51 | return errors.New("verbose and silent can't be used together") 52 | } 53 | 54 | if options.Asn == nil && options.Ip == nil && options.Org == nil && options.Domain == nil && !fileutil.HasStdin() && cfgFile == "" { 55 | return errors.New("no input defined") 56 | } 57 | 58 | if options.Asn != nil && (options.Ip != nil || options.Org != nil || options.Domain != nil) { 59 | return errors.New("Asn and other options like ip, org and domain can't be used together as input to get data") 60 | } else if options.Ip != nil && (options.Asn != nil || options.Org != nil || options.Domain != nil) { 61 | return errors.New("Ip and other options like asn, org and domain can't be used together as input to get data") 62 | } else if options.Org != nil && (options.Asn != nil || options.Ip != nil || options.Domain != nil) { 63 | return errors.New("Org and other options like asn, ip and domain can't be used together as input to get data") 64 | } else if options.Domain != nil && (options.Asn != nil || options.Ip != nil || options.Org != nil) { 65 | return errors.New("Domain and other options like asn, ip and org can't be used together as input to get data") 66 | } 67 | 68 | if options.DisplayInJSON && options.DisplayInCSV { 69 | return errors.New("Can either display in JSON or CSV.") 70 | } 71 | 72 | // validate asn input 73 | if options.Asn != nil { 74 | for _, asn := range options.Asn { 75 | if !strings.HasPrefix(strings.ToUpper(asn), "AS") { 76 | return errors.New("Invalid ASN given. it should start with prefix 'AS', example : AS14421") 77 | } 78 | } 79 | } 80 | return nil 81 | } 82 | 83 | // ParseOptions parses the command line options for application 84 | func ParseOptions() *Options { 85 | options := &Options{} 86 | flagSet := goflags.NewFlagSet() 87 | flagSet.SetDescription(`Go CLI and Library for quickly mapping organization network ranges using ASN information.`) 88 | 89 | // Input 90 | flagSet.CreateGroup("input", "Input", 91 | flagSet.StringSliceVarP(&options.Asn, "asn", "a", nil, "target asn to lookup, example: -a AS5650", goflags.FileNormalizedStringSliceOptions), 92 | flagSet.StringSliceVarP(&options.Ip, "ip", "i", nil, "target ip to lookup, example: -i 100.19.12.21, -i 2a10:ad40:: ", goflags.FileNormalizedStringSliceOptions), 93 | flagSet.StringSliceVarP(&options.Domain, "domain", "d", nil, "target domain to lookup, example: -d google.com, -d facebook.com", goflags.FileNormalizedStringSliceOptions), 94 | flagSet.StringSliceVar(&options.Org, "org", nil, "target organization to lookup, example: -org GOOGLE", goflags.StringSliceOptions), 95 | flagSet.StringSliceVarP(&options.FileInput, "file", "f", nil, "targets to lookup from file", goflags.CommaSeparatedStringSliceOptions), 96 | ) 97 | 98 | flagSet.CreateGroup("configs", "Configurations", 99 | flagSet.StringVar(&cfgFile, "config", "", "path to the asnmap configuration file"), 100 | flagSet.StringSliceVarP(&options.Resolvers, "resolvers", "r", nil, "list of resolvers to use", goflags.FileCommaSeparatedStringSliceOptions), 101 | flagSet.StringSliceVarP(&options.Proxy, "proxy", "p", nil, "list of proxy to use (comma separated or file input)", goflags.FileCommaSeparatedStringSliceOptions), 102 | ) 103 | 104 | // Output 105 | flagSet.CreateGroup("output", "Output", 106 | flagSet.StringVarP(&options.OutputFile, "output", "o", "", "file to write output to"), 107 | flagSet.BoolVarP(&options.DisplayInJSON, "json", "j", false, "display json format output"), 108 | flagSet.BoolVarP(&options.DisplayInCSV, "csv", "c", false, "display csv format output"), 109 | flagSet.BoolVar(&options.DisplayIPv6, "v6", false, "display ipv6 cidr ranges in cli output"), 110 | flagSet.BoolVarP(&options.Verbose, "verbose", "v", false, "display verbose output"), 111 | flagSet.BoolVar(&options.Silent, "silent", false, "display silent output"), 112 | flagSet.BoolVar(&options.Version, "version", false, "show version of the project"), 113 | ) 114 | 115 | if err := flagSet.Parse(); err != nil { 116 | gologger.Fatal().Msgf("%s\n", err) 117 | } 118 | 119 | // Read the inputs and configure the logging 120 | options.configureOutput() 121 | 122 | if options.OutputFile == "" { 123 | options.Output = os.Stdout 124 | } 125 | 126 | if cfgFile != "" { 127 | if err := flagSet.MergeConfigFile(cfgFile); err != nil { 128 | gologger.Fatal().Msgf("Could not read config file.") 129 | } 130 | } 131 | 132 | if options.Version { 133 | gologger.Info().Msgf("Current Version: %s\n", Version) 134 | os.Exit(0) 135 | } 136 | 137 | showBanner() 138 | 139 | if err := options.validateOptions(); err != nil { 140 | gologger.Fatal().Msgf("%s\n", err) 141 | } 142 | 143 | return options 144 | } 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | asnmap 3 |
4 |

5 | 6 |

Go CLI and Library for quickly mapping organization network ranges using ASN information.

7 | 8 |

9 | 10 | 11 | 12 | 13 | 14 |

15 | 16 |

17 | Features • 18 | Installation • 19 | Usage • 20 | Running asnmap • 21 | Join Discord 22 |

23 | 24 | 25 | **** 26 | 27 | 28 | 29 | # Features 30 | 31 | ![image](https://user-images.githubusercontent.com/8293321/192092220-5d734305-fd3e-43fb-919a-91ff5296dfd2.png) 32 | 33 | 34 | - **ASN to CIDR** Lookup 35 | - **ORG to CIDR** Lookup 36 | - **DNS to CIDR** Lookup 37 | - **IP to CIDR** Lookup 38 | - **ASN/DNS/IP/ORG** input 39 | - **JSON/CSV/TEXT** output 40 | - STD **IN/OUT** support 41 | 42 | ## Installation 43 | 44 | asnmap requires **Go 1.18** to install successfully. To install, just run the below command or download pre-compiled binary from [release page](https://github.com/projectdiscovery/asnmap/releases). 45 | 46 | ```console 47 | go install github.com/projectdiscovery/asnmap/cmd/asnmap@latest 48 | ``` 49 | 50 | ## Usage 51 | 52 | ```console 53 | asnmap -h 54 | ``` 55 | 56 | This will display help for the tool. Here are all the flag it supports. 57 | 58 | ```console 59 | Usage: 60 | ./asnmap [flags] 61 | 62 | Flags: 63 | INPUT: 64 | -a, -asn string[] target asn to lookup, example: -a AS5650 65 | -i, -ip string[] target ip to lookup, example: -i 100.19.12.21, -i 2a10:ad40:: 66 | -d, -domain string[] target domain to lookup, example: -d google.com, -d facebook.com 67 | -org string[] target organization to lookup, example: -org GOOGLE 68 | -f, -file string[] targets to lookup from file 69 | 70 | CONFIGURATIONS: 71 | -config string path to the asnmap configuration file 72 | -r, -resolvers string[] list of resolvers to use 73 | 74 | OUTPUT: 75 | -o, -output string file to write output to 76 | -j, -json display json format output 77 | -c, -csv display csv format output 78 | -v6 display ipv6 cidr ranges in cli output 79 | -v, -verbose display verbose output 80 | -silent display silent output 81 | -version show version of the project 82 | ``` 83 | 84 | ## Running asnmap 85 | 86 | ### Input for asnmap 87 | 88 | **asnmap** support multiple inputs including **ASN**, **IP**, **DNS** and **ORG** name to query ASN/CIDR information. 89 | 90 | 91 | | Input | ASN | DNS | IP | ORG | 92 | | ------- | --------- | ------------- | --------------- | -------- | 93 | | Example | `AS14421` | `example.com` | `93.184.216.34` | `GOOGLE` | 94 | 95 | 96 | 97 | Input can be provided either using specific options or STDIN which accepts all the supported formats. Single, multiple (comma-separated) and file input is supported for all the options. 98 | 99 | ```console 100 | echo GOOGLE | ./asnmap -silent 101 | ``` 102 | 103 | Example input for asnmap: 104 | 105 | ```console 106 | asnmap -a AS45596 -silent 107 | asnmap -i 100.19.12.21 -silent 108 | asnmap -d hackerone.com -silent 109 | asnmap -o GOOGLE -silent 110 | ``` 111 | 112 | ### Default Run 113 | 114 | **asnmap** by default returns the CIDR range for given input. 115 | 116 | ```console 117 | echo GOOGLE | ./asnmap 118 | 119 | ___ _____ __ 120 | / _ | / __/ |/ /_ _ ___ ____ 121 | / __ |_\ \/ / ' \/ _ / _ \ 122 | /_/ |_/___/_/|_/_/_/_/\_,_/ .__/ 123 | /_/ v0.0.1 124 | projectdiscovery.io 125 | 126 | Use with caution. You are responsible for your actions 127 | Developers assume no liability and are not responsible for any misuse or damage. 128 | 129 | 8.8.4.0/24 130 | 8.8.8.0/24 131 | 8.35.200.0/21 132 | 34.3.3.0/24 133 | 34.4.4.0/24 134 | 34.96.0.0/20 135 | 34.96.32.0/19 136 | 34.96.64.0/18 137 | 34.98.64.0/18 138 | 34.98.136.0/21 139 | 34.98.144.0/21 140 | ``` 141 | ### JSON Output 142 | 143 | **asnmap** by default displays CIDR range, and all the information is always available in JSON format, for automation and post processing using `-json` output is most convenient option to use. 144 | 145 | ```console 146 | echo hackerone.com | ./asnmap -json -silent | jq 147 | ``` 148 | 149 | ```json 150 | { 151 | "timestamp": "2022-09-19 12:14:33.267339314 +0530 IST", 152 | "input": "hackerone.com", 153 | "as_number": "AS13335", 154 | "as_name": "CLOUDFLARENET", 155 | "as_country": "US", 156 | "as_range": [ 157 | "104.16.0.0/14", 158 | "104.20.0.0/16", 159 | "104.21.0.0/17" 160 | ] 161 | } 162 | { 163 | "timestamp": "2022-09-19 12:14:33.457401266 +0530 IST", 164 | "input": "hackerone.com", 165 | "as_number": "AS13335", 166 | "as_name": "CLOUDFLARENET", 167 | "as_country": "US", 168 | "as_range": [ 169 | "2606:4700:8390::/44" 170 | ] 171 | } 172 | ``` 173 | ### CSV Output 174 | 175 | **asnmap** also support csv format output which has all the information just like JSON output 176 | 177 | ```console 178 | echo hackerone.com | ./asnmap -csv -silent 179 | ``` 180 | 181 | ```console 182 | timestamp|input|as_number|as_name|as_country|as_range 183 | 2022-09-19 12:15:04.906664007 +0530 IST|hackerone.com|AS13335|CLOUDFLARENET|US|104.16.0.0/14,104.20.0.0/16,104.21.0.0/17 184 | 2022-09-19 12:15:05.201328136 +0530 IST|hackerone.com|AS13335|CLOUDFLARENET|US|2606:4700:9760::/44 185 | ``` 186 | 187 | ### Using with other PD projects 188 | 189 | Output of asnmap can be directly piped into other projects in workflow accepting stdin as input, for example: 190 | 191 | - `echo AS54115 | asnmap | tlsx` 192 | - `echo AS54115 | asnmap | dnsx -ptr` 193 | - `echo AS54115 | asnmap | naabu -p 443` 194 | - `echo AS54115 | asnmap | naabu -p 443 | httpx` 195 | - `echo AS54115 | asnmap | naabu -p 443 | httpx | nuclei -id tech-detect` 196 | 197 | ## Use asnmap as a library 198 | 199 | Examples of using asnmap from Go code are provided in the [examples](examples/) folder. 200 | 201 | ## Acknowledgements 202 | 203 | - [Frank Denis](https://github.com/jedisct1/) for maintaining free IPtoASN database. 204 | 205 | ----- 206 | 207 |
208 | 209 | **asnmap** is made with ❤️ by the [projectdiscovery](https://projectdiscovery.io) team and distributed under [MIT License](LICENSE). 210 | 211 | 212 | Join Discord 213 | 214 |
215 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 h1:ZbFL+BDfBqegi+/Ssh7im5+aQfBRx6it+kHnC7jaDU8= 2 | github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809/go.mod h1:upgc3Zs45jBDnBT4tVRgRcgm26ABpaP7MoTSdgysca4= 3 | github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w= 4 | github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI= 5 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= 6 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 7 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 8 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 9 | github.com/bits-and-blooms/bitset v1.3.1 h1:y+qrlmq3XsWi+xZqSaueaE8ry8Y127iMxlMfqcK8p0g= 10 | github.com/bits-and-blooms/bloom/v3 v3.3.1 h1:K2+A19bXT8gJR5mU7y+1yW6hsKfNCjcP2uNfLFKncjQ= 11 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ= 12 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4= 13 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 16 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= 18 | github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= 19 | github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= 20 | github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= 21 | github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= 22 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 23 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 24 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 25 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 26 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 27 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 28 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 29 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 30 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 31 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 32 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 33 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 34 | github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= 35 | github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= 36 | github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= 37 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 38 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 39 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 40 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 41 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 42 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 43 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 44 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 45 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 46 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 47 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 48 | github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= 49 | github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 50 | github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU= 51 | github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU= 52 | github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg= 53 | github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= 54 | github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= 55 | github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= 56 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 57 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 58 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 59 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 60 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 61 | github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= 62 | github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= 63 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 64 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 65 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 66 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 67 | github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= 68 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 69 | github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= 70 | github.com/pierrec/lz4 v2.6.0+incompatible h1:Ix9yFKn1nSPBLFl/yZknTp8TU5G4Ps0JDmguYK6iH1A= 71 | github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 72 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 73 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 74 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 75 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 76 | github.com/projectdiscovery/blackrock v0.0.0-20221025011524-9e4efe804fb4 h1:EsrQ/zkotVodSJLOch3pV/UYt1vQcwyIs5HX0sm1ljE= 77 | github.com/projectdiscovery/blackrock v0.0.0-20221025011524-9e4efe804fb4/go.mod h1:5tNGQP9kOfW+X5+40pZP8aqPYLHs45nJkFaSHLxdeH8= 78 | github.com/projectdiscovery/goflags v0.1.8 h1:Urhm2Isq2BdRt8h4h062lHKYXO65RHRjGTDSkUwex/g= 79 | github.com/projectdiscovery/goflags v0.1.8/go.mod h1:Yxi9tclgwGczzDU65ntrwaIql5cXeTvW5j2WxFuF+Jk= 80 | github.com/projectdiscovery/gologger v1.1.8 h1:CFlCzGlqAhPqWIrAXBt1OVh5jkMs1qgoR/z4xhdzLNE= 81 | github.com/projectdiscovery/gologger v1.1.8/go.mod h1:bNyVaC1U/NpJtFkJltcesn01NR3K8Hg6RsLVce6yvrw= 82 | github.com/projectdiscovery/hmap v0.0.10 h1:O6ALGW3BK+FmknLXW7ENwQevLs+faRJuoRbDtakZZus= 83 | github.com/projectdiscovery/hmap v0.0.10/go.mod h1:xdtyejCgl5LJW7yz7nf/ut32tWuV/l7FjUzItiCtJIg= 84 | github.com/projectdiscovery/mapcidr v1.1.0 h1:Yeb+CGVsRYvHmZ9YSHb9iy4tzY9YuOm3oTFX/xzGhVU= 85 | github.com/projectdiscovery/mapcidr v1.1.0/go.mod h1:hck0bWXka5ZkUaBG+TWt99bzLy+4hAg9oANhEmm3GNs= 86 | github.com/projectdiscovery/retryabledns v1.0.21 h1:vOpPQR1q8Z824uoA8JXCI/RyvDAssPeD68Onz9hP/ds= 87 | github.com/projectdiscovery/retryabledns v1.0.21/go.mod h1:6oTPKMRlKZ7lIIEzTH723K6RvNRjmm6fe9br4Dom3UI= 88 | github.com/projectdiscovery/retryablehttp-go v1.0.10 h1:A8tsJZhzpijgnvHT41JNrhiXtn8F8s7TWMC5ZxLV/Cg= 89 | github.com/projectdiscovery/retryablehttp-go v1.0.10/go.mod h1:a5bmSbaxgHvC0P80csOymMOwKaJirMnsS6otRUH/vcU= 90 | github.com/projectdiscovery/utils v0.0.14 h1:ZtyZu0stPQ/qQSw8hsB0pQll2mLOkuB4d08J3ZdbZPw= 91 | github.com/projectdiscovery/utils v0.0.14/go.mod h1:2CyxZXcx62NUiGJZZam23CpphqXy3kaomE9uvgHgkEo= 92 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 93 | github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= 94 | github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= 95 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 96 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 97 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 98 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 99 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 100 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 101 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 102 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 103 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 104 | github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= 105 | github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= 106 | github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI= 107 | github.com/tidwall/btree v1.4.3 h1:Lf5U/66bk0ftNppOBjVoy/AIPBrLMkheBp4NnSNiYOo= 108 | github.com/tidwall/btree v1.4.3/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE= 109 | github.com/tidwall/buntdb v1.2.10 h1:U/ebfkmYPBnyiNZIirUiWFcxA/mgzjbKlyPynFsPtyM= 110 | github.com/tidwall/buntdb v1.2.10/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU= 111 | github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 112 | github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= 113 | github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 114 | github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg= 115 | github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q= 116 | github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8= 117 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 118 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 119 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= 120 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 121 | github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8= 122 | github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ= 123 | github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE= 124 | github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= 125 | github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= 126 | github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 127 | github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= 128 | github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 129 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= 130 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= 131 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 132 | go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= 133 | go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= 134 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 135 | go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= 136 | go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 137 | go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= 138 | go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= 139 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 140 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 141 | golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= 142 | golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 143 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 144 | golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= 145 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 146 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 147 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 148 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 149 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 150 | golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 151 | golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= 152 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 153 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 154 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 155 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 156 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 157 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 158 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 159 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 160 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 161 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 162 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 163 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 164 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 165 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 166 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 167 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 168 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 169 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 170 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 171 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 172 | golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= 173 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 174 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 175 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 176 | golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 177 | golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= 178 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 179 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 180 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 181 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 182 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 183 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 184 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 185 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 186 | gopkg.in/djherbis/times.v1 v1.3.0 h1:uxMS4iMtH6Pwsxog094W0FYldiNnfY/xba00vq6C2+o= 187 | gopkg.in/djherbis/times.v1 v1.3.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8= 188 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 189 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 190 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 191 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 192 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 193 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 194 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 195 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 196 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 197 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 198 | --------------------------------------------------------------------------------