├── .gitignore ├── requirements.txt ├── Dockerfile ├── bad-list.yml ├── readme-header.md ├── .github └── workflows │ └── run-iperf3.yml ├── list.yml ├── README.md ├── check-iperf3.py └── speedtest.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.log -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyyaml -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13.5-alpine3.22 2 | 3 | COPY . . 4 | 5 | RUN pip install -r requirements.txt --no-cache-dir && apk add --no-cache iperf3 6 | 7 | CMD ["python", "check-iperf3.py"] -------------------------------------------------------------------------------- /bad-list.yml: -------------------------------------------------------------------------------- 1 | - Name: Ertelecom Novosibirsk 2 | City: Novosibirsk 3 | address: st.nsk.ertelecom.ru 4 | port: 5201 5 | - Name: Ertelecom Novokuznetsk 6 | City: Novokuznetsk 7 | address: st.nk.ertelecom.ru 8 | port: 5201 9 | - Name: TTK Ufa 10 | City: Ufa 11 | address: speed-ufa.vtt.net 12 | port: 5201 13 | - Name: TTK Samara 14 | City: Samara 15 | address: speed-sma.vtt.net 16 | port: 5201 17 | - Name: TTK Vladimir 18 | City: Vladimir 19 | address: speed-vld.vtt.net 20 | port: 5201 21 | - Name: Ertelecom Orenburg 22 | City: Orenburg 23 | address: st.oren.ertelecom.ru 24 | port: 5201 -------------------------------------------------------------------------------- /readme-header.md: -------------------------------------------------------------------------------- 1 | # Public iPerf3 servers in Russia 2 | 3 | List of public iPerf3 servers in Russia, checked daily for availability. 4 | 5 | ## How it works 6 | - The server list is stored in YAML format in `list.yml`. 7 | - The script automatically tests each iPerf3 server and records the results in the table below. 8 | 9 | ## Suggest more servers 10 | We need more servers! Please create an issue or PR if you know of others. 11 | 12 | ## Test script 13 | ``` 14 | bash <(wget -qO- https://github.com/itdoginfo/russian-iperf3-servers/raw/main/speedtest.sh) 15 | ``` 16 | 17 | Fast Mode 18 | ``` 19 | bash <(wget -qO- https://github.com/itdoginfo/russian-iperf3-servers/raw/main/speedtest.sh) -f 20 | ``` 21 | 22 | ### Dependencies 23 | - iperf3 24 | - jq 25 | - ping 26 | - awk 27 | 28 | ### Fallback 29 | For each city, there is a primary and a fallback server. The test first checks the primary server across the port range 5201–5209, only if all ports fail does it fall back to the secondary server. In the results table, this is marked as "City (F)". 30 | 31 | ## Table of servers -------------------------------------------------------------------------------- /.github/workflows/run-iperf3.yml: -------------------------------------------------------------------------------- 1 | name: Run iPerf3 Check and Update README 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '25 4 * * *' 7 | push: 8 | paths: 9 | - 'readme-header.md' 10 | - 'list.yml' 11 | - 'check-iperf3.py' 12 | - 'Dockerfile' 13 | - '.github/workflows/run-iperf3.yml' 14 | jobs: 15 | run-iperf3-check: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v4.2.2 21 | 22 | - name: Build Docker image 23 | run: docker build -t iperf3-check . 24 | 25 | - name: Run the check 26 | run: docker run --name iperf3-runner iperf3-check 27 | 28 | - name: Copy README.md from container 29 | run: docker cp iperf3-runner:/README.md README.md 30 | 31 | - name: Set up Git user 32 | run: | 33 | git config --global user.name "iperf3-checker-bot" 34 | git config --global user.email "iperf3-checker-bot@itdog.info" 35 | 36 | - name: Commit and push if README.md changed 37 | run: | 38 | git add README.md 39 | git diff --cached --quiet || git commit -m "Update from the bot" 40 | git push 41 | -------------------------------------------------------------------------------- /list.yml: -------------------------------------------------------------------------------- 1 | - Name: Ertelecom Barnaul 2 | City: Barnaul 3 | address: st.barnaul.ertelecom.ru 4 | port: 5201-5209 5 | - Name: Ertelecom Yekaterinburg 6 | City: Yekaterinburg 7 | address: st.ekat.ertelecom.ru 8 | port: 5201-5209 9 | - Name: Ertelecom Izhevsk 10 | City: Izhevsk 11 | address: st.izhevsk.ertelecom.ru 12 | port: 5201-5209 13 | - Name: Ertelecom Irkutsk 14 | City: Irkutsk 15 | address: st.irkutsk.ertelecom.ru 16 | port: 5201-5209 17 | - Name: Ertelecom Kazan 18 | City: Kazan 19 | address: st.kzn.ertelecom.ru 20 | port: 5201-5209 21 | - Name: Ertelecom Kirov 22 | City: Kirov 23 | address: st.kirov.ertelecom.ru 24 | port: 5201-5209 25 | - Name: Ertelecom Krasnoyarsk 26 | City: Krasnoyarsk 27 | address: st.krsk.ertelecom.ru 28 | port: 5201-5209 29 | - Name: Ertelecom Kurgan 30 | City: Kurgan 31 | address: st.kurgan.ertelecom.ru 32 | port: 5201-5209 33 | - Name: Ertelecom Kursk 34 | City: Kursk 35 | address: st.kursk.ertelecom.ru 36 | port: 5201-5209 37 | - Name: Ertelecom Magnitogorsk 38 | City: Magnitogorsk 39 | address: st.mgn.ertelecom.ru 40 | port: 5201-5209 41 | - Name: Ertelecom Naberezhnye Chelny 42 | City: Naberezhnye Chelny 43 | address: st.chelny.ertelecom.ru 44 | port: 5201-5209 45 | - Name: Ertelecom Omsk 46 | City: Omsk 47 | address: st.omsk.ertelecom.ru 48 | port: 5201-5209 49 | - Name: Ertelecom Penza 50 | City: Penza 51 | address: st.penza.ertelecom.ru 52 | port: 5201-5209 53 | - Name: Ertelecom Perm 54 | City: Perm 55 | address: st.perm.ertelecom.ru 56 | port: 5201-5209 57 | - Name: Ertelecom Rostov-on-Don 58 | City: Rostov-on-Don 59 | address: st.rostov.ertelecom.ru 60 | port: 5201-5209 61 | - Name: Ertelecom Ryazan 62 | City: Ryazan 63 | address: st.ryazan.ertelecom.ru 64 | port: 5201-5209 65 | - Name: Ertelecom Samara 66 | City: Samara 67 | address: st.samara.ertelecom.ru 68 | port: 5201-5209 69 | - Name: Ertelecom Saint Petersburg 70 | City: Saint Petersburg 71 | address: st.spb.ertelecom.ru 72 | port: 5201-5209 73 | - Name: Ertelecom Saratov 74 | City: Saratov 75 | address: st.saratov.ertelecom.ru 76 | port: 5201-5209 77 | - Name: Ertelecom Tver 78 | City: Tver 79 | address: st.tver.ertelecom.ru 80 | port: 5201-5209 81 | - Name: Ertelecom Tomsk 82 | City: Tomsk 83 | address: st.tomsk.ertelecom.ru 84 | port: 5201-5209 85 | - Name: Ertelecom Tula 86 | City: Tula 87 | address: st.tula.ertelecom.ru 88 | port: 5201-5209 89 | - Name: Ertelecom Tyumen 90 | City: Tyumen 91 | address: st.tmn.ertelecom.ru 92 | port: 5201-5209 93 | - Name: Ertelecom Ulyanovsk 94 | City: Ulyanovsk 95 | address: st.ulsk.ertelecom.ru 96 | port: 5201-5209 97 | - Name: Ertelecom Ufa 98 | City: Ufa 99 | address: st.ufa.ertelecom.ru 100 | port: 5201-5209 101 | - Name: Ertelecom Cheboksary 102 | City: Cheboksary 103 | address: st.cheb.ertelecom.ru 104 | port: 5201-5209 105 | - Name: Ertelecom Chelyabinsk 106 | City: Chelyabinsk 107 | address: st.chel.ertelecom.ru 108 | port: 5201-5209 109 | - Name: Ertelecom Yaroslavl 110 | City: Yaroslavl 111 | address: st.yar.ertelecom.ru 112 | port: 5201-5209 113 | - Name: Ertelecom Lipetsk 114 | City: Lipetsk 115 | address: st.lipetsk.ertelecom.ru 116 | port: 5201-5209 117 | - Name: Ertelecom Bryansk 118 | City: Bryansk 119 | address: st.bryansk.ertelecom.ru 120 | port: 5201-5209 121 | - Name: Ertelecom Volgograd 122 | City: Volgograd 123 | address: st.volgograd.ertelecom.ru 124 | port: 5201-5209 125 | - Name: Ertelecom Voronezh 126 | City: Voronezh 127 | address: st.voronezh.ertelecom.ru 128 | port: 5201-5209 129 | - Name: Ertelecom Nizhny Novgorod 130 | City: Nizhny Novgorod 131 | address: st.nn.ertelecom.ru 132 | port: 5201-5209 133 | - Name: Ertelecom Yoshkar-Ola 134 | City: Yoshkar-Ola 135 | address: st.yola.ertelecom.ru 136 | port: 5201-5209 137 | - Name: TTK Volgograd 138 | City: Volgograd 139 | address: speed-vgd.vtt.net 140 | port: 5201 141 | - Name: TTK Nizhny Novgorod 142 | City: Nizhny Novgorod 143 | address: speed-nn.vtt.net 144 | port: 5201 145 | - Name: TTK Saratov 146 | City: Saratov 147 | address: speed.vtt.net 148 | port: 5201 149 | - Name: TTK Saransk 150 | City: Saransk 151 | address: speed-sar.vtt.net 152 | port: 5201 153 | - Name: Beeline Voronezh 154 | City: Voronezh 155 | address: voronezh-speedtest.corbina.net 156 | port: 5201 157 | - Name: Beeline Astrakhan 158 | City: Astrakhan 159 | address: astrakhan1.speedtest.corbina.net 160 | port: 5201 161 | - Name: Hostkey Moscow 162 | City: Moscow 163 | address: spd-rudp.hostkey.ru 164 | port: 5201-5209 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Public iPerf3 servers in Russia 2 | 3 | List of public iPerf3 servers in Russia, checked daily for availability. 4 | 5 | ## How it works 6 | - The server list is stored in YAML format in `list.yml`. 7 | - The script automatically tests each iPerf3 server and records the results in the table below. 8 | 9 | ## Suggest more servers 10 | We need more servers! Please create an issue or PR if you know of others. 11 | 12 | ## Test script 13 | ``` 14 | bash <(wget -qO- https://github.com/itdoginfo/russian-iperf3-servers/raw/main/speedtest.sh) 15 | ``` 16 | 17 | Fast Mode 18 | ``` 19 | bash <(wget -qO- https://github.com/itdoginfo/russian-iperf3-servers/raw/main/speedtest.sh) -f 20 | ``` 21 | 22 | ### Dependencies 23 | - iperf3 24 | - jq 25 | - ping 26 | - awk 27 | 28 | ### Fallback 29 | For each city, there is a primary and a fallback server. The test first checks the primary server across the port range 5201–5209, only if all ports fail does it fall back to the secondary server. In the results table, this is marked as "City (F)". 30 | 31 | ## Table of servers 32 | 33 | | Name | City | Address | Port | Status | 34 | |------|------|---------|------|--------| 35 | | Ertelecom Barnaul | Barnaul | st.barnaul.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208 | ✅ | 36 | | Ertelecom Yekaterinburg | Yekaterinburg | st.ekat.ertelecom.ru | 5202
5203
5204
5205
5206
5207
5209 | ✅ | 37 | | Ertelecom Izhevsk | Izhevsk | st.izhevsk.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208
5209 | ✅ | 38 | | Ertelecom Irkutsk | Irkutsk | st.irkutsk.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208
5209 | ✅ | 39 | | Ertelecom Kazan | Kazan | st.kzn.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208 | ✅ | 40 | | Ertelecom Kirov | Kirov | st.kirov.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208
5209 | ✅ | 41 | | Ertelecom Krasnoyarsk | Krasnoyarsk | st.krsk.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208
5209 | ✅ | 42 | | Ertelecom Kurgan | Kurgan | st.kurgan.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5209 | ✅ | 43 | | Ertelecom Kursk | Kursk | st.kursk.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208
5209 | ✅ | 44 | | Ertelecom Magnitogorsk | Magnitogorsk | st.mgn.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5209 | ✅ | 45 | | Ertelecom Naberezhnye Chelny | Naberezhnye Chelny | st.chelny.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5209 | ✅ | 46 | | Ertelecom Omsk | Omsk | st.omsk.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5209 | ✅ | 47 | | Ertelecom Penza | Penza | st.penza.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208 | ✅ | 48 | | Ertelecom Perm | Perm | st.perm.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208 | ✅ | 49 | | Ertelecom Rostov-on-Don | Rostov-on-Don | st.rostov.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5209 | ✅ | 50 | | Ertelecom Ryazan | Ryazan | st.ryazan.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208
5209 | ✅ | 51 | | Ertelecom Samara | Samara | st.samara.ertelecom.ru | 5201
5202
5203
5204
5205
5208 | ✅ | 52 | | Ertelecom Saint Petersburg | Saint Petersburg | st.spb.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208
5209 | ✅ | 53 | | Ertelecom Saratov | Saratov | st.saratov.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5209 | ✅ | 54 | | Ertelecom Tver | Tver | st.tver.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207 | ✅ | 55 | | Ertelecom Tomsk | Tomsk | st.tomsk.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5209 | ✅ | 56 | | Ertelecom Tula | Tula | st.tula.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208
5209 | ✅ | 57 | | Ertelecom Tyumen | Tyumen | st.tmn.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208 | ✅ | 58 | | Ertelecom Ulyanovsk | Ulyanovsk | st.ulsk.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208
5209 | ✅ | 59 | | Ertelecom Ufa | Ufa | st.ufa.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5208
5209 | ✅ | 60 | | Ertelecom Cheboksary | Cheboksary | st.cheb.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5209 | ✅ | 61 | | Ertelecom Chelyabinsk | Chelyabinsk | st.chel.ertelecom.ru | 5201
5202
5203
5204
5206
5207 | ✅ | 62 | | Ertelecom Yaroslavl | Yaroslavl | st.yar.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208
5209 | ✅ | 63 | | Ertelecom Lipetsk | Lipetsk | st.lipetsk.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208 | ✅ | 64 | | Ertelecom Bryansk | Bryansk | st.bryansk.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5209 | ✅ | 65 | | Ertelecom Volgograd | Volgograd | st.volgograd.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208 | ✅ | 66 | | Ertelecom Voronezh | Voronezh | st.voronezh.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5209 | ✅ | 67 | | Ertelecom Nizhny Novgorod | Nizhny Novgorod | st.nn.ertelecom.ru | 5201
5202
5203
5204
5205
5206
5207
5208 | ✅ | 68 | | Ertelecom Yoshkar-Ola | Yoshkar-Ola | st.yola.ertelecom.ru | 5201
5202
5203
5204
5205
5207
5208
5209 | ✅ | 69 | | TTK Volgograd | Volgograd | speed-vgd.vtt.net | 5201 | ✅ | 70 | | TTK Nizhny Novgorod | Nizhny Novgorod | speed-nn.vtt.net | 5201 | ❌ | 71 | | TTK Saratov | Saratov | speed.vtt.net | 5201 | ❌ | 72 | | TTK Saransk | Saransk | speed-sar.vtt.net | 5201 | ✅ | 73 | | Beeline Voronezh | Voronezh | voronezh-speedtest.corbina.net | 5201 | ✅ | 74 | | Beeline Astrakhan | Astrakhan | astrakhan1.speedtest.corbina.net | 5201 | ✅ | 75 | | Hostkey Moscow | Moscow | spd-rudp.hostkey.ru | 5201
5202
5203
5204
5205
5206
5207 | ✅ | 76 | 77 | 📅 **Latest test:** 22.12.2025 07:58:27 (MSK, UTC+3) 78 | 79 | ✅ **Available**: 39/41 servers 80 | 81 | ❌ **Unavailable**: 2/41 servers 82 | 83 | ⏱️ **Execution time**: 223.3 seconds 84 | 85 | -------------------------------------------------------------------------------- /check-iperf3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import yaml 4 | import asyncio 5 | import sys 6 | from datetime import datetime, timezone, timedelta 7 | 8 | PORT_CHECK_TIMEOUT = 3 9 | IPERF3_TIMEOUT = 8 10 | IPERF3_TEST_DURATION = 3 11 | MAX_CONCURRENT = 15 12 | RETRY_ATTEMPTS = 3 13 | RETRY_DELAY = 2 14 | 15 | async def load_servers_from_yaml(file_path='list.yml'): 16 | """Loads the list of servers from a YAML file""" 17 | try: 18 | with open(file_path, 'r', encoding='utf-8') as f: 19 | servers = yaml.safe_load(f) 20 | return servers 21 | except FileNotFoundError: 22 | print(f"Error: file {file_path} not found") 23 | sys.exit(1) 24 | except yaml.YAMLError as e: 25 | print(f"Error reading YAML file: {e}") 26 | sys.exit(1) 27 | 28 | async def check_port_open(address, port, timeout=PORT_CHECK_TIMEOUT): 29 | """Asynchronously checks if the port is open on the server""" 30 | try: 31 | future = asyncio.open_connection(address, port) 32 | reader, writer = await asyncio.wait_for(future, timeout=timeout) 33 | writer.close() 34 | await writer.wait_closed() 35 | return True 36 | except (asyncio.TimeoutError, ConnectionRefusedError, OSError): 37 | return False 38 | except Exception: 39 | return False 40 | 41 | async def run_iperf3(address, port, timeout=IPERF3_TIMEOUT): 42 | """Simplified version of running iperf3""" 43 | try: 44 | process = await asyncio.create_subprocess_exec( 45 | 'iperf3', '-c', address, '-p', str(port), '-t', str(IPERF3_TEST_DURATION), 46 | stdout=asyncio.subprocess.PIPE, 47 | stderr=asyncio.subprocess.PIPE 48 | ) 49 | 50 | stdout, stderr = await asyncio.wait_for( 51 | process.communicate(), timeout=timeout 52 | ) 53 | 54 | if process.returncode == 0 and stdout: 55 | return True, "Test passed" 56 | 57 | return False, stderr.decode('utf-8', errors='ignore') if stderr else "Test error" 58 | 59 | except asyncio.TimeoutError: 60 | if 'process' in locals(): 61 | try: 62 | process.terminate() 63 | await asyncio.wait_for(process.wait(), timeout=2) 64 | except: 65 | process.kill() 66 | return False, "Timeout" 67 | except Exception as e: 68 | return False, str(e) 69 | 70 | def parse_ports(port_field): 71 | if isinstance(port_field, int): 72 | return [port_field] 73 | 74 | if not isinstance(port_field, str): 75 | return [5201] 76 | 77 | s = port_field.strip() 78 | if '-' in s: 79 | start, end = map(int, s.split('-', 1)) 80 | return list(range(min(start, end), max(start, end) + 1)) 81 | 82 | if ',' in s: 83 | return [int(x.strip()) for x in s.split(',') if x.strip()] 84 | 85 | return [int(s)] if s else [5201] 86 | 87 | async def test_server(server, semaphore): 88 | async with semaphore: 89 | address = server['address'] 90 | ports = parse_ports(server.get('port', 5201)) 91 | server_name = server['Name'] 92 | 93 | passed_ports = [] 94 | failed_ports = [] 95 | 96 | open_ports = [] 97 | for port in ports: 98 | if await check_port_open(address, port): 99 | open_ports.append(port) 100 | 101 | if not open_ports: 102 | print(f"{server_name} ❌ (all ports closed)") 103 | server.update({'passed_ports': [], 'failed_ports': ports}) 104 | return False 105 | 106 | for port in open_ports: 107 | success = False 108 | last_error = None 109 | 110 | for attempt in range(1, RETRY_ATTEMPTS + 1): 111 | success, error_msg = await run_iperf3(address, port) 112 | if success: 113 | print(f"{server_name} ✅ (port {port})" + 114 | (f", attempt {attempt}" if attempt > 1 else "")) 115 | passed_ports.append(port) 116 | break 117 | else: 118 | last_error = error_msg 119 | if attempt < RETRY_ATTEMPTS: 120 | print(f"{server_name} ❌ attempt {attempt} on port {port}: {error_msg}") 121 | await asyncio.sleep(RETRY_DELAY) 122 | 123 | if not success: 124 | failed_ports.append(port) 125 | if attempt == RETRY_ATTEMPTS: 126 | print(f"{server_name} ❌ port {port} failed: {last_error}") 127 | 128 | server.update({ 129 | 'passed_ports': passed_ports, 130 | 'failed_ports': failed_ports + [p for p in ports if p not in open_ports] 131 | }) 132 | 133 | return bool(passed_ports) 134 | 135 | async def test_all_servers(servers, max_concurrent=MAX_CONCURRENT): 136 | """Asynchronously tests all servers""" 137 | semaphore = asyncio.Semaphore(max_concurrent) 138 | 139 | tasks = [ 140 | asyncio.create_task(test_server(server, semaphore)) 141 | for server in servers 142 | ] 143 | 144 | results = await asyncio.gather(*tasks, return_exceptions=True) 145 | 146 | for i, server in enumerate(servers): 147 | if isinstance(results[i], Exception): 148 | server['status'] = False 149 | else: 150 | server['status'] = results[i] 151 | 152 | return servers 153 | 154 | def load_readme_header(file_path='readme-header.md'): 155 | """Loads the header content for README.md from a separate file""" 156 | try: 157 | with open(file_path, 'r', encoding='utf-8') as f: 158 | return f.read() 159 | except FileNotFoundError: 160 | print(f"Warning: {file_path} not found, using default header") 161 | return "## Table of servers\n" 162 | except Exception as e: 163 | print(f"Warning: Error reading {file_path}: {e}") 164 | return "## Table of servers\n" 165 | 166 | def generate_readme(servers, duration): 167 | """Generates README.md with results table""" 168 | content = load_readme_header() 169 | 170 | if not content.endswith('\n\n'): 171 | content += '\n\n' 172 | 173 | content += "| Name | City | Address | Port | Status |\n" 174 | content += "|------|------|---------|------|--------|\n" 175 | 176 | for server in servers: 177 | status_emoji = '✅' if server.get('status', False) else '❌' 178 | passed_ports = server.get('passed_ports', []) 179 | 180 | if server.get('status', False): 181 | port_display = "
".join(map(str, passed_ports)) 182 | else: 183 | all_ports = parse_ports(server.get('port', 5201)) 184 | port_display = "
".join(map(str, all_ports)) 185 | 186 | content += (f"| {server['Name']} | {server['City']} | " 187 | f"{server['address']} | {port_display} | {status_emoji} |\n") 188 | 189 | available = sum(1 for s in servers if s.get('status', False)) 190 | total = len(servers) 191 | current_time = datetime.now(timezone(timedelta(hours=3))).strftime("%d.%m.%Y %H:%M:%S") 192 | 193 | content += (f"\n📅 **Latest test:** {current_time} (MSK, UTC+3)\n\n" 194 | f"✅ **Available**: {available}/{total} servers\n\n" 195 | f"❌ **Unavailable**: {total - available}/{total} servers\n\n" 196 | f"⏱️ **Execution time**: {duration:.1f} seconds\n\n") 197 | 198 | try: 199 | with open('README.md', 'w', encoding='utf-8') as f: 200 | f.write(content) 201 | print(f"\n📄 README.md created successfully!") 202 | print(f"✅ Available: {available}/{total} servers") 203 | print(f"❌ Unavailable: {total - available}/{total} servers") 204 | print(f"⏱️ Execution time: {duration:.1f} seconds") 205 | except Exception as e: 206 | print(f"Error creating README.md: {e}") 207 | 208 | async def main(): 209 | """Main asynchronous function""" 210 | servers = await load_servers_from_yaml('list.yml') 211 | print(f"⚡ Starting iPerf3 testing on \033[1m{len(servers)}\033[0m servers\n") 212 | 213 | start_time = datetime.now() 214 | 215 | servers = await test_all_servers(servers) 216 | 217 | end_time = datetime.now() 218 | duration = (end_time - start_time).total_seconds() 219 | 220 | generate_readme(servers, duration) 221 | 222 | if __name__ == '__main__': 223 | asyncio.run(main()) 224 | -------------------------------------------------------------------------------- /speedtest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Network Speed Test Script from https://github.com/itdoginfo/russian-iperf3-servers 4 | 5 | # Configuration variables 6 | IPERF_TIMEOUT=15 7 | IPERF_TEST_DURATION=10 8 | PARALLEL_STREAMS=8 9 | FALLBACK_STREAMS=8 10 | INTER_TEST_DELAY=1 11 | MAX_JSON_LENGTH=50 12 | 13 | # Port range for iperf3 servers 14 | IPERF_PORT_RANGE=(5201 5202 5203 5204 5205 5206 5207 5208 5209) 15 | 16 | # Runtime flags 17 | DEBUG=false 18 | RESULTS=() 19 | 20 | # Server configurations 21 | declare -A SERVERS=( 22 | ["Moscow"]="spd-rudp.hostkey.ru" 23 | ["Saint Petersburg"]="st.spb.ertelecom.ru" 24 | ["Nizhny Novgorod"]="st.nn.ertelecom.ru" 25 | ["Chelyabinsk"]="st.chel.ertelecom.ru" 26 | ["Tyumen"]="st.tmn.ertelecom.ru" 27 | ) 28 | 29 | declare -A FALLBACK_SERVERS=( 30 | ["Moscow"]="st.tver.ertelecom.ru" 31 | ["Saint Petersburg"]="st.yar.ertelecom.ru" 32 | ["Nizhny Novgorod"]="speed-nn.vtt.net" 33 | ["Chelyabinsk"]="st.mgn.ertelecom.ru" 34 | ["Tyumen"]="st.krsk.ertelecom.ru" 35 | ) 36 | 37 | declare -A FALLBACK_CITIES=( 38 | ["Moscow"]="Tver" 39 | ["Saint Petersburg"]="Yaroslavl" 40 | ["Nizhny Novgorod"]="Nizhny Novgorod" 41 | ["Chelyabinsk"]="Magnitogorsk" 42 | ["Tyumen"]="Krasnoyarsk" 43 | ) 44 | 45 | # Test order 46 | CITY_ORDER=("Moscow" "Saint Petersburg" "Nizhny Novgorod" "Chelyabinsk" "Tyumen") 47 | 48 | # Functions 49 | find_available_port() { 50 | local host="$1" 51 | 52 | log_debug "Scanning ports for $host" 53 | 54 | for port in "${IPERF_PORT_RANGE[@]}"; do 55 | log_debug "Trying port $port on $host" 56 | 57 | local test_result 58 | test_result=$(timeout "$IPERF_TIMEOUT" iperf3 -c "$host" -p "$port" -t 1 2>&1 || echo "") 59 | 60 | if [[ "$test_result" == *"receiver"* && "$test_result" != *"error"* ]]; then 61 | log_debug "Found working port $port on $host" 62 | echo "$port" 63 | return 0 64 | fi 65 | done 66 | 67 | log_debug "No working ports found on $host" 68 | return 1 69 | } 70 | 71 | log_debug() { 72 | if [[ "$DEBUG" == true ]]; then 73 | if [[ -n "${SPINNER_PID:-}" ]]; then 74 | echo -e "\n\e[37m[DEBUG] $1\e[0m" >&2 75 | else 76 | echo -e "\e[37m[DEBUG] $1\e[0m" >&2 77 | fi 78 | fi 79 | } 80 | 81 | start_spinner() { 82 | local message="$1" 83 | echo -n "$message" 84 | ( 85 | local chars=("⠇" "⠏" "⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧") 86 | local i=0 87 | while true; do 88 | printf "\r$message %s" "${chars[$i]}" 89 | i=$(( (i + 1) % ${#chars[@]} )) 90 | sleep 0.15 91 | done 92 | ) & 93 | SPINNER_PID=$! 94 | } 95 | 96 | stop_spinner() { 97 | local result="$1" 98 | [[ -n "${SPINNER_PID:-}" ]] && kill "$SPINNER_PID" 2>/dev/null 99 | printf "\r\033[K" 100 | 101 | if [[ "$DEBUG" == true ]]; then 102 | echo "$result" 103 | fi 104 | 105 | unset SPINNER_PID 106 | } 107 | 108 | show_usage() { 109 | cat << EOF 110 | Usage: $0 [OPTIONS] 111 | 112 | OPTIONS: 113 | -d, --debug Enable debug output 114 | -f, --fast Fast mode (shorter test duration) 115 | -h, --help Show this help message 116 | 117 | EXAMPLES: 118 | $0 Run standard speed test 119 | $0 -f Run fast speed test 120 | $0 -d Run with debug output 121 | EOF 122 | } 123 | 124 | test_iperf_server() { 125 | local host="$1" 126 | local port="$2" 127 | local streams="$3" 128 | 129 | log_debug "Testing iperf3 server $host:$port with $streams streams" 130 | 131 | local result 132 | result=$(timeout "$IPERF_TIMEOUT" iperf3 -c "$host" -p "$port" -P "$streams" -t "$IPERF_TEST_DURATION" -J 2>/dev/null || echo "") 133 | 134 | # Check if we got valid JSON with receiver data 135 | if [[ -n "$result" && "$result" == *'"receiver"'* && "${#result}" -gt "$MAX_JSON_LENGTH" ]]; then 136 | echo "$result" 137 | return 0 138 | else 139 | return 1 140 | fi 141 | } 142 | 143 | parse_speed() { 144 | local json="$1" 145 | local direction="$2" 146 | 147 | if [[ "$direction" == "sender" ]]; then 148 | echo "$json" | jq -r ".end.sum_sent.bits_per_second // 0" | awk '{printf "%.1f", $1/1000000}' 149 | else 150 | echo "$json" | jq -r ".end.sum_received.bits_per_second // 0" | awk '{printf "%.1f", $1/1000000}' 151 | fi 152 | } 153 | 154 | get_ping() { 155 | local host="$1" 156 | ping -c 5 -W 2 "$host" 2>/dev/null | grep -oP 'rtt min/avg/max/mdev = [0-9.]+/\K[0-9]+' || echo "N/A" 157 | } 158 | 159 | process_test_result() { 160 | local result="$1" 161 | local city="$2" 162 | local host="$3" 163 | local port="$4" 164 | local is_fallback="${5:-false}" 165 | 166 | local download upload ping_result 167 | download=$(parse_speed "$result" "receiver") 168 | upload=$(parse_speed "$result" "sender") 169 | ping_result=$(get_ping "$host") 170 | 171 | if [[ "$download" != "0.0" ]] || [[ "$upload" != "0.0" ]]; then 172 | local display_city="$city" 173 | [[ "$is_fallback" == "true" ]] && display_city="$city (F)" 174 | 175 | stop_spinner "Testing $city ($host:$port)... ✓" 176 | RESULTS+=("$(printf "%-18s %-15s %-15s %-10s" "$display_city" "${download} Mbps" "${upload} Mbps" "${ping_result} ms")") 177 | return 0 178 | fi 179 | return 1 180 | } 181 | 182 | test_server() { 183 | local city="$1" 184 | local host="$2" 185 | local fallback_host="$3" 186 | 187 | local fallback_city="${FALLBACK_CITIES[$city]}" 188 | 189 | start_spinner "Testing $city ($host)..." 190 | 191 | # Test primary server 192 | local port 193 | if port=$(find_available_port "$host"); then 194 | local result 195 | # Try with full streams 196 | if result=$(test_iperf_server "$host" "$port" "$PARALLEL_STREAMS"); then 197 | if process_test_result "$result" "$city" "$host" "$port"; then 198 | return 0 199 | fi 200 | fi 201 | 202 | # Retry with fewer streams 203 | log_debug "Retrying with $FALLBACK_STREAMS stream" 204 | if result=$(test_iperf_server "$host" "$port" "$FALLBACK_STREAMS"); then 205 | if process_test_result "$result" "$city" "$host" "$port"; then 206 | return 0 207 | fi 208 | fi 209 | fi 210 | 211 | # Test fallback server 212 | log_debug "Primary server failed, trying fallback $fallback_host" 213 | 214 | local fallback_port 215 | if fallback_port=$(find_available_port "$fallback_host"); then 216 | local result 217 | # Try fallback with full streams 218 | if result=$(test_iperf_server "$fallback_host" "$fallback_port" "$PARALLEL_STREAMS"); then 219 | if process_test_result "$result" "$fallback_city" "$fallback_host" "$fallback_port" "true"; then 220 | return 0 221 | fi 222 | fi 223 | 224 | # Retry fallback with fewer streams 225 | log_debug "Retrying fallback with $FALLBACK_STREAMS stream" 226 | if result=$(test_iperf_server "$fallback_host" "$fallback_port" "$FALLBACK_STREAMS"); then 227 | if process_test_result "$result" "$fallback_city" "$fallback_host" "$fallback_port" "true"; then 228 | return 0 229 | fi 230 | fi 231 | fi 232 | 233 | stop_spinner "Testing $city ($host)... ✗" 234 | RESULTS+=("$(printf "%-18s %-15s %-15s %-10s" "$city" "\e[31m-\e[0m" "\e[31m-\e[0m" "N/A")") 235 | return 1 236 | } 237 | 238 | run_tests() { 239 | log_debug "Starting network speed tests" 240 | 241 | for city in "${CITY_ORDER[@]}"; do 242 | local server="${SERVERS[$city]}" 243 | local fallback="${FALLBACK_SERVERS[$city]}" 244 | 245 | test_server "$city" "$server" "$fallback" 246 | 247 | # Add delay between tests to prevent server overload 248 | [[ ${#CITY_ORDER[@]} -gt 1 ]] && sleep "$INTER_TEST_DELAY" 249 | done 250 | } 251 | 252 | print_results() { 253 | echo 254 | printf '🤝 \033[1;32m%s\033[0m\n' "From the community, for the community:" 255 | printf '\033[0;34m%s\033[0m\n\n' "https://github.com/itdoginfo/russian-iperf3-servers" 256 | printf "%-18s %-15s %-15s %-10s\n" "Server" "Download" "Upload" "Ping" 257 | printf "%-18s %-15s %-15s %-10s\n" "------" "--------" "------" "----" 258 | 259 | for result in "${RESULTS[@]}"; do 260 | echo -e "$result" 261 | done 262 | } 263 | 264 | cleanup() { 265 | [[ -n "${SPINNER_PID:-}" ]] && kill "$SPINNER_PID" 2>/dev/null 266 | exit 0 267 | } 268 | 269 | main() { 270 | local start_time=$(date +%s) 271 | 272 | # Set up signal handlers 273 | trap cleanup SIGINT SIGTERM 274 | 275 | # Parse command line arguments 276 | while [[ $# -gt 0 ]]; do 277 | case $1 in 278 | -d|--debug) 279 | DEBUG=true 280 | echo "Debug mode enabled" 281 | shift 282 | ;; 283 | -f|--fast) 284 | IPERF_TEST_DURATION=1 285 | IPERF_TIMEOUT=5 286 | echo "Fast mode enabled" 287 | shift 288 | ;; 289 | -h|--help) 290 | show_usage 291 | exit 0 292 | ;; 293 | *) 294 | echo "Unknown option: $1" >&2 295 | show_usage >&2 296 | exit 1 297 | ;; 298 | esac 299 | done 300 | 301 | # Check dependencies 302 | for cmd in iperf3 jq awk ping; do 303 | if ! command -v "$cmd" &> /dev/null; then 304 | echo "Error: Required command '$cmd' not found. Please install it and rerun the script." >&2 305 | exit 1 306 | fi 307 | done 308 | 309 | run_tests 310 | print_results 311 | 312 | local end_time=$(date +%s) 313 | local execution_time=$((end_time - start_time)) 314 | echo 315 | printf "\033[0;36mExecution time: %d seconds\033[0m\n" "$execution_time" 316 | } 317 | 318 | main "$@" --------------------------------------------------------------------------------