| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- package amneziawgnet
- import (
- "fmt"
- "strings"
- "github.com/amnezia-vpn/amneziawg-go/v3/device"
- "github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
- "github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
- )
- // buildClientUAPIConfig renders a client-mode UAPI set string: the device
- // lines of buildUAPIConfig plus per-peer endpoint/keepalive for dialing.
- func buildClientUAPIConfig(inst amneziawg.OutboundInstance, opts DeviceOptions) (string, error) {
- var b strings.Builder
- privHex, err := wireguard.KeyToHex(inst.PrivateKey)
- if err != nil {
- return "", fmt.Errorf("invalid private key: %w", err)
- }
- fmt.Fprintf(&b, "private_key=%s\n", privHex)
- if inst.ListenPort > 0 {
- fmt.Fprintf(&b, "listen_port=%d\n", inst.ListenPort)
- }
- b.WriteString("replace_peers=true\n")
- o := inst.Obfuscation
- fmt.Fprintf(&b, "jc=%d\njmin=%d\njmax=%d\n", o.Jc, o.Jmin, o.Jmax)
- fmt.Fprintf(&b, "s1=%d\ns2=%d\ns3=%d\ns4=%d\n", o.S1, o.S2, o.S3, o.S4)
- writeOptionalLine(&b, "h1", o.H1)
- writeOptionalLine(&b, "h2", o.H2)
- writeOptionalLine(&b, "h3", o.H3)
- writeOptionalLine(&b, "h4", o.H4)
- writeOptionalLine(&b, "i1", o.I1)
- writeOptionalLine(&b, "i2", o.I2)
- writeOptionalLine(&b, "i3", o.I3)
- writeOptionalLine(&b, "i4", o.I4)
- writeOptionalLine(&b, "i5", o.I5)
- // An omitted line means "unchanged" to amneziawg-go, so a cleared key can
- // only reach a live device as the all-zero one that disables the feature.
- hpHex := strings.Repeat("0", 64)
- if opts.HeaderProtectionKey != "" {
- var err error
- hpHex, err = wireguard.KeyToHex(opts.HeaderProtectionKey)
- if err != nil {
- return "", fmt.Errorf("invalid header protection key: %w", err)
- }
- }
- fmt.Fprintf(&b, "header_protection_key=%s\n", hpHex)
- if opts.ContentPaddingAddition != "" {
- fmt.Fprintf(&b, "content_padding_addition=%s\n", opts.ContentPaddingAddition)
- }
- if opts.RekeyAfterTime != "" {
- fmt.Fprintf(&b, "rekey_after_time=%s\n", opts.RekeyAfterTime)
- }
- if opts.RekeyTimeout != "" {
- fmt.Fprintf(&b, "rekey_timeout=%s\n", opts.RekeyTimeout)
- }
- if opts.RejectAfterTime != "" {
- fmt.Fprintf(&b, "reject_after_time=%s\n", opts.RejectAfterTime)
- }
- if opts.KeepaliveTimeout != "" {
- fmt.Fprintf(&b, "keepalive_timeout=%s\n", opts.KeepaliveTimeout)
- }
- if opts.MaxHandshakeAttempts != "" {
- fmt.Fprintf(&b, "max_handshake_attempts=%s\n", opts.MaxHandshakeAttempts)
- }
- fmt.Fprintf(&b, "random_trailers=%t\n", opts.RandomTrailers)
- fmt.Fprintf(&b, "disable_cookies=%t\n", opts.DisableCookies)
- for _, p := range inst.Peers {
- pubHex, err := wireguard.KeyToHex(p.PublicKey)
- if err != nil {
- return "", fmt.Errorf("peer %q: invalid public key: %w", p.Endpoint, err)
- }
- fmt.Fprintf(&b, "public_key=%s\n", pubHex)
- if p.PresharedKey != "" {
- pskHex, err := wireguard.KeyToHex(p.PresharedKey)
- if err != nil {
- return "", fmt.Errorf("peer %q: invalid preshared key: %w", p.Endpoint, err)
- }
- fmt.Fprintf(&b, "preshared_key=%s\n", pskHex)
- }
- fmt.Fprintf(&b, "endpoint=%s\n", p.Endpoint)
- if p.KeepAlive > 0 {
- fmt.Fprintf(&b, "persistent_keepalive_interval=%d\n", p.KeepAlive)
- }
- for _, allowedIP := range p.AllowedIPs {
- fmt.Fprintf(&b, "allowed_ip=%s\n", allowedIP)
- }
- }
- return b.String(), nil
- }
- // newUnconfiguredClientDevice builds the tun/netstack/device trio for a
- // client-mode instance; same construction rules as newUnconfiguredDevice.
- func newUnconfiguredClientDevice(inst amneziawg.OutboundInstance, opts DeviceOptions) (*Device, error) {
- addrs, err := hostAddresses(inst.Address)
- if err != nil {
- return nil, fmt.Errorf("amneziawgnet: %w", err)
- }
- mtu := amneziawg.EffectiveMTU(inst.MTU, inst.Obfuscation.S4)
- tun, gstack, err := createNetTUNWithStack(addrs, mtu)
- if err != nil {
- return nil, fmt.Errorf("amneziawgnet: create netstack: %w", err)
- }
- logger := opts.Logger
- if logger == nil {
- logger = device.NewLogger(device.LogLevelSilent, fmt.Sprintf("(awg-out %s) ", inst.Tag))
- }
- dev := device.NewDevice(tun, newResolvingBind(), logger)
- return &Device{Device: dev, Stack: gstack, localAddrs: addrs}, nil
- }
- // ConfigureClient applies inst/opts via UAPI and brings the interface up;
- // same single-call contract as Configure.
- func (d *Device) ConfigureClient(inst amneziawg.OutboundInstance, opts DeviceOptions) error {
- conf, err := buildClientUAPIConfig(inst, opts)
- if err != nil {
- d.Close()
- return fmt.Errorf("amneziawgnet: %w", err)
- }
- if err := d.IpcSet(conf); err != nil {
- d.Close()
- return fmt.Errorf("amneziawgnet: IpcSet for outbound %q: %w", inst.Tag, err)
- }
- if err := d.Up(); err != nil {
- d.Close()
- return fmt.Errorf("amneziawgnet: bring up outbound %q: %w", inst.Tag, err)
- }
- return nil
- }
|