mirror of
https://github.com/zu1k/nali.git
synced 2025-01-22 13:19:02 +08:00
commit
0151892abb
4
Makefile
4
Makefile
@ -2,8 +2,8 @@ NAME=nali
|
||||
BINDIR=bin
|
||||
VERSION=$(shell git describe --tags || echo "unknown version")
|
||||
BUILDTIME=$(shell date -u)
|
||||
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/zu1k/nali/constant.Version=$(VERSION)" \
|
||||
-X "github.com/zu1k/nali/constant.BuildTime=$(BUILDTIME)" \
|
||||
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/zu1k/nali/internal/constant.Version=$(VERSION)" \
|
||||
-X "github.com/zu1k/nali/internal/constant.BuildTime=$(BUILDTIME)" \
|
||||
-w -s'
|
||||
|
||||
PLATFORM_LIST = \
|
||||
|
68
README.md
68
README.md
@ -36,8 +36,10 @@ However the C version has too few functions, and the js version is too big and t
|
||||
- Interactive query
|
||||
- Offline query
|
||||
- Both ipv4 and ipv6 supported
|
||||
- Multilingual support
|
||||
- CDN provider query
|
||||
- Full platform support
|
||||
- Color print
|
||||
|
||||
## Install
|
||||
|
||||
@ -154,28 +156,6 @@ Address: 2a00:1450:400e:809::200e [荷兰Amsterdam Google Inc. 服务器网段]
|
||||
|
||||
### Query CDN provider
|
||||
|
||||
#### Query CDN provider only
|
||||
|
||||
```
|
||||
$ nslookup www.gov.cn | nali cdn
|
||||
Server: 127.0.0.53
|
||||
Address: 127.0.0.53#53
|
||||
|
||||
Non-authoritative answer:
|
||||
www.gov.cn canonical name = www.gov.cn.bsgslb.cn [白山云 CDN].
|
||||
www.gov.cn.bsgslb.cn [白山云 CDN] canonical name = zgovweb.v.bsgslb.cn [白山云 CDN].
|
||||
Name: zgovweb.v.bsgslb.cn [白山云 CDN]
|
||||
Address: 185.232.56.148
|
||||
Name: zgovweb.v.bsgslb.cn [白山云 CDN]
|
||||
Address: 185.232.56.147
|
||||
Name: zgovweb.v.bsgslb.cn [白山云 CDN]
|
||||
Address: 2001:428:6402:21b::6
|
||||
Name: zgovweb.v.bsgslb.cn [白山云 CDN]
|
||||
Address: 2001:428:6402:21b::5
|
||||
```
|
||||
|
||||
#### Also query IP geo
|
||||
|
||||
```
|
||||
$ nslookup www.gov.cn | nali
|
||||
Server: 127.0.0.53 [局域网 IP]
|
||||
@ -192,14 +172,6 @@ Name: zgovweb.v.bsgslb.cn [白山云 CDN]
|
||||
Address: 2001:428:6402:21b::6 [美国Louisiana州Monroe Qwest Communications Company, LLC (CenturyLink)]
|
||||
```
|
||||
|
||||
#### Use standalone
|
||||
|
||||
You should parse cname by yourself
|
||||
|
||||
```
|
||||
$ nali cdn cdn.somecdncname.com
|
||||
```
|
||||
|
||||
## Interface
|
||||
|
||||
### Help
|
||||
@ -211,19 +183,18 @@ Usage:
|
||||
nali [command]
|
||||
|
||||
Available Commands:
|
||||
cdn Query cdn service provider
|
||||
completion generate the autocompletion script for the specified shell
|
||||
help Help about any command
|
||||
parse Query IP information
|
||||
update update chunzhen ip database
|
||||
|
||||
Flags:
|
||||
-h, --help help for nali
|
||||
-t, --toggle Help message for toggle
|
||||
--gbk Use GBK decoder
|
||||
-h, --help help for nali
|
||||
|
||||
Use "nali [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
### Update chunzhen IP database
|
||||
### Update database
|
||||
|
||||
```
|
||||
$ nali update
|
||||
@ -231,9 +202,9 @@ $ nali update
|
||||
2020/07/17 12:54:05 已将最新的纯真 IP 库保存到本地 /root/.nali/qqwry.dat
|
||||
```
|
||||
|
||||
### Use other database
|
||||
### Select database
|
||||
|
||||
Set environment variables `NALI_DB`
|
||||
Users can specify which database to use, set environment variables `NALI_DB_IP4`, `NALI_DB_IP6` or both.
|
||||
|
||||
supported database:
|
||||
|
||||
@ -246,21 +217,21 @@ supported database:
|
||||
##### Use geoip db
|
||||
|
||||
```
|
||||
set NALI_DB=geoip
|
||||
set NALI_DB_IP4=geoip
|
||||
|
||||
or use powershell
|
||||
|
||||
$env:NALI_DB="geoip"
|
||||
$env:NALI_DB_IP4="geoip"
|
||||
```
|
||||
|
||||
##### Use ipip db
|
||||
|
||||
```
|
||||
set NALI_DB=ipip
|
||||
set NALI_DB_IP6=ipip
|
||||
|
||||
or use powershell
|
||||
|
||||
$env:NALI_DB="ipip"
|
||||
$env:NALI_DB_IP6="ipip"
|
||||
```
|
||||
|
||||
#### Linux
|
||||
@ -268,13 +239,24 @@ $env:NALI_DB="ipip"
|
||||
##### Use geoip db
|
||||
|
||||
```
|
||||
export NALI_DB=geoip
|
||||
export NALI_DB_IP4=geoip
|
||||
```
|
||||
|
||||
##### Use ipip db
|
||||
|
||||
```
|
||||
export NALI_DB=ipip
|
||||
export NALI_DB_IP6=ipip
|
||||
```
|
||||
|
||||
### Multilingual support
|
||||
|
||||
Specify the language to be used by modifying the environment variable `NALI_LANG`, when using a non-Chinese language only the GeoIP2 database is supported
|
||||
|
||||
The values that can be set for this parameter can be found in the list of supported databases for GeoIP2
|
||||
|
||||
```
|
||||
# NALI_LANG=en nali 1.1.1.1
|
||||
1.1.1.1 [Australia]
|
||||
```
|
||||
|
||||
### Change database directory
|
||||
|
@ -35,8 +35,10 @@
|
||||
- 支持管道处理
|
||||
- 支持交互式查询
|
||||
- 同时支持IPv4和IPv6
|
||||
- 支持多语言
|
||||
- 查询完全离线
|
||||
- 全平台支持
|
||||
- 支持彩色输出
|
||||
|
||||
## 安装
|
||||
|
||||
@ -159,28 +161,6 @@ Address: 2a00:1450:400e:809::200e [荷兰Amsterdam Google Inc. 服务器网段]
|
||||
|
||||
因为 CDN 服务通常使用 CNAME 的域名解析方式,所以推荐与 `nslookup` 或者 `dig` 配合使用,在已经知道 CNAME 后可单独使用
|
||||
|
||||
#### 只查询 CDN 服务提供商
|
||||
|
||||
```
|
||||
$ nslookup www.gov.cn | nali cdn
|
||||
Server: 127.0.0.53
|
||||
Address: 127.0.0.53#53
|
||||
|
||||
Non-authoritative answer:
|
||||
www.gov.cn canonical name = www.gov.cn.bsgslb.cn [白山云 CDN].
|
||||
www.gov.cn.bsgslb.cn [白山云 CDN] canonical name = zgovweb.v.bsgslb.cn [白山云 CDN].
|
||||
Name: zgovweb.v.bsgslb.cn [白山云 CDN]
|
||||
Address: 185.232.56.148
|
||||
Name: zgovweb.v.bsgslb.cn [白山云 CDN]
|
||||
Address: 185.232.56.147
|
||||
Name: zgovweb.v.bsgslb.cn [白山云 CDN]
|
||||
Address: 2001:428:6402:21b::6
|
||||
Name: zgovweb.v.bsgslb.cn [白山云 CDN]
|
||||
Address: 2001:428:6402:21b::5
|
||||
```
|
||||
|
||||
#### 查询所有信息
|
||||
|
||||
```
|
||||
$ nslookup www.gov.cn | nali
|
||||
Server: 127.0.0.53 [局域网 IP]
|
||||
@ -197,14 +177,6 @@ Name: zgovweb.v.bsgslb.cn [白山云 CDN]
|
||||
Address: 2001:428:6402:21b::6 [美国Louisiana州Monroe Qwest Communications Company, LLC (CenturyLink)]
|
||||
```
|
||||
|
||||
#### 单独使用
|
||||
|
||||
需要提前查询到 CNAME 域名
|
||||
|
||||
```
|
||||
$ nali cdn cdn.somecdncname.com
|
||||
```
|
||||
|
||||
## 用户交互
|
||||
|
||||
### 查看帮助
|
||||
@ -216,9 +188,7 @@ Usage:
|
||||
nali [command]
|
||||
|
||||
Available Commands:
|
||||
cdn Query cdn service provider
|
||||
help Help about any command
|
||||
parse Query IP information
|
||||
update update chunzhen ip database
|
||||
|
||||
Flags:
|
||||
@ -228,7 +198,7 @@ Flags:
|
||||
Use "nali [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
### 更新纯真数据库
|
||||
### 更新数据库
|
||||
|
||||
```
|
||||
$ nali update
|
||||
@ -236,35 +206,36 @@ $ nali update
|
||||
2020/07/17 12:54:05 已将最新的纯真 IP 库保存到本地 /root/.nali/qqwry.dat
|
||||
```
|
||||
|
||||
### 使用 Geoip2 数据库
|
||||
### 自选数据库
|
||||
|
||||
需要设置环境变量: `NALI_DB`
|
||||
用户可以指定使用哪个数据库,需要设置环境变量: `NALI_DB_IP4`、`NALI_DB_IP6` 或者两个同时设置
|
||||
|
||||
支持的变量内容:
|
||||
|
||||
- Geoip2 `['geoip', 'geoip2', 'geo']`
|
||||
- Chunzhen `['chunzhen', 'qqip', 'qqwry']`
|
||||
- IPIP `['ipip', 'ipipfree', 'ipip.net']`
|
||||
|
||||
#### Windows平台
|
||||
|
||||
##### 使用geoip数据库
|
||||
|
||||
```
|
||||
set NALI_DB=geoip
|
||||
set NALI_DB_IP4=geoip
|
||||
|
||||
或者使用 powershell
|
||||
|
||||
$env:NALI_DB="geoip"
|
||||
$env:NALI_DB_IP4="geoip"
|
||||
```
|
||||
|
||||
##### 使用ipip数据库
|
||||
|
||||
```
|
||||
set NALI_DB=ipip
|
||||
set NALI_DB_IP6=ipip
|
||||
|
||||
或者使用 powershell
|
||||
|
||||
$env:NALI_DB="ipip"
|
||||
$env:NALI_DB_IP6="ipip"
|
||||
```
|
||||
|
||||
#### Linux平台
|
||||
@ -272,13 +243,24 @@ $env:NALI_DB="ipip"
|
||||
##### 使用geoip数据库
|
||||
|
||||
```
|
||||
export NALI_DB=geoip
|
||||
export NALI_DB_IP4=geoip
|
||||
```
|
||||
|
||||
##### 使用ipip数据库
|
||||
|
||||
```
|
||||
export NALI_DB=ipip
|
||||
export NALI_DB_IP4=ipip
|
||||
```
|
||||
|
||||
### 多语言支持
|
||||
|
||||
通过修改环境变量 `NALI_LANG` 来指定使用的语言,当使用非中文语言时仅支持GeoIP2这个数据库
|
||||
|
||||
该参数可设置的值见 GeoIP2 这个数据库的支持列表
|
||||
|
||||
```
|
||||
# NALI_LANG=en nali 1.1.1.1
|
||||
1.1.1.1 [Australia]
|
||||
```
|
||||
|
||||
### 更换数据库目录
|
||||
|
31
cmd/cdn.go
31
cmd/cdn.go
@ -1,31 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/zu1k/nali/internal/app"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// cdnCmd represents the cdn command
|
||||
var cdnCmd = &cobra.Command{
|
||||
Use: "cdn",
|
||||
Short: "Query cdn service provider",
|
||||
Long: `Query cdn service provider`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if update {
|
||||
app.UpdateDB()
|
||||
}
|
||||
|
||||
app.InitCDNDB()
|
||||
app.CDN(args)
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
update = false
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(cdnCmd)
|
||||
cdnCmd.Flags().BoolVarP(&update, "update", "u", false, "Update CDN database")
|
||||
}
|
59
cmd/parse.go
59
cmd/parse.go
@ -1,59 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/zu1k/nali/internal/app"
|
||||
"github.com/zu1k/nali/internal/ipdb"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var parseCmd = &cobra.Command{
|
||||
Use: "parse",
|
||||
Short: "Query IP information",
|
||||
Long: `Query IP information.
|
||||
|
||||
#1 Query a simple IP address
|
||||
|
||||
$ nali 1.2.3.4
|
||||
|
||||
or use pipe
|
||||
|
||||
$ echo IP 6.6.6.6 | nali
|
||||
|
||||
#2 Query multiple IP addresses
|
||||
|
||||
$ nali 1.2.3.4 4.3.2.1 123.23.3.0
|
||||
|
||||
#3 Interactive query
|
||||
|
||||
$ nali
|
||||
123.23.23.23
|
||||
123.23.23.23 [越南 越南邮电集团公司]
|
||||
quit
|
||||
|
||||
#4 Use with dig
|
||||
|
||||
$ dig nali.lgf.im +short | nali
|
||||
|
||||
#5 Use with nslookup
|
||||
|
||||
$ nslookup nali.lgf.im 8.8.8.8 | nali
|
||||
|
||||
#6 Use with any other program
|
||||
|
||||
bash abc.sh | nali
|
||||
|
||||
#7 IPV6 support
|
||||
|
||||
$ nslookup google.com | nali
|
||||
`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app.InitIPDB(ipdb.GetIPDBType())
|
||||
app.ParseIPs(args)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(parseCmd)
|
||||
}
|
13
cmd/root.go
13
cmd/root.go
@ -4,9 +4,9 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zu1k/nali/internal/app"
|
||||
"github.com/zu1k/nali/internal/ipdb"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
@ -53,9 +53,8 @@ Find document on: https://github.com/zu1k/nali
|
||||
`,
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app.InitIPDB(ipdb.GetIPDBType())
|
||||
app.InitCDNDB()
|
||||
app.Root(args)
|
||||
gbk, _ := cmd.Flags().GetBool("gbk")
|
||||
app.Root(args, gbk)
|
||||
},
|
||||
}
|
||||
|
||||
@ -66,3 +65,7 @@ func Execute() {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.Flags().Bool("gbk", false, "Use GBK decoder")
|
||||
}
|
||||
|
@ -4,13 +4,11 @@ import (
|
||||
"log"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/zu1k/nali/pkg/cdn"
|
||||
"github.com/zu1k/nali/pkg/zxipv6wry"
|
||||
|
||||
"github.com/zu1k/nali/constant"
|
||||
"github.com/zu1k/nali/pkg/qqwry"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zu1k/nali/internal/constant"
|
||||
"github.com/zu1k/nali/pkg/cdn"
|
||||
"github.com/zu1k/nali/pkg/qqwry"
|
||||
"github.com/zu1k/nali/pkg/zxipv6wry"
|
||||
)
|
||||
|
||||
// updateCmd represents the update command
|
||||
@ -29,7 +27,7 @@ var updateCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
// ZX ipv6
|
||||
filePath = filepath.Join(constant.HomePath, "ipv6wry.db")
|
||||
filePath = filepath.Join(constant.HomePath, "ipv6wry.database")
|
||||
log.Println("正在下载最新 ZX IPv6数据库...")
|
||||
_, err = zxipv6wry.Download(filePath)
|
||||
if err != nil {
|
||||
|
@ -1,6 +0,0 @@
|
||||
package constant
|
||||
|
||||
var (
|
||||
// HomePath db home path
|
||||
HomePath string
|
||||
)
|
1
go.mod
1
go.mod
@ -3,6 +3,7 @@ module github.com/zu1k/nali
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.12.0 // indirect
|
||||
github.com/ipipdotnet/ipdb-go v1.3.1
|
||||
github.com/oschwald/geoip2-golang v1.5.0
|
||||
github.com/saracen/go7z v0.0.0-20191010121135-9c09b6bd7fda
|
||||
|
7
go.sum
7
go.sum
@ -67,6 +67,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
@ -177,7 +179,11 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
@ -372,6 +378,7 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -1,86 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/zu1k/nali/constant"
|
||||
"github.com/zu1k/nali/internal/tools"
|
||||
"github.com/zu1k/nali/pkg/cdn"
|
||||
)
|
||||
|
||||
var (
|
||||
cdnDB cdn.CDN
|
||||
domainRe *regexp.Regexp
|
||||
)
|
||||
|
||||
func init() {
|
||||
domainRe = regexp.MustCompile(`[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+`)
|
||||
}
|
||||
|
||||
func InitCDNDB() {
|
||||
cdnDB = cdn.NewCDN(filepath.Join(constant.HomePath, "cdn.json"))
|
||||
}
|
||||
|
||||
func ParseCDNs(str []string) {
|
||||
for _, cname := range str {
|
||||
name := find(cname)
|
||||
fmt.Printf("%s [%s]\n", cname, name)
|
||||
}
|
||||
}
|
||||
|
||||
func find(cname string) string {
|
||||
baseCname := parseBaseCname(cname)
|
||||
if baseCname == "" {
|
||||
return "无法解析"
|
||||
}
|
||||
cdnResult, found := cdnDB.Data[baseCname]
|
||||
if found {
|
||||
return cdnResult.Name
|
||||
}
|
||||
|
||||
if strings.Contains(baseCname, "kunlun") {
|
||||
return "阿里云 CDN"
|
||||
}
|
||||
return "未找到"
|
||||
}
|
||||
|
||||
func ReplaceCDNInString(str string) (result string) {
|
||||
done := make(map[string]bool)
|
||||
cnames := domainRe.FindAllString(str, -1)
|
||||
result = str
|
||||
for _, cname := range cnames {
|
||||
name := find(cname)
|
||||
if name != "未找到" && name != "无法解析" {
|
||||
if _, found := done[cname]; found {
|
||||
continue
|
||||
}
|
||||
result = tools.AddInfoDomain(result, cname, name)
|
||||
done[cname] = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseBaseCname(domain string) string {
|
||||
hostParts := strings.Split(domain, ".")
|
||||
if len(hostParts) < 2 {
|
||||
return domain
|
||||
}
|
||||
baseCname := hostParts[len(hostParts)-2] + "." + hostParts[len(hostParts)-1]
|
||||
return baseCname
|
||||
}
|
||||
|
||||
func UpdateDB() {
|
||||
filePath := filepath.Join(constant.HomePath, "cdn.json")
|
||||
|
||||
log.Println("正在下载最新 CDN数据库...")
|
||||
_, err := cdn.Download(filePath)
|
||||
if err != nil {
|
||||
log.Fatalln("下载失败", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
@ -4,20 +4,14 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/zu1k/nali/internal/entity"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
var needTransform = false
|
||||
|
||||
func init() {
|
||||
stat, _ := os.Stdin.Stat()
|
||||
needTransform = ((stat.Mode() & os.ModeNamedPipe) != 0) && runtime.GOOS == "windows"
|
||||
}
|
||||
|
||||
func Root(args []string) {
|
||||
func Root(args []string, needTransform bool) {
|
||||
if len(args) == 0 {
|
||||
stdin := bufio.NewScanner(os.Stdin)
|
||||
for stdin.Scan() {
|
||||
@ -28,27 +22,9 @@ func Root(args []string) {
|
||||
if line == "quit" || line == "exit" {
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s\n", ReplaceIPInString(ReplaceCDNInString(line)))
|
||||
fmt.Printf("%s\n", entity.ParseLine(line).ColorString())
|
||||
}
|
||||
} else {
|
||||
ParseIPs(args)
|
||||
}
|
||||
}
|
||||
|
||||
func CDN(args []string) {
|
||||
if len(args) == 0 {
|
||||
stdin := bufio.NewScanner(os.Stdin)
|
||||
for stdin.Scan() {
|
||||
line := stdin.Text()
|
||||
if needTransform {
|
||||
line, _, _ = transform.String(simplifiedchinese.GBK.NewDecoder(), line)
|
||||
}
|
||||
if line == "quit" || line == "exit" {
|
||||
return
|
||||
}
|
||||
fmt.Println(ReplaceCDNInString(line))
|
||||
}
|
||||
} else {
|
||||
ParseCDNs(args)
|
||||
fmt.Printf("%s\n", entity.ParseLine(strings.Join(args, " ")).ColorString())
|
||||
}
|
||||
}
|
||||
|
@ -1,111 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/zu1k/nali/constant"
|
||||
"github.com/zu1k/nali/internal/ipdb"
|
||||
"github.com/zu1k/nali/internal/tools"
|
||||
geoip2 "github.com/zu1k/nali/pkg/geoip"
|
||||
"github.com/zu1k/nali/pkg/ipip"
|
||||
"github.com/zu1k/nali/pkg/qqwry"
|
||||
"github.com/zu1k/nali/pkg/zxipv6wry"
|
||||
)
|
||||
|
||||
var (
|
||||
db []ipdb.IPDB
|
||||
qqip qqwry.QQwry
|
||||
geoip geoip2.GeoIP
|
||||
)
|
||||
|
||||
// InitIPDB init ip db content
|
||||
func InitIPDB(ipdbtype ipdb.IPDBType) {
|
||||
db = make([]ipdb.IPDB, 1)
|
||||
switch ipdbtype {
|
||||
case ipdb.GEOIP2:
|
||||
db[0] = geoip2.NewGeoIP(filepath.Join(constant.HomePath, "GeoLite2-City.mmdb"))
|
||||
case ipdb.QQIP:
|
||||
db[0] = qqwry.NewQQwry(filepath.Join(constant.HomePath, "qqwry.dat"))
|
||||
db = append(db, zxipv6wry.NewZXwry(filepath.Join(constant.HomePath, "ipv6wry.db")))
|
||||
case ipdb.IPIP:
|
||||
db[0] = ipip.NewIPIPFree(filepath.Join(constant.HomePath, "ipipfree.ipdb"))
|
||||
db = append(db, zxipv6wry.NewZXwry(filepath.Join(constant.HomePath, "ipv6wry.db")))
|
||||
}
|
||||
}
|
||||
|
||||
// parse several ips
|
||||
func ParseIPs(ips []string) {
|
||||
db0 := db[0]
|
||||
var db1 ipdb.IPDB
|
||||
if len(db) > 1 {
|
||||
db1 = db[1]
|
||||
} else {
|
||||
db1 = db[0]
|
||||
}
|
||||
for _, ip := range ips {
|
||||
v := tools.ValidIP(ip)
|
||||
switch v {
|
||||
case tools.ValidIPv4:
|
||||
result := db0.Find(ip)
|
||||
fmt.Println(formatResult(ip, result))
|
||||
case tools.ValidIPv6:
|
||||
if db1 != nil {
|
||||
result := db1.Find(ip)
|
||||
fmt.Println(formatResult(ip, result))
|
||||
}
|
||||
default:
|
||||
fmt.Println(ReplaceIPInString(ip))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveRepeatedElement(arr []string) (newArr []string) {
|
||||
newArr = make([]string, 0)
|
||||
for i := 0; i < len(arr); i++ {
|
||||
repeat := false
|
||||
for j := i + 1; j < len(arr); j++ {
|
||||
if arr[i] == arr[j] {
|
||||
repeat = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !repeat {
|
||||
newArr = append(newArr, arr[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReplaceIPInString(str string) (result string) {
|
||||
db0 := db[0]
|
||||
var db1 ipdb.IPDB
|
||||
if len(db) > 1 {
|
||||
db1 = db[1]
|
||||
} else {
|
||||
db1 = db[0]
|
||||
}
|
||||
|
||||
result = str
|
||||
ip4s := tools.GetIP4FromString(str)
|
||||
ip4s = RemoveRepeatedElement(ip4s)
|
||||
for _, ip := range ip4s {
|
||||
info := db0.Find(ip)
|
||||
result = tools.AddInfoIp4(result, ip, info)
|
||||
}
|
||||
|
||||
ip6s := tools.GetIP6FromString(str)
|
||||
ip6s = RemoveRepeatedElement(ip6s)
|
||||
for _, ip := range ip6s {
|
||||
info := db1.Find(ip)
|
||||
result = tools.AddInfoIp6(result, ip, info)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func formatResult(ip string, result string) string {
|
||||
if result == "" {
|
||||
result = "未找到"
|
||||
}
|
||||
return fmt.Sprintf("%s [%s]", ip, result)
|
||||
}
|
28
internal/constant/path.go
Normal file
28
internal/constant/path.go
Normal file
@ -0,0 +1,28 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var (
|
||||
// HomePath database home path
|
||||
HomePath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
HomePath = os.Getenv("NALI_DB_HOME")
|
||||
if HomePath == "" {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
HomePath = filepath.Join(homeDir, ".nali")
|
||||
}
|
||||
if _, err := os.Stat(HomePath); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(HomePath, 0777); err != nil {
|
||||
log.Fatal("can not create", HomePath, ", use bin dir instead")
|
||||
}
|
||||
}
|
||||
}
|
6
internal/db/cache.go
Normal file
6
internal/db/cache.go
Normal file
@ -0,0 +1,6 @@
|
||||
package db
|
||||
|
||||
import "github.com/zu1k/nali/pkg/dbif"
|
||||
|
||||
var dbCache = make(map[dbif.QueryType]dbif.DB)
|
||||
var queryCache = make(map[string]string)
|
111
internal/db/db.go
Normal file
111
internal/db/db.go
Normal file
@ -0,0 +1,111 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/zu1k/nali/pkg/ipip"
|
||||
|
||||
"github.com/zu1k/nali/pkg/geoip"
|
||||
|
||||
"github.com/zu1k/nali/internal/constant"
|
||||
"github.com/zu1k/nali/pkg/cdn"
|
||||
"github.com/zu1k/nali/pkg/dbif"
|
||||
"github.com/zu1k/nali/pkg/qqwry"
|
||||
"github.com/zu1k/nali/pkg/zxipv6wry"
|
||||
)
|
||||
|
||||
var (
|
||||
QQWryPath = filepath.Join(constant.HomePath, "qqwry.dat")
|
||||
ZXIPv6WryPath = filepath.Join(constant.HomePath, "zxipv6wry.db")
|
||||
GeoLite2CityPath = filepath.Join(constant.HomePath, "GeoLite2-City.mmdb")
|
||||
IPIPFreePath = filepath.Join(constant.HomePath, "ipipfree.ipdb")
|
||||
CDNPath = filepath.Join(constant.HomePath, "cdn.json")
|
||||
|
||||
Language = "zh-CN"
|
||||
IPv4DBSelected = ""
|
||||
IPv6DBSelected = ""
|
||||
)
|
||||
|
||||
func init() {
|
||||
lang := os.Getenv("NALI_LANG")
|
||||
if lang != "" {
|
||||
Language = lang
|
||||
}
|
||||
|
||||
ipv4DB := os.Getenv("NALI_DB_IP4")
|
||||
if ipv4DB != "" {
|
||||
IPv4DBSelected = ipv4DB
|
||||
}
|
||||
|
||||
ipv6DB := os.Getenv("NALI_DB_IP6")
|
||||
if ipv6DB != "" {
|
||||
IPv6DBSelected = ipv6DB
|
||||
}
|
||||
}
|
||||
|
||||
func GetDB(typ dbif.QueryType) (db dbif.DB) {
|
||||
if db, found := dbCache[typ]; found {
|
||||
return db
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case dbif.TypeIPv4:
|
||||
if IPv4DBSelected != "" {
|
||||
db = GetIPDBbyName(IPv4DBSelected)
|
||||
} else {
|
||||
if Language == "zh-CN" {
|
||||
db = qqwry.NewQQwry(QQWryPath)
|
||||
} else {
|
||||
db = geoip.NewGeoIP(GeoLite2CityPath)
|
||||
}
|
||||
}
|
||||
case dbif.TypeIPv6:
|
||||
if IPv6DBSelected != "" {
|
||||
db = GetIPDBbyName(IPv6DBSelected)
|
||||
} else {
|
||||
if Language == "zh-CN" {
|
||||
db = zxipv6wry.NewZXwry(ZXIPv6WryPath)
|
||||
} else {
|
||||
geoip.NewGeoIP(GeoLite2CityPath)
|
||||
}
|
||||
}
|
||||
case dbif.TypeDomain:
|
||||
db = cdn.NewCDN(CDNPath)
|
||||
default:
|
||||
panic("Query type not supported!")
|
||||
}
|
||||
|
||||
dbCache[typ] = db
|
||||
return
|
||||
}
|
||||
|
||||
func GetIPDBbyName(name string) (db dbif.DB) {
|
||||
switch name {
|
||||
case "geo", "geoip", "geoip2":
|
||||
return geoip.NewGeoIP(GeoLite2CityPath)
|
||||
case "chunzhen", "qqip", "qqwry":
|
||||
return qqwry.NewQQwry(QQWryPath)
|
||||
case "ipip", "ipipfree", "ipip.net":
|
||||
return ipip.NewIPIPFree(IPIPFreePath)
|
||||
default:
|
||||
return qqwry.NewQQwry(QQWryPath)
|
||||
}
|
||||
}
|
||||
|
||||
func Update() {
|
||||
qqwry.Download(QQWryPath)
|
||||
zxipv6wry.Download(ZXIPv6WryPath)
|
||||
cdn.Download(CDNPath)
|
||||
}
|
||||
|
||||
func Find(typ dbif.QueryType, query string) string {
|
||||
if result, found := queryCache[query]; found {
|
||||
return result
|
||||
}
|
||||
result, err := GetDB(typ).Find(query, Language)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return result.String()
|
||||
}
|
76
internal/entity/entity.go
Normal file
76
internal/entity/entity.go
Normal file
@ -0,0 +1,76 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
|
||||
"github.com/zu1k/nali/pkg/dbif"
|
||||
)
|
||||
|
||||
type EntityType uint
|
||||
|
||||
const (
|
||||
TypeIPv4 = dbif.TypeIPv4
|
||||
TypeIPv6 = dbif.TypeIPv6
|
||||
TypeDomain = dbif.TypeDomain
|
||||
|
||||
TypePlain = 100
|
||||
)
|
||||
|
||||
type Entity struct {
|
||||
Loc []int // s[Loc[0]:Loc[1]]
|
||||
Type EntityType
|
||||
|
||||
Text string
|
||||
Info string
|
||||
}
|
||||
|
||||
func (e Entity) ParseInfo() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type Entities []*Entity
|
||||
|
||||
func (es Entities) Len() int {
|
||||
return len(es)
|
||||
}
|
||||
|
||||
func (es Entities) Less(i, j int) bool {
|
||||
return es[i].Loc[0] < es[j].Loc[0]
|
||||
}
|
||||
|
||||
func (es Entities) Swap(i, j int) {
|
||||
es[i], es[j] = es[j], es[i]
|
||||
}
|
||||
|
||||
func (es Entities) String() string {
|
||||
var result strings.Builder
|
||||
for _, entity := range es {
|
||||
result.WriteString(entity.Text)
|
||||
if entity.Type != TypePlain && len(entity.Info) > 0 {
|
||||
result.WriteString("[" + entity.Info + "] ")
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func (es Entities) ColorString() string {
|
||||
var line strings.Builder
|
||||
for _, e := range es {
|
||||
s := e.Text
|
||||
switch e.Type {
|
||||
case TypeIPv4:
|
||||
s = color.GreenString(e.Text)
|
||||
case TypeIPv6:
|
||||
s = color.BlueString(e.Text)
|
||||
case TypeDomain:
|
||||
s = color.YellowString(e.Text)
|
||||
}
|
||||
if e.Type != TypePlain && len(e.Info) > 0 {
|
||||
s += " [" + color.RedString(e.Info) + "] "
|
||||
}
|
||||
line.WriteString(s)
|
||||
}
|
||||
return line.String()
|
||||
}
|
68
internal/entity/parse.go
Normal file
68
internal/entity/parse.go
Normal file
@ -0,0 +1,68 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/zu1k/nali/internal/db"
|
||||
"github.com/zu1k/nali/internal/re"
|
||||
"github.com/zu1k/nali/pkg/dbif"
|
||||
)
|
||||
|
||||
// ParseLine parse a line into entities
|
||||
func ParseLine(line string) Entities {
|
||||
ip4sLoc := re.IPv4Re.FindAllStringIndex(line, -1)
|
||||
ip6sLoc := re.IPv6Re.FindAllStringIndex(line, -1)
|
||||
domainsLoc := re.DomainRe.FindAllStringIndex(line, -1)
|
||||
|
||||
tmp := make(Entities, 0, len(ip4sLoc)+len(ip6sLoc)+len(domainsLoc))
|
||||
for _, e := range ip4sLoc {
|
||||
tmp = append(tmp, &Entity{
|
||||
Loc: e,
|
||||
Type: TypeIPv4,
|
||||
Text: line[e[0]:e[1]],
|
||||
})
|
||||
}
|
||||
for _, e := range ip6sLoc {
|
||||
tmp = append(tmp, &Entity{
|
||||
Loc: e,
|
||||
Type: TypeIPv6,
|
||||
Text: line[e[0]:e[1]],
|
||||
})
|
||||
}
|
||||
for _, e := range domainsLoc {
|
||||
tmp = append(tmp, &Entity{
|
||||
Loc: e,
|
||||
Type: TypeDomain,
|
||||
Text: line[e[0]:e[1]],
|
||||
})
|
||||
}
|
||||
|
||||
sort.Sort(tmp)
|
||||
es := make(Entities, 0, len(tmp))
|
||||
|
||||
idx := 0
|
||||
for _, e := range tmp {
|
||||
start := e.Loc[0]
|
||||
if start >= idx {
|
||||
if start > idx {
|
||||
es = append(es, &Entity{
|
||||
Loc: []int{idx, start},
|
||||
Type: TypePlain,
|
||||
Text: line[idx:start],
|
||||
})
|
||||
}
|
||||
e.Info = db.Find(dbif.QueryType(e.Type), e.Text)
|
||||
es = append(es, e)
|
||||
idx = e.Loc[1]
|
||||
}
|
||||
}
|
||||
if total := len(line); idx < total {
|
||||
es = append(es, &Entity{
|
||||
Loc: []int{idx, total},
|
||||
Type: TypePlain,
|
||||
Text: line[idx:total],
|
||||
})
|
||||
}
|
||||
|
||||
return es
|
||||
}
|
15
internal/entity/parse_test.go
Normal file
15
internal/entity/parse_test.go
Normal file
@ -0,0 +1,15 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
fmt.Println(ParseLine("2001:0db8:85a3:0000:0000:8a2e:0370:7334 baidu.com 1.2.3.4 baidu.com"))
|
||||
fmt.Println(ParseLine("a.cn.b.com.c.org d.com"))
|
||||
}
|
||||
|
||||
func TestColorPrint(t *testing.T) {
|
||||
fmt.Println(ParseLine("2001:0db8:85a3:0000:0000:8a2e:0370:7334 baidu.com 1.2.3.4 baidu.com").ColorString())
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package ipdb
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ip db interface
|
||||
type IPDB interface {
|
||||
Find(ip string) string
|
||||
}
|
||||
|
||||
func GetIPDBType() IPDBType {
|
||||
dbname := os.Getenv("NALI_DB")
|
||||
dbname = strings.ToLower(dbname)
|
||||
switch dbname {
|
||||
case "geo", "geoip", "geoip2":
|
||||
return GEOIP2
|
||||
case "chunzhen", "qqip", "qqwry":
|
||||
return QQIP
|
||||
case "ipip", "ipipfree", "ipip.net":
|
||||
return IPIP
|
||||
default:
|
||||
return QQIP
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package ipdb
|
||||
|
||||
// ip db type
|
||||
type IPDBType int
|
||||
|
||||
const (
|
||||
GEOIP2 = iota // geoip2
|
||||
QQIP // chunzhen
|
||||
IPIP // ipip.net
|
||||
)
|
10
internal/re/re.go
Normal file
10
internal/re/re.go
Normal file
@ -0,0 +1,10 @@
|
||||
package re
|
||||
|
||||
import "regexp"
|
||||
|
||||
var (
|
||||
DomainRe = regexp.MustCompile(`[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+`)
|
||||
|
||||
IPv4Re = regexp.MustCompile(`(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}`)
|
||||
IPv6Re = regexp.MustCompile(`fe80:(:[0-9a-fA-F]{1,4}){0,4}(%\w+)?|([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?::(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?`)
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
package app
|
||||
package re
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -14,10 +14,10 @@ var domainList = []string{
|
||||
|
||||
func TestDomainRe(t *testing.T) {
|
||||
for _, domain := range domainList {
|
||||
if !domainRe.MatchString(domain) {
|
||||
if !DomainRe.MatchString(domain) {
|
||||
t.Error(domain)
|
||||
t.Fail()
|
||||
}
|
||||
fmt.Println(domainRe.FindAllString(domain, -1))
|
||||
fmt.Println(DomainRe.FindAllString(domain, -1))
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ipv4re *regexp.Regexp
|
||||
|
||||
ipv6re0 *regexp.Regexp
|
||||
ipv6re *regexp.Regexp
|
||||
)
|
||||
|
||||
func init() {
|
||||
ipv4re = regexp.MustCompile(`(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}`)
|
||||
|
||||
ipv6re0 = regexp.MustCompile(`^fe80:(:[0-9a-fA-F]{1,4}){0,4}(%\w+)?|([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?::(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?$`)
|
||||
ipv6re = regexp.MustCompile(`fe80:(:[0-9a-fA-F]{1,4}){0,4}(%\w+)?|([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?::(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?`)
|
||||
}
|
||||
|
||||
func GetIP4FromString(str string) []string {
|
||||
str = strings.Trim(str, " ")
|
||||
return ipv4re.FindAllString(str, -1)
|
||||
}
|
||||
|
||||
func GetIP6FromString(str string) []string {
|
||||
str = strings.Trim(str, " ")
|
||||
return ipv6re.FindAllString(str, -1)
|
||||
}
|
||||
|
||||
const (
|
||||
ValidIPv4 = iota
|
||||
ValidIPv6
|
||||
InvalidIP
|
||||
)
|
||||
|
||||
type Valid int
|
||||
|
||||
func ValidIP(IP string) (v Valid) {
|
||||
for i := 0; i < len(IP); i++ {
|
||||
switch IP[i] {
|
||||
case '.':
|
||||
v = ValidIPv4
|
||||
case ':':
|
||||
v = ValidIPv6
|
||||
}
|
||||
}
|
||||
if ip := net.ParseIP(IP); ip != nil {
|
||||
return v
|
||||
}
|
||||
return InvalidIP
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIP4Re(t *testing.T) {
|
||||
str := "aaa1.1.11.23a36.36.32.200"
|
||||
fmt.Println(GetIP4FromString(str))
|
||||
ValidIP(str)
|
||||
}
|
||||
|
||||
func TestValidIP6(t *testing.T) {
|
||||
ipv6Valid := []string{
|
||||
"1:2:3:4:5:6:7::",
|
||||
"1:2:3:4:5:6:7:8",
|
||||
|
||||
"1:2:3:4:5:6::",
|
||||
"1:2:3:4:5:6::8",
|
||||
|
||||
"1:2:3:4:5::",
|
||||
"1:2:3:4:5::8",
|
||||
|
||||
"1:2:3:4::",
|
||||
"1:2:3:4::8",
|
||||
|
||||
"1:2:3::",
|
||||
"1:2:3::8",
|
||||
|
||||
"1:2::",
|
||||
"1:2::8",
|
||||
|
||||
"1::",
|
||||
"1::8",
|
||||
|
||||
"::",
|
||||
"::8",
|
||||
"::7:8",
|
||||
"::6:7:8",
|
||||
"::5:6:7:8",
|
||||
"::4:5:6:7:8",
|
||||
"::3:4:5:6:7:8",
|
||||
"::2:3:4:5:6:7:8",
|
||||
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
|
||||
|
||||
"::192.168.1.1",
|
||||
"::ffff:135.75.43.52",
|
||||
"A:0f:0F:FFFF:5:6:7:8",
|
||||
}
|
||||
|
||||
ipv6Invalid := []string{
|
||||
"A:0f:0F:FFFF1:5:6:7:8",
|
||||
"G:0f:0F:FFFF:5:6:7:8",
|
||||
"2001::25de::cade",
|
||||
"2001:0db8:85a3:0:0:8A2E:0370:73341",
|
||||
"a1:a2:a3:a4::b1:b2:b3:b4",
|
||||
}
|
||||
|
||||
for _, i := range ipv6Valid {
|
||||
if v := ValidIP(i); v == InvalidIP {
|
||||
t.Log("valid:", i)
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range ipv6Invalid {
|
||||
if v := ValidIP(i); v != InvalidIP {
|
||||
t.Log("invalid:", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkValidIP6STD(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
origin := "::ffff:135.75.43.52"
|
||||
for i := 0; i < b.N; i++ {
|
||||
ValidIP(origin)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
IPv6-addr = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp
|
||||
|
||||
IPv6-hex = 1*4HEXDIG
|
||||
|
||||
IPv6-full = IPv6-hex 7(":" IPv6-hex)
|
||||
|
||||
IPv6-comp = [IPv6-hex *5(":" IPv6-hex)] "::"
|
||||
[IPv6-hex *5(":" IPv6-hex)]
|
||||
; The "::" represents at least 2 16-bit groups of
|
||||
; zeros. No more than 6 groups in addition to the
|
||||
; "::" may be present.
|
||||
|
||||
IPv6v4-full = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal
|
||||
|
||||
IPv6v4-comp = [IPv6-hex *3(":" IPv6-hex)] "::"
|
||||
[IPv6-hex *3(":" IPv6-hex) ":"]
|
||||
IPv4-address-literal
|
||||
; The "::" represents at least 2 16-bit groups of
|
||||
; zeros. No more than 4 groups in addition to the
|
||||
; "::" and IPv4-address-literal may be present.
|
||||
*/
|
@ -1,24 +0,0 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func AddInfoIp4(origin string, ip string, info string) (result string) {
|
||||
re := regexp.MustCompile("(^|[^0-9.])(" + strings.ReplaceAll(ip, ".", "\\.") + ")($|[^0-9.])")
|
||||
result = re.ReplaceAllString(origin, "$1$2"+" ["+info+"]$3")
|
||||
return strings.TrimRight(result, " \t")
|
||||
}
|
||||
|
||||
func AddInfoIp6(origin string, ip string, info string) (result string) {
|
||||
re := regexp.MustCompile("(^|[^0-9a-fA-F:])(" + strings.ReplaceAll(ip, ".", "\\.") + ")($|[^0-9a-fA-F:])")
|
||||
result = re.ReplaceAllString(origin, "$1$2"+" ["+info+"]$3")
|
||||
return strings.TrimRight(result, " \t")
|
||||
}
|
||||
|
||||
func AddInfoDomain(origin string, domain string, info string) (result string) {
|
||||
re := regexp.MustCompile("(^|[^0-9a-zA-Z-])(" + strings.ReplaceAll(domain, ".", "\\.") + ")($|[^0-9a-zA-Z-\\.])")
|
||||
result = re.ReplaceAllString(origin, "$1$2"+" ["+info+"]$3")
|
||||
return strings.TrimRight(result, " \t")
|
||||
}
|
23
main.go
23
main.go
@ -1,32 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/zu1k/nali/cmd"
|
||||
"github.com/zu1k/nali/constant"
|
||||
)
|
||||
|
||||
func main() {
|
||||
setHomePath()
|
||||
cmd.Execute()
|
||||
}
|
||||
|
||||
func setHomePath() {
|
||||
homePath := os.Getenv("NALI_DB_HOME")
|
||||
if homePath == "" {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
homePath = filepath.Join(homeDir, ".nali")
|
||||
}
|
||||
constant.HomePath = homePath
|
||||
if _, err := os.Stat(homePath); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(homePath, 0777); err != nil {
|
||||
log.Fatal("can not create", homePath, ", use bin dir instead")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,12 @@ package cdn
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CDN struct {
|
||||
@ -18,7 +21,11 @@ type CDNResult struct {
|
||||
Link string `json:"link"`
|
||||
}
|
||||
|
||||
func NewCDN(filePath string) CDN {
|
||||
func (r CDNResult) String() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func NewCDN(filePath string) *CDN {
|
||||
cdnDist := make(CDNDist)
|
||||
cdnData := make([]byte, 0)
|
||||
|
||||
@ -46,5 +53,32 @@ func NewCDN(filePath string) CDN {
|
||||
if err != nil {
|
||||
panic("cdn data parse failed!")
|
||||
}
|
||||
return CDN{Data: cdnDist}
|
||||
return &CDN{Data: cdnDist}
|
||||
}
|
||||
|
||||
func (db CDN) Find(query string, params ...string) (result fmt.Stringer, err error) {
|
||||
baseCname := parseBaseCname(query)
|
||||
if baseCname == "" {
|
||||
return nil, errors.New("base domain parse failed")
|
||||
}
|
||||
cdnResult, found := db.Data[baseCname]
|
||||
if found {
|
||||
return cdnResult, nil
|
||||
}
|
||||
|
||||
if strings.Contains(baseCname, "kunlun") {
|
||||
return CDNResult{
|
||||
Name: "阿里云 CDN",
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
func parseBaseCname(domain string) string {
|
||||
hostParts := strings.Split(domain, ".")
|
||||
if len(hostParts) < 2 {
|
||||
return domain
|
||||
}
|
||||
baseCname := hostParts[len(hostParts)-2] + "." + hostParts[len(hostParts)-1]
|
||||
return baseCname
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
package common
|
||||
|
||||
import "os"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// FileData: info of db file
|
||||
// FileData: info of database file
|
||||
type FileData struct {
|
||||
Data []byte
|
||||
FilePath string
|
||||
@ -85,3 +88,12 @@ func GetMiddleOffset(start uint32, end uint32, indexLen uint32) uint32 {
|
||||
records := ((end - start) / indexLen) >> 1
|
||||
return start + records*indexLen
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Country string
|
||||
Area string
|
||||
}
|
||||
|
||||
func (r Result) String() string {
|
||||
return fmt.Sprintf("%s %s", r.Country, r.Area)
|
||||
}
|
||||
|
33
pkg/dbif/db.go
Normal file
33
pkg/dbif/db.go
Normal file
@ -0,0 +1,33 @@
|
||||
package dbif
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/zu1k/nali/pkg/cdn"
|
||||
|
||||
"github.com/zu1k/nali/pkg/geoip"
|
||||
|
||||
"github.com/zu1k/nali/pkg/ipip"
|
||||
"github.com/zu1k/nali/pkg/qqwry"
|
||||
"github.com/zu1k/nali/pkg/zxipv6wry"
|
||||
)
|
||||
|
||||
type QueryType uint
|
||||
|
||||
const (
|
||||
TypeIPv4 = iota
|
||||
TypeIPv6
|
||||
TypeDomain
|
||||
)
|
||||
|
||||
type DB interface {
|
||||
Find(query string, params ...string) (result fmt.Stringer, err error)
|
||||
}
|
||||
|
||||
var (
|
||||
_ DB = qqwry.QQwry{}
|
||||
_ DB = zxipv6wry.ZXwry{}
|
||||
_ DB = ipip.IPIPFree{}
|
||||
_ DB = geoip.GeoIP{}
|
||||
_ DB = cdn.CDN{}
|
||||
)
|
@ -1,6 +1,7 @@
|
||||
package geoip
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
@ -14,7 +15,7 @@ type GeoIP struct {
|
||||
db *geoip2.Reader
|
||||
}
|
||||
|
||||
// new geoip from db file
|
||||
// new geoip from database file
|
||||
func NewGeoIP(filePath string) (geoip GeoIP) {
|
||||
// 判断文件是否存在
|
||||
_, err := os.Stat(filePath)
|
||||
@ -31,18 +32,39 @@ func NewGeoIP(filePath string) (geoip GeoIP) {
|
||||
return
|
||||
}
|
||||
|
||||
// find ip info
|
||||
func (g GeoIP) Find(ip string) string {
|
||||
ipData := net.ParseIP(ip)
|
||||
record, err := g.db.City(ipData)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
func (g GeoIP) Find(query string, params ...string) (result fmt.Stringer, err error) {
|
||||
ip := net.ParseIP(query)
|
||||
if ip == nil {
|
||||
return nil, errors.New("Query should be valid IP")
|
||||
}
|
||||
country := record.Country.Names["zh-CN"]
|
||||
city := record.City.Names["zh-CN"]
|
||||
if city == "" {
|
||||
return country
|
||||
record, err := g.db.City(ip)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lang := "zh-CN"
|
||||
if len(params) > 0 {
|
||||
if _, ok := record.Country.Names[params[0]]; ok {
|
||||
lang = params[0]
|
||||
}
|
||||
}
|
||||
|
||||
result = Result{
|
||||
Country: record.Country.Names[lang],
|
||||
City: record.City.Names[lang],
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Country string
|
||||
City string
|
||||
}
|
||||
|
||||
func (r Result) String() string {
|
||||
if r.City == "" {
|
||||
return r.Country
|
||||
} else {
|
||||
return fmt.Sprintf("%s %s", country, city)
|
||||
return fmt.Sprintf("%s %s", r.Country, r.City)
|
||||
}
|
||||
}
|
||||
|
@ -30,15 +30,30 @@ func NewIPIPFree(filePath string) IPIPFree {
|
||||
}
|
||||
}
|
||||
|
||||
func (db IPIPFree) Find(ip string) string {
|
||||
info, err := db.FindInfo(ip, "CN")
|
||||
if err != nil {
|
||||
log.Fatalln("IPIP 查询失败:", err.Error())
|
||||
return ""
|
||||
type Result struct {
|
||||
Country string
|
||||
Region string
|
||||
City string
|
||||
}
|
||||
|
||||
func (r Result) String() string {
|
||||
if r.City == "" {
|
||||
return fmt.Sprintf("%s %s", r.Country, r.Region)
|
||||
}
|
||||
return fmt.Sprintf("%s %s %s", r.Country, r.Region, r.City)
|
||||
}
|
||||
|
||||
func (db IPIPFree) Find(query string, params ...string) (result fmt.Stringer, err error) {
|
||||
info, err := db.FindInfo(query, "CN")
|
||||
if err != nil || info == nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if info.CityName == "" {
|
||||
return fmt.Sprintf("%s %s", info.CountryName, info.RegionName)
|
||||
// info contains more info
|
||||
result = Result{
|
||||
Country: info.CountryName,
|
||||
Region: info.RegionName,
|
||||
City: info.CityName,
|
||||
}
|
||||
return fmt.Sprintf("%s %s %s", info.CountryName, info.RegionName, info.CityName)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package qqwry
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@ -17,7 +18,7 @@ type QQwry struct {
|
||||
common.IPDB
|
||||
}
|
||||
|
||||
// NewQQwry new db from path
|
||||
// NewQQwry new database from path
|
||||
func NewQQwry(filePath string) QQwry {
|
||||
var fileData []byte
|
||||
var fileInfo common.FileData
|
||||
@ -55,25 +56,28 @@ func NewQQwry(filePath string) QQwry {
|
||||
}
|
||||
}
|
||||
|
||||
// Find ip地址查询对应归属地信息
|
||||
func (db QQwry) Find(ip string) (res string) {
|
||||
if strings.Count(ip, ".") != 3 {
|
||||
return
|
||||
func (db QQwry) Find(query string, params ...string) (result fmt.Stringer, err error) {
|
||||
ip := net.ParseIP(query)
|
||||
if ip == nil {
|
||||
return nil, errors.New("Query should be IPv4")
|
||||
}
|
||||
ip4 := ip.To4()
|
||||
if ip4 == nil {
|
||||
return nil, errors.New("Query should be IPv4")
|
||||
}
|
||||
ip4uint := binary.BigEndian.Uint32(ip4)
|
||||
|
||||
ip4 := binary.BigEndian.Uint32(net.ParseIP(ip).To4())
|
||||
|
||||
offset := db.searchIndex(ip4)
|
||||
offset := db.searchIndex(ip4uint)
|
||||
if offset <= 0 {
|
||||
return
|
||||
return nil, errors.New("Query not valid")
|
||||
}
|
||||
|
||||
var gbkCountry []byte
|
||||
var gbkArea []byte
|
||||
|
||||
mode := db.ReadMode(offset + 4)
|
||||
// [IP][0x01][国家和地区信息的绝对偏移地址]
|
||||
if mode == common.RedirectMode1 {
|
||||
switch mode {
|
||||
case common.RedirectMode1: // [IP][0x01][国家和地区信息的绝对偏移地址]
|
||||
countryOffset := db.ReadUInt24()
|
||||
mode = db.ReadMode(countryOffset)
|
||||
if mode == common.RedirectMode2 {
|
||||
@ -85,11 +89,11 @@ func (db QQwry) Find(ip string) (res string) {
|
||||
countryOffset += uint32(len(gbkCountry) + 1)
|
||||
}
|
||||
gbkArea = db.ReadArea(countryOffset)
|
||||
} else if mode == common.RedirectMode2 {
|
||||
case common.RedirectMode2:
|
||||
countryOffset := db.ReadUInt24()
|
||||
gbkCountry = db.ReadString(countryOffset)
|
||||
gbkArea = db.ReadArea(offset + 8)
|
||||
} else {
|
||||
default:
|
||||
gbkCountry = db.ReadString(offset + 4)
|
||||
gbkArea = db.ReadArea(offset + uint32(5+len(gbkCountry)))
|
||||
}
|
||||
@ -98,13 +102,11 @@ func (db QQwry) Find(ip string) (res string) {
|
||||
country, _ := enc.String(string(gbkCountry))
|
||||
area, _ := enc.String(string(gbkArea))
|
||||
|
||||
country = strings.ReplaceAll(country, " CZ88.NET", "")
|
||||
area = strings.ReplaceAll(area, " CZ88.NET", "")
|
||||
|
||||
if area == "" {
|
||||
return country
|
||||
result = common.Result{
|
||||
Country: strings.ReplaceAll(country, " CZ88.NET", ""),
|
||||
Area: strings.ReplaceAll(area, " CZ88.NET", ""),
|
||||
}
|
||||
return fmt.Sprintf("%s %s", country, area)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// searchIndex 查找索引位置
|
||||
|
@ -2,6 +2,7 @@ package zxipv6wry
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@ -50,10 +51,19 @@ func NewZXwry(filePath string) ZXwry {
|
||||
}
|
||||
}
|
||||
|
||||
func (db ZXwry) Find(ip string) (result string) {
|
||||
func (db ZXwry) Find(query string, params ...string) (result fmt.Stringer, err error) {
|
||||
ip := net.ParseIP(query)
|
||||
if ip == nil {
|
||||
return nil, errors.New("Query should be IPv6")
|
||||
}
|
||||
ip6 := ip.To16()
|
||||
if ip6 == nil {
|
||||
return nil, errors.New("Query should be IPv6")
|
||||
}
|
||||
|
||||
tp := big.NewInt(0)
|
||||
op := big.NewInt(0)
|
||||
tp.SetBytes(net.ParseIP(ip).To16())
|
||||
tp.SetBytes(ip6)
|
||||
op.SetString("18446744073709551616", 10)
|
||||
op.Div(tp, op)
|
||||
tp.SetString("FFFFFFFFFFFFFFFF", 16)
|
||||
@ -63,10 +73,11 @@ func (db ZXwry) Find(ip string) (result string) {
|
||||
offset := db.searchIndex(ipv6)
|
||||
country, area := db.getAddr(offset)
|
||||
|
||||
country = strings.ReplaceAll(country, " CZ88.NET", "")
|
||||
area = strings.ReplaceAll(area, " CZ88.NET", "")
|
||||
|
||||
return fmt.Sprintf("%s %s", country, area)
|
||||
result = common.Result{
|
||||
Country: strings.ReplaceAll(country, " CZ88.NET", ""),
|
||||
Area: strings.ReplaceAll(area, " CZ88.NET", ""),
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (db *ZXwry) getAddr(offset uint32) (string, string) {
|
||||
|
Loading…
Reference in New Issue
Block a user