Skip to content

Commit 7826d8b

Browse files
[Feature] Skip test when Public IP is in a list (alexjustesen#1714) (alexjustesen#1795)
* [Feature] Skip test when Public IP is in an list (alexjustesen#1714) * first commit * Commit it * lint * update-all-charts * push_local_git * add-timepicker * add-some-predefined-ranges * remove whiteline * Add_env_for_chart_start * change-env-and_time-ranges * change_average_to_orange * Revert "Add failed and thresholds" * first commit * change_api * update comments * Simplify * Seperate the IP check * extracted get external ip address to reusable action * moved ip in range to network helper * made skip ips config plural * refactored speedtest job to use actions and helpers * always show data message * removed dup code * fixed capturing error message --------- Co-authored-by: Sven van Ginkel <[email protected]>
1 parent 0bdd141 commit 7826d8b

File tree

7 files changed

+147
-5
lines changed

7 files changed

+147
-5
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace App\Actions\Helpers;
4+
5+
use Illuminate\Support\Facades\Http;
6+
use Illuminate\Support\Facades\Log;
7+
use Illuminate\Support\Str;
8+
use Lorisleiva\Actions\Concerns\AsAction;
9+
10+
class GetExternalIpAddress
11+
{
12+
use AsAction;
13+
14+
public function handle(): bool|string
15+
{
16+
$response = Http::retry(3, 100)
17+
->get('https://icanhazip.com/');
18+
19+
if ($response->failed()) {
20+
$message = sprintf('Failed to fetch public IP address, %d', $response->status());
21+
22+
Log::warning($message);
23+
24+
return false;
25+
}
26+
27+
return Str::trim($response->body());
28+
}
29+
}

app/Enums/ResultStatus.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ enum ResultStatus: string implements HasColor, HasLabel
99
{
1010
case Completed = 'completed'; // a speedtest that ran successfully.
1111
case Failed = 'failed'; // a speedtest that failed to run successfully.
12-
case Started = 'started'; // a speedtest that has been started by a worker but has not finish running.
12+
case Started = 'started'; // a speedtest that has been started by a worker but has not finished running.
13+
case Skipped = 'skipped'; // a speedtest that was skipped.
1314

1415
public function getColor(): ?string
1516
{
1617
return match ($this) {
1718
self::Completed => 'success',
1819
self::Failed => 'danger',
1920
self::Started => 'warning',
21+
self::Skipped => 'info', // Adding Skipped state with a color
2022
};
2123
}
2224

@@ -26,6 +28,7 @@ public function getLabel(): ?string
2628
self::Completed => 'Completed',
2729
self::Failed => 'Failed',
2830
self::Started => 'Started',
31+
self::Skipped => 'Skipped',
2932
};
3033
}
3134
}

app/Events/SpeedtestSkipped.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace App\Events;
4+
5+
use App\Models\Result;
6+
use Illuminate\Broadcasting\InteractsWithSockets;
7+
use Illuminate\Foundation\Events\Dispatchable;
8+
use Illuminate\Queue\SerializesModels;
9+
10+
class SpeedtestSkipped
11+
{
12+
use Dispatchable, InteractsWithSockets, SerializesModels;
13+
14+
/**
15+
* Create a new event instance.
16+
*/
17+
public function __construct(
18+
public Result $result,
19+
) {}
20+
}

app/Filament/Resources/ResultResource.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,8 @@ public static function form(Form $form): Form
117117
return number_format((float) $state, 2, '.', '').' %';
118118
}),
119119
Forms\Components\Textarea::make('data.message')
120-
->label('Error Message')
120+
->label('Message')
121121
->hint(new HtmlString('&#x1f517;<a href="https://docs.speedtest-tracker.dev/help/error-messages" target="_blank" rel="nofollow">Error Messages</a>'))
122-
->hidden(fn (Result $record): bool => $record->status !== ResultStatus::Failed)
123122
->columnSpanFull(),
124123
])
125124
->columnSpan(2),

app/Helpers/Network.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace App\Helpers;
4+
5+
class Network
6+
{
7+
/**
8+
* Check if the given ip is in a network range.
9+
*/
10+
public static function ipInRange(string $ip, string $range): bool
11+
{
12+
[$range, $mask] = explode('/', $range) + [1 => '32'];
13+
14+
$rangeDecimal = ip2long($range);
15+
16+
$ipDecimal = ip2long($ip);
17+
18+
$maskDecimal = ~((1 << (32 - (int) $mask)) - 1);
19+
20+
return ($rangeDecimal & $maskDecimal) === ($ipDecimal & $maskDecimal);
21+
}
22+
}

app/Jobs/Speedtests/ExecuteOoklaSpeedtest.php

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
namespace App\Jobs\Speedtests;
44

