From cce775c370961fddaf89a16d65430b9f5a294314 Mon Sep 17 00:00:00 2001 From: memorz Date: Sun, 9 Feb 2020 18:33:36 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E7=BD=91=E5=8D=A1=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- run.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/run.py b/run.py index 181c9eb5..cc2090fc 100755 --- a/run.py +++ b/run.py @@ -88,6 +88,8 @@ def get_ip(ip_type): return False elif str(index).isdigit(): # 数字 local eth value = getattr(ip, "local_v" + ip_type)(index) + elif index.startswith('interface:'): # 自定义网卡界面比如eno1 + value = getattr(ip, "interface_v" + ip_type)(index[10:]) elif index.startswith('cmd:'): # cmd value = str(check_output(index[4:]).strip().decode('utf-8')) elif index.startswith('shell:'): # shell From eb94adccce4224b666630b83a7fe0546734659ac Mon Sep 17 00:00:00 2001 From: memorz Date: Sun, 9 Feb 2020 18:34:46 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E6=8C=87=E5=AE=9A=E7=BD=91=E5=8D=A1IP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- util/ip.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/util/ip.py b/util/ip.py index 229f1abe..8ba441c1 100644 --- a/util/ip.py +++ b/util/ip.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding:utf-8 -*- - +import netifaces as ni from re import compile from os import name as os_name, popen from socket import socket, getaddrinfo, gethostname, AF_INET, AF_INET6, SOCK_DGRAM @@ -46,6 +46,18 @@ def local_v4(i=0): # 本地ipv4地址 debug(info) return info[int(i)][-1][0] +def interface_v6(interface=None): # 本地网卡的ipv6地址 + debug("open: %s", interface) + info = ni.ifaddresses(interface)[ni.AF_INET6][0]['addr'] + return info + + +def interface_v4(interface=None): # 本地网卡的ipv4地址 + debug("open: %s", interface) + info = ni.ifaddresses()[ni.AF_INET][0]['addr'] + debug(info) + return info + def _open(url, reg): try: From ee3e3ae19279d061a2950f72a19ecd58dce31ba5 Mon Sep 17 00:00:00 2001 From: memorz Date: Sun, 9 Feb 2020 18:35:45 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E6=8C=87=E5=AE=9A=E7=BD=91=E5=8D=A1?= =?UTF-8?q?=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2c42f784..7d082bb0 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ python run.py -c /path/to/config.json - 数字(`0`,`1`,`2`,`3`等): 第 i 个网卡 ip - 字符串`"default"`(或者无此项): 系统访问外网默认 IP - 字符串`"public"`: 使用公网 ip(使用公网 API 查询,url 的简化模式) +- 字符串`"interface"`: 使用指定网卡 ip(如:`"interface:eno1"`) - 字符串`"url:xxx"`: 打开 URL `xxx`(如:`"url:http://ip.sb"`),从返回的数据提取 IP 地址 - 字符串`"regex:xxx"` 正则表达(如`"regex:192.*"`): 提取`ifconfig`/`ipconfig`中与之匹配的首个 IP 地址,**注意 json 转义**(`\`要写成`\\`) - `"192.*"`表示 192 开头的所有 ip From b7fccc3ef5210d3fa0be67d8d372e01ea36ee724 Mon Sep 17 00:00:00 2001 From: memorz Date: Mon, 24 Feb 2020 10:34:08 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8D=8E=E4=B8=BADNS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加华为DNS --- dns/huaweidns.py | 247 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 dns/huaweidns.py diff --git a/dns/huaweidns.py b/dns/huaweidns.py new file mode 100644 index 00000000..0b867ef7 --- /dev/null +++ b/dns/huaweidns.py @@ -0,0 +1,247 @@ +# coding=utf-8 +""" +HuaweiDNS API +华为DNS解析操作库 +https://support.huaweicloud.com/api-dns/zh-cn_topic_0037134406.html +@author: New Future、TongYifan、cybmp3 +""" + +import hashlib +import hmac +import binascii +import json +from uuid import uuid4 +from base64 import b64encode + +#from json import loads as jsondecode + +from logging import debug, info, warning +from datetime import datetime + +try: + # python 2 + from httplib import HTTPSConnection + from urllib import urlencode, quote_plus, quote +except ImportError: + # python 3 + from http.client import HTTPSConnection + from urllib.parse import urlencode, quote_plus, quote + +__author__ = 'New Future' +BasicDateFormat = "%Y%m%dT%H%M%SZ" +Algorithm = "SDK-HMAC-SHA256" + + +# __all__ = ["request", "ID", "TOKEN", "PROXY"] + + +class Config: + ID = "id" # AK + TOKEN = "TOKEN" # AS + PROXY = None # 代理设置 + TTL = None + + +class API: + # API 配置 + SCHEME = 'https' + SITE = 'dns.myhuaweicloud.com' # API endpoint + + +def HexEncodeSHA256Hash(data): + sha256 = hashlib.sha256() + sha256.update(data) + return sha256.hexdigest() + + +def StringToSign(canonical_request, t): + b = HexEncodeSHA256Hash(canonical_request) + return "%s\n%s\n%s" % (Algorithm, datetime.strftime(t, BasicDateFormat), b) + + +def CanonicalHeaders(headers, signed_headers): + a = [] + __headers = {} + for key in headers: + key_encoded = key.lower() + value = headers[key] + value_encoded = value.strip() + __headers[key_encoded] = value_encoded + for key in signed_headers: + a.append(key + ":" + __headers[key]) + return '\n'.join(a) + "\n" + + +def request(method, path, param=None, body=None, **params): + # path 是不带host但是 前面需要带 / , body json 字符串或者自己从dict转换下 + # 也可以自己改成 判断下是不是post 是post params就是body + if param: + params.update(param) + + query = urlencode(sorted(params.items())) + headers = {"content-type": "application/json"} # 初始化header + headers["X-Sdk-Date"] = datetime.strftime(datetime.utcnow(), BasicDateFormat) + headers["host"] = API.SITE + # 如何后来有需要把header头 key转换为小写 value 删除前导空格和尾随空格 + sign_headers = [] + for key in headers: + sign_headers.append(key.lower()) + # 先排序 + sign_headers.sort() + + if body is None: + body = "" + + hex_encode = HexEncodeSHA256Hash(body.encode('utf-8')) + # 生成文档中的CanonicalRequest + canonical_headers = CanonicalHeaders(headers, sign_headers) + + # 签名中的path 必须 / 结尾 + if path[-1] != '/': + sign_path = path + "/" + else: + sign_path = path + + canonical_request = "%s\n%s\n%s\n%s\n%s\n%s" % (method.upper(), sign_path, query, + canonical_headers, ";".join(sign_headers), hex_encode) + + hashed_canonical_request = HexEncodeSHA256Hash(canonical_request.encode('utf-8')) + + # StringToSign + str_to_sign = "%s\n%s\n%s" % (Algorithm, headers['X-Sdk-Date'], hashed_canonical_request) + + secret = Config.TOKEN + # 计算签名 HexEncode(HMAC(Access Secret Key, string to sign)) + signature = hmac.new(secret.encode('utf-8'), str_to_sign.encode('utf-8'), digestmod=hashlib.sha256).digest() + signature = binascii.hexlify(signature).decode() + # 添加签名信息到请求头 + auth_header = "%s Access=%s, SignedHeaders=%s, Signature=%s" % ( + Algorithm, Config.ID, ";".join(sign_headers), signature) + headers['Authorization'] = auth_header + # 创建Http请求 + + if Config.PROXY: + conn = HTTPSConnection(Config.PROXY) + conn.set_tunnel(API.SITE, 443) + else: + conn = HTTPSConnection(API.SITE) + conn.request(method, API.SCHEME + "://" + API.SITE + path + '?' + query, body, headers) + info(API.SCHEME + "://" + API.SITE + path + '?' + query, body) + resp = conn.getresponse() + data = resp.read().decode('utf8') + resp.close() + if resp.status < 200 or resp.status >= 300: + + warning('%s : error[%d]: %s', path, resp.status, data) + raise Exception(data) + else: + data = json.loads(data) + debug('%s : result:%s', path, data) + return data + + +def get_zone_id(domain): + """ + 切割域名获取主域名和对应ID https://support.huaweicloud.com/api-dns/zh-cn_topic_0037134402.html + """ + zones = request('GET', '/v2/zones', limit=500)['zones'] + domain += '.' + zone = next((z for z in zones if domain.endswith(z.get('name'))), None) + zoneid = zone and zone['id'] + return zoneid + +def get_records(zoneid, **conditions): + """ + 获取记录ID + 返回满足条件的所有记录[] + https://support.huaweicloud.com/api-dns/zh-cn_topic_0037129970.html + TODO 大于500翻页 + """ + cache_key = zoneid + "_" + \ + conditions.get('name', "") + "_" + conditions.get('type', "") + if not hasattr(get_records, 'records'): + get_records.records = {} # "静态变量"存储已查询过的id + get_records.keys = ('id', 'type', 'name', 'records', 'ttl') + + if not zoneid in get_records.records: + get_records.records[cache_key] = {} + + data = request('GET', '/v2/zones/' + zoneid + '/recordsets', + limit=500, **conditions) + + # https://{DNS_Endpoint}/v2/zones/2c9eb155587194ec01587224c9f90149/recordsets?limit=&offset= + if data: + for record in data['recordsets']: + info(record) + get_records.records[cache_key][record['id']] = { + k: v for (k, v) in record.items() if k in get_records.keys} + records = {} + for (zid, record) in get_records.records[cache_key].items(): + for (k, value) in conditions.items(): + if record.get(k) != value: + break + else: # for else push + records[zid] = record + return records + + +def update_record(domain, value, record_type='A', name=None): + """ + 更新记录 + update + https://support.huaweicloud.com/api-dns/dns_api_64006.html + add + https://support.huaweicloud.com/api-dns/zh-cn_topic_0037134404.html + """ + info(">>>>>%s(%s)", domain, record_type) + zoneid = get_zone_id(domain) + if not zoneid: + raise Exception("invalid domain: [ %s ] " % domain) + domain += '.' + records = get_records(zoneid, name=domain, type=record_type) + cache_key = zoneid + "_" + domain + "_" + record_type + result = {} + if records: # update + for (rid, record) in records.items(): + if record['records'] != value: + """ + { + "description": "This is an example record set.", + "ttl": 3600, + "records": [ + "192.168.10.1", + "192.168.10.2" + ] + } + """ + body = { + "records":[ + value + ] + } + res = request('PUT', '/v2/zones/' + zoneid + '/recordsets/' + record['id'], + body=str(json.dumps(body))) + if res: + get_records.records[cache_key][rid]['records'] = value + result[rid] = res.get("name") + else: + result[rid] = "Update fail!\n" + str(res) + else: + result[rid] = domain + else: # create + print(domain) + body = { + "name": domain, + "type": record_type, + "records":[ + value + ] + } + res = request('POST', '/v2/zones/' + zoneid + '/recordsets', + body=str(json.dumps(body))) + if res: + get_records.records[cache_key][res['id']] = res + result = res + else: + result = domain + " created fail!" + return result From b868e90e843745bea2eacb819971065732603e7f Mon Sep 17 00:00:00 2001 From: memorz Date: Mon, 24 Feb 2020 10:42:40 +0800 Subject: [PATCH 5/5] Update ip.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更换get获取IP地址的接口 --- util/ip.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/util/ip.py b/util/ip.py index 8ba441c1..365ca1d3 100644 --- a/util/ip.py +++ b/util/ip.py @@ -64,18 +64,18 @@ def _open(url, reg): debug("open: %s", url) res = urlopen( Request(url, headers={'User-Agent': 'curl/7.63.0-ddns'}), timeout=60 - ).read().decode('utf8') + ).read().decode('unicode_escape') debug("response: %s", res) return compile(reg).search(res).group() except Exception as e: error(e) -def public_v4(url="https://api-ipv4.ip.sb/ip", reg=IPV4_REG): # 公网IPV4地址 +def public_v4(url="https://pv.sohu.com/cityjson", reg=IPV4_REG): # 公网IPV4地址 return _open(url, reg) -def public_v6(url="https://api-ipv6.ip.sb/ip", reg=IPV6_REG): # 公网IPV6地址 +def public_v6(url="http://ipv6-test.com/api/myip.php", reg=IPV6_REG): # 公网IPV6地址 return _open(url, reg)