| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885 |
- package ike
- import (
- "context"
- "crypto"
- "crypto/rand"
- "crypto/sha256"
- "crypto/x509"
- "encoding/binary"
- "encoding/hex"
- "errors"
- "fmt"
- "io"
- "net"
- "strings"
- "sync"
- "time"
- "vocat/internal/vowifi"
- )
- type Config struct {
- Random io.Reader
- Resolver *net.Resolver
- Dialer *net.Dialer
- RootCAs *x509.CertPool
- ResponderPublicKey crypto.PublicKey
- ServerName string
- Timeout time.Duration
- KeepaliveInterval time.Duration
- Installer ChildSAInstaller
- IdentityType uint8
- APN string
- }
- type Provider struct {
- config Config
- transportFactory func(context.Context, transportConfig, vowifi.ProxyRoute, string) (datagramTransport, error)
- }
- func NewProvider(config Config) (*Provider, error) {
- if config.Random == nil {
- config.Random = rand.Reader
- }
- if config.Resolver == nil {
- config.Resolver = net.DefaultResolver
- }
- if config.Dialer == nil {
- config.Dialer = &net.Dialer{}
- }
- if config.Timeout < 0 {
- return nil, errors.New("ike: timeout must not be negative")
- }
- if config.Timeout == 0 {
- config.Timeout = 12 * time.Second
- }
- if config.KeepaliveInterval < 0 {
- return nil, errors.New("ike: keepalive interval must not be negative")
- }
- if config.KeepaliveInterval == 0 {
- config.KeepaliveInterval = 20 * time.Second
- }
- if config.IdentityType == 0 {
- config.IdentityType = 3 // ID_RFC822_ADDR, carrying the permanent NAI.
- }
- config.APN = strings.TrimSpace(config.APN)
- if config.APN == "" {
- config.APN = "ims"
- }
- if len(config.APN) > 253 || strings.ContainsAny(config.APN, " \t\r\n/:@") {
- return nil, errors.New("ike: APN is invalid")
- }
- if config.Installer == nil {
- config.Installer = defaultChildSAInstaller()
- }
- return &Provider{config: config, transportFactory: newDatagramTransport}, nil
- }
- func (provider *Provider) Start(ctx context.Context, request vowifi.TunnelRequest) (vowifi.TunnelSession, error) {
- if provider == nil {
- return nil, errors.New("ike: nil provider")
- }
- if ctx == nil {
- ctx = context.Background()
- }
- if request.AKA == nil {
- return nil, errors.New("ike: AKA provider is required")
- }
- epdg := strings.TrimSpace(request.EPDG)
- if epdg == "" || strings.ContainsAny(epdg, " \t\r\n/:") {
- return nil, errors.New("ike: ePDG must be a hostname")
- }
- aka, err := newAKAClient(request.Identity, request.AKA)
- if err != nil {
- return nil, err
- }
- transport, err := provider.transportFactory(ctx, transportConfig{
- Resolver: provider.config.Resolver,
- Dialer: provider.config.Dialer,
- Timeout: provider.config.Timeout,
- }, request.Proxy, epdg)
- if err != nil {
- return nil, err
- }
- closeTransport := true
- defer func() {
- if closeTransport {
- _ = transport.Close()
- }
- }()
- group := uint16(dhMODP2048)
- legacyFirst := request.Identity.HomeMCC == "234" && request.Identity.HomeMNC == "15"
- if legacyFirst {
- group = dhMODP1024
- }
- dh, err := newDHExchange(group, provider.config.Random)
- if err != nil {
- return nil, err
- }
- var initiatorSPI [8]byte
- if err := fillNonzero(provider.config.Random, initiatorSPI[:]); err != nil {
- return nil, err
- }
- initiatorNonce := make([]byte, 32)
- if _, err := io.ReadFull(provider.config.Random, initiatorNonce); err != nil {
- return nil, fmt.Errorf("ike: generate initiator nonce: %w", err)
- }
- ikeProposalBody, err := marshalProposals([]proposal{ikeOffer(group, legacyFirst)})
- if err != nil {
- return nil, err
- }
- keBody := make([]byte, 4+len(dh.Public))
- binary.BigEndian.PutUint16(keBody[0:2], group)
- copy(keBody[4:], dh.Public)
- localAddress := transport.LocalAddr()
- remoteAddress := transport.RemoteAddr()
- if localAddress == nil || remoteAddress == nil {
- return nil, errors.New("ike: transport did not expose UDP endpoints")
- }
- sourceHash, err := natDetectionHash(initiatorSPI, [8]byte{}, localAddress.IP, uint16(localAddress.Port))
- if err != nil {
- return nil, err
- }
- destinationHash, err := natDetectionHash(initiatorSPI, [8]byte{}, remoteAddress.IP, uint16(remoteAddress.Port))
- if err != nil {
- return nil, err
- }
- initPayloads := []payload{
- {Type: payloadSA, Body: ikeProposalBody},
- {Type: payloadKE, Body: keBody},
- {Type: payloadNonce, Body: initiatorNonce},
- makeNotify(notifyNATSource, sourceHash),
- makeNotify(notifyNATDestination, destinationHash),
- }
- first, initBody, err := marshalPayloadChain(initPayloads)
- if err != nil {
- return nil, err
- }
- initRequest := ikeHeader{
- InitiatorSPI: initiatorSPI,
- NextPayload: first,
- Exchange: exchangeIKEInit,
- Flags: flagInitiator,
- MessageID: 0,
- }.marshal(initBody)
- initResponse, err := transport.RoundTrip(ctx, initRequest)
- if err != nil {
- return nil, err
- }
- responseHeader, responseBody, err := validateResponse(initResponse, initiatorSPI, [8]byte{}, exchangeIKEInit, 0)
- if err != nil {
- return nil, err
- }
- if responseHeader.ResponderSPI == [8]byte{} {
- return nil, errors.New("ike: responder returned a zero SPI")
- }
- initResponsePayloads, err := parsePayloadChain(responseHeader.NextPayload, responseBody)
- if err != nil {
- return nil, err
- }
- if err := rejectFatalNotifications(initResponsePayloads); err != nil {
- return nil, err
- }
- saPayload, err := onePayload(initResponsePayloads, payloadSA)
- if err != nil {
- return nil, err
- }
- selectedProposals, err := parseProposals(saPayload.Body)
- if err != nil || len(selectedProposals) != 1 {
- return nil, errors.New("ike: responder did not select exactly one IKE proposal")
- }
- ikeSuite, err := parseIKESuite(selectedProposals[0])
- if err != nil {
- return nil, err
- }
- if ikeSuite.DHID != group {
- return nil, fmt.Errorf("ike: responder selected DH group %d but KE used group %d", ikeSuite.DHID, group)
- }
- responderKE, err := onePayload(initResponsePayloads, payloadKE)
- if err != nil {
- return nil, err
- }
- if len(responderKE.Body) < 4 || binary.BigEndian.Uint16(responderKE.Body[0:2]) != group {
- return nil, errors.New("ike: responder KE group does not match the selected proposal")
- }
- sharedSecret, err := dh.shared(responderKE.Body[4:])
- if err != nil {
- return nil, err
- }
- responderNoncePayload, err := onePayload(initResponsePayloads, payloadNonce)
- if err != nil {
- return nil, err
- }
- if len(responderNoncePayload.Body) < 16 || len(responderNoncePayload.Body) > 256 {
- return nil, errors.New("ike: responder nonce length is outside 16..256 bytes")
- }
- responderNonce := responderNoncePayload.Body
- keys, err := deriveIKEKeys(
- ikeSuite,
- sharedSecret,
- initiatorNonce,
- responderNonce,
- initiatorSPI,
- responseHeader.ResponderSPI,
- )
- if err != nil {
- return nil, err
- }
- natDetected, err := detectNAT(
- initResponsePayloads,
- initiatorSPI,
- responseHeader.ResponderSPI,
- transport.LocalAddr(),
- transport.RemoteAddr(),
- )
- if err != nil {
- return nil, err
- }
- if request.Proxy.Mode == vowifi.ProxyModeSOCKS5 {
- natDetected = true
- }
- if natDetected {
- if err := transport.Float(ctx); err != nil {
- return nil, err
- }
- }
- var childInboundSPIBytes [4]byte
- if err := fillNonzero(provider.config.Random, childInboundSPIBytes[:]); err != nil {
- return nil, err
- }
- childInboundSPI := binary.BigEndian.Uint32(childInboundSPIBytes[:])
- childOfferBody, err := marshalProposals([]proposal{espOffer(childInboundSPIBytes[:], legacyFirst)})
- if err != nil {
- return nil, err
- }
- idi := payload{Type: payloadIDi, Body: append([]byte{provider.config.IdentityType, 0, 0, 0}, aka.identity...)}
- requestedIDr := payload{Type: payloadIDr, Body: append([]byte{2, 0, 0, 0}, []byte(provider.config.APN)...)}
- tsi := dualStackTrafficSelectors(payloadTSi)
- tsr := dualStackTrafficSelectors(payloadTSr)
- firstAuthPayloads := buildInitialEAPOnlyAuth(idi, requestedIDr, childOfferBody, tsi, tsr)
- authHeader := ikeHeader{
- InitiatorSPI: initiatorSPI,
- ResponderSPI: responseHeader.ResponderSPI,
- Exchange: exchangeIKEAuth,
- Flags: flagInitiator,
- MessageID: 1,
- }
- authRequest, err := encryptPayloads(authHeader, firstAuthPayloads, ikeSuite, keys.SKei, keys.SKai, provider.config.Random)
- if err != nil {
- return nil, err
- }
- authResponse, err := transport.RoundTrip(ctx, authRequest)
- if err != nil {
- return nil, err
- }
- authResponseHeader, authResponsePayloads, err := decryptAndValidate(
- authResponse, initiatorSPI, responseHeader.ResponderSPI, exchangeIKEAuth, 1, ikeSuite, keys,
- )
- if err != nil {
- return nil, err
- }
- _ = authResponseHeader
- serverName := strings.TrimSpace(provider.config.ServerName)
- if serverName == "" {
- serverName = epdg
- }
- responderAUTH, responderID, err := validateInitialResponderAUTH(
- authResponsePayloads,
- initResponse,
- initiatorNonce,
- ikeSuite,
- keys.SKpr,
- serverName,
- serverName,
- provider.config.RootCAs,
- provider.config.ResponderPublicKey,
- true, // RFC 5998 EAP-only authentication defers responder AUTH.
- )
- if err != nil {
- return nil, err
- }
- messageID := uint32(1)
- currentPayloads := authResponsePayloads
- for round := 0; round < 10; round++ {
- eapPayload, err := onePayload(currentPayloads, payloadEAP)
- if err != nil {
- return nil, fmt.Errorf("ike: IKE_AUTH EAP round %d: %w", round+1, err)
- }
- action, err := aka.handle(ctx, eapPayload.Body)
- if err != nil {
- return nil, err
- }
- if action.Success {
- break
- }
- if len(action.Response) == 0 {
- return nil, errors.New("ike: EAP state machine produced no response")
- }
- messageID++
- eapRequest, err := encryptPayloads(ikeHeader{
- InitiatorSPI: initiatorSPI,
- ResponderSPI: responseHeader.ResponderSPI,
- Exchange: exchangeIKEAuth,
- Flags: flagInitiator,
- MessageID: messageID,
- }, []payload{{Type: payloadEAP, Body: action.Response}}, ikeSuite, keys.SKei, keys.SKai, provider.config.Random)
- if err != nil {
- return nil, err
- }
- eapResponse, err := transport.RoundTrip(ctx, eapRequest)
- if err != nil {
- return nil, err
- }
- _, currentPayloads, err = decryptAndValidate(
- eapResponse, initiatorSPI, responseHeader.ResponderSPI, exchangeIKEAuth, messageID, ikeSuite, keys,
- )
- if err != nil {
- return nil, err
- }
- if round == 9 {
- return nil, errors.New("ike: EAP exchange exceeded ten IKE_AUTH rounds")
- }
- }
- if !aka.challengeComplete || len(aka.keys.MSK) != 64 {
- return nil, errors.New("ike: EAP-AKA did not produce an authenticated MSK")
- }
- initiatorAUTH, err := makeEAPInitiatorAUTH(
- aka.keys.MSK,
- initRequest,
- responderNonce,
- ikeSuite,
- keys.SKpi,
- idi,
- )
- if err != nil {
- return nil, err
- }
- messageID++
- finalRequest, err := encryptPayloads(ikeHeader{
- InitiatorSPI: initiatorSPI,
- ResponderSPI: responseHeader.ResponderSPI,
- Exchange: exchangeIKEAuth,
- Flags: flagInitiator,
- MessageID: messageID,
- }, []payload{initiatorAUTH}, ikeSuite, keys.SKei, keys.SKai, provider.config.Random)
- if err != nil {
- return nil, err
- }
- finalResponse, err := transport.RoundTrip(ctx, finalRequest)
- if err != nil {
- return nil, err
- }
- _, finalPayloads, err := decryptAndValidate(
- finalResponse, initiatorSPI, responseHeader.ResponderSPI, exchangeIKEAuth, messageID, ikeSuite, keys,
- )
- if err != nil {
- return nil, err
- }
- if err := rejectFatalNotifications(finalPayloads); err != nil {
- return nil, err
- }
- finalAUTHs := payloadsOfType(finalPayloads, payloadAuth)
- if len(finalAUTHs) != 1 {
- return nil, fmt.Errorf("%w: final EAP-only response must contain exactly one MSK AUTH payload", vowifi.ErrResponderAUTHRequired)
- }
- if len(responderID.Body) == 0 {
- return nil, errors.New("ike: EAP-only exchange has no initial ePDG IDr for the responder AUTH transcript")
- }
- finalIDs := payloadsOfType(finalPayloads, payloadIDr)
- if len(finalIDs) > 1 {
- return nil, errors.New("ike: duplicate final responder IDr payload")
- }
- if len(finalIDs) == 1 {
- if err := validateFQDNIDr(finalIDs[0], provider.config.APN, "final APN"); err != nil {
- return nil, fmt.Errorf("ike: final APN IDr: %w", err)
- }
- }
- if err := verifyEAPResponderAUTH(
- finalAUTHs[0],
- aka.keys.MSK,
- initResponse,
- initiatorNonce,
- ikeSuite,
- keys.SKpr,
- responderID,
- ); err != nil {
- return nil, err
- }
- responderAUTH = vowifi.ResponderAUTHVerified
- childSA, err := onePayload(finalPayloads, payloadSA)
- if err != nil {
- return nil, err
- }
- childProposals, err := parseProposals(childSA.Body)
- if err != nil || len(childProposals) != 1 {
- return nil, errors.New("ike: responder did not select exactly one ESP proposal")
- }
- childSuite, err := parseESPSuite(childProposals[0])
- if err != nil {
- return nil, err
- }
- childOutboundSPI := binary.BigEndian.Uint32(childProposals[0].SPI)
- finalTSi, err := onePayload(finalPayloads, payloadTSi)
- if err != nil {
- return nil, err
- }
- finalTSr, err := onePayload(finalPayloads, payloadTSr)
- if err != nil {
- return nil, err
- }
- initiatorSelectors, err := parseTrafficSelectors(finalTSi)
- if err != nil {
- return nil, err
- }
- responderSelectors, err := parseTrafficSelectors(finalTSr)
- if err != nil {
- return nil, err
- }
- cpPayload, err := onePayload(finalPayloads, payloadCP)
- if err != nil {
- return nil, err
- }
- network, err := parseConfiguration(cpPayload)
- if err != nil {
- return nil, err
- }
- if network.LocalIPv4 == nil && network.LocalIPv6 == nil {
- return nil, errors.New("ike: responder did not assign an inner IP address")
- }
- network.PCSCF = pcscfForAssignedFamilies(
- network.PCSCF,
- network.LocalIPv4 != nil,
- network.LocalIPv6 != nil,
- )
- if len(network.PCSCF) == 0 {
- return nil, errors.New("ike: responder did not provide a P-CSCF matching an assigned address family")
- }
- outboundEncryption, outboundIntegrity, inboundEncryption, inboundIntegrity, err := deriveChildSAKeys(
- ikeSuite, childSuite, keys.SKd, initiatorNonce, responderNonce,
- )
- if err != nil {
- return nil, err
- }
- encryptionName, integrityName := espSuiteNames(childSuite)
- name := tunnelName(request.DeviceID)
- relay := newSessionRelay(
- transport,
- ikeSuite,
- keys,
- initiatorSPI,
- responseHeader.ResponderSPI,
- natDetected,
- provider.config.KeepaliveInterval,
- )
- installed, err := provider.config.Installer.Install(ctx, ChildSAConfig{
- Name: name,
- OuterLocal: append(net.IP(nil), transport.LocalAddr().IP...),
- OuterRemote: append(net.IP(nil), transport.RemoteAddr().IP...),
- InnerLocalIPv4: append(net.IP(nil), network.LocalIPv4...),
- InnerLocalIPv6: append(net.IP(nil), network.LocalIPv6...),
- InnerIPv6Prefix: network.IPv6Prefix,
- PCSCF: cloneIPs(network.PCSCF),
- DNS: cloneIPs(network.DNS),
- InboundSPI: childInboundSPI,
- OutboundSPI: childOutboundSPI,
- Encryption: encryptionName,
- Integrity: integrityName,
- InboundEncKey: inboundEncryption,
- InboundAuthKey: inboundIntegrity,
- OutboundEncKey: outboundEncryption,
- OutboundAuthKey: outboundIntegrity,
- InitiatorSelectors: initiatorSelectors,
- ResponderSelectors: responderSelectors,
- UDPEncapsulation: natDetected,
- ProxyMode: request.Proxy.Mode,
- Relay: relay,
- })
- if err != nil {
- _ = relay.Close()
- return nil, fmt.Errorf("ike: install CHILD_SA: %w", err)
- }
- if installed == nil {
- _ = relay.Close()
- return nil, errors.New("ike: CHILD_SA installer returned a nil handle")
- }
- dataplaneMode := "unknown"
- if mode, ok := installed.(DataplaneEvidence); ok {
- switch mode.DataplaneMode() {
- case "userspace", "xfrm":
- dataplaneMode = mode.DataplaneMode()
- }
- }
- evidence := vowifi.TunnelEvidence{
- Established: true,
- Name: name,
- DataplaneMode: dataplaneMode,
- LocalIPv4: ipString(network.LocalIPv4),
- LocalIPv6: ipString(network.LocalIPv6),
- PCSCF: ipStrings(network.PCSCF),
- ResponderAUTH: responderAUTH,
- IKEEncryption: fmt.Sprintf("aes-cbc-%d", ikeSuite.EncryptionBits),
- IKEIntegrity: ikeIntegrityName(ikeSuite.IntegrityID),
- IKEDHGroup: dhName(ikeSuite.DHID),
- ESPEncryption: encryptionName,
- ESPIntegrity: integrityName,
- }
- session := &Session{
- evidence: evidence,
- network: NetworkEvidence{
- LocalIPv4: ipString(network.LocalIPv4),
- LocalIPv6: ipString(network.LocalIPv6),
- DNS: ipStrings(network.DNS),
- PCSCF: ipStrings(network.PCSCF),
- DataplaneMode: dataplaneMode,
- },
- child: installed,
- relay: relay,
- transport: transport,
- }
- closeTransport = false
- return session, nil
- }
- func buildInitialEAPOnlyAuth(
- idi payload,
- requestedIDr payload,
- childOfferBody []byte,
- tsi payload,
- tsr payload,
- ) []payload {
- return []payload{
- idi,
- requestedIDr,
- makeNotify(notifyEAPOnlyAuth, nil),
- {Type: payloadSA, Body: append([]byte(nil), childOfferBody...)},
- tsi,
- tsr,
- configurationRequest(),
- }
- }
- func ikeOffer(group uint16, legacyFirst bool) proposal {
- transforms := []transform{
- {Type: transformEncryption, ID: encryptionAESCBC, KeyLength: 128},
- {Type: transformEncryption, ID: encryptionAESCBC, KeyLength: 256},
- {Type: transformPRF, ID: prfHMACSHA1},
- {Type: transformPRF, ID: prfHMACSHA256},
- {Type: transformIntegrity, ID: integrityHMACSHA1_96},
- {Type: transformIntegrity, ID: integrityHMACSHA256_128},
- {Type: transformDH, ID: group},
- }
- if !legacyFirst {
- transforms[2], transforms[3] = transforms[3], transforms[2]
- transforms[4], transforms[5] = transforms[5], transforms[4]
- }
- return proposal{Number: 1, Protocol: protocolIKE, Transforms: transforms}
- }
- func espOffer(spi []byte, legacyFirst bool) proposal {
- transforms := []transform{
- {Type: transformEncryption, ID: encryptionAESCBC, KeyLength: 128},
- {Type: transformEncryption, ID: encryptionAESCBC, KeyLength: 256},
- {Type: transformIntegrity, ID: integrityHMACSHA1_96},
- {Type: transformIntegrity, ID: integrityHMACSHA256_128},
- {Type: transformESN, ID: 0},
- }
- if !legacyFirst {
- transforms[2], transforms[3] = transforms[3], transforms[2]
- }
- return proposal{Number: 1, Protocol: protocolESP, SPI: append([]byte(nil), spi...), Transforms: transforms}
- }
- func validateResponse(
- packet []byte,
- initiatorSPI [8]byte,
- responderSPI [8]byte,
- exchange uint8,
- messageID uint32,
- ) (ikeHeader, []byte, error) {
- header, body, err := parseIKEPacket(packet)
- if err != nil {
- return ikeHeader{}, nil, err
- }
- if header.InitiatorSPI != initiatorSPI ||
- (responderSPI != [8]byte{} && header.ResponderSPI != responderSPI) ||
- header.Exchange != exchange ||
- header.MessageID != messageID ||
- header.Flags&flagResponse == 0 ||
- header.Flags&flagInitiator != 0 {
- return ikeHeader{}, nil, fmt.Errorf("%w: response header does not match the request", errUnexpectedPacket)
- }
- return header, body, nil
- }
- func decryptAndValidate(
- packet []byte,
- initiatorSPI [8]byte,
- responderSPI [8]byte,
- exchange uint8,
- messageID uint32,
- suite negotiatedSuite,
- keys ikeKeys,
- ) (ikeHeader, []payload, error) {
- header, payloads, err := decryptPayloads(packet, suite, keys.SKer, keys.SKar)
- if err != nil {
- return ikeHeader{}, nil, err
- }
- if header.InitiatorSPI != initiatorSPI ||
- header.ResponderSPI != responderSPI ||
- header.Exchange != exchange ||
- header.MessageID != messageID ||
- header.Flags&flagResponse == 0 ||
- header.Flags&flagInitiator != 0 {
- return ikeHeader{}, nil, fmt.Errorf("%w: encrypted response header does not match the request", errUnexpectedPacket)
- }
- return header, payloads, nil
- }
- func rejectFatalNotifications(payloads []payload) error {
- for _, item := range payloadsOfType(payloads, payloadNotify) {
- kind, data, err := parseNotify(item)
- if err != nil {
- return err
- }
- switch kind {
- case notifyNoProposal:
- return errors.New("ike: responder reported NO_PROPOSAL_CHOSEN")
- case notifyInvalidKE:
- if len(data) == 2 {
- return fmt.Errorf("ike: responder requires DH group %d", binary.BigEndian.Uint16(data))
- }
- return errors.New("ike: responder reported INVALID_KE_PAYLOAD")
- }
- if kind < 16384 {
- return fmt.Errorf("ike: responder reported fatal notification %d", kind)
- }
- }
- return nil
- }
- func detectNAT(
- payloads []payload,
- initiatorSPI [8]byte,
- responderSPI [8]byte,
- local *net.UDPAddr,
- remote *net.UDPAddr,
- ) (bool, error) {
- var sourceValue, destinationValue []byte
- for _, item := range payloadsOfType(payloads, payloadNotify) {
- kind, data, err := parseNotify(item)
- if err != nil {
- return false, err
- }
- switch kind {
- case notifyNATSource:
- sourceValue = data
- case notifyNATDestination:
- destinationValue = data
- }
- }
- if len(sourceValue) == 0 && len(destinationValue) == 0 {
- return false, nil
- }
- if len(sourceValue) != sha1Size || len(destinationValue) != sha1Size {
- return false, errors.New("ike: NAT detection notification has an invalid hash length")
- }
- expectedSource, err := natDetectionHash(initiatorSPI, responderSPI, remote.IP, uint16(remote.Port))
- if err != nil {
- return false, err
- }
- expectedDestination, err := natDetectionHash(initiatorSPI, responderSPI, local.IP, uint16(local.Port))
- if err != nil {
- return false, err
- }
- return !equalBytes(sourceValue, expectedSource) || !equalBytes(destinationValue, expectedDestination), nil
- }
- const sha1Size = 20
- func equalBytes(first, second []byte) bool {
- if len(first) != len(second) {
- return false
- }
- var difference byte
- for index := range first {
- difference |= first[index] ^ second[index]
- }
- return difference == 0
- }
- func fillNonzero(random io.Reader, destination []byte) error {
- for attempt := 0; attempt < 8; attempt++ {
- if _, err := io.ReadFull(random, destination); err != nil {
- return fmt.Errorf("ike: generate SPI: %w", err)
- }
- var aggregate byte
- for _, value := range destination {
- aggregate |= value
- }
- if aggregate != 0 {
- return nil
- }
- }
- return errors.New("ike: random source generated a zero SPI repeatedly")
- }
- func tunnelName(deviceID string) string {
- // IFNAMSIZ leaves 15 visible bytes. Hash the complete stable device ID so
- // devices with the same long USB/product prefix never collide at TUNSETIFF.
- normalized := strings.ToLower(strings.TrimSpace(deviceID))
- digest := sha256.Sum256([]byte(normalized))
- return "vocat" + hex.EncodeToString(digest[:5])
- }
- func ipString(ip net.IP) string {
- if ip == nil {
- return ""
- }
- return ip.String()
- }
- func ipStrings(ips []net.IP) []string {
- result := make([]string, 0, len(ips))
- for _, ip := range ips {
- if value := ipString(ip); value != "" {
- result = append(result, value)
- }
- }
- return result
- }
- func pcscfForAssignedFamilies(ips []net.IP, hasIPv4 bool, hasIPv6 bool) []net.IP {
- result := make([]net.IP, 0, len(ips))
- seen := make(map[string]struct{}, len(ips))
- for _, ip := range ips {
- key := ip.String()
- if _, duplicate := seen[key]; duplicate {
- continue
- }
- switch {
- case ip.To4() != nil && hasIPv4:
- result = append(result, append(net.IP(nil), ip...))
- seen[key] = struct{}{}
- case ip.To4() == nil && ip.To16() != nil && hasIPv6:
- result = append(result, append(net.IP(nil), ip...))
- seen[key] = struct{}{}
- }
- }
- return result
- }
- func cloneIPs(ips []net.IP) []net.IP {
- result := make([]net.IP, 0, len(ips))
- for _, ip := range ips {
- result = append(result, append(net.IP(nil), ip...))
- }
- return result
- }
- func ikeIntegrityName(identifier uint16) string {
- switch identifier {
- case integrityHMACSHA1_96:
- return "hmac-sha1-96"
- case integrityHMACSHA256_128:
- return "hmac-sha2-256-128"
- default:
- return ""
- }
- }
- func dhName(identifier uint16) string {
- switch identifier {
- case dhMODP1024:
- return "modp1024"
- case dhMODP2048:
- return "modp2048"
- default:
- return ""
- }
- }
- type NetworkEvidence struct {
- LocalIPv4 string
- LocalIPv6 string
- DNS []string
- PCSCF []string
- DataplaneMode string
- }
- type Session struct {
- mu sync.Mutex
- evidence vowifi.TunnelEvidence
- network NetworkEvidence
- child ChildSAHandle
- relay *sessionRelay
- transport datagramTransport
- closed bool
- }
- func (session *Session) Evidence() vowifi.TunnelEvidence {
- session.mu.Lock()
- defer session.mu.Unlock()
- evidence := session.evidence
- evidence.PCSCF = append([]string(nil), session.evidence.PCSCF...)
- return evidence
- }
- func (session *Session) Network() NetworkEvidence {
- session.mu.Lock()
- defer session.mu.Unlock()
- network := session.network
- network.DNS = append([]string(nil), session.network.DNS...)
- network.PCSCF = append([]string(nil), session.network.PCSCF...)
- return network
- }
- func (session *Session) Failures() <-chan error {
- session.mu.Lock()
- defer session.mu.Unlock()
- if notifier, ok := session.child.(DataplaneFailureNotifier); ok {
- return notifier.Failures()
- }
- return nil
- }
- func (session *Session) Close(ctx context.Context) error {
- session.mu.Lock()
- if session.closed {
- session.mu.Unlock()
- return nil
- }
- session.closed = true
- child := session.child
- relay := session.relay
- transport := session.transport
- session.evidence.Established = false
- session.child = nil
- session.relay = nil
- session.transport = nil
- session.mu.Unlock()
- var errs []error
- if child != nil {
- if err := child.Close(ctx); err != nil {
- errs = append(errs, fmt.Errorf("remove CHILD_SA: %w", err))
- }
- }
- if relay != nil {
- if err := relay.Close(); err != nil {
- errs = append(errs, fmt.Errorf("close session relay: %w", err))
- }
- }
- if transport != nil {
- if err := transport.Close(); err != nil {
- errs = append(errs, fmt.Errorf("close IKE transport: %w", err))
- }
- }
- return errors.Join(errs...)
- }
- var _ vowifi.TunnelProvider = (*Provider)(nil)
- var _ vowifi.TunnelSession = (*Session)(nil)
- var _ vowifi.RuntimeFailureNotifier = (*Session)(nil)
|