client_device.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. package amneziawgnet
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/amnezia-vpn/amneziawg-go/v3/device"
  6. "github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
  7. "github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
  8. )
  9. // buildClientUAPIConfig renders a client-mode UAPI set string: the device
  10. // lines of buildUAPIConfig plus per-peer endpoint/keepalive for dialing.
  11. func buildClientUAPIConfig(inst amneziawg.OutboundInstance, opts DeviceOptions) (string, error) {
  12. var b strings.Builder
  13. privHex, err := wireguard.KeyToHex(inst.PrivateKey)
  14. if err != nil {
  15. return "", fmt.Errorf("invalid private key: %w", err)
  16. }
  17. fmt.Fprintf(&b, "private_key=%s\n", privHex)
  18. if inst.ListenPort > 0 {
  19. fmt.Fprintf(&b, "listen_port=%d\n", inst.ListenPort)
  20. }
  21. b.WriteString("replace_peers=true\n")
  22. o := inst.Obfuscation
  23. fmt.Fprintf(&b, "jc=%d\njmin=%d\njmax=%d\n", o.Jc, o.Jmin, o.Jmax)
  24. fmt.Fprintf(&b, "s1=%d\ns2=%d\ns3=%d\ns4=%d\n", o.S1, o.S2, o.S3, o.S4)
  25. writeOptionalLine(&b, "h1", o.H1)
  26. writeOptionalLine(&b, "h2", o.H2)
  27. writeOptionalLine(&b, "h3", o.H3)
  28. writeOptionalLine(&b, "h4", o.H4)
  29. writeOptionalLine(&b, "i1", o.I1)
  30. writeOptionalLine(&b, "i2", o.I2)
  31. writeOptionalLine(&b, "i3", o.I3)
  32. writeOptionalLine(&b, "i4", o.I4)
  33. writeOptionalLine(&b, "i5", o.I5)
  34. // An omitted line means "unchanged" to amneziawg-go, so a cleared key can
  35. // only reach a live device as the all-zero one that disables the feature.
  36. hpHex := strings.Repeat("0", 64)
  37. if opts.HeaderProtectionKey != "" {
  38. var err error
  39. hpHex, err = wireguard.KeyToHex(opts.HeaderProtectionKey)
  40. if err != nil {
  41. return "", fmt.Errorf("invalid header protection key: %w", err)
  42. }
  43. }
  44. fmt.Fprintf(&b, "header_protection_key=%s\n", hpHex)
  45. if opts.ContentPaddingAddition != "" {
  46. fmt.Fprintf(&b, "content_padding_addition=%s\n", opts.ContentPaddingAddition)
  47. }
  48. if opts.RekeyAfterTime != "" {
  49. fmt.Fprintf(&b, "rekey_after_time=%s\n", opts.RekeyAfterTime)
  50. }
  51. if opts.RekeyTimeout != "" {
  52. fmt.Fprintf(&b, "rekey_timeout=%s\n", opts.RekeyTimeout)
  53. }
  54. if opts.RejectAfterTime != "" {
  55. fmt.Fprintf(&b, "reject_after_time=%s\n", opts.RejectAfterTime)
  56. }
  57. if opts.KeepaliveTimeout != "" {
  58. fmt.Fprintf(&b, "keepalive_timeout=%s\n", opts.KeepaliveTimeout)
  59. }
  60. if opts.MaxHandshakeAttempts != "" {
  61. fmt.Fprintf(&b, "max_handshake_attempts=%s\n", opts.MaxHandshakeAttempts)
  62. }
  63. fmt.Fprintf(&b, "random_trailers=%t\n", opts.RandomTrailers)
  64. fmt.Fprintf(&b, "disable_cookies=%t\n", opts.DisableCookies)
  65. for _, p := range inst.Peers {
  66. pubHex, err := wireguard.KeyToHex(p.PublicKey)
  67. if err != nil {
  68. return "", fmt.Errorf("peer %q: invalid public key: %w", p.Endpoint, err)
  69. }
  70. fmt.Fprintf(&b, "public_key=%s\n", pubHex)
  71. if p.PresharedKey != "" {
  72. pskHex, err := wireguard.KeyToHex(p.PresharedKey)
  73. if err != nil {
  74. return "", fmt.Errorf("peer %q: invalid preshared key: %w", p.Endpoint, err)
  75. }
  76. fmt.Fprintf(&b, "preshared_key=%s\n", pskHex)
  77. }
  78. fmt.Fprintf(&b, "endpoint=%s\n", p.Endpoint)
  79. if p.KeepAlive > 0 {
  80. fmt.Fprintf(&b, "persistent_keepalive_interval=%d\n", p.KeepAlive)
  81. }
  82. for _, allowedIP := range p.AllowedIPs {
  83. fmt.Fprintf(&b, "allowed_ip=%s\n", allowedIP)
  84. }
  85. }
  86. return b.String(), nil
  87. }
  88. // newUnconfiguredClientDevice builds the tun/netstack/device trio for a
  89. // client-mode instance; same construction rules as newUnconfiguredDevice.
  90. func newUnconfiguredClientDevice(inst amneziawg.OutboundInstance, opts DeviceOptions) (*Device, error) {
  91. addrs, err := hostAddresses(inst.Address)
  92. if err != nil {
  93. return nil, fmt.Errorf("amneziawgnet: %w", err)
  94. }
  95. mtu := amneziawg.EffectiveMTU(inst.MTU, inst.Obfuscation.S4)
  96. tun, gstack, err := createNetTUNWithStack(addrs, mtu)
  97. if err != nil {
  98. return nil, fmt.Errorf("amneziawgnet: create netstack: %w", err)
  99. }
  100. logger := opts.Logger
  101. if logger == nil {
  102. logger = device.NewLogger(device.LogLevelSilent, fmt.Sprintf("(awg-out %s) ", inst.Tag))
  103. }
  104. dev := device.NewDevice(tun, newResolvingBind(), logger)
  105. return &Device{Device: dev, Stack: gstack, localAddrs: addrs}, nil
  106. }
  107. // ConfigureClient applies inst/opts via UAPI and brings the interface up;
  108. // same single-call contract as Configure.
  109. func (d *Device) ConfigureClient(inst amneziawg.OutboundInstance, opts DeviceOptions) error {
  110. conf, err := buildClientUAPIConfig(inst, opts)
  111. if err != nil {
  112. d.Close()
  113. return fmt.Errorf("amneziawgnet: %w", err)
  114. }
  115. if err := d.IpcSet(conf); err != nil {
  116. d.Close()
  117. return fmt.Errorf("amneziawgnet: IpcSet for outbound %q: %w", inst.Tag, err)
  118. }
  119. if err := d.Up(); err != nil {
  120. d.Close()
  121. return fmt.Errorf("amneziawgnet: bring up outbound %q: %w", inst.Tag, err)
  122. }
  123. return nil
  124. }