hostConfig_linux.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /*
  2. * Copyright (c) 2025, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package server
  20. import (
  21. "fmt"
  22. "strings"
  23. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  24. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  25. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
  26. "golang.org/x/sys/unix"
  27. )
  28. func addHostConfig(config *Config) error {
  29. // Disable Transparent Huge Pages; huge pages can result in false
  30. // positives in "low free memory" checks performed by load limiting
  31. // scripts which inspect host/server process memory usage, further
  32. // resulting in improper SIGTSTP signals.
  33. err := unix.Prctl(unix.PR_SET_THP_DISABLE, 1, 0, 0, 0)
  34. if err != nil {
  35. return errors.Trace(err)
  36. }
  37. // Programmatically configure iptables rules to allow and apply rate
  38. // limits to tunnel protocol ports.
  39. err = configureIptablesAcceptRateLimitChain(config, true)
  40. if err != nil {
  41. return errors.Trace(err)
  42. }
  43. return nil
  44. }
  45. func removeHostConfig(config *Config) error {
  46. err := configureIptablesAcceptRateLimitChain(config, false)
  47. if err != nil {
  48. return errors.Trace(err)
  49. }
  50. return nil
  51. }
  52. func configureIptablesAcceptRateLimitChain(config *Config, add bool) error {
  53. // Adapted from:
  54. // https://github.com/Psiphon-Inc/psiphon-automation/blob/8fce7c72/Automation/psi_ops_install.py#L936
  55. // The chain is assumed to be created by the host (iptables -N); the host
  56. // is also responsible any default DROP rule and for jumping to the
  57. // specified chain.
  58. chainName := config.IptablesAcceptRateLimitChainName
  59. if chainName == "" {
  60. return nil
  61. }
  62. for _, c := range chainName {
  63. if !(c == '_' || c == '-' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) {
  64. return errors.TraceNew("invalid chain name")
  65. }
  66. }
  67. // Direct protocols in which the original client IP directly connects to the Psiphon server will
  68. // use the "recent" rule, which limits connections by client IP. Fronted and other indirect
  69. // protocols where many clients can arrive from a few intermediate IPs use a simple frequency
  70. // rate limit; this rule is also used for direct meek protocols, since one client tunnel can
  71. // consist of many TCP connections, with meek resiliency.
  72. //
  73. // Custom rate limits may be set, per tunnel protocol, in IptablesAcceptRateLimitTunnelProtocolRateLimits.
  74. // When no custom rate is set, or if IptablesAcceptRateLimitTunnelProtocolRateLimits contains zero
  75. // values, default values are used.
  76. //
  77. // For the [2]int value in IptablesAcceptRateLimitTunnelProtocolRateLimits:
  78. // - In the "recent" rule case, value[0] specifies --seconds N and value[1] specifies --hitcount N.
  79. // - In the other case, value[0] specifies --limit N/sec and value[1] is ignored.
  80. inproxyAcceptRateLimitRules := func(networkProtocol string, portNumber int, rateLimit [2]int) ([]string, error) {
  81. if rateLimit[0] == 0 {
  82. rateLimit[0] = 1000
  83. }
  84. return []string{
  85. fmt.Sprintf("-A %s -p %s -m state --state NEW -m %s --dport %d -m limit --limit %d/sec -j ACCEPT",
  86. chainName, networkProtocol, networkProtocol, portNumber, rateLimit[0]),
  87. }, nil
  88. }
  89. meekAcceptRateLimitRules := func(portNumber int, rateLimit [2]int) ([]string, error) {
  90. if rateLimit[0] == 0 {
  91. rateLimit[0] = 1000
  92. }
  93. return []string{
  94. fmt.Sprintf("-A %s -p tcp -m state --state NEW -m tcp --dport %d -m limit --limit %d/sec -j ACCEPT",
  95. chainName, portNumber, rateLimit[0]),
  96. }, nil
  97. }
  98. refractionNetworkingRateLimitRules := meekAcceptRateLimitRules
  99. directAcceptRateLimitRules := func(networkProtocol string, portNumber int, rateLimit [2]int) ([]string, error) {
  100. if rateLimit[0] == 0 {
  101. rateLimit[0] = 60
  102. }
  103. if rateLimit[1] == 0 {
  104. rateLimit[1] = 3
  105. }
  106. name := fmt.Sprintf("LIMIT-%s-%d", networkProtocol, portNumber)
  107. return []string{
  108. fmt.Sprintf("-A %s -p %s -m state --state NEW -m %s --dport %d -m recent --set --name %s",
  109. chainName, networkProtocol, networkProtocol, portNumber, name),
  110. fmt.Sprintf("-A %s -p %s -m state --state NEW -m %s --dport %d -m recent --update --name %s --seconds %d --hitcount %d -j DROP",
  111. chainName, networkProtocol, networkProtocol, portNumber, name, rateLimit[0], rateLimit[1]),
  112. fmt.Sprintf("-A %s -p %s -m state --state NEW -m %s --dport %d -j ACCEPT",
  113. chainName, networkProtocol, networkProtocol, portNumber),
  114. }, nil
  115. }
  116. rules := []string{fmt.Sprintf("-F %s", chainName)}
  117. if add {
  118. for tunnelProtocol, portNumber := range config.TunnelProtocolPorts {
  119. rateLimit := config.IptablesAcceptRateLimitTunnelProtocolRateLimits[tunnelProtocol]
  120. var protocolRules []string
  121. var err error
  122. if protocol.TunnelProtocolUsesInproxy(tunnelProtocol) {
  123. networkProtocol := "tcp"
  124. if !protocol.TunnelProtocolUsesTCP(tunnelProtocol) {
  125. networkProtocol = "udp"
  126. }
  127. protocolRules, err = inproxyAcceptRateLimitRules(networkProtocol, portNumber, rateLimit)
  128. if err != nil {
  129. return errors.Trace(err)
  130. }
  131. } else if protocol.TunnelProtocolUsesMeek(tunnelProtocol) {
  132. // Assumes all FRONTED-MEEK is HTTPS over TCP between the edge
  133. // and Psiphon server.
  134. if protocol.TunnelProtocolUsesFrontedMeekNonHTTPS(tunnelProtocol) {
  135. continue
  136. }
  137. protocolRules, err = meekAcceptRateLimitRules(portNumber, rateLimit)
  138. if err != nil {
  139. return errors.Trace(err)
  140. }
  141. } else if protocol.TunnelProtocolUsesRefractionNetworking(tunnelProtocol) {
  142. protocolRules, err = refractionNetworkingRateLimitRules(portNumber, rateLimit)
  143. if err != nil {
  144. return errors.Trace(err)
  145. }
  146. } else {
  147. networkProtocol := "tcp"
  148. if !protocol.TunnelProtocolUsesTCP(tunnelProtocol) {
  149. networkProtocol = "udp"
  150. }
  151. protocolRules, err = directAcceptRateLimitRules(networkProtocol, portNumber, rateLimit)
  152. if err != nil {
  153. return errors.Trace(err)
  154. }
  155. }
  156. rules = append(rules, protocolRules...)
  157. }
  158. rules = append(rules, fmt.Sprintf("-A %s -j RETURN", chainName))
  159. }
  160. for _, rule := range rules {
  161. // While the config values IptablesAcceptRateLimitChainName and
  162. // IptablesAcceptRateLimitTunnelProtocolRateLimits are considered
  163. // trusted inputs, the risk of command injection is mitigated by
  164. // input validation and common.RunNetworkConfigCommand using
  165. // exec.Command and not invoking a shell.
  166. //
  167. // The command will be logged at log level debug.
  168. err := common.RunNetworkConfigCommand(
  169. CommonLogger(log),
  170. config.PacketTunnelSudoNetworkConfigCommands,
  171. "iptables",
  172. strings.Split(rule, " ")...)
  173. if err != nil {
  174. return errors.Trace(err)
  175. }
  176. }
  177. return nil
  178. }