5+
use App\Actions\Helpers\GetExternalIpAddress;
56
use App\Enums\ResultStatus;
67
use App\Events\SpeedtestCompleted;
78
use App\Events\SpeedtestFailed;
9+
use App\Events\SpeedtestSkipped;
10+
use App\Helpers\Network;
811
use App\Models\Result;
912
use Illuminate\Bus\Queueable;
1013
use Illuminate\Contracts\Queue\ShouldBeUnique;
@@ -13,7 +16,6 @@
1316
use Illuminate\Queue\InteractsWithQueue;
1417
use Illuminate\Queue\SerializesModels;
1518
use Illuminate\Support\Arr;
16-
use Illuminate\Support\Facades\URL;
1719
use JJG\Ping;
1820
use Symfony\Component\Process\Exception\ProcessFailedException;
1921
use Symfony\Component\Process\Process;
@@ -46,6 +48,20 @@ public function handle(): void
4648
return;
4749
}
4850

51+
$externalIp = GetExternalIpAddress::run();
52+
53+
$shouldSkip = $this->shouldSkip($externalIp);
54+
55+
if ($shouldSkip !== false) {
56+
$this->markAsSkipped(
57+
message: $shouldSkip,
58+
externalIp: $externalIp,
59+
);
60+
61+
return;
62+
}
63+
64+
// Execute Speedtest
4965
$options = array_filter([
5066
'speedtest',
5167
'--accept-license',
@@ -110,6 +126,26 @@ public function handle(): void
110126
SpeedtestCompleted::dispatch($this->result);
111127
}
112128

129+
/**
130+
* Mark the test as skipped with a specific message.
131+
*/
132+
protected function markAsSkipped(string $message, string $externalIp): void
133+
{
134+
$this->result->update([
135+
'data' => [
136+
'type' => 'log',
137+
'level' => 'warning',
138+
'message' => $message,
139+
'interface' => [
140+
'externalIp' => $externalIp,
141+
],
142+
],
143+
'status' => ResultStatus::Skipped,
144+
]);
145+
146+
SpeedtestSkipped::dispatch($this->result);
147+
}
148+
113149
/**
114150
* Check for internet connection.
115151
*
@@ -120,6 +156,7 @@ protected function checkForInternetConnection(): bool
120156
$url = config('speedtest.ping_url');
121157

122158
// Skip checking for internet connection if ping url isn't set (disabled)
159+
123160
if (blank($url)) {
124161
return true;
125162
}
@@ -139,7 +176,6 @@ protected function checkForInternetConnection(): bool
139176
return false;
140177
}
141178

142-
// Remove http:// or https:// from the URL if present
143179
$url = preg_replace('/^https?:\/\//', '', $url);
144180

145181
$ping = new Ping(
@@ -167,6 +203,8 @@ protected function checkForInternetConnection(): bool
167203

168204
/**
169205
* Check if the given URL is a valid ping URL.
206+
*
207+
* TODO: move to Network helper
170208
*/
171209
public function isValidPingUrl(string $url): bool
172210
{
@@ -180,4 +218,30 @@ public function isValidPingUrl(string $url): bool
180218
|| (filter_var('https://'.$url, FILTER_VALIDATE_URL) && $hasTLD($url))
181219
|| filter_var($url, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 || FILTER_FLAG_IPV6) !== false;
182220
}
221+
222+
/**
223+
* Check if the speedtest should be skipped based on the skip ips list.
224+
*/
225+
public function shouldSkip(string $externalIp): bool|string
226+
{
227+
if (blank(config('speedtest.skip_ips'))) {
228+
return false;
229+
}
230+
231+
$skipIPs = array_map('trim', explode(',', config('speedtest.skip_ips')));
232+
233+
foreach ($skipIPs as $ip) {
234+
// Check for exact IP match
235+
if (filter_var($ip, FILTER_VALIDATE_IP) && $externalIp === $ip) {
236+
return sprintf('"%s" was found in public IP address skip list.', $externalIp);
237+
}
238+
239+
// Check for IP range match
240+
if (strpos($ip, '/') !== false && Network::ipInRange($externalIp, $ip)) {
241+
return sprintf('"%s" was found in public IP address skip list within range "%s".', $externalIp, $ip);
242+
}
243+
}
244+
245+
return false;
246+
}
183247
}

config/speedtest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,9 @@
3535

3636
'servers' => env('SPEEDTEST_SERVERS', ''),
3737

38+
/**
39+
* IP filtering settings.
40+
*/
41+
'skip_ips' => env('SPEEDTEST_SKIP_IPS'),
42+
3843
];

0 commit comments

Comments
 (0)