Files
Archive/v2rayu/V2rayU/V2rayRouting.swift
2025-05-27 20:37:12 +02:00

502 lines
16 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// V2rayRouting.swift
// V2rayU
//
// Created by yanue on 2024/6/27.
// Copyright © 2024 yanue. All rights reserved.
//
import Foundation
let RoutingRuleGlobal = "routing.global"
let RoutingRuleLAN = "routing.lan"
let RoutingRuleCn = "routing.cn"
let RoutingRuleLANAndCn = "routing.lanAndCn"
let defaultRuleCn = Dictionary(uniqueKeysWithValues: [
(RoutingRuleGlobal, "🌏 全局"),
(RoutingRuleLAN, "🌏 绕过局域网"),
(RoutingRuleCn, "🌏 绕过中国大陆"),
(RoutingRuleLANAndCn, "🌏 绕过局域网和中国大陆")
])
let defaultRuleEn = Dictionary(uniqueKeysWithValues: [
(RoutingRuleGlobal, "🌏 Global"),
(RoutingRuleLAN, "🌏 Bypassing the LAN Address"),
(RoutingRuleCn, "🌏 Bypassing mainland address"),
(RoutingRuleLANAndCn, "🌏 Bypassing LAN and mainland address")
])
let defaultRules = Dictionary(uniqueKeysWithValues: [
(RoutingRuleGlobal,RoutingItem(name: RoutingRuleGlobal, remark: "")),
(RoutingRuleLAN,RoutingItem(name: RoutingRuleLAN, remark: "")),
(RoutingRuleCn,RoutingItem(name: RoutingRuleCn, remark: "")),
(RoutingRuleLANAndCn,RoutingItem(name: RoutingRuleLANAndCn,remark:""))
])
// ----- routing server manager -----
class V2rayRoutings: NSObject {
static var shared = V2rayRoutings()
static let lock = NSLock()
static let default_rule_content = """
{
"domainStrategy": "AsIs",
"rules": [
]
}
"""
// Initialization
override init() {
super.init()
V2rayRoutings.loadConfig()
}
// routing server list
static private var routings: [RoutingItem] = []
// (init) load routing server list from UserDefaults
static func loadConfig() {
self.lock.lock()
defer {
self.lock.unlock()
}
// static reset
self.routings = V2rayRoutings.all()
self.saveItemList()
print("V2rayRoutings-loadConfig", self.routings.count)
}
static func isDefaultRule(name: String) -> Bool {
return defaultRules.keys.contains(name)
}
// get list from routing server list
static func list() -> [RoutingItem] {
return self.routings
}
static func all() -> [RoutingItem] {
// static reset
var items : [RoutingItem] = []
// load name list from UserDefaults
var list = UserDefaults.getArray(forKey: .routingCustomList) ?? [];
print("V2rayRoutings-loadConfig", list)
// for defaultRules
for (key, rule) in defaultRules {
// load and check
if nil == RoutingItem.load(name: key) {
if isMainland {
rule.remark = defaultRuleCn[key] ?? rule.remark
} else {
rule.remark = defaultRuleEn[key] ?? rule.remark
}
// create new
rule.store()
}
// if not in list, append
if !list.contains(key) {
list.append(key)
}
}
// load each RoutingItem
for item in list {
guard let routing = RoutingItem.load(name: item) else {
// delete from UserDefaults
RoutingItem.remove(name: item)
continue
}
// append
items.append(routing)
}
return items
}
// get count from routing server list
static func count() -> Int {
return self.routings.count
}
// move item to new index
static func move(oldIndex: Int, newIndex: Int) {
if !V2rayRoutings.routings.indices.contains(oldIndex) {
NSLog("index out of range", oldIndex)
return
}
if !V2rayRoutings.routings.indices.contains(newIndex) {
NSLog("index out of range", newIndex)
return
}
let o = self.routings[oldIndex]
self.routings.remove(at: oldIndex)
self.routings.insert(o, at: newIndex)
// update server list UserDefaults
self.saveItemList()
}
// add routing server (by scan qrcode)
static func add(remark: String, json: String) {
var remark_ = remark
if remark.count == 0 {
remark_ = "new routing"
}
// name is : routing. + uuid
let name = "routing." + UUID().uuidString
let routing = RoutingItem(name: name, remark: remark_, json: json)
// save to routing UserDefaults
routing.store()
// just add to mem
self.routings.append(routing)
print("routing", name, self.routings)
// update server list UserDefaults
self.saveItemList()
}
// remove routing server (tmp and UserDefaults and config json file)
static func remove(idx: Int) {
if !V2rayRoutings.routings.indices.contains(idx) {
NSLog("index out of range", idx)
return
}
let routing = V2rayRoutings.routings[idx]
// delete from tmp
self.routings.remove(at: idx)
// delete from routing UserDefaults
RoutingItem.remove(name: routing.name)
// update server list UserDefaults
self.saveItemList()
// if cuerrent item is default
let curName = UserDefaults.get(forKey: .routingSelectedRule)
if curName != nil && routing.name == curName {
UserDefaults.del(forKey: .routingSelectedRule)
}
}
// update server list UserDefaults
static func saveItemList() {
var routingCustomList: Array<String> = []
for item in self.routings {
routingCustomList.append(item.name)
}
print("routingCustomList", routingCustomList);
UserDefaults.setArray(forKey: .routingCustomList, value: routingCustomList)
}
// load json file data
static func load(idx: Int) -> RoutingItem? {
if !V2rayRoutings.routings.indices.contains(idx) {
NSLog("index out of range", idx)
return nil
}
return self.routings[idx]
}
static func save(routing: RoutingItem) {
// store
routing.store()
// refresh data
for (idx, item) in self.routings.enumerated() {
if item.name == routing.name {
self.routings[idx].remark = routing.remark
break
}
}
}
// get by name
static func getIndex(name: String) -> Int {
for (idx, item) in self.routings.enumerated() {
if item.name == name {
return idx
}
}
return -1
}
}
// ----- routing routing item -----
class RoutingItem: NSObject, NSCoding {
var name: String
var remark: String
var json: String
var domainStrategy: String
var block: String
var proxy: String
var direct: String
// Initializer
init(name: String, remark: String, json: String = "", domainStrategy: String = "AsIs", block: String="", proxy: String="", direct: String="") {
self.name = name
self.remark = remark
self.json = json
self.domainStrategy = domainStrategy
self.block = block
self.proxy = proxy
self.direct = direct
}
// NSCoding required initializer (decoding)
required init(coder decoder: NSCoder) {
self.name = decoder.decodeObject(forKey: "Name") as? String ?? ""
self.remark = decoder.decodeObject(forKey: "Remark") as? String ?? ""
self.json = decoder.decodeObject(forKey: "Json") as? String ?? ""
self.domainStrategy = decoder.decodeObject(forKey: "DomainStrategy") as? String ?? "AsIs"
self.block = decoder.decodeObject(forKey: "Block") as? String ?? ""
self.proxy = decoder.decodeObject(forKey: "Proxy") as? String ?? ""
self.direct = decoder.decodeObject(forKey: "Direct") as? String ?? ""
}
// NSCoding required method (encoding)
func encode(with coder: NSCoder) {
coder.encode(name, forKey: "Name")
coder.encode(remark, forKey: "Remark")
coder.encode(json, forKey: "Json")
coder.encode(domainStrategy, forKey: "DomainStrategy")
coder.encode(block, forKey: "Block")
coder.encode(proxy, forKey: "Proxy")
coder.encode(direct, forKey: "Direct")
}
// Store into UserDefaults
func store() {
let modelData = NSKeyedArchiver.archivedData(withRootObject: self)
UserDefaults.standard.set(modelData, forKey: self.name)
}
func parseRule() -> V2rayRouting {
if defaultRules.keys.contains(self.name) {
return self.parseDefaultSettings()
}
let (res, err) = parseRoutingRuleJson(json: self.json)
if err != nil {
print("parseRule err", err)
}
return res
}
// parse default settings
func parseDefaultSettings() -> V2rayRouting {
var rules: [V2rayRoutingRule] = []
let (blockDomains, blockIps) = parseDomainOrIp(domainIpStr: self.block)
let (proxyDomains, proxyIps) = parseDomainOrIp(domainIpStr: self.proxy)
let (directDomains, directIps) = parseDomainOrIp(domainIpStr: self.direct)
// // rules
var ruleProxyDomain, ruleProxyIp, ruleDirectDomain, ruleDirectIp, ruleBlockDomain, ruleBlockIp, ruleDirectIpDefault, ruleDirectDomainDefault: V2rayRoutingRule?
// proxy
if proxyDomains.count > 0 {
ruleProxyDomain = getRoutingRule(outTag: "proxy", domain: proxyDomains, ip: nil, port: nil)
}
if proxyIps.count > 0 {
ruleProxyIp = getRoutingRule(outTag: "proxy", domain: nil, ip: proxyIps, port: nil)
}
// direct
if directDomains.count > 0 {
ruleDirectDomain = getRoutingRule(outTag: "direct", domain: directDomains, ip: nil, port: nil)
}
if directIps.count > 0 {
ruleDirectIp = getRoutingRule(outTag: "direct", domain: nil, ip: directIps, port: nil)
}
// block
if blockDomains.count > 0 {
ruleBlockDomain = getRoutingRule(outTag: "block", domain: blockDomains, ip: nil, port: nil)
}
if blockIps.count > 0 {
ruleBlockIp = getRoutingRule(outTag: "block", domain: nil, ip: blockIps, port: nil)
}
switch self.name {
case RoutingRuleGlobal:
break
case RoutingRuleLAN:
ruleDirectIpDefault = getRoutingRule(outTag: "direct", domain: nil, ip: ["geoip:private"], port: nil)
ruleDirectDomainDefault = getRoutingRule(outTag: "direct", domain: ["localhost"], ip: nil, port: nil)
break
case RoutingRuleCn:
ruleDirectIpDefault = getRoutingRule(outTag: "direct", domain: nil, ip: ["geoip:cn"], port: nil)
ruleDirectDomainDefault = getRoutingRule(outTag: "direct", domain: ["geosite:cn"], ip: nil, port: nil)
break
case RoutingRuleLANAndCn:
ruleDirectIpDefault = getRoutingRule(outTag: "direct", domain: nil, ip: ["geoip:cn","geoip:private"], port: nil)
ruleDirectDomainDefault = getRoutingRule(outTag: "direct", domain: ["geosite:cn","localhost"], ip: nil, port: nil)
break
default: break
}
// -> -> -> IP -> IP -> IP
//
if ruleBlockDomain != nil {
ruleBlockDomain?.ip = nil
rules.append(ruleBlockDomain!)
}
//
if ruleProxyDomain != nil {
ruleProxyDomain?.ip = nil
rules.append(ruleProxyDomain!)
}
//
if ruleDirectDomain != nil {
ruleDirectDomain!.ip = nil
rules.append(ruleDirectDomain!)
}
// IP
if ruleBlockIp != nil {
ruleBlockIp!.domain = nil
rules.append(ruleBlockIp!)
}
// IP
if ruleProxyIp != nil {
ruleProxyIp!.domain = nil
rules.append(ruleProxyIp!)
}
// IP
if ruleDirectIp != nil {
ruleDirectIp!.domain = nil
rules.append(ruleDirectIp!)
}
//
if ruleDirectIpDefault != nil {
ruleDirectIpDefault!.domain = nil
rules.append(ruleDirectIpDefault!)
}
if ruleDirectDomainDefault != nil {
ruleDirectDomainDefault!.ip = nil
rules.append(ruleDirectDomainDefault!)
}
// ,
var settings = V2rayRouting()
if V2rayRouting.domainStrategy(rawValue: self.domainStrategy) == nil {
settings.domainStrategy = .AsIs
} else {
settings.domainStrategy = V2rayRouting.domainStrategy(rawValue: self.domainStrategy) ?? .AsIs
}
settings.rules = rules
return settings
}
func getRoutingRule(outTag: String, domain:[String]?, ip: [String]?, port:String?) -> V2rayRoutingRule {
var rule = V2rayRoutingRule()
rule.outboundTag = outTag
rule.type = "field"
rule.domain = domain
rule.ip = ip
rule.port = port
return rule
}
func parseDomainOrIp(domainIpStr: String) -> (domains: [String], ips: [String]) {
let all = domainIpStr.split(separator: "\n")
var domains: [String] = []
var ips: [String] = []
for item in all {
let tmp = item.trimmingCharacters(in: .whitespacesAndNewlines)
// is ip
if isIp(str: tmp) || tmp.contains("geoip:") {
ips.append(tmp)
continue
}
// is domain
if tmp.contains("domain:") || tmp.contains("geosite:") {
domains.append(tmp)
continue
}
if isDomain(str: tmp) {
domains.append(tmp)
continue
}
}
// print("ips", ips, "domains", domains)
return (domains, ips)
}
func isIp(str: String) -> Bool {
let pattern = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(/[0-9]{2})?$"
if ((str.count == 0) || (str.range(of: pattern, options: .regularExpression) == nil)) {
return false
}
return true
}
func isDomain(str: String) -> Bool {
let pattern = "[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+"
if ((str.count == 0) || (str.range(of: pattern, options: .regularExpression) == nil)) {
return false
}
return true
}
// static load from UserDefaults
static func load(name: String) -> RoutingItem? {
guard let myModelData = UserDefaults.standard.data(forKey: name) else {
print("load userDefault not found:",name)
return nil
}
do {
// unarchivedObject(ofClass:from:)
let result = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(myModelData)
return result as? RoutingItem
} catch let error {
print("load userDefault error:", error)
return nil
}
}
// remove from UserDefaults
static func remove(name: String) {
UserDefaults.standard.removeObject(forKey: name)
}
}
// parse json to V2rayRouting
func parseRoutingRuleJson(json: String) -> (V2rayRouting, err: Error?) {
// utf8
let jsonData = json.data(using: String.Encoding.utf8, allowLossyConversion: false)
if jsonData == nil {
return (V2rayRouting(), nil)
}
let jsonDecoder = JSONDecoder()
var res = V2rayRouting()
var err: Error?
do {
res = try jsonDecoder.decode(V2rayRouting.self, from: jsonData!)
} catch let error {
print("parseJson err",error)
err = error
}
return (res, err)
}