Skip to content

Commit ef95643

Browse files
Implement HTTP proxy option (#515). (#521)
This commit adds a "Proxy" field to the network settings screen, which can be used to specify a HTTP proxy for any outgoing requests from the device.
1 parent 1fc603b commit ef95643

File tree

6 files changed

+66
-4
lines changed

6 files changed

+66
-4
lines changed

internal/confparser/confparser.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package confparser
33
import (
44
"fmt"
55
"net"
6+
"net/url"
67
"reflect"
78
"slices"
89
"strconv"
@@ -372,6 +373,10 @@ func (f *FieldConfig) validateField() error {
372373
if _, err := idna.Lookup.ToASCII(val); err != nil {
373374
return fmt.Errorf("field `%s` is not a valid hostname: %s", f.Name, val)
374375
}
376+
case "proxy":
377+
if url, err := url.Parse(val); err != nil || (url.Scheme != "http" && url.Scheme != "https") || url.Host == "" {
378+
return fmt.Errorf("field `%s` is not a valid HTTP proxy URL: %s", f.Name, val)
379+
}
375380
default:
376381
return fmt.Errorf("field `%s` cannot use validate_type: unsupported validator: %s", f.Name, validateType)
377382
}

internal/network/config.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package network
33
import (
44
"fmt"
55
"net"
6+
"net/http"
7+
"net/url"
68
"time"
79

810
"github.com/guregu/null/v6"
@@ -32,8 +34,9 @@ type IPv6StaticConfig struct {
3234
DNS []string `json:"dns,omitempty" validate_type:"ipv6" required:"true"`
3335
}
3436
type NetworkConfig struct {
35-
Hostname null.String `json:"hostname,omitempty" validate_type:"hostname"`
36-
Domain null.String `json:"domain,omitempty" validate_type:"hostname"`
37+
Hostname null.String `json:"hostname,omitempty" validate_type:"hostname"`
38+
HTTPProxy null.String `json:"http_proxy,omitempty" validate_type:"proxy"`
39+
Domain null.String `json:"domain,omitempty" validate_type:"hostname"`
3740

3841
IPv4Mode null.String `json:"ipv4_mode,omitempty" one_of:"dhcp,static,disabled" default:"dhcp"`
3942
IPv4Static *IPv4StaticConfig `json:"ipv4_static,omitempty" required_if:"IPv4Mode=static"`
@@ -71,6 +74,18 @@ func (c *NetworkConfig) GetMDNSMode() *mdns.MDNSListenOptions {
7174

7275
return listenOptions
7376
}
77+
78+
func (s *NetworkConfig) GetTransportProxyFunc() func(*http.Request) (*url.URL, error) {
79+
return func(*http.Request) (*url.URL, error) {
80+
if s.HTTPProxy.String == "" {
81+
return nil, nil
82+
} else {
83+
proxyUrl, _ := url.Parse(s.HTTPProxy.String)
84+
return proxyUrl, nil
85+
}
86+
}
87+
}
88+
7489
func (s *NetworkInterfaceState) GetHostname() string {
7590
hostname := ToValidHostname(s.config.Hostname.String)
7691

internal/timesync/http.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"math/rand"
77
"net/http"
8+
"net/url"
89
"strconv"
910
"time"
1011
)
@@ -57,6 +58,7 @@ func (t *TimeSync) queryMultipleHttp(urls []string, timeout time.Duration) (now
5758
ctx,
5859
url,
5960
timeout,
61+
t.networkConfig.GetTransportProxyFunc(),
6062
)
6163
duration := time.Since(startTime)
6264

@@ -122,10 +124,16 @@ func queryHttpTime(
122124
ctx context.Context,
123125
url string,
124126
timeout time.Duration,
127+
proxyFunc func(*http.Request) (*url.URL, error),
125128
) (now *time.Time, response *http.Response, err error) {
129+
transport := http.DefaultTransport.(*http.Transport).Clone()
130+
transport.Proxy = proxyFunc
131+
126132
client := http.Client{
127-
Timeout: timeout,
133+
Transport: transport,
134+
Timeout: timeout,
128135
}
136+
129137
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
130138
if err != nil {
131139
return nil, nil, err

ota.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,14 @@ func fetchUpdateMetadata(ctx context.Context, deviceId string, includePreRelease
9393
return nil, fmt.Errorf("error creating request: %w", err)
9494
}
9595

96-
resp, err := http.DefaultClient.Do(req)
96+
transport := http.DefaultTransport.(*http.Transport).Clone()
97+
transport.Proxy = config.NetworkConfig.GetTransportProxyFunc()
98+
99+
client := &http.Client{
100+
Transport: transport,
101+
}
102+
103+
resp, err := client.Do(req)
97104
if err != nil {
98105
return nil, fmt.Errorf("error sending request: %w", err)
99106
}
@@ -139,6 +146,7 @@ func downloadFile(ctx context.Context, path string, url string, downloadProgress
139146
client := http.Client{
140147
Timeout: 10 * time.Minute,
141148
Transport: &http.Transport{
149+
Proxy: config.NetworkConfig.GetTransportProxyFunc(),
142150
TLSHandshakeTimeout: 30 * time.Second,
143151
TLSClientConfig: &tls.Config{
144152
RootCAs: rootcerts.ServerCertPool(),

ui/src/hooks/stores.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,7 @@ export type TimeSyncMode =
747747
export interface NetworkSettings {
748748
hostname: string;
749749
domain: string;
750+
http_proxy: string;
750751
ipv4_mode: IPv4Mode;
751752
ipv6_mode: IPv6Mode;
752753
lldp_mode: LLDPMode;

ui/src/routes/devices.$id.settings.network.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ dayjs.extend(relativeTime);
3434

3535
const defaultNetworkSettings: NetworkSettings = {
3636
hostname: "",
37+
http_proxy: "",
3738
domain: "",
3839
ipv4_mode: "unknown",
3940
ipv6_mode: "unknown",
@@ -185,6 +186,10 @@ export default function SettingsNetworkRoute() {
185186
setNetworkSettings({ ...networkSettings, hostname: value });
186187
};
187188

189+
const handleProxyChange = (value: string) => {
190+
setNetworkSettings({ ...networkSettings, http_proxy: value });
191+
};
192+
188193
const handleDomainChange = (value: string) => {
189194
setNetworkSettings({ ...networkSettings, domain: value });
190195
};
@@ -253,6 +258,26 @@ export default function SettingsNetworkRoute() {
253258
</div>
254259
</SettingsItem>
255260
</div>
261+
<div className="space-y-4">
262+
<SettingsItem
263+
title="HTTP Proxy"
264+
description="Proxy server for outgoing HTTP(S) requests from the device. Blank for none."
265+
>
266+
<div className="relative">
267+
<div>
268+
<InputField
269+
size="SM"
270+
type="text"
271+
placeholder="http://proxy.example.com:8080/"
272+
defaultValue={networkSettings.http_proxy}
273+
onChange={e => {
274+
handleProxyChange(e.target.value);
275+
}}
276+
/>
277+
</div>
278+
</div>
279+
</SettingsItem>
280+
</div>
256281

257282
<div className="space-y-4">
258283
<div className="space-y-1">

0 commit comments

Comments
 (0)