├── .gitignore ├── README.md ├── merge-payload.py ├── CMCC.json ├── ChinaUnicom.json ├── export-configure.py ├── ChinaNet.json └── fetch-timeout.py /.gitignore: -------------------------------------------------------------------------------- 1 | /apple-cdn-speed.report -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Final AppleDNS Pro 2 | 3 | AppleDNS 通过收集 Apple 在中国的 CDN 数据,解决 iTunes iCloud 等 Apple 网络服务在中国大陆部分地区加载缓慢的问题。 4 | 5 | 目前该项目已暂停更新,部分内容可能已经过期,请慎重使用。 6 | 7 | 8 | ## 生成教程: 9 | 确保你系统中安装了 Python 3.4+ 或者 Python 2.7+ (macOS 和多数 Linux 发行版内建) 10 | 将本项目[下载](https://github.com/gongjianhui/AppleDNS/archive/master.zip)到本地 11 | 12 | ```bash 13 | cd /path/to/AppleDNS 14 | # 切换到 AppleDNS 的文件夹 15 | 16 | python fetch-timeout.py {ChinaUnicom.json/ChinaNet.json/CMCC.json} 17 | 18 | # 兼容 Python 2.7+ / Python 3.4+ 19 | #(请选择你的运营商对应文件 ChinaUnicom 联通、ChinaNet 电信、CMCC 移动) 20 | # 确认即开始进行测速,需等待数秒 21 | 22 | python export-configure.py {surge,hosts,dnsmasq,ros,unbound} 23 | 24 | # 生成各种形式的配置(如 Surge 执行 python export-configure.py surge) 25 | 26 | # ** 将配置文件放到相应的位置(HOSTS 放入系统相应位置、路由器用户请独立配置路由器后台)** 27 | # ** Surge 用户请在配置文件中新建 [Host] 并将配置复制到下方)。** 28 | ``` 29 | 30 | ## 设置完成后可按需清理 DNS 缓存 31 | 32 | macOS:[#41](https://github.com/gongjianhui/AppleDNS/issues/41) 33 | 34 | Windows:ipconfig /flushdns 35 | 36 | ## dnsmasq 配置警告: 37 | 38 | 请删除配置文件中的 39 | 40 | ```ini 41 | address=/itunes.apple.com/*** 42 | ``` 43 | 44 | 该配置在 dnsmasq 中意味着将 `itunes.apple.com` 泛解析,请务必删除。 45 | 46 | ## 其他 47 | 多运营商切换用户可以尝试配合 [SwitchHosts!](https://github.com/oldj/SwitchHosts) 使用。 48 | 49 | ----------------------------------------------------- 50 | 51 | iTunes、iTunes Store、App Store、iBooks Store、Apple Music 以及与服务有关而使用的其他 Apple 商标、服务商标、图形和标识是 Apple Inc. 在美国和世界其他国家 / 地区的商标或注册商标。 52 | AppleDNS 不是由 Apple Inc. 提供的服务。 53 | -------------------------------------------------------------------------------- /merge-payload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import json 3 | from argparse import ArgumentParser 4 | from collections import OrderedDict, defaultdict 5 | from urllib.parse import urlparse 6 | 7 | from ipaddress import ip_address 8 | 9 | 10 | def handle_ip(target): 11 | address = urlparse('http://%s' % target) 12 | return ip_address(address.hostname), address.port or 80 13 | 14 | 15 | def merge_service(payloads): 16 | services = defaultdict(list) 17 | for service in sum(payloads, []): 18 | services[service['title']].append(service) 19 | for title, service in services.items(): 20 | ips = defaultdict(list) 21 | domains = set() 22 | for x in service: 23 | domains |= set(x['domains']) 24 | for name, ipset in x['ips'].items(): 25 | ipset = map(lambda item: item.strip(), ipset) 26 | ipset = list(set(ips[name]) | set(ipset)) 27 | ips[name] = sorted(ipset, key=handle_ip) 28 | yield OrderedDict([ 29 | ('title', title), 30 | ('domains', sorted(domains, key=lambda item: (len(item), item))), 31 | ('ips', OrderedDict(sorted(ips.items()))) 32 | ]) 33 | 34 | 35 | def output(files): 36 | payloads = map(lambda filename: json.load(open(filename)), files) 37 | return json.dumps( 38 | sorted(merge_service(payloads), key=lambda item: item['title']), 39 | indent=4, 40 | ensure_ascii=False 41 | ) 42 | 43 | 44 | def main(): 45 | parser = ArgumentParser() 46 | parser.add_argument('files', nargs='+') 47 | args = parser.parse_args() 48 | 49 | print(output(args.files)) 50 | 51 | if __name__ == '__main__': 52 | main() 53 | -------------------------------------------------------------------------------- /CMCC.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "API-1-WS", 4 | "domains": [ 5 | "se.itunes.apple.com", 6 | "su.itunes.apple.com", 7 | "upp.itunes.apple.com", 8 | "apps.itunes.apple.com", 9 | "play.itunes.apple.com", 10 | "client-api.itunes.apple.com" 11 | ], 12 | "ips": { 13 | "CMCC": [ 14 | "111.62.245.9:443", 15 | "112.29.202.176:443", 16 | "117.148.166.22:443", 17 | "120.221.25.187:443", 18 | "223.113.13.142:443" 19 | ] 20 | } 21 | }, 22 | { 23 | "title": "API-2-WS", 24 | "domains": [ 25 | "s.mzstatic.com", 26 | "itunes.apple.com", 27 | "init.itunes.apple.com", 28 | "itunesconnect.apple.com", 29 | "search.itunes.apple.com" 30 | ], 31 | "ips": { 32 | "CMCC": [ 33 | "61.236.251.74:443", 34 | "117.148.164.160:443", 35 | "117.148.166.22:443", 36 | "117.148.166.72:443", 37 | "120.221.65.74:443", 38 | "120.221.65.126:443", 39 | "120.221.65.130:443", 40 | "120.221.65.233:443", 41 | "183.222.99.115:443", 42 | "222.55.13.77:443", 43 | "223.111.212.16:443", 44 | "223.111.212.240:443", 45 | "223.113.13.142:443" 46 | ] 47 | } 48 | }, 49 | { 50 | "title": "Beta-HK-Akamai", 51 | "domains": [ 52 | "beta.itunes.apple.com" 53 | ], 54 | "ips": { 55 | "HongKong": [ 56 | "23.42.189.88:443", 57 | "23.50.17.214:443", 58 | "23.198.133.66:443", 59 | "60.254.170.217:443", 60 | "104.114.184.38:443", 61 | "184.87.97.50:443" 62 | ] 63 | } 64 | }, 65 | { 66 | "title": "Radio-HK-Akamai", 67 | "domains": [ 68 | "radio.itunes.apple.com", 69 | "radio-activity.itunes.apple.com", 70 | "radio-services.itunes.apple.com" 71 | ], 72 | "ips": { 73 | "HongKong": [ 74 | "23.50.28.36:443", 75 | "23.212.17.170:443", 76 | "23.213.75.141:443", 77 | "23.222.167.119:443", 78 | "104.70.138.152:443", 79 | "104.115.242.174:443", 80 | "104.124.219.201:443", 81 | "184.86.26.130:443", 82 | "184.87.133.146:443" 83 | ] 84 | } 85 | } 86 | ] 87 | -------------------------------------------------------------------------------- /ChinaUnicom.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "API-1-WS", 4 | "domains": [ 5 | "se.itunes.apple.com", 6 | "su.itunes.apple.com", 7 | "upp.itunes.apple.com", 8 | "apps.itunes.apple.com", 9 | "play.itunes.apple.com", 10 | "client-api.itunes.apple.com" 11 | ], 12 | "ips": { 13 | "ChinaUnicom": [ 14 | "36.248.11.175:443", 15 | "60.213.22.72:443", 16 | "113.8.150.53:443", 17 | "124.167.217.20:443", 18 | "218.61.15.68:443", 19 | "221.193.246.66:443", 20 | "222.141.205.17:443" 21 | ] 22 | } 23 | }, 24 | { 25 | "title": "API-2-WS", 26 | "domains": [ 27 | "s.mzstatic.com", 28 | "itunes.apple.com", 29 | "init.itunes.apple.com", 30 | "itunesconnect.apple.com", 31 | "search.itunes.apple.com" 32 | ], 33 | "ips": { 34 | "ChinaUnicom": [ 35 | "36.248.11.175:443", 36 | "36.250.74.100:443", 37 | "60.213.22.72:443", 38 | "60.220.194.17:443", 39 | "60.222.223.59:443", 40 | "61.53.232.133:443", 41 | "101.66.225.163:443", 42 | "111.161.121.26:443", 43 | "111.161.121.27:443", 44 | "113.8.150.53:443", 45 | "122.13.197.173:443", 46 | "124.167.217.20:443", 47 | "218.61.15.68:443", 48 | "218.61.15.121:443", 49 | "221.193.246.66:443", 50 | "221.194.190.108:443", 51 | "221.204.31.100:443", 52 | "222.141.205.17:443" 53 | ] 54 | } 55 | }, 56 | { 57 | "title": "Beta-HK-Akamai", 58 | "domains": [ 59 | "beta.itunes.apple.com" 60 | ], 61 | "ips": { 62 | "HongKong": [ 63 | "23.42.189.88:443", 64 | "23.50.17.214:443", 65 | "23.198.133.66:443", 66 | "60.254.170.217:443", 67 | "104.114.184.38:443", 68 | "184.87.97.50:443" 69 | ] 70 | } 71 | }, 72 | { 73 | "title": "Radio-HK-Akamai", 74 | "domains": [ 75 | "radio.itunes.apple.com", 76 | "radio-activity.itunes.apple.com", 77 | "radio-services.itunes.apple.com" 78 | ], 79 | "ips": { 80 | "HongKong": [ 81 | "23.50.28.36:443", 82 | "23.212.17.170:443", 83 | "23.213.75.141:443", 84 | "23.222.167.119:443", 85 | "104.70.138.152:443", 86 | "104.115.242.174:443", 87 | "104.124.219.201:443", 88 | "184.86.26.130:443", 89 | "184.87.133.146:443" 90 | ] 91 | } 92 | } 93 | ] 94 | -------------------------------------------------------------------------------- /export-configure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from __future__ import print_function, unicode_literals 4 | 5 | import json 6 | import os.path 7 | import sys 8 | from argparse import ArgumentParser 9 | from collections import namedtuple 10 | from datetime import datetime 11 | from math import isnan 12 | from operator import attrgetter 13 | 14 | from io import open 15 | 16 | formats = { 17 | 'hosts': '{ip:<15} {domain}', 18 | 'surge': '{domain} = {ip}', 19 | 'dnsmasq': 'address=/{domain}/{ip}', 20 | 'ros': 'add name {domain} address={ip}', 21 | 'unbound': '{domain} IN A {ip}' 22 | } 23 | 24 | 25 | def check_requirements(): 26 | def check_python_version(): 27 | if 0x2000000 <= sys.hexversion <= 0x2070000: 28 | print('your "python" lower than 2.7.0 upgrade.') 29 | return False 30 | if 0x3000000 <= sys.hexversion <= 0x3040000: 31 | print('your "python" lower than 3.4.0 upgrade.') 32 | return False 33 | return True 34 | 35 | return check_python_version() 36 | 37 | 38 | def find_fast_ip(ipset): 39 | Item = namedtuple('Item', ['tag', 'ip', 'avg_rtt']) 40 | 41 | def handle_delta(items): 42 | tag, delta_map = items 43 | 44 | def handle(item): 45 | ip, delta = item 46 | delta = list(item for item in delta if item != None) 47 | if delta: 48 | return Item(tag, ip, sum(delta) / float(len(delta))) 49 | return Item(tag, ip, float('NaN')) 50 | 51 | return list(map(handle, delta_map.items())) 52 | 53 | def handle_sorted(): 54 | data = sum(list(map(handle_delta, ipset.items())), []) 55 | return sorted(filter(lambda x: x.avg_rtt>0, data), key=attrgetter('avg_rtt')) 56 | 57 | iptable = handle_sorted() 58 | return iptable[0] if iptable else None 59 | 60 | 61 | def export(payload, target): 62 | if not payload: 63 | return 64 | print('# Build Date: %s (UTC)' % datetime.utcnow().isoformat()) 65 | for service in sorted(payload, key=lambda item: item['title']): 66 | tag, ip, avg_rtt = find_fast_ip(service['ips']) 67 | if isnan(avg_rtt): 68 | continue 69 | print('# %s [%s] (Avg RTT: %.3fms)' % (service['title'], tag, avg_rtt)) 70 | for domain in sorted(service['domains'], key=len): 71 | template = '%s' if ip else '# %s' 72 | print(template % formats[target].format(domain=domain, ip=ip)) 73 | 74 | 75 | def load_payload(): 76 | target_filename = 'apple-cdn-speed.report' 77 | if os.path.exists(target_filename): 78 | return json.load(open(target_filename, encoding='UTF-8')) 79 | print('please run "fetch-timeout.py" build "%s".' % target_filename) 80 | 81 | 82 | def main(): 83 | parser = ArgumentParser() 84 | parser.add_argument( 85 | 'target', 86 | help='output target', 87 | choices=sorted(formats.keys(), key=len) 88 | ) 89 | 90 | if len(sys.argv) == 1: 91 | parser.print_help() 92 | sys.exit(1) 93 | 94 | args = parser.parse_args() 95 | 96 | export(load_payload(), args.target) 97 | 98 | 99 | if __name__ == '__main__' and check_requirements(): 100 | main() 101 | -------------------------------------------------------------------------------- /ChinaNet.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "API-1-WS", 4 | "domains": [ 5 | "se.itunes.apple.com", 6 | "su.itunes.apple.com", 7 | "upp.itunes.apple.com", 8 | "apps.itunes.apple.com", 9 | "play.itunes.apple.com", 10 | "client-api.itunes.apple.com" 11 | ], 12 | "ips": { 13 | "ChinaNet": [ 14 | "42.81.28.75:443", 15 | "58.223.166.19:443", 16 | "59.47.131.83:443", 17 | "59.63.81.125:443", 18 | "60.213.22.72:443", 19 | "61.136.165.32:443", 20 | "61.157.124.209:443", 21 | "113.16.210.129:443", 22 | "113.107.45.173:443", 23 | "119.84.95.19:443", 24 | "125.75.32.223:443", 25 | "125.90.206.143:443", 26 | "183.131.165.106:443", 27 | "183.136.217.66:443", 28 | "218.92.221.212:443", 29 | "222.141.205.17:443", 30 | "223.151.245.45:443" 31 | ] 32 | } 33 | }, 34 | { 35 | "title": "API-2-WS", 36 | "domains": [ 37 | "s.mzstatic.com", 38 | "itunes.apple.com", 39 | "init.itunes.apple.com", 40 | "itunesconnect.apple.com", 41 | "search.itunes.apple.com" 42 | ], 43 | "ips": { 44 | "ChinaNet": [ 45 | "14.215.102.173:443", 46 | "14.215.103.42:443", 47 | "14.215.231.244:443", 48 | "27.155.70.26:443", 49 | "27.159.181.128:443", 50 | "42.81.28.75:443", 51 | "42.123.103.78:443", 52 | "58.216.109.133:443", 53 | "58.220.40.221:443", 54 | "58.223.166.19:443", 55 | "59.63.81.125:443", 56 | "59.63.243.83:443", 57 | "61.53.232.133:443", 58 | "61.136.165.32:443", 59 | "61.155.163.165:443", 60 | "61.157.124.209:443", 61 | "113.16.209.19:443", 62 | "113.16.209.227:443", 63 | "114.64.223.22:443", 64 | "115.231.85.95:443", 65 | "119.84.95.223:443", 66 | "122.228.23.104:443", 67 | "125.75.32.223:443", 68 | "125.90.206.143:443", 69 | "180.97.218.220:443", 70 | "180.101.199.224:443", 71 | "182.108.171.247:443", 72 | "183.61.27.21:443", 73 | "183.131.68.44:443", 74 | "183.136.217.66:443", 75 | "218.6.111.176:443", 76 | "218.75.154.228:443", 77 | "218.76.109.156:443", 78 | "218.86.111.68:443", 79 | "218.92.221.212:443", 80 | "219.144.71.163:443", 81 | "220.165.142.222:443", 82 | "222.141.205.17:443" 83 | ] 84 | } 85 | }, 86 | { 87 | "title": "Beta-HK-Akamai", 88 | "domains": [ 89 | "beta.itunes.apple.com" 90 | ], 91 | "ips": { 92 | "HongKong": [ 93 | "23.42.189.88:443", 94 | "23.50.17.214:443", 95 | "23.198.133.66:443", 96 | "60.254.170.217:443", 97 | "104.114.184.38:443", 98 | "184.87.97.50:443" 99 | ] 100 | } 101 | }, 102 | { 103 | "title": "Radio-HK-Akamai", 104 | "domains": [ 105 | "radio.itunes.apple.com", 106 | "radio-activity.itunes.apple.com", 107 | "radio-services.itunes.apple.com" 108 | ], 109 | "ips": { 110 | "HongKong": [ 111 | "23.50.28.36:443", 112 | "23.212.17.170:443", 113 | "23.213.75.141:443", 114 | "23.222.167.119:443", 115 | "104.70.138.152:443", 116 | "104.115.242.174:443", 117 | "104.124.219.201:443", 118 | "184.86.26.130:443", 119 | "184.87.133.146:443" 120 | ] 121 | } 122 | } 123 | ] 124 | -------------------------------------------------------------------------------- /fetch-timeout.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from __future__ import print_function, unicode_literals 4 | 5 | import json 6 | import os.path 7 | import random 8 | import sys 9 | from argparse import ArgumentParser 10 | from collections import defaultdict 11 | from contextlib import closing 12 | from datetime import datetime 13 | from multiprocessing.dummy import Pool as ParallelPool 14 | from socket import AF_INET, IPPROTO_TCP, SOCK_STREAM, TCP_NODELAY, socket 15 | from time import time 16 | 17 | from io import open 18 | 19 | if sys.version_info[0] == 2: 20 | from urlparse import urlparse 21 | str = unicode 22 | else: 23 | from urllib.parse import urlparse 24 | 25 | 26 | def check_requirements(): 27 | def check_python_version(): 28 | if 0x2000000 <= sys.hexversion <= 0x2070000: 29 | print('your "python" lower than 2.7.0 upgrade.') 30 | return False 31 | if 0x3000000 <= sys.hexversion <= 0x3040000: 32 | print('your "python" lower than 3.4.0 upgrade.') 33 | return False 34 | return True 35 | 36 | return check_python_version() 37 | 38 | 39 | def request_with_socket(host, port, timeout): 40 | with closing(socket(AF_INET, SOCK_STREAM)) as connection: 41 | connection.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) 42 | connection.settimeout(timeout) 43 | connection.connect((host, port)) 44 | 45 | 46 | def timeit(callback): 47 | begin_time = time() 48 | callback() 49 | end_time = time() 50 | return end_time - begin_time 51 | 52 | 53 | def request(target): 54 | host, port, timeout = target 55 | 56 | try: 57 | rtt = timeit(lambda: request_with_socket(host, port, timeout)) 58 | return host, rtt * 1000 59 | except: 60 | return host, None 61 | 62 | 63 | def fetch(payload, timeout, concurrent, testing_times): 64 | if not payload: 65 | return 66 | 67 | def handle_ip(target): 68 | address = urlparse('http://%s' % str(target)) 69 | return address.hostname, address.port or 80, timeout 70 | 71 | def handle_ipset(ips): 72 | ips *= testing_times 73 | random.shuffle(ips) 74 | return ips 75 | 76 | with closing(ParallelPool(concurrent)) as pool: 77 | for service_item in payload: 78 | print(str(service_item['title'])) 79 | print(', '.join(service_item['domains'])) 80 | 81 | iptable = service_item['ips'] 82 | for name, ips in iptable.items(): 83 | print('\t%s' % name) 84 | 85 | iptable[name] = defaultdict(list) 86 | request_payload = map(handle_ip, handle_ipset(ips)) 87 | for ip, delta in pool.imap_unordered(request, request_payload): 88 | iptable[name][ip].append(delta) 89 | if delta: 90 | print('\t\t%-15s\t%.3fms' % (ip, delta)) 91 | save_result(payload) 92 | 93 | 94 | def load_payload(path): 95 | if os.path.exists(path): 96 | with open(path, encoding='UTF-8') as fp: 97 | return json.loads(fp.read()) 98 | else: 99 | print('"%s" file not found.' % path) 100 | sys.exit(1) 101 | 102 | 103 | def save_result(payload): 104 | target_filename = 'apple-cdn-speed.report' 105 | with open(target_filename, 'w', encoding='UTF-8') as fp: 106 | report_data = json.dumps( 107 | payload, 108 | sort_keys=True, 109 | indent=4, 110 | ensure_ascii=False 111 | ) 112 | fp.write(str(report_data)) 113 | 114 | 115 | def main(): 116 | parser = ArgumentParser() 117 | parser.add_argument( 118 | 'payload', 119 | type=str, 120 | help='payload' 121 | ) 122 | 123 | parser.add_argument( 124 | '--timeout', 125 | type=int, 126 | help='timeout (default: %(default)s) (unit ms)', 127 | dest='timeout', 128 | default=400 129 | ) 130 | 131 | parser.add_argument( 132 | '--concurrent', 133 | type=int, 134 | help='concurrent (default: %(default)s)', 135 | dest='concurrent', 136 | default=10 137 | ) 138 | 139 | parser.add_argument( 140 | '--testing_times', 141 | type=int, 142 | help='testing times (default: %(default)s)', 143 | dest='testing_times', 144 | default=20 145 | ) 146 | 147 | if len(sys.argv) == 1: 148 | parser.print_help() 149 | sys.exit(1) 150 | 151 | args = parser.parse_args() 152 | 153 | fetch( 154 | load_payload(args.payload), 155 | timeout=args.timeout / 1000.0, 156 | concurrent=args.concurrent, 157 | testing_times=args.testing_times 158 | ) 159 | 160 | 161 | if __name__ == '__main__' and check_requirements(): 162 | main() 163 | --------------------------------------------------------------------------------