├── .all-contributorsrc ├── .github ├── dependabot.yml └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .goreleaser.yaml ├── .octocov.yml ├── LICENSE ├── README.md ├── cmd └── root.go ├── go.mod ├── go.sum ├── logo.png ├── main.go └── whris ├── whris.go └── whris_test.go /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "dim13", 10 | "name": "Dimitri Sokolyuk", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/4006042?v=4", 12 | "profile": "https://www.dim13.org", 13 | "contributions": [ 14 | "ideas" 15 | ] 16 | }, 17 | { 18 | "login": "mattn", 19 | "name": "mattn", 20 | "avatar_url": "https://avatars.githubusercontent.com/u/10111?v=4", 21 | "profile": "https://mattn.kaoriya.net/", 22 | "contributions": [ 23 | "ideas" 24 | ] 25 | }, 26 | { 27 | "login": "Code-Hex", 28 | "name": "Kei Kamikawa", 29 | "avatar_url": "https://avatars.githubusercontent.com/u/6500104?v=4", 30 | "profile": "https://codehex.dev", 31 | "contributions": [ 32 | "code" 33 | ] 34 | }, 35 | { 36 | "login": "koyashiro", 37 | "name": "koyashiro", 38 | "avatar_url": "https://avatars.githubusercontent.com/u/6698252?v=4", 39 | "profile": "https://koyashi.ro", 40 | "contributions": [ 41 | "doc" 42 | ] 43 | }, 44 | { 45 | "login": "noborus", 46 | "name": "Noboru Saito", 47 | "avatar_url": "https://avatars.githubusercontent.com/u/2296563?v=4", 48 | "profile": "https://noborus.github.io/", 49 | "contributions": [ 50 | "code" 51 | ] 52 | }, 53 | { 54 | "login": "Ben131-Go", 55 | "name": "Ben131-Go", 56 | "avatar_url": "https://avatars.githubusercontent.com/u/123849733?v=4", 57 | "profile": "https://github.com/Ben131-Go", 58 | "contributions": [ 59 | "bug" 60 | ] 61 | } 62 | ], 63 | "contributorsPerLine": 7, 64 | "projectName": "whris", 65 | "projectOwner": "harakeishi", 66 | "repoType": "github", 67 | "repoHost": "https://github.com", 68 | "skipCi": true, 69 | "commitConvention": "angular" 70 | } 71 | -------------------------------------------------------------------------------- /.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://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - 13 | name: Checkout 14 | uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - 18 | name: Set up Go 19 | uses: actions/setup-go@v2 20 | with: 21 | go-version: 1.16 22 | - 23 | name: Create tag 24 | id: create-tag 25 | run: | 26 | echo 'deb [trusted=yes] https://apt.fury.io/caarlos0/ /' | sudo tee /etc/apt/sources.list.d/caarlos0.list 27 | sudo apt update 28 | sudo apt install svu 29 | echo "::set-output name=Version::$(svu p)" 30 | git tag "$(svu p)" 31 | git push --tags 32 | - 33 | name: Run GoReleaser 34 | uses: goreleaser/goreleaser-action@v3 35 | with: 36 | distribution: goreleaser 37 | version: latest 38 | args: release --rm-dist 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.TOKEN }} 41 | Version: ${{ steps.create-tag.outputs.Version }} 42 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | jobs: 9 | golang-test: 10 | strategy: 11 | matrix: 12 | go-version: [1.17.x] 13 | os: [ubuntu-latest] 14 | name: lint 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: golangci-lint 19 | uses: golangci/golangci-lint-action@v2 20 | with: 21 | version: latest 22 | - name: testing 23 | run: go test ./... -coverprofile=coverage.out 24 | - name: create report 25 | uses: k1LoW/octocov-action@v0 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | dist/ 3 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sensible defaults. 2 | # Make sure to check the documentation at https://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod tidy 7 | builds: 8 | - env: 9 | - CGO_ENABLED=0 10 | goos: 11 | - linux 12 | - windows 13 | - darwin 14 | ignore: 15 | - goos: windows 16 | goarch: arm64 17 | ldflags: 18 | - -s -w -X github.com/harakeishi/curver.Version={{.Version}} 19 | archives: 20 | - replacements: 21 | darwin: Darwin 22 | linux: Linux 23 | windows: Windows 24 | 386: i386 25 | amd64: x86_64 26 | changelog: 27 | sort: asc 28 | filters: 29 | exclude: 30 | - '^docs:' 31 | - '^test:' 32 | -------------------------------------------------------------------------------- /.octocov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | paths: 3 | - coverage.out 4 | codeToTestRatio: 5 | code: 6 | - '**/*.go' 7 | - '!**/*_test.go' 8 | test: 9 | - '**/*_test.go' 10 | testExecutionTime: 11 | if: true 12 | comment: 13 | if: is_pull_request 14 | report: 15 | if: is_default_branch 16 | datastores: 17 | - artifact://${GITHUB_REPOSITORY} 18 | diff: 19 | datastores: 20 | - artifact://${GITHUB_REPOSITORY} 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 harakeishi. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](logo.png) 2 | 3 | [![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors-) 4 | 5 | [![GitHub release](https://img.shields.io/github/release/harakeishi/whris.svg)](https://github.com/harakeishi/whris/releases) [![Go Report Card](https://goreportcard.com/badge/github.com/harakeishi/whris)](https://goreportcard.com/report/github.com/harakeishi/whris) [![Test](https://github.com/harakeishi/whris/actions/workflows/test.yml/badge.svg)](https://github.com/harakeishi/whris/actions/workflows/test.yml) 6 | 7 | "whris" is Displays management information for IPs associated with the domain. 8 | 9 | 10 | # Table of Contents 11 | - [Table of Contents](#table-of-contents) 12 | - [DEMO](#demo) 13 | - [Installation](#installation) 14 | - [Usage](#usage) 15 | - [License](#license) 16 | - [Contributing](#contributing) 17 | # DEMO 18 | 19 | ```bash 20 | $ whris example.com 21 | Target domain:example.com 22 | Target ip :93.184.216.34 23 | 24 | Network Admin:NETBLK-03-EU-93-184-216-0-24 25 | Network name :EDGECAST-NETBLK-03 26 | ip range :93.184.216.0 - 93.184.216.255 27 | country :EU 28 | ``` 29 | 30 | ```bash 31 | $ whris yaserarenai.com 32 | Target domain:yaserarenai.com 33 | Target ip :163.44.185.212 34 | 35 | Network Admin:GMO Pepabo, Inc. 36 | Network name :LOLIPOP 37 | ip range :163.44.185.0 - 163.44.185.255 38 | country :JP 39 | ``` 40 | # Installation 41 | 42 | ```bash 43 | $ go install github.com/harakeishi/whris@latest 44 | ``` 45 | 46 | # Usage 47 | 48 | If you want to know the administrator of the IP associated with the domain, type `whris [target]`. 49 | 50 | You will then see the target domain and IP, and the administrator information for that IP (administrator name, network name, range of IPs to be managed, and country). 51 | 52 | ```bash 53 | $ whris yaserarenai.com 54 | Target domain:yaserarenai.com 55 | Target ip :163.44.185.212 56 | 57 | Network Admin:GMO Pepabo, Inc. 58 | Network name :LOLIPOP 59 | ip range :163.44.185.0 - 163.44.185.255 60 | country :JP 61 | ``` 62 | 63 | If you want to see more details, use the `-v` option. 64 | 65 | Then you will be able to see the higher level network administrator information. 66 | 67 | ```bash 68 | $ whris yaserarenai.com -v 69 | Target domain:yaserarenai.com 70 | Target ip :163.44.185.212 71 | 72 | Network Admin:GMO Pepabo, Inc. 73 | Network name :LOLIPOP 74 | ip range :163.44.185.0 - 163.44.185.255 75 | country :JP 76 | 77 | =========Network Details========= 78 | 0: 79 | Network Admin:Administered by APNIC 80 | Network name : 81 | ip range :163.0.0.0 - 163.255.255.255 82 | country : 83 | 1: 84 | Network Admin:GMO Internet, Inc. 85 | Network name :interQ 86 | ip range :163.44.64.0 - 163.44.191.255 87 | country :JP 88 | 2: 89 | Network Admin:GMO Pepabo, Inc. 90 | Network name :LOLIPOP 91 | ip range :163.44.185.0 - 163.44.185.255 92 | country :JP 93 | ``` 94 | # License 95 | [MIT](LICENSE) 96 | 97 | This software includes the work that is distributed in the Apache License 2.0. 98 | 99 | このソフトウェアは、 Apache 2.0ライセンスで配布されている製作物が含まれています。 100 | 101 | ## Contributors ✨ 102 | 103 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
Dimitri Sokolyuk
Dimitri Sokolyuk

🤔
mattn
mattn

🤔
Kei Kamikawa
Kei Kamikawa

💻
koyashiro
koyashiro

📖
Noboru Saito
Noboru Saito

💻
Ben131-Go
Ben131-Go

🐛
120 | 121 | 122 | 123 | 124 | 125 | 126 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 127 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | curver "github.com/harakeishi/curver" 8 | whris "github.com/harakeishi/whris/whris" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var rootCmd = &cobra.Command{ 13 | Use: "whris", 14 | Args: cobra.MinimumNArgs(1), 15 | Short: "`whris` is Displays management information for IPs associated with the domain.", 16 | Long: `"whris" outputs the target domain and IP from the input domain, as well as the administrator information for that IP (administrator name, network name, range of IPs to be managed, and country name).`, 17 | RunE: func(cmd *cobra.Command, args []string) error { 18 | version, err := cmd.Flags().GetBool("version") 19 | if err != nil { 20 | return err 21 | } 22 | if version { 23 | curver.EchoVersion() 24 | return nil 25 | } 26 | 27 | v, err := cmd.Flags().GetBool("verbose") 28 | if err != nil { 29 | return err 30 | } 31 | 32 | for n, domain := range args { 33 | if n > 0 { 34 | fmt.Println() 35 | } 36 | if _, err := whris.Resolve(domain, v); err != nil { 37 | return err 38 | } 39 | } 40 | return nil 41 | }, 42 | } 43 | 44 | func Execute() { 45 | err := rootCmd.Execute() 46 | if err != nil { 47 | os.Exit(1) 48 | } 49 | } 50 | 51 | func init() { 52 | rootCmd.Flags().BoolP("verbose", "v", false, "verbose output") 53 | rootCmd.Flags().BoolP("version", "V", false, "echo version") 54 | } 55 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/harakeishi/whris 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/harakeishi/curver v0.1.2 7 | github.com/likexian/whois v1.15.0 8 | github.com/spf13/cobra v1.7.0 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/harakeishi/curver v0.1.2 h1:jyPmANcNfpZIau+MJZ+cXAm8E5Hutl5EvItugaBuQmA= 3 | github.com/harakeishi/curver v0.1.2/go.mod h1:G8ZUue/D5LVD1MFMv8bcsrn1SdwL/LbnqC+BFoFuVJw= 4 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 5 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 6 | github.com/likexian/gokit v0.25.13 h1:p2Uw3+6fGG53CwdU2Dz0T6bOycdb2+bAFAa3ymwWVkM= 7 | github.com/likexian/gokit v0.25.13/go.mod h1:qQhEWFBEfqLCO3/vOEo2EDKd+EycekVtUK4tex+l2H4= 8 | github.com/likexian/whois v1.15.0 h1:AYYJ5bNUo8Qy2T1Z5GgMp1oIcIlCcTDfg1buYz6TdAE= 9 | github.com/likexian/whois v1.15.0/go.mod h1:456fUTkh+O8F8v09bGdVl7XxBjRaQ4LvYHyVWX5Bxyg= 10 | github.com/likexian/whois-parser v1.24.8/go.mod h1:b6STMHHDaSKbd4PzGrP50wWE5NzeBUETa/hT9gI0G9I= 11 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 12 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 13 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 14 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 15 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 16 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 17 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 18 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 19 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 20 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 21 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 22 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 23 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 24 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 25 | golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= 26 | golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 27 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 28 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 29 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 30 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 31 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 32 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 33 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 34 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 35 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 36 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 37 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 38 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 39 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 40 | golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= 41 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 42 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 43 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 44 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 45 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 46 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 47 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 48 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 49 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 50 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 51 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 52 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 53 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harakeishi/whris/9d53bc72c13c2f9043329bb652d858578eea875d/logo.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | 4 | */ 5 | package main 6 | 7 | import ( 8 | "github.com/harakeishi/whris/cmd" 9 | ) 10 | 11 | func main() { 12 | cmd.Execute() 13 | } 14 | -------------------------------------------------------------------------------- /whris/whris.go: -------------------------------------------------------------------------------- 1 | package whris 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | 8 | "github.com/likexian/whois" 9 | ) 10 | 11 | type NetworkAdmin struct { 12 | IpRange string 13 | NetName string 14 | Country string 15 | Admin string 16 | } 17 | 18 | type Summary struct { 19 | TargetDomain string 20 | TargetIp string 21 | WhoisResponseServer string 22 | WhoisResponse string 23 | ParseResult []NetworkAdmin 24 | } 25 | 26 | func Resolve(domain string, verbose bool) (Summary, error) { 27 | var summary Summary 28 | summary.WhoisResponseServer = "whois.apnic.net" 29 | summary.TargetDomain = domain 30 | addr, err := net.ResolveIPAddr("ip", domain) 31 | if err != nil { 32 | return Summary{}, err 33 | } 34 | summary.TargetIp = addr.String() 35 | summary.WhoisResponse, err = whois.Whois(summary.TargetIp, "whois.iana.org") 36 | if err != nil { 37 | return Summary{}, err 38 | } 39 | summary.SetWhoisResponseServerFromWhoisResponse() 40 | summary.ParseWhoisResponse() 41 | if !summary.ParseCheck() { 42 | summary.ParseResult = summary.ParseResult[1:] 43 | summary.WhoisResponse, err = whois.Whois(summary.TargetIp, summary.WhoisResponseServer) 44 | if err != nil { 45 | return Summary{}, err 46 | } 47 | summary.ParseWhoisResponse() 48 | } 49 | summary.EchoResult(verbose) 50 | return summary, nil 51 | } 52 | 53 | func (s *Summary) ParseWhoisResponse() { 54 | paragraph := s.BreakDownWhoisResponseIntoParagraphs() 55 | for _, v := range paragraph { 56 | tmp := NetworkAdmin{} 57 | row := strings.Split(v, "\n") 58 | for _, val := range row { 59 | col := strings.Split(val, ":") 60 | switch col[0] { 61 | case "inetnum", "NetRange": 62 | tmp.IpRange = strings.TrimSpace(col[1]) 63 | case "netname", "NetName": 64 | tmp.NetName = strings.TrimSpace(col[1]) 65 | case "country", "Country": 66 | tmp.Country = strings.TrimSpace(col[1]) 67 | case "descr", "Organization", "organisation", "owner": 68 | if tmp.Admin == "" { 69 | tmp.Admin = strings.TrimSpace(col[1]) 70 | } 71 | } 72 | } 73 | if tmp.IpRange != "" { 74 | s.ParseResult = append(s.ParseResult, tmp) 75 | } 76 | } 77 | } 78 | 79 | func (s *Summary) EchoResult(verbose bool) { 80 | fmt.Printf("Target domain:%s\n", s.TargetDomain) 81 | fmt.Printf("Target ip :%s\n\n", s.TargetIp) 82 | fmt.Printf("Network Admin:%s\n", s.ParseResult[len(s.ParseResult)-1].Admin) 83 | fmt.Printf("Network name :%s\n", s.ParseResult[len(s.ParseResult)-1].NetName) 84 | fmt.Printf("ip range :%s\n", s.ParseResult[len(s.ParseResult)-1].IpRange) 85 | fmt.Printf("country :%s\n", s.ParseResult[len(s.ParseResult)-1].Country) 86 | if verbose { 87 | fmt.Printf("\n=========Network Details=========\n") 88 | for i, v := range s.ParseResult { 89 | fmt.Printf("%d:\n", i) 90 | fmt.Printf(" Network Admin:%s\n", v.Admin) 91 | fmt.Printf(" Network name :%s\n", v.NetName) 92 | fmt.Printf(" ip range :%s\n", v.IpRange) 93 | fmt.Printf(" country :%s\n", v.Country) 94 | } 95 | } 96 | } 97 | 98 | func (s *Summary) BreakDownWhoisResponseIntoParagraphs() []string { 99 | switch s.WhoisResponseServer { 100 | case "whois.arin.net": 101 | return strings.Split(s.WhoisResponse, "#") 102 | case "whois.apnic.net", "whois.ripe.net", "whois.lacnic.net": 103 | return strings.Split(s.WhoisResponse, "%") 104 | default: 105 | return strings.Split(s.WhoisResponse, "\n\n") 106 | } 107 | } 108 | 109 | func (s *Summary) SetWhoisResponseServerFromWhoisResponse() { 110 | tmp := strings.Split(s.WhoisResponse, "\n") 111 | for _, v := range tmp { 112 | col := strings.Split(v, ":") 113 | if col[0] == "refer" { 114 | s.WhoisResponseServer = strings.TrimSpace(col[1]) 115 | break 116 | } 117 | } 118 | } 119 | 120 | func (s *Summary) ParseCheck() bool { 121 | // 転送されていないかチェックする 122 | list := []string{"apnic", "arin", "ripe", "lacnic"} 123 | for _, v := range list { 124 | if strings.Contains(strings.ToLower(s.ParseResult[1].NetName), v) { 125 | s.WhoisResponseServer = fmt.Sprintf("whois.%s.net", v) 126 | return false 127 | } 128 | } 129 | return true 130 | } 131 | -------------------------------------------------------------------------------- /whris/whris_test.go: -------------------------------------------------------------------------------- 1 | package whris 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | const TestRipeWhoisResponse = `% IANA WHOIS server 9 | % for more information on IANA, visit http://www.iana.org 10 | % This query returned 1 object 11 | 12 | refer: whois.ripe.net 13 | 14 | inetnum: 93.0.0.0 - 93.255.255.255 15 | organisation: RIPE NCC 16 | status: ALLOCATED 17 | 18 | whois: whois.ripe.net 19 | 20 | changed: 2007-03 21 | source: IANA 22 | 23 | % This is the RIPE Database query service. 24 | % The objects are in RPSL format. 25 | % 26 | % The RIPE Database is subject to Terms and Conditions. 27 | % See http://www.ripe.net/db/support/db-terms-conditions.pdf 28 | 29 | % Note: this output has been filtered. 30 | % To receive output for a database update, use the "-B" flag. 31 | 32 | % Information related to '93.184.216.0 - 93.184.216.255' 33 | 34 | % Abuse contact for '93.184.216.0 - 93.184.216.255' is 'abuse@verizondigitalmedia.com' 35 | 36 | inetnum: 93.184.216.0 - 93.184.216.255 37 | netname: EDGECAST-NETBLK-03 38 | descr: NETBLK-03-EU-93-184-216-0-24 39 | country: EU 40 | admin-c: DS7892-RIPE 41 | tech-c: DS7892-RIPE 42 | status: ASSIGNED PA 43 | mnt-by: MNT-EDGECAST 44 | created: 2012-06-22T21:48:41Z 45 | last-modified: 2012-06-22T21:48:41Z 46 | source: RIPE # Filtered 47 | 48 | person: Derrick Sawyer 49 | address: 13031 W Jefferson Blvd #900, Los Angeles, CA 90094 50 | phone: +18773343236 51 | nic-hdl: DS7892-RIPE 52 | created: 2010-08-25T18:44:19Z 53 | last-modified: 2017-03-03T09:06:18Z 54 | source: RIPE 55 | mnt-by: MNT-EDGECAST 56 | 57 | % This query was served by the RIPE Database Query Service version 1.102.2 (WAGYU) 58 | 59 | ;; Query time: 1194 msec 60 | ;; WHEN: Tue Feb 01 21:12:31 JST 2022` 61 | 62 | func TestResolve(t *testing.T) { 63 | type fields struct { 64 | TargetDomain string 65 | TargetIp string 66 | WhoisResponseServer string 67 | WhoisResponse string 68 | ParseResult []NetworkAdmin 69 | } 70 | type args struct { 71 | domain string 72 | verbose bool 73 | } 74 | tests := []struct { 75 | name string 76 | args args 77 | want fields 78 | }{ 79 | { 80 | name: "the_result_of_example.com_must_be_returned", 81 | args: args{ 82 | domain: "example.com", 83 | verbose: false, 84 | }, 85 | want: fields{ 86 | ParseResult: []NetworkAdmin{ 87 | { 88 | IpRange: "93.0.0.0 - 93.255.255.255", 89 | Admin: "RIPE NCC", 90 | }, 91 | { 92 | IpRange: "93.184.216.0 - 93.184.216.255", 93 | Admin: "NETBLK-03-EU-93-184-216-0-24", 94 | Country: "EU", 95 | NetName: "EDGECAST-NETBLK-03", 96 | }, 97 | }, 98 | }, 99 | }, 100 | } 101 | for _, tt := range tests { 102 | t.Run(tt.name, func(t *testing.T) { 103 | got, err := Resolve(tt.args.domain, tt.args.verbose) 104 | if err != nil { 105 | t.Errorf("Resolve() error: %v", err) 106 | } 107 | if !reflect.DeepEqual(got.ParseResult, tt.want.ParseResult) { 108 | t.Fatalf("Summary.ParseWhoisResponse() = %v, want %v", got.ParseResult, tt.want.ParseResult) 109 | } 110 | }) 111 | } 112 | } 113 | 114 | func TestSummary_ParseWhoisResponse(t *testing.T) { 115 | type fields struct { 116 | WhoisResponseServer string 117 | WhoisResponse string 118 | ParseResult []NetworkAdmin 119 | } 120 | tests := []struct { 121 | name string 122 | fields fields 123 | want fields 124 | }{ 125 | { 126 | name: "be_able_to_correctly_parse_the_response_from_ripe", 127 | fields: fields{ 128 | WhoisResponseServer: "whois.ripe.net", 129 | WhoisResponse: TestRipeWhoisResponse, 130 | }, 131 | want: fields{ 132 | WhoisResponseServer: "whois.ripe.net", 133 | WhoisResponse: TestRipeWhoisResponse, 134 | ParseResult: []NetworkAdmin{ 135 | { 136 | IpRange: "93.0.0.0 - 93.255.255.255", 137 | Admin: "RIPE NCC", 138 | }, 139 | { 140 | IpRange: "93.184.216.0 - 93.184.216.255", 141 | Admin: "NETBLK-03-EU-93-184-216-0-24", 142 | Country: "EU", 143 | NetName: "EDGECAST-NETBLK-03", 144 | }, 145 | }, 146 | }, 147 | }, 148 | } 149 | for _, tt := range tests { 150 | t.Run(tt.name, func(t *testing.T) { 151 | s := &Summary{ 152 | WhoisResponseServer: tt.fields.WhoisResponseServer, 153 | WhoisResponse: tt.fields.WhoisResponse, 154 | ParseResult: tt.fields.ParseResult, 155 | } 156 | s.ParseWhoisResponse() 157 | if !reflect.DeepEqual(s.ParseResult, tt.want.ParseResult) { 158 | t.Fatalf("Summary.ParseWhoisResponse() = %v, want %v", s.ParseResult, tt.want.ParseResult) 159 | } 160 | }) 161 | } 162 | } 163 | 164 | func TestSummary_SetWhoisResponseServerFromWhoisResponse(t *testing.T) { 165 | type fields struct { 166 | WhoisResponseServer string 167 | WhoisResponse string 168 | } 169 | tests := []struct { 170 | name string 171 | fields fields 172 | want fields 173 | }{ 174 | { 175 | name: "the_response_server_can_be_set_correctly_from_the_response_of_ripe", 176 | fields: fields{ 177 | WhoisResponseServer: "whois.apnic.net", 178 | WhoisResponse: TestRipeWhoisResponse, 179 | }, 180 | want: fields{ 181 | WhoisResponseServer: "whois.ripe.net", 182 | WhoisResponse: TestRipeWhoisResponse, 183 | }, 184 | }, 185 | } 186 | for _, tt := range tests { 187 | t.Run(tt.name, func(t *testing.T) { 188 | s := &Summary{ 189 | WhoisResponseServer: tt.fields.WhoisResponseServer, 190 | WhoisResponse: tt.fields.WhoisResponse, 191 | } 192 | s.SetWhoisResponseServerFromWhoisResponse() 193 | if s.WhoisResponseServer != tt.want.WhoisResponseServer { 194 | t.Errorf("Summary.SetWhoisResponseServerFromWhoisResponse() = %v, want %v", s.WhoisResponseServer, tt.want.WhoisResponseServer) 195 | } 196 | }) 197 | } 198 | } 199 | 200 | func TestSummary_ParseCheck(t *testing.T) { 201 | type fields struct { 202 | TargetDomain string 203 | TargetIp string 204 | WhoisResponseServer string 205 | WhoisResponse string 206 | ParseResult []NetworkAdmin 207 | } 208 | type wants struct { 209 | WhoisResponseServer string 210 | Result bool 211 | } 212 | tests := []struct { 213 | name string 214 | fields fields 215 | want wants 216 | }{ 217 | { 218 | name: "return_true_in_non-redirected_response", 219 | fields: fields{ 220 | WhoisResponseServer: "whois.ripe.net", 221 | ParseResult: []NetworkAdmin{ 222 | { 223 | IpRange: "93.0.0.0 - 93.255.255.255", 224 | Admin: "RIPE NCC", 225 | }, 226 | { 227 | IpRange: "93.184.216.0 - 93.184.216.255", 228 | Admin: "NETBLK-03-EU-93-184-216-0-24", 229 | Country: "EU", 230 | NetName: "EDGECAST-NETBLK-03", 231 | }, 232 | }, 233 | }, 234 | want: wants{ 235 | WhoisResponseServer: "whois.ripe.net", 236 | Result: true, 237 | }, 238 | }, 239 | { 240 | name: "return_false_in_redirected_response", 241 | fields: fields{ 242 | WhoisResponseServer: "whois.apnic.net", 243 | ParseResult: []NetworkAdmin{ 244 | { 245 | IpRange: "157.1.0.0 - 157.14.255.255", 246 | Admin: "APNIC", 247 | Country: "", 248 | NetName: "", 249 | }, 250 | { 251 | IpRange: "93.0.0.0 - 93.255.255.255", 252 | Admin: "DS7892-RIPE", 253 | Country: "", 254 | NetName: "RIPE NCC", 255 | }, 256 | }, 257 | }, 258 | want: wants{ 259 | WhoisResponseServer: "whois.ripe.net", 260 | Result: false, 261 | }, 262 | }, 263 | } 264 | for _, tt := range tests { 265 | t.Run(tt.name, func(t *testing.T) { 266 | s := &Summary{ 267 | TargetDomain: tt.fields.TargetDomain, 268 | TargetIp: tt.fields.TargetIp, 269 | WhoisResponseServer: tt.fields.WhoisResponseServer, 270 | WhoisResponse: tt.fields.WhoisResponse, 271 | ParseResult: tt.fields.ParseResult, 272 | } 273 | got := s.ParseCheck() 274 | if got != tt.want.Result { 275 | t.Errorf("Summary.ParseCheck() Return = %v, want %v", got, tt.want.Result) 276 | } 277 | if s.WhoisResponseServer != tt.want.WhoisResponseServer { 278 | t.Errorf("Summary.ParseCheck() WhoisResponseServer = %v, want %v", s.WhoisResponseServer, tt.want.WhoisResponseServer) 279 | } 280 | }) 281 | } 282 | } 283 | --------------------------------------------------------------------------------