Add Fortinet hostcheck support

This commit is contained in:
世界
2026-07-21 20:42:10 +08:00
parent 897a664b37
commit de47602923
6 changed files with 114 additions and 46 deletions
@@ -58,6 +58,10 @@
}
]
},
"fortinet_host_check": {
"hostcheck": "",
"check_virtual_desktop": ""
},
"no_udp": false,
"dtls_local_port": 0,
"compression_disabled": false,
@@ -342,6 +346,30 @@ TNCC machine certificate path in PEM format.
Conflict with `tncc.certificates.certificate`.
### fortinet_host_check
Fortinet hostcheck result override.
Hostcheck is disabled by default. It is enabled only when `fortinet_host_check.hostcheck` is non-empty. No operating system, security product, or network interface information is collected automatically.
When enabled and a successful Fortinet login response requests hostcheck, both configured values are submitted to the server before the VPN session is used. The values are sent unchanged as `application/x-www-form-urlencoded` fields.
Some Fortinet servers only request hostcheck from recognized FortiClient user agents. Configure `user_agent` when required by the server policy.
### fortinet_host_check.hostcheck
Fortinet hostcheck result string.
The conventional format is `<security-status>,<os-version>`, for example `0100,10.0.19042`. `security-status` contains four `0` or `1` characters representing, in order, third-party firewall, third-party antivirus, FortiClient firewall, and FortiClient antivirus.
An empty value disables Fortinet hostcheck, even if `fortinet_host_check.check_virtual_desktop` is configured.
### fortinet_host_check.check_virtual_desktop
Fortinet virtual desktop check result string.
FortiClient conventionally sends colon-separated MAC addresses joined by `|`, for example `74:78:27:4d:81:93|84:1b:77:3a:95:84`. An empty value is submitted as an empty field when hostcheck is enabled.
### no_udp
Disable the DTLS or ESP secondary data channel and use the TLS data channel only.
@@ -58,6 +58,10 @@
}
]
},
"fortinet_host_check": {
"hostcheck": "",
"check_virtual_desktop": ""
},
"no_udp": false,
"dtls_local_port": 0,
"compression_disabled": false,
@@ -342,6 +346,30 @@ PEM 格式的 TNCC 机器证书路径。
`tncc.certificates.certificate` 冲突。
### fortinet_host_check
Fortinet hostcheck 结果覆盖选项。
默认禁用 hostcheck。仅当 `fortinet_host_check.hostcheck` 非空时启用。不会自动收集操作系统、安全产品或网络接口信息。
启用后,如果成功的 Fortinet 登录响应要求 hostcheck,将在使用 VPN 会话前向服务器提交两个配置值。这些值不经修改,作为 `application/x-www-form-urlencoded` 字段发送。
部分 Fortinet 服务器只会要求可识别的 FortiClient User-Agent 执行 hostcheck。服务器策略有要求时请配置 `user_agent`
### fortinet_host_check.hostcheck
Fortinet hostcheck 结果字符串。
通常格式为 `<security-status>,<os-version>`,例如 `0100,10.0.19042``security-status` 包含四个 `0``1` 字符,依次表示第三方防火墙、第三方杀毒软件、FortiClient 防火墙和 FortiClient 杀毒软件。
空值会禁用 Fortinet hostcheck,即使配置了 `fortinet_host_check.check_virtual_desktop`
### fortinet_host_check.check_virtual_desktop
Fortinet virtual desktop 检查结果字符串。
FortiClient 通常发送以冒号分隔的 MAC 地址,多个地址使用 `|` 连接,例如 `74:78:27:4d:81:93|84:1b:77:3a:95:84`。启用 hostcheck 时,空值会作为空字段提交。
### no_udp
禁用 DTLS 或 ESP 辅助数据通道,仅使用 TLS 数据通道。
+1 -1
View File
@@ -46,7 +46,7 @@ require (
github.com/sagernet/sing v0.8.12-0.20260721063414-596db5dd6ef4
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3
github.com/sagernet/sing-mux v0.3.5
github.com/sagernet/sing-openconnect v0.0.0-20260721013312-6c25fa7e089a
github.com/sagernet/sing-openconnect v0.0.0-20260721123934-0d945d43288f
github.com/sagernet/sing-openvpn v0.0.0-20260721005523-64b754d1c277
github.com/sagernet/sing-quic v0.6.4-0.20260720125339-63fa6a7ebc31
github.com/sagernet/sing-shadowsocks v0.2.8
+2 -4
View File
@@ -190,8 +190,6 @@ github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKc
github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=
github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
github.com/metacubex/utls v1.8.7 h1:Cp+yWkNTFkSihETgGWq34hlVFds5HpYWVOR1xovUVTs=
github.com/metacubex/utls v1.8.7/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk=
@@ -319,8 +317,8 @@ github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 h1:3y6
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3/go.mod h1:XEqEDYRCAYLaoPjZ1ifVWJg5iWAJHL2gOAXe/PM28Cg=
github.com/sagernet/sing-mux v0.3.5 h1:RHnhVEc+SFqkrK4xMygYjDwwLhzp2Bj3lztSukONfhI=
github.com/sagernet/sing-mux v0.3.5/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
github.com/sagernet/sing-openconnect v0.0.0-20260721013312-6c25fa7e089a h1:DafepSfytV5uShjQctZ0Cnw+Q9cOBIMT88p8jU2DSck=
github.com/sagernet/sing-openconnect v0.0.0-20260721013312-6c25fa7e089a/go.mod h1:4AKZLVcvY3r54UaK2Gbnm7aN8pOwdLz+y4EP0QFZ5Eg=
github.com/sagernet/sing-openconnect v0.0.0-20260721123934-0d945d43288f h1:FKT5eCVyNiRNpa5jov6Fl9aQpAgKuCmkuhqVXvBK8b4=
github.com/sagernet/sing-openconnect v0.0.0-20260721123934-0d945d43288f/go.mod h1:4AKZLVcvY3r54UaK2Gbnm7aN8pOwdLz+y4EP0QFZ5Eg=
github.com/sagernet/sing-openvpn v0.0.0-20260721005523-64b754d1c277 h1:4H38L3OxOx1fGEuH4n9lh/5O7XtZTgQ/1V/gdQ+b+Es=
github.com/sagernet/sing-openvpn v0.0.0-20260721005523-64b754d1c277/go.mod h1:PWX7WygD8jpwfqfaGNySXpJYTn0SOwjBI1BKHHC2+Bw=
github.com/sagernet/sing-quic v0.6.4-0.20260720125339-63fa6a7ebc31 h1:FuiTs45PaA1/f2Kj6E2QlctFBrJUUFB8QAHnV5im9c8=
+47 -41
View File
@@ -4,47 +4,48 @@ import "github.com/sagernet/sing/common/json/badoption"
type OpenConnectEndpointOptions struct {
DialerOptions
System bool `json:"system,omitempty"`
Name string `json:"name,omitempty"`
UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"`
UDPMapping UDPNATBehavior `json:"udp_mapping,omitempty"`
UDPFiltering UDPNATBehavior `json:"udp_filtering,omitempty"`
UDPNATMax uint32 `json:"udp_nat_max,omitempty"`
Server string `json:"server"`
Flavor string `json:"flavor,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
AuthGroup string `json:"auth_group,omitempty"`
Cookie string `json:"cookie,omitempty"`
Token *OpenConnectTokenOptions `json:"token,omitempty"`
ReportedOS string `json:"reported_os,omitempty"`
UserAgent string `json:"user_agent,omitempty"`
Version string `json:"version,omitempty"`
LocalHostname string `json:"local_hostname,omitempty"`
Mobile *OpenConnectMobileOptions `json:"mobile,omitempty"`
CSD *OpenConnectCSDOptions `json:"csd,omitempty"`
HIP *OpenConnectHIPOptions `json:"hip,omitempty"`
TNCC *OpenConnectTNCCOptions `json:"tncc,omitempty"`
NoUDP bool `json:"no_udp,omitempty"`
DTLSLocalPort uint16 `json:"dtls_local_port,omitempty"`
CompressionDisabled bool `json:"compression_disabled,omitempty"`
CompressionMode string `json:"compression_mode,omitempty"`
IPv6Disabled bool `json:"ipv6_disabled,omitempty"`
HTTPKeepAliveDisabled bool `json:"http_keepalive_disabled,omitempty"`
XMLPostDisabled bool `json:"xml_post_disabled,omitempty"`
ExternalAuthDisabled bool `json:"external_auth_disabled,omitempty"`
PasswordAuthenticationDisabled bool `json:"password_authentication_disabled,omitempty"`
TCPKeepAliveEnabled bool `json:"tcp_keep_alive_enabled,omitempty"`
PFS bool `json:"pfs,omitempty"`
MTU uint32 `json:"mtu,omitempty"`
BaseMTU uint32 `json:"base_mtu,omitempty"`
DPDInterval badoption.Duration `json:"dpd_interval,omitempty"`
ReconnectTimeout badoption.Duration `json:"reconnect_timeout,omitempty"`
TrojanInterval badoption.Duration `json:"trojan_interval,omitempty"`
QueueLength uint32 `json:"queue_length,omitempty"`
AllowInsecureCrypto bool `json:"allow_insecure_crypto,omitempty"`
TLS OpenConnectTLSOptions `json:"tls,omitempty"`
FormEntries []OpenConnectFormEntryOptions `json:"form_entries,omitempty"`
System bool `json:"system,omitempty"`
Name string `json:"name,omitempty"`
UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"`
UDPMapping UDPNATBehavior `json:"udp_mapping,omitempty"`
UDPFiltering UDPNATBehavior `json:"udp_filtering,omitempty"`
UDPNATMax uint32 `json:"udp_nat_max,omitempty"`
Server string `json:"server"`
Flavor string `json:"flavor,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
AuthGroup string `json:"auth_group,omitempty"`
Cookie string `json:"cookie,omitempty"`
Token *OpenConnectTokenOptions `json:"token,omitempty"`
ReportedOS string `json:"reported_os,omitempty"`
UserAgent string `json:"user_agent,omitempty"`
Version string `json:"version,omitempty"`
LocalHostname string `json:"local_hostname,omitempty"`
Mobile *OpenConnectMobileOptions `json:"mobile,omitempty"`
CSD *OpenConnectCSDOptions `json:"csd,omitempty"`
HIP *OpenConnectHIPOptions `json:"hip,omitempty"`
TNCC *OpenConnectTNCCOptions `json:"tncc,omitempty"`
FortinetHostCheck *OpenConnectFortinetHostCheckOptions `json:"fortinet_host_check,omitempty"`
NoUDP bool `json:"no_udp,omitempty"`
DTLSLocalPort uint16 `json:"dtls_local_port,omitempty"`
CompressionDisabled bool `json:"compression_disabled,omitempty"`
CompressionMode string `json:"compression_mode,omitempty"`
IPv6Disabled bool `json:"ipv6_disabled,omitempty"`
HTTPKeepAliveDisabled bool `json:"http_keepalive_disabled,omitempty"`
XMLPostDisabled bool `json:"xml_post_disabled,omitempty"`
ExternalAuthDisabled bool `json:"external_auth_disabled,omitempty"`
PasswordAuthenticationDisabled bool `json:"password_authentication_disabled,omitempty"`
TCPKeepAliveEnabled bool `json:"tcp_keep_alive_enabled,omitempty"`
PFS bool `json:"pfs,omitempty"`
MTU uint32 `json:"mtu,omitempty"`
BaseMTU uint32 `json:"base_mtu,omitempty"`
DPDInterval badoption.Duration `json:"dpd_interval,omitempty"`
ReconnectTimeout badoption.Duration `json:"reconnect_timeout,omitempty"`
TrojanInterval badoption.Duration `json:"trojan_interval,omitempty"`
QueueLength uint32 `json:"queue_length,omitempty"`
AllowInsecureCrypto bool `json:"allow_insecure_crypto,omitempty"`
TLS OpenConnectTLSOptions `json:"tls,omitempty"`
FormEntries []OpenConnectFormEntryOptions `json:"form_entries,omitempty"`
}
type OpenConnectTokenOptions struct {
@@ -79,6 +80,11 @@ type OpenConnectTNCCOptions struct {
Certificates []OpenConnectTNCCCertificateOptions `json:"certificates,omitempty"`
}
type OpenConnectFortinetHostCheckOptions struct {
HostCheck string `json:"hostcheck,omitempty"`
CheckVirtualDesktop string `json:"check_virtual_desktop,omitempty"`
}
type OpenConnectTNCCCertificateOptions struct {
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
CertificatePath string `json:"certificate_path,omitempty"`
+8
View File
@@ -254,6 +254,13 @@ func (e *Endpoint) buildClientOptions(options option.OpenConnectEndpointOptions,
Certificates: tnccCertificates,
}
}
var fortinetHostCheckOptions *openconnect.FortinetHostCheckOptions
if options.FortinetHostCheck != nil {
fortinetHostCheckOptions = &openconnect.FortinetHostCheckOptions{
HostCheck: options.FortinetHostCheck.HostCheck,
CheckVirtualDesktop: options.FortinetHostCheck.CheckVirtualDesktop,
}
}
formEntries := common.Map(options.FormEntries, func(entry option.OpenConnectFormEntryOptions) openconnect.FormEntry {
return openconnect.FormEntry{
FormID: entry.FormID,
@@ -280,6 +287,7 @@ func (e *Endpoint) buildClientOptions(options option.OpenConnectEndpointOptions,
CSD: csdOptions,
HIP: hipOptions,
TNCC: tnccOptions,
FortinetHostCheck: fortinetHostCheckOptions,
NoUDP: options.NoUDP,
DTLSLocalPort: options.DTLSLocalPort,
CompressionDisabled: options.CompressionDisabled,