Skip to content

Commit

Permalink
feat: finish first version
Browse files Browse the repository at this point in the history
  • Loading branch information
yourtion committed Dec 2, 2020
1 parent 010f5b2 commit b756d6c
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 42 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ os:
- osx

node_js:
- 11
- 12
- 13
- 14
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
[coveralls-url]: https://coveralls.io/r/yourtion/node-ip2region?branch=master
[david-image]: https://img.shields.io/david/yourtion/node-ip2region.svg?style=flat-square
[david-url]: https://david-dm.org/yourtion/node-ip2region
[node-image]: https://img.shields.io/badge/node.js-%3E=4.0-green.svg?style=flat-square
[node-image]: https://img.shields.io/badge/node.js-%3E=12.0-green.svg?style=flat-square
[node-url]: http://nodejs.org/download/
[download-image]: https://img.shields.io/npm/dm/ip2region.svg?style=flat-square
[download-url]: https://npmjs.org/package/ip2region
[license-image]: https://img.shields.io/npm/l/ip2region.svg

# node-ip2region

IP 地址到区域运营商 IP to region on Node.js
IP 地址到区域运营商 IP(支持IPv6) to region on Node.js

## 安装使用使用

Expand All @@ -37,5 +37,8 @@ import IP2Region from "ip2region";
const query = new IP2Region();
const res = query.search('120.24.78.68');
console.log(res);
> { id: 2163, country: '中国', region: '华南', province: '广东省', city: '深圳市', isp: '阿里云' }
> { country: '中国', province: '广东省', city: '深圳市', isp: '阿里云' }
const res2 = query.search('240e:47d:c20:1627:30a3:ba0d:a5e6:ec19');
console.log(res2);
> { country: "中国", province: "广东省", city: "0", isp: "中国电信" }
```
17 changes: 13 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ip2region",
"version": "2.0.0",
"description": "ip to geo database, IP 地址到区域运营商",
"description": "ip/ipv6 to geo database, IP(支持IPv6)地址到区域运营商",
"keywords": [
"ip",
"ipaddress-to-address",
Expand All @@ -10,7 +10,15 @@
"ip-region",
"ip-lookup",
"ip-search",
"ip-geo"
"ip-geo",
"ipv6",
"ipv6address-to-address",
"ipv6-address",
"ipv6-location",
"ipv6-region",
"ipv6-lookup",
"ipv6-search",
"ipv6-geo"
],
"main": "dist/lib/index.js",
"repository": {
Expand All @@ -21,8 +29,9 @@
"url": "https:/yourtion/node-ip2region/issues"
},
"files": [
"lib/index.js",
"data/ip2region.db"
"dist/lib",
"data/ip2region.db",
"data/ipv6wry.db"
],
"homepage": "https:/yourtion/node-ip2region#readme",
"scripts": {
Expand Down
54 changes: 52 additions & 2 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,66 @@
import Ipv4ToRegion from "./ipv4";
import Ipv4ToRegion, { Ipv4ToRegionResult, Ipv4ToRegionRes } from "./ipv4";
import Ipv6ToRegion, { Ipv6ToRegionResult, Ipv6ToRegionRes } from "./ipv6";
import { isIP } from "net";

/** IP2Region 配置 */
export interface IP2RegionOpts {
/** ipv4 数据库地址 */
ipv4db?: string;
/** ipv6 数据库地址 */
ipv6db?: string;
/** 关闭 ipv6 */
disableIpv6?: boolean;
}

export interface IP2RegionResult {
/** 国家 */
country: string;
/** 省份 */
province: string;
/** 城市 */
city: string;
/** ISP 供应商 */
isp: string;
}

export default class IP2Region {
private ipv4: Ipv4ToRegion;
private ipv6: Ipv6ToRegion | null = null;

constructor(opts: IP2RegionOpts = {}) {
this.ipv4 = new Ipv4ToRegion(opts.ipv4db);
if (!opts.disableIpv6) {
this.ipv6 = new Ipv6ToRegion(opts.ipv6db);
this.ipv6.setIpv4Ins(this.ipv4);
}
}

search(ipaddr: string, parse = true) {
/**
* 原始搜索
* @param ipaddr IP 地址
*/
searchRaw(ipaddr: string): Ipv6ToRegionResult | Ipv4ToRegionResult | null;
/**
* 原始搜索
* @param ipaddr IP 地址
* @param parse 是否解析
*/
searchRaw(ipaddr: string, parse: boolean): Ipv4ToRegionRes | Ipv6ToRegionRes | null;
searchRaw(ipaddr: string, parse = true) {
const v = isIP(ipaddr);
if (v === 6 && this.ipv6) {
return this.ipv6.search(ipaddr, parse);
}
return this.ipv4.search(ipaddr, parse);
}

/**
* 搜索
* @param ipaddr IP 地址
*/
search(ipaddr: string): IP2RegionResult | null {
const res = this.searchRaw(ipaddr);
if (res === null) return res;
return { country: res.country, province: res.province, city: res.city, isp: res.isp };
}
}
13 changes: 5 additions & 8 deletions src/lib/ipv4.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ export default class Ipv4ToRegion {
}
}

private parseResult(res: Ipv4ToRegionRes) {
parseResult(res: Ipv4ToRegionRes | null) {
if (res === null) return res;
// 城市Id|国家|区域|省份|城市|ISP
const data = res.region.split("|");
return {
Expand All @@ -102,7 +103,7 @@ export default class Ipv4ToRegion {
};
}

searchLong(ip: number) {
searchLong(ip: number): Ipv4ToRegionRes | null {
let low = 0;
let mid = 0;
let high = this.headerLen;
Expand Down Expand Up @@ -188,7 +189,7 @@ export default class Ipv4ToRegion {

debug(city_id);
debug(data);
return { city_id, data };
return { city: city_id, region: data };
}

search(ipaddr: string): Ipv4ToRegionResult;
Expand All @@ -198,10 +199,6 @@ export default class Ipv4ToRegion {
const ip = ipv4ToLong(ipaddr);
// first search (in header index)
const ret = this.searchLong(ip);
if (ret === null) {
return ret;
}
const { city_id, data } = ret;
return parse ? this.parseResult({ city: city_id, region: data }) : { city: city_id, region: data };
return parse ? this.parseResult(ret) : ret;
}
}
100 changes: 96 additions & 4 deletions src/lib/ipv6.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,38 @@ import { createDebug, ipv6ToLong } from "./utils";
import { existsSync, readFileSync } from "fs";
import { resolve as pathResolve, isAbsolute } from "path";
import { isIPv6 } from "net";
import Ipv4ToRegion from "./ipv4";
import Ipv4ToRegion, { Ipv4ToRegionRes, Ipv4ToRegionResult } from "./ipv4";

const debug = createDebug("ipv6");
const FF = 0xffffffffffffffffn;
const N64 = 64n;

/**
* IP 结果
*/
export interface Ipv6ToRegionRes {
/** 区域字符串 */
cArea: string;
/** 运营商 */
aArea: string;
}

/**
* IP 解析结果
*/
export interface Ipv6ToRegionResult {
/** 国家 */
country: string;
/** 省份 */
province: string;
/** 城市 */
city: string;
/** ISP 供应商 */
isp: string;
/** 原始数据 */
data: string;
}

export default class Ipv6ToRegion {
/** 数据库文件位置 */
private dbFilePath: string;
Expand Down Expand Up @@ -111,7 +137,7 @@ export default class Ipv6ToRegion {
* 获取地址信息
* @param offset 偏移量
*/
private getAddr(offset: number): { cArea: string; aArea: string } {
private getAddr(offset: number): Ipv6ToRegionRes {
let o = offset;
const byte = this.data.readInt8(o);
debug(byte);
Expand All @@ -133,6 +159,70 @@ export default class Ipv6ToRegion {
}
}

private parseResult(ret: Ipv4ToRegionRes | Ipv6ToRegionRes | null): Ipv4ToRegionResult | Ipv6ToRegionResult | null {
if (ret === null) return ret;
if ("city" in ret) {
return this.ipv4 ? this.ipv4.parseResult(ret) : null;
}
let country = "0";
let province = "0";
let city = "0";
let first = ret.cArea.indexOf("国");
if (first >= 0) {
first += 1;
country = ret.cArea.slice(0, first);
} else {
first = 0;
}
let isCity = false;
let second = ret.cArea.indexOf("省");
if (second >= 0) {
second += 1;
province = ret.cArea.slice(first, second);
} else {
const provinceArr = ["内蒙古", "广西", "西藏", "宁夏", "新疆"];
const goverCity = ["北京市", "天津市", "上海市", "重庆市"];
for (let gover of goverCity) {
second = ret.cArea.indexOf(gover);
if (second >= 0) {
second = second + gover.length;
province = ret.cArea.slice(first, second);
isCity = true;
break;
}
}
if (isCity != true) {
for (let province of provinceArr) {
second = ret.cArea.indexOf(province);
if (second >= 0) {
second = second + province.length;
province = ret.cArea.slice(first, second);
break;
}
}
}
}
if (isCity != true) {
const city1 = ret.cArea.indexOf("市");
const city2 = ret.cArea.indexOf("州");
// fmt.Println("second", second, city1, city2, string(value))
if (city1 >= 0 && city2 >= 0 && city1 < city2 && second < city1) {
city = ret.cArea.slice(second, city1 + 1);
} else if (city1 >= 0 && city2 >= 0 && city1 > city2 && second < city2) {
if (city1 - city2 == 1) {
city = ret.cArea.slice(second, city2 + 2);
} else {
city = ret.cArea.slice(second, city2 + 1);
}
} else if (city1 >= 0 && second < city1) {
city = ret.cArea.slice(second, city1 + 1);
} else if (city2 >= 0 && second < city2) {
city = ret.cArea.slice(second, city2 + 1);
}
}
return { isp: ret.aArea, data: ret.cArea, country, province, city };
}

private getIpAddrLong(ip: bigint) {
// 查找ip的索引偏移
const idx = this.find(ip, 0, this.record);
Expand Down Expand Up @@ -180,11 +270,13 @@ export default class Ipv6ToRegion {
return this.getIpAddrLong(ip);
}

search(ipaddr: string) {
search(ipaddr: string, parse = true) {
// 把IP地址转成数字
const { ip, num } = ipv6ToLong(ipaddr);
if (!isIPv6(ip)) return null;
debug({ ipaddr, ip, num });
return this.searchLong(num);
const ret = this.searchLong(num);
debug(ret);
return parse ? this.parseResult(ret) : ret;
}
}
35 changes: 24 additions & 11 deletions src/test/test_ipv6.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,27 @@ const queryInMemoey = new Ipv6ToRegion();
queryInMemoey.setIpv4Ins(v4);

const IP1 = "240e:47d:c20:1627:30a3:ba0d:a5e6:ec19";
const RET1 = Object.freeze({ aArea: "中国电信", cArea: "中国广东省" });
const RET1 = Object.freeze({ city: "0", country: "中国", data: "中国广东省", isp: "中国电信", province: "广东省" });
const NEIWAN_IP = "0:0:0:0:0:0:0:1";
const NEIWAN2 = Object.freeze({ aArea: "本机地址", cArea: "IANA保留地址" });
const NEIWAN2 = Object.freeze({ city: "0", country: "0", data: "IANA保留地址", isp: "本机地址", province: "0" });
const IP4on6 = "0000:0000:0000:0000:0000:0000:135.75.43.52";
const IP4on6_RET = Object.freeze({ city_id: 166, data: "美国|0|0|0|美国电话电报" });
const IP4on6_RET = Object.freeze({ city: 166, region: "美国|0|0|0|美国电话电报" });
const IP6to4 = "2002:0C9B:A665:0001:0000:0000:0C9B:A665";
const IP6to4_RET = Object.freeze({ city_id: 0, data: "美国|0|加利福尼亚|0|美国电话电报" });
const IP6to4_RET = Object.freeze({ city: 0, region: "美国|0|加利福尼亚|0|美国电话电报" });
const IPTeredo = "2002:0C9B:A665:0001:0000:0000:874b:2b34";
const IPTeredo_RET = Object.freeze({ city_id: 0, data: "美国|0|加利福尼亚|0|美国电话电报" });
const IPTeredo_RET = Object.freeze({ city: 0, region: "美国|0|加利福尼亚|0|美国电话电报" });
const IPISATAP = "fe80::200:5efe:874b:2b34";
const IPISATAP_RET = Object.freeze({ city_id: 166, data: "美国|0|0|0|美国电话电报" });
const IPISATAP_RET = Object.freeze({ city: 166, region: "美国|0|0|0|美国电话电报" });
const IP2 = "2406:840::1";
const RET2 = Object.freeze({ aArea: "ZX Network Anycast网段", cArea: "全球" });
const RET2 = Object.freeze({ isp: "ZX Network Anycast网段", data: "全球", city: "0", country: "0", province: "0" });
const ALIYUN = "2400:3200::1";
const ALIYUN_RET = Object.freeze({
city: "杭州市",
country: "中国",
data: "中国浙江省杭州市",
isp: "阿里云计算有限公司",
province: "浙江省",
});

describe("search", function () {
it("Found", function () {
Expand All @@ -32,25 +40,30 @@ describe("search", function () {
});

it("Found - 4on6", function () {
const res = queryInMemoey.search(IP4on6);
const res = queryInMemoey.search(IP4on6, false);
expect(res).toMatchObject(IP4on6_RET);
});

it("Found - 6to4", function () {
const res = queryInMemoey.search(IP6to4);
const res = queryInMemoey.search(IP6to4, false);
expect(res).toMatchObject(IP6to4_RET);
});

it("Found - Teredo", function () {
const res = queryInMemoey.search(IPTeredo);
const res = queryInMemoey.search(IPTeredo, false);
expect(res).toMatchObject(IPTeredo_RET);
});

it("Found - ISATAP", function () {
const res = queryInMemoey.search(IPISATAP);
const res = queryInMemoey.search(IPISATAP, false);
expect(res).toMatchObject(IPISATAP_RET);
});

it("Found - ALIYUN", function () {
const res = queryInMemoey.search(ALIYUN);
expect(res).toMatchObject(ALIYUN_RET);
});

it("Found", function () {
const res = queryInMemoey.search(IP2);
expect(res).toMatchObject(RET2);
Expand Down
Loading

0 comments on commit b756d6c

Please sign in to comment.