Skip to content
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ env/

# IDE
.vscode/
.vs/
.idea/
*.code-workspace
*.swp
Expand Down
9 changes: 7 additions & 2 deletions config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,13 @@
".doubleclick.net"
],
"direct_hosts": [
"rubika.ir",
"soft98.ir"
"collector.github.com",
"api.github.com",
"www.github.com",
"regex:^github\\.com$",
"geosite:ir",
"geoip:ir",
"geosite:deepseek"
],
"youtube_via_relay": false,
"hosts": {
Expand Down
35 changes: 35 additions & 0 deletions src/geo/ReadMe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
اضافه شدن geoip , geosite جهت سهلوت فیلترینگ سایت های ایرانی و یا اتصال دایرکت به سرویس هایی مانند deepseek با استفاده از تگ های استاندارد
v2dat => xray
جهت پردازش اتصال به سایت پیش از ارسال به GAS

نمونه تغییرات در کانفیگ
"collector.github.com",
"api.github.com",
"www.github.com",
"regex:^github\\.com$",
"geosite:ir",
"geoip:ir",
"geosite:deepseek"
توضیحات تگ ها
geoip
با استفاده از این کانفیگ ها می توان علاوه بر اتصال مستقیم به برخی سایتها یک حوزه IP مانند ایران را مستقیم و بدون نیاز به پروکسی باز کرد
geosite
می توان با استفاده از این تگ سایت و زیرمجموعه های دیگر آن را به لیست دایرکت یا مسدودی اضافه کرد. به عنوان مثال با تگه دیپ‌سیک تمامی وبسایتها مرتبط با وبسایت و پروژه دیپ‌سیک مستقیم باز خواهد شد
regex
با استفاده از رجکس می توان علاوه بر محدود سازی محدوده یا زیر دامنه می توان به صورت مطلق یک دامنه را صرفا با ریشه باز کرد و یا بست. به عنوان مثال دامنه های
https://github.com
https://www.github.com
مستقیم وصل بشند و باقی زیر دامنه ها از GAS عبور کند.

در صورتی که فایلهای geoip , geosite در ریشه اصلی برنامه موجود نباشد، برنامه سایر تگ ها را بررسی می کند و پیش فرض های تعریف شده خودش رو نیز لحاظ می کند.

جهت دریافت فایلهای geosite ,geoip می توان از مسیر های زیر اقدام به دانلود نمود:

Iran Xray Chocolate4U:
https://github.com/Chocolate4U/Iran-v2ray-rules/releases/latest/download/{0}.dat

Russia Xray runetfreedom:
https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/{0}.dat

پس از دانلود فایلهای geosite و geoip و با قرارگرفتن آنها در کنار پروژه اصلی، برای اولین فراخوانی و اولین اتصال به یک دامنه، دیتای فایلهای مربوطه استراخ می گردد
پس از استخراج اولیه که حدود 2 تا 7 ثانیه زمان خواهد برد، پس از آن با الگوریتم بهینه شده موجود در کمترین زمان ممکته فیلترینگ دامنه اتفاق می افد
4 changes: 4 additions & 0 deletions src/geo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .geosite_parser import *
from .geoip_parser import *
from .rule_engine import *

152 changes: 152 additions & 0 deletions src/geo/geoip_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""
GeoIP Manager - Using dat-editor parser
"""

import os
import sys
import logging
from typing import List, Optional

log = logging.getLogger("GeoParser")

# Add current directory to path to ensure geoparser is found
_current_dir = os.path.dirname(os.path.abspath(__file__))
if _current_dir not in sys.path:
sys.path.insert(0, _current_dir)

# Now import from geoparser
try:
from src.geo.geoparser.parser import load_geoip
from src.geo.geoparser.models import GeoIPList, GeoIP, CIDR
from src.geo.geoparser.storage import load as load_dat_file
GEOPARSER_AVAILABLE = True
except ImportError as e:
log.info(f"Warning: Could not import geoparser: {e}")
log.info("Falling back to built-in parser...")
GEOPARSER_AVAILABLE = False


class GeoIPManager:
"""Manager for geoip operations"""

def __init__(self, dat_path: str = "geoip.dat"):
self.dat_path = dat_path
self.geoip_list = None
self.is_loaded = False

def load(self) -> bool:
if (self.is_loaded):
return True

"""Load geoip.dat file"""
if not os.path.exists(self.dat_path):
log.info(f"geoip.dat not found at {self.dat_path}")
return False

if not GEOPARSER_AVAILABLE:
log.info("Geoparser not available, using fallback mode")
return False

try:
# Use storage.load() which auto-detects file type
from geoparser.storage import load as load_dat
ftype, data = load_dat(self.dat_path)

if ftype == "geoip":
self.geoip_list = data
self.is_loaded = True

total_cidrs = sum(len(entry.cidrs) for entry in self.geoip_list.entries)
log.info(f"Loaded {len(self.geoip_list.entries)} country records from geoip.dat")
log.info(f"Total IP ranges: {total_cidrs}")

return True
else:
log.error(f"File type mismatch: expected geoip, got {ftype}")
return False

except Exception as e:
log.error(f"Error loading geoip.dat: {e}")
return False

def get_ip_ranges_by_country(self, country_code: str) -> List[str]:
"""Get IP ranges for a country code"""
if not self.is_loaded:
if not self.load():
return self._get_builtin_fallback(country_code)

if not self.geoip_list:
return self._get_builtin_fallback(country_code)

country_upper = country_code.upper()

# Search in loaded data
for entry in self.geoip_list.entries:
if entry.code.upper() == country_upper:
return [cidr.to_string() for cidr in entry.cidrs]

# Try partial match
for entry in self.geoip_list.entries:
if country_upper in entry.code.upper() or entry.code.upper() in country_upper:
return [cidr.to_string() for cidr in entry.cidrs]

return self._get_builtin_fallback(country_code)

def get_all_countries(self) -> List[str]:
"""Get all available country codes"""
if not self.is_loaded:
self.load()

if self.geoip_list:
return sorted([entry.code for entry in self.geoip_list.entries])
return []

def _get_builtin_fallback(self, country_code: str) -> List[str]:
"""Fallback built-in IP ranges"""
builtin_ranges = {
'US': ['8.8.8.0/24', '8.8.4.0/24', '4.4.4.0/24', '13.32.0.0/15'],
'CN': ['1.0.0.0/8', '14.0.0.0/8', '27.0.0.0/8', '36.0.0.0/8', '39.0.0.0/8'],
'RU': ['5.0.0.0/8', '31.0.0.0/8', '46.0.0.0/8', '77.0.0.0/8', '80.0.0.0/8'],
'DE': ['2.200.0.0/13', '5.144.128.0/19', '13.224.0.0/14'],
'GB': ['2.96.0.0/11', '2.120.0.0/13', '5.64.0.0/13'],
'JP': ['1.0.16.0/20', '1.0.64.0/18', '1.1.0.0/16', '1.21.0.0/16'],
'KR': ['1.11.0.0/16', '1.96.0.0/13', '1.208.0.0/12', '3.32.0.0/14'],
'FR': ['2.0.0.0/12', '2.16.0.0/13', '2.24.0.0/14', '5.48.0.0/13'],
}

country_upper = country_code.upper()
if country_upper in builtin_ranges:
log.info(f"Using built-in IP ranges for '{country_upper}'")
return builtin_ranges[country_upper]

return []


# Quick test
if __name__ == "__main__":
manager = GeoIPManager("geoip.dat")

print("="*50)
print("Testing GeoIP Manager")
print("="*50)

if os.path.exists("geoip.dat"):
manager.load()
countries = manager.get_all_countries()
print(f"\nCountries found: {len(countries)}")
if countries:
print(f"Sample: {', '.join(countries[:2000])}")
else:
print("geoip.dat not found, using built-in fallback")

# Test extraction
print("\n" + "="*50)
print("IP Range Extraction Test:")
print("="*50)

for test_code in ['IR']:
ranges = manager.get_ip_ranges_by_country(test_code)
print(f"\n{test_code}: {len(ranges)} IP ranges")
if ranges:
print(f" Sample: {ranges[:30000]}")
7 changes: 7 additions & 0 deletions src/geo/geoparser/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env python3
"""
Geoparser module - for reading geosite.dat and geoip.dat files
"""
from .models import *
from .parser import *
from .storage import *
80 changes: 80 additions & 0 deletions src/geo/geoparser/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""
Data models for geosite.dat and geoip.dat entries.
"""
from __future__ import annotations

from dataclasses import dataclass, field
from typing import List, Optional


DOMAIN_TYPE_TO_NUM = {
"plain": 0, # substring match
"regex": 1,
"root": 2, # root domain match
"full": 3, # exact match
}
NUM_TO_DOMAIN_TYPE = {v: k for k, v in DOMAIN_TYPE_TO_NUM.items()}


@dataclass
class Attribute:
key: str
bool_value: Optional[bool] = None
int_value: Optional[int] = None

def render(self) -> str:
if self.int_value is not None:
return f"{self.key}={self.int_value}"
if self.bool_value in (True, None):
return self.key
if self.bool_value is False:
return f"{self.key}=false"
return self.key


@dataclass
class Domain:
value: str
dtype: str = "plain"
attributes: List[Attribute] = field(default_factory=list)

def render(self) -> str:
attr = "" if not self.attributes else " @" + ",".join(a.render() for a in self.attributes)
return f"{self.dtype}: {self.value}{attr}"


@dataclass
class CIDR:
ip: bytes
prefix: int

def to_string(self) -> str:
import ipaddress

addr = ipaddress.ip_address(self.ip)
return f"{addr}/{self.prefix}"


@dataclass
class GeoSite:
code: str
country_code: str = ""
domains: List[Domain] = field(default_factory=list)


@dataclass
class GeoIP:
code: str
country_code: str = ""
cidrs: List[CIDR] = field(default_factory=list)
inverse_match: bool = False


@dataclass
class GeoSiteList:
entries: List[GeoSite] = field(default_factory=list)


@dataclass
class GeoIPList:
entries: List[GeoIP] = field(default_factory=list)
Loading