This commit is contained in:
Daniel Collins 2025-06-20 14:19:18 -05:00 committed by GitHub
commit a64cd261bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 66 additions and 4 deletions

View File

@ -3,6 +3,7 @@ package confparser
import ( import (
"fmt" "fmt"
"net" "net"
"net/url"
"reflect" "reflect"
"slices" "slices"
"strconv" "strconv"
@ -372,6 +373,10 @@ func (f *FieldConfig) validateField() error {
if _, err := idna.Lookup.ToASCII(val); err != nil { if _, err := idna.Lookup.ToASCII(val); err != nil {
return fmt.Errorf("field `%s` is not a valid hostname: %s", f.Name, val) return fmt.Errorf("field `%s` is not a valid hostname: %s", f.Name, val)
} }
case "proxy":
if url, err := url.Parse(val); err != nil || (url.Scheme != "http" && url.Scheme != "https") || url.Host == "" {
return fmt.Errorf("field `%s` is not a valid HTTP proxy URL: %s", f.Name, val)
}
default: default:
return fmt.Errorf("field `%s` cannot use validate_type: unsupported validator: %s", f.Name, validateType) return fmt.Errorf("field `%s` cannot use validate_type: unsupported validator: %s", f.Name, validateType)
} }

View File

@ -3,6 +3,8 @@ package network
import ( import (
"fmt" "fmt"
"net" "net"
"net/http"
"net/url"
"time" "time"
"github.com/guregu/null/v6" "github.com/guregu/null/v6"
@ -32,8 +34,9 @@ type IPv6StaticConfig struct {
DNS []string `json:"dns,omitempty" validate_type:"ipv6" required:"true"` DNS []string `json:"dns,omitempty" validate_type:"ipv6" required:"true"`
} }
type NetworkConfig struct { type NetworkConfig struct {
Hostname null.String `json:"hostname,omitempty" validate_type:"hostname"` Hostname null.String `json:"hostname,omitempty" validate_type:"hostname"`
Domain null.String `json:"domain,omitempty" validate_type:"hostname"` HTTPProxy null.String `json:"http_proxy,omitempty" validate_type:"proxy"`
Domain null.String `json:"domain,omitempty" validate_type:"hostname"`
IPv4Mode null.String `json:"ipv4_mode,omitempty" one_of:"dhcp,static,disabled" default:"dhcp"` IPv4Mode null.String `json:"ipv4_mode,omitempty" one_of:"dhcp,static,disabled" default:"dhcp"`
IPv4Static *IPv4StaticConfig `json:"ipv4_static,omitempty" required_if:"IPv4Mode=static"` IPv4Static *IPv4StaticConfig `json:"ipv4_static,omitempty" required_if:"IPv4Mode=static"`
@ -69,6 +72,18 @@ func (c *NetworkConfig) GetMDNSMode() *mdns.MDNSListenOptions {
return listenOptions return listenOptions
} }
func (s *NetworkConfig) GetTransportProxyFunc() func(*http.Request) (*url.URL, error) {
return func(*http.Request) (*url.URL, error) {
if s.HTTPProxy.String == "" {
return nil, nil
} else {
proxyUrl, _ := url.Parse(s.HTTPProxy.String)
return proxyUrl, nil
}
}
}
func (s *NetworkInterfaceState) GetHostname() string { func (s *NetworkInterfaceState) GetHostname() string {
hostname := ToValidHostname(s.config.Hostname.String) hostname := ToValidHostname(s.config.Hostname.String)

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"math/rand" "math/rand"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"time" "time"
) )
@ -57,6 +58,7 @@ func (t *TimeSync) queryMultipleHttp(urls []string, timeout time.Duration) (now
ctx, ctx,
url, url,
timeout, timeout,
t.networkConfig.GetTransportProxyFunc(),
) )
duration := time.Since(startTime) duration := time.Since(startTime)
@ -122,10 +124,16 @@ func queryHttpTime(
ctx context.Context, ctx context.Context,
url string, url string,
timeout time.Duration, timeout time.Duration,
proxyFunc func(*http.Request) (*url.URL, error),
) (now *time.Time, response *http.Response, err error) { ) (now *time.Time, response *http.Response, err error) {
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.Proxy = proxyFunc
client := http.Client{ client := http.Client{
Timeout: timeout, Transport: transport,
Timeout: timeout,
} }
req, err := http.NewRequestWithContext(ctx, "GET", url, nil) req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err

10
ota.go
View File

@ -89,7 +89,14 @@ func fetchUpdateMetadata(ctx context.Context, deviceId string, includePreRelease
return nil, fmt.Errorf("error creating request: %w", err) return nil, fmt.Errorf("error creating request: %w", err)
} }
resp, err := http.DefaultClient.Do(req) transport := http.DefaultTransport.(*http.Transport).Clone()
transport.Proxy = config.NetworkConfig.GetTransportProxyFunc()
client := &http.Client{
Transport: transport,
}
resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("error sending request: %w", err) return nil, fmt.Errorf("error sending request: %w", err)
} }
@ -135,6 +142,7 @@ func downloadFile(ctx context.Context, path string, url string, downloadProgress
client := http.Client{ client := http.Client{
Timeout: 10 * time.Minute, Timeout: 10 * time.Minute,
Transport: &http.Transport{ Transport: &http.Transport{
Proxy: config.NetworkConfig.GetTransportProxyFunc(),
TLSHandshakeTimeout: 30 * time.Second, TLSHandshakeTimeout: 30 * time.Second,
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
RootCAs: rootcerts.ServerCertPool(), RootCAs: rootcerts.ServerCertPool(),

View File

@ -753,6 +753,7 @@ export type TimeSyncMode =
export interface NetworkSettings { export interface NetworkSettings {
hostname: string; hostname: string;
domain: string; domain: string;
http_proxy: string;
ipv4_mode: IPv4Mode; ipv4_mode: IPv4Mode;
ipv6_mode: IPv6Mode; ipv6_mode: IPv6Mode;
lldp_mode: LLDPMode; lldp_mode: LLDPMode;

View File

@ -34,6 +34,7 @@ dayjs.extend(relativeTime);
const defaultNetworkSettings: NetworkSettings = { const defaultNetworkSettings: NetworkSettings = {
hostname: "", hostname: "",
http_proxy: "",
domain: "", domain: "",
ipv4_mode: "unknown", ipv4_mode: "unknown",
ipv6_mode: "unknown", ipv6_mode: "unknown",
@ -185,6 +186,10 @@ export default function SettingsNetworkRoute() {
setNetworkSettings({ ...networkSettings, hostname: value }); setNetworkSettings({ ...networkSettings, hostname: value });
}; };
const handleProxyChange = (value: string) => {
setNetworkSettings({ ...networkSettings, http_proxy: value });
};
const handleDomainChange = (value: string) => { const handleDomainChange = (value: string) => {
setNetworkSettings({ ...networkSettings, domain: value }); setNetworkSettings({ ...networkSettings, domain: value });
}; };
@ -253,6 +258,26 @@ export default function SettingsNetworkRoute() {
</div> </div>
</SettingsItem> </SettingsItem>
</div> </div>
<div className="space-y-4">
<SettingsItem
title="HTTP Proxy"
description="Proxy server for outgoing HTTP(S) requests from the device. Blank for none."
>
<div className="relative">
<div>
<InputField
size="SM"
type="text"
placeholder="http://proxy.example.com:8080/"
defaultValue={networkSettings.http_proxy}
onChange={e => {
handleProxyChange(e.target.value);
}}
/>
</div>
</div>
</SettingsItem>
</div>
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-1"> <div className="space-y-1">