Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

增加指定网卡功能 #129

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
247 changes: 247 additions & 0 deletions dns/huaweidns.py
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 16 additions & 4 deletions util/ip.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -46,24 +46,36 @@ 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:
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)


Expand Down