installer_linux.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. //go:build linux
  2. package ike
  3. import (
  4. "context"
  5. "encoding/hex"
  6. "errors"
  7. "fmt"
  8. "net"
  9. "os/exec"
  10. "strconv"
  11. "strings"
  12. "sync"
  13. "vocat/internal/vowifi"
  14. )
  15. type linuxXFRMInstaller struct {
  16. ipCommand string
  17. }
  18. func defaultChildSAInstaller() ChildSAInstaller {
  19. return linuxChildSAInstallerRouter{ipCommand: "ip"}
  20. }
  21. type linuxChildSAInstallerRouter struct {
  22. ipCommand string
  23. }
  24. func (router linuxChildSAInstallerRouter) Install(ctx context.Context, config ChildSAConfig) (ChildSAHandle, error) {
  25. if config.ProxyMode == vowifi.ProxyModeSOCKS5 || config.UDPEncapsulation {
  26. return (linuxUserspaceInstaller{ipCommand: router.ipCommand}).Install(ctx, config)
  27. }
  28. return (linuxXFRMInstaller{ipCommand: router.ipCommand}).Install(ctx, config)
  29. }
  30. type linuxXFRMHandle struct {
  31. mu sync.Mutex
  32. ipCommand string
  33. config ChildSAConfig
  34. reqid string
  35. closed bool
  36. }
  37. func (*linuxXFRMHandle) DataplaneMode() string { return "xfrm" }
  38. func (installer linuxXFRMInstaller) Install(ctx context.Context, config ChildSAConfig) (ChildSAHandle, error) {
  39. if config.ProxyMode == vowifi.ProxyModeSOCKS5 || config.UDPEncapsulation {
  40. return nil, errors.New("NAT-T and SOCKS5 require a user-space ESP/TUN installer using NATTPacketRelay; kernel XFRM cannot own the user-space UDP association")
  41. }
  42. if config.OuterLocal == nil || config.OuterRemote == nil {
  43. return nil, errors.New("outer IP addresses are required")
  44. }
  45. if config.InboundSPI == 0 || config.OutboundSPI == 0 {
  46. return nil, errors.New("ESP SPIs must be nonzero")
  47. }
  48. command := installer.ipCommand
  49. if command == "" {
  50. command = "ip"
  51. }
  52. if _, err := exec.LookPath(command); err != nil {
  53. return nil, errors.New("Linux iproute2 is required to install the CHILD_SA")
  54. }
  55. handle := &linuxXFRMHandle{
  56. ipCommand: command,
  57. config: cloneChildSAConfig(config),
  58. reqid: strconv.FormatUint(uint64(config.InboundSPI), 10),
  59. }
  60. if err := handle.install(ctx); err != nil {
  61. _ = handle.Close(context.Background())
  62. return nil, err
  63. }
  64. return handle, nil
  65. }
  66. func (handle *linuxXFRMHandle) install(ctx context.Context) error {
  67. config := handle.config
  68. if err := handle.run(ctx, "create tunnel interface", "link", "add", config.Name, "type", "dummy"); err != nil {
  69. return err
  70. }
  71. if config.InnerLocalIPv4 != nil {
  72. if err := handle.run(ctx, "assign tunnel IPv4 address", "address", "add", config.InnerLocalIPv4.String()+"/32", "dev", config.Name); err != nil {
  73. return err
  74. }
  75. }
  76. if config.InnerLocalIPv6 != nil {
  77. if err := handle.run(ctx, "assign tunnel IPv6 address", "-6", "address", "add", fmt.Sprintf("%s/%d", config.InnerLocalIPv6.String(), config.InnerIPv6Prefix), "dev", config.Name); err != nil {
  78. return err
  79. }
  80. }
  81. if err := handle.run(ctx, "enable tunnel interface", "link", "set", "dev", config.Name, "up"); err != nil {
  82. return err
  83. }
  84. outboundState := handle.stateArguments(
  85. config.OuterLocal, config.OuterRemote, config.OutboundSPI,
  86. config.OutboundEncKey, config.OutboundAuthKey,
  87. )
  88. if err := handle.run(ctx, "install outbound ESP state", append([]string{"xfrm", "state", "add"}, outboundState...)...); err != nil {
  89. return err
  90. }
  91. inboundState := handle.stateArguments(
  92. config.OuterRemote, config.OuterLocal, config.InboundSPI,
  93. config.InboundEncKey, config.InboundAuthKey,
  94. )
  95. if err := handle.run(ctx, "install inbound ESP state", append([]string{"xfrm", "state", "add"}, inboundState...)...); err != nil {
  96. return err
  97. }
  98. for _, initiator := range config.InitiatorSelectors {
  99. for _, responder := range config.ResponderSelectors {
  100. if (initiator.StartIP.To4() == nil) != (responder.StartIP.To4() == nil) {
  101. continue
  102. }
  103. if err := handle.installPolicyPair(ctx, initiator, responder); err != nil {
  104. return err
  105. }
  106. }
  107. }
  108. return nil
  109. }
  110. func (handle *linuxXFRMHandle) installPolicyPair(
  111. ctx context.Context,
  112. initiator trafficSelector,
  113. responder trafficSelector,
  114. ) error {
  115. initiatorPrefix, err := selectorPrefix(initiator)
  116. if err != nil {
  117. return err
  118. }
  119. responderPrefix, err := selectorPrefix(responder)
  120. if err != nil {
  121. return err
  122. }
  123. if initiator.IPProtocol != responder.IPProtocol &&
  124. initiator.IPProtocol != 0 && responder.IPProtocol != 0 {
  125. return errors.New("negotiated traffic selectors use conflicting IP protocols")
  126. }
  127. protocol := initiator.IPProtocol
  128. if protocol == 0 {
  129. protocol = responder.IPProtocol
  130. }
  131. family := "-4"
  132. if initiator.StartIP.To4() == nil {
  133. family = "-6"
  134. }
  135. outbound := []string{
  136. family, "xfrm", "policy", "add",
  137. "src", initiatorPrefix, "dst", responderPrefix, "dir", "out",
  138. }
  139. inbound := []string{
  140. family, "xfrm", "policy", "add",
  141. "src", responderPrefix, "dst", initiatorPrefix, "dir", "in",
  142. }
  143. if protocol != 0 {
  144. outbound = append(outbound, "proto", strconv.Itoa(int(protocol)))
  145. inbound = append(inbound, "proto", strconv.Itoa(int(protocol)))
  146. }
  147. outbound, err = appendSelectorPorts(outbound, initiator, responder)
  148. if err != nil {
  149. return err
  150. }
  151. inbound, err = appendSelectorPorts(inbound, responder, initiator)
  152. if err != nil {
  153. return err
  154. }
  155. outbound = append(outbound,
  156. "tmpl", "src", handle.config.OuterLocal.String(), "dst", handle.config.OuterRemote.String(),
  157. "proto", "esp", "mode", "tunnel", "reqid", handle.reqid,
  158. )
  159. inbound = append(inbound,
  160. "tmpl", "src", handle.config.OuterRemote.String(), "dst", handle.config.OuterLocal.String(),
  161. "proto", "esp", "mode", "tunnel", "reqid", handle.reqid,
  162. )
  163. if err := handle.run(ctx, "install outbound ESP policy", outbound...); err != nil {
  164. return err
  165. }
  166. return handle.run(ctx, "install inbound ESP policy", inbound...)
  167. }
  168. func appendSelectorPorts(
  169. arguments []string,
  170. source trafficSelector,
  171. destination trafficSelector,
  172. ) ([]string, error) {
  173. appendPort := func(label string, start uint16, end uint16) error {
  174. if start == 0 && end == 65535 {
  175. return nil
  176. }
  177. if start != end {
  178. return fmt.Errorf("negotiated %s port range %d-%d cannot be represented safely by XFRM", label, start, end)
  179. }
  180. arguments = append(arguments, label, strconv.Itoa(int(start)))
  181. return nil
  182. }
  183. if err := appendPort("sport", source.StartPort, source.EndPort); err != nil {
  184. return nil, err
  185. }
  186. if err := appendPort("dport", destination.StartPort, destination.EndPort); err != nil {
  187. return nil, err
  188. }
  189. return arguments, nil
  190. }
  191. func selectorPrefix(selector trafficSelector) (string, error) {
  192. start := selector.StartIP
  193. end := selector.EndIP
  194. bits := 128
  195. if start4 := start.To4(); start4 != nil {
  196. start = start4
  197. end = end.To4()
  198. bits = 32
  199. } else {
  200. start = start.To16()
  201. end = end.To16()
  202. }
  203. if start == nil || end == nil || len(start) != len(end) {
  204. return "", errors.New("negotiated traffic selector IP range is invalid")
  205. }
  206. prefix := 0
  207. different := false
  208. for index := 0; index < len(start); index++ {
  209. for bit := 7; bit >= 0; bit-- {
  210. startBit := start[index] & (1 << bit)
  211. endBit := end[index] & (1 << bit)
  212. if !different && startBit == endBit {
  213. prefix++
  214. continue
  215. }
  216. different = true
  217. if startBit != 0 || endBit == 0 {
  218. return "", errors.New("negotiated traffic selector range is not a CIDR prefix")
  219. }
  220. }
  221. }
  222. network := &net.IPNet{IP: start, Mask: net.CIDRMask(prefix, bits)}
  223. return network.String(), nil
  224. }
  225. func (handle *linuxXFRMHandle) stateArguments(
  226. source net.IP,
  227. destination net.IP,
  228. spi uint32,
  229. encryptionKey []byte,
  230. integrityKey []byte,
  231. ) []string {
  232. arguments := []string{
  233. "src", source.String(),
  234. "dst", destination.String(),
  235. "proto", "esp",
  236. "spi", fmt.Sprintf("0x%08x", spi),
  237. "reqid", handle.reqid,
  238. "mode", "tunnel",
  239. }
  240. switch handle.config.Integrity {
  241. case "hmac-sha1-96":
  242. arguments = append(arguments, "auth-trunc", "hmac(sha1)", "0x"+hex.EncodeToString(integrityKey), "96")
  243. case "hmac-sha2-256-128":
  244. arguments = append(arguments, "auth-trunc", "hmac(sha256)", "0x"+hex.EncodeToString(integrityKey), "128")
  245. }
  246. arguments = append(arguments, "enc", "cbc(aes)", "0x"+hex.EncodeToString(encryptionKey))
  247. if handle.config.UDPEncapsulation {
  248. arguments = append(arguments, "encap", "espinudp", "4500", "4500", "0.0.0.0")
  249. }
  250. return arguments
  251. }
  252. func (handle *linuxXFRMHandle) run(ctx context.Context, operation string, arguments ...string) error {
  253. command := exec.CommandContext(ctx, handle.ipCommand, arguments...)
  254. output, err := command.CombinedOutput()
  255. if err != nil {
  256. message := strings.TrimSpace(string(output))
  257. if message == "" {
  258. message = err.Error()
  259. }
  260. return fmt.Errorf("%s: %s", operation, message)
  261. }
  262. return nil
  263. }
  264. func (handle *linuxXFRMHandle) Close(ctx context.Context) error {
  265. handle.mu.Lock()
  266. defer handle.mu.Unlock()
  267. if handle.closed {
  268. return nil
  269. }
  270. handle.closed = true
  271. config := handle.config
  272. var errs []error
  273. deletePolicy := func(family, source, destination, direction string) {
  274. command := exec.CommandContext(ctx, handle.ipCommand,
  275. family, "xfrm", "policy", "delete",
  276. "src", source, "dst", destination, "dir", direction,
  277. )
  278. if err := command.Run(); err != nil {
  279. errs = append(errs, err)
  280. }
  281. }
  282. for _, initiator := range config.InitiatorSelectors {
  283. for _, responder := range config.ResponderSelectors {
  284. if (initiator.StartIP.To4() == nil) != (responder.StartIP.To4() == nil) {
  285. continue
  286. }
  287. initiatorPrefix, initiatorErr := selectorPrefix(initiator)
  288. responderPrefix, responderErr := selectorPrefix(responder)
  289. if initiatorErr != nil || responderErr != nil {
  290. continue
  291. }
  292. family := "-4"
  293. if initiator.StartIP.To4() == nil {
  294. family = "-6"
  295. }
  296. deletePolicy(family, initiatorPrefix, responderPrefix, "out")
  297. deletePolicy(family, responderPrefix, initiatorPrefix, "in")
  298. }
  299. }
  300. deleteState := func(source net.IP, destination net.IP, spi uint32) {
  301. command := exec.CommandContext(ctx, handle.ipCommand,
  302. "xfrm", "state", "delete",
  303. "src", source.String(), "dst", destination.String(),
  304. "proto", "esp", "spi", fmt.Sprintf("0x%08x", spi),
  305. )
  306. if err := command.Run(); err != nil {
  307. errs = append(errs, err)
  308. }
  309. }
  310. deleteState(config.OuterLocal, config.OuterRemote, config.OutboundSPI)
  311. deleteState(config.OuterRemote, config.OuterLocal, config.InboundSPI)
  312. if err := exec.CommandContext(ctx, handle.ipCommand, "link", "delete", config.Name).Run(); err != nil {
  313. errs = append(errs, err)
  314. }
  315. return errors.Join(errs...)
  316. }
  317. func cloneChildSAConfig(config ChildSAConfig) ChildSAConfig {
  318. config.OuterLocal = append(net.IP(nil), config.OuterLocal...)
  319. config.OuterRemote = append(net.IP(nil), config.OuterRemote...)
  320. config.InnerLocalIPv4 = append(net.IP(nil), config.InnerLocalIPv4...)
  321. config.InnerLocalIPv6 = append(net.IP(nil), config.InnerLocalIPv6...)
  322. config.InboundEncKey = append([]byte(nil), config.InboundEncKey...)
  323. config.InboundAuthKey = append([]byte(nil), config.InboundAuthKey...)
  324. config.OutboundEncKey = append([]byte(nil), config.OutboundEncKey...)
  325. config.OutboundAuthKey = append([]byte(nil), config.OutboundAuthKey...)
  326. config.InitiatorSelectors = cloneTrafficSelectors(config.InitiatorSelectors)
  327. config.ResponderSelectors = cloneTrafficSelectors(config.ResponderSelectors)
  328. config.PCSCF = cloneIPs(config.PCSCF)
  329. config.DNS = cloneIPs(config.DNS)
  330. return config
  331. }
  332. func cloneTrafficSelectors(selectors []trafficSelector) []trafficSelector {
  333. result := append([]trafficSelector(nil), selectors...)
  334. for index := range result {
  335. result[index].StartIP = append(net.IP(nil), result[index].StartIP...)
  336. result[index].EndIP = append(net.IP(nil), result[index].EndIP...)
  337. }
  338. return result
  339. }