├── internal ├── context │ ├── 3gpp_types.go │ ├── testing_app.go │ ├── nwuup.go │ ├── gtp.go │ ├── timer.go │ ├── n3iwf_ue.go │ ├── context_test.go │ ├── ike.go │ ├── amf.go │ ├── ranue.go │ ├── ngap.go │ ├── ikeue.go │ └── context.go ├── util │ ├── hash.go │ ├── test │ │ └── testN3iwfcfg.yaml │ └── ngap_convert.go ├── nas │ └── nas_security │ │ └── security.go ├── ngap │ ├── message │ │ ├── types.go │ │ └── forwardIE.go │ ├── server_test.go │ ├── handler_test.go │ ├── dispatcher.go │ ├── server.go │ └── eap5g.go ├── ike │ ├── dispatcher.go │ ├── send.go │ ├── server_test.go │ ├── xfrm │ │ └── xfrm.go │ ├── handler_test.go │ └── server.go ├── logger │ └── logger.go ├── gre │ └── message.go ├── gtp │ └── message │ │ └── message.go ├── nwucp │ └── server.go └── nwuup │ └── server.go ├── .gitignore ├── .github └── workflows │ ├── commit-msg-check.yml │ ├── go.yml │ └── golangci-lint.yml ├── pkg ├── app │ └── app.go ├── factory │ ├── factory.go │ └── config.go └── service │ └── init.go ├── go.mod ├── .golangci.yml ├── cmd └── main.go ├── LICENSE └── go.sum /internal/context/3gpp_types.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | const ( 4 | MaxValueOfRanUeNgapID int64 = 4294967295 5 | MaxNumOfPDUSessions int = 256 6 | ) 7 | -------------------------------------------------------------------------------- /internal/util/hash.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "hash/crc32" 4 | 5 | func HashCRC32(text string) (uint32, error) { 6 | h := crc32.NewIEEE() 7 | _, err := h.Write([]byte(text)) 8 | if err != nil { 9 | return 0, err 10 | } 11 | 12 | return h.Sum32(), nil 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Swap files 2 | *.swp 3 | 4 | # Toolchain 5 | # Golang project folder 6 | .idea/ 7 | 8 | # Visual Studio Code 9 | .vscode/ 10 | 11 | # Build 12 | build/ 13 | log/ 14 | vendor/ 15 | 16 | # emacs/vim 17 | GPATH 18 | GRTAGS 19 | GTAGS 20 | TAGS 21 | tags 22 | cscope.* 23 | 24 | # macOS 25 | .DS_Store 26 | 27 | # Debug 28 | *.log 29 | *.pcap 30 | -------------------------------------------------------------------------------- /.github/workflows/commit-msg-check.yml: -------------------------------------------------------------------------------- 1 | name: 'Commit Message Check' 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ "*" ] 8 | 9 | jobs: 10 | build: 11 | name: Conventional Commits 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: webiny/action-conventional-commits@v1.3.0 -------------------------------------------------------------------------------- /pkg/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | n3iwf_context "github.com/free5gc/n3iwf/internal/context" 5 | "github.com/free5gc/n3iwf/pkg/factory" 6 | ) 7 | 8 | type App interface { 9 | SetLogEnable(enable bool) 10 | SetLogLevel(level string) 11 | SetReportCaller(reportCaller bool) 12 | 13 | Start() 14 | Terminate() 15 | 16 | Context() *n3iwf_context.N3IWFContext 17 | Config() *factory.Config 18 | } 19 | -------------------------------------------------------------------------------- /internal/context/testing_app.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/free5gc/util/idgenerator" 7 | ) 8 | 9 | func NewTestContext(n3iwf n3iwf) (*N3IWFContext, error) { 10 | n := &N3IWFContext{ 11 | n3iwf: n3iwf, 12 | RANUENGAPIDGenerator: idgenerator.NewGenerator(0, math.MaxInt64), 13 | TEIDGenerator: idgenerator.NewGenerator(1, math.MaxUint32), 14 | } 15 | return n, nil 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | go: [ '1.24' ] 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Set up Go 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version: ${{ matrix.go }} 22 | 23 | - name: Build 24 | run: go build -v ./... 25 | 26 | - name: Test 27 | run: go test -v ./... 28 | -------------------------------------------------------------------------------- /internal/context/nwuup.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import gtpQoSMsg "github.com/free5gc/n3iwf/internal/gtp/message" 4 | 5 | type NwuupEventType int64 6 | 7 | // NWuup Event Type 8 | const ( 9 | NwuupForwardDL NwuupEventType = iota 10 | ) 11 | 12 | type NwuupEvt interface { 13 | Type() NwuupEventType 14 | } 15 | 16 | type NwuupForwardDLEvt struct { 17 | Packet gtpQoSMsg.QoSTPDUPacket 18 | } 19 | 20 | func (nwuupForwardDLEvt *NwuupForwardDLEvt) Type() NwuupEventType { 21 | return NwuupForwardDL 22 | } 23 | 24 | func NewNwuupForwardDLEvt(packet gtpQoSMsg.QoSTPDUPacket) *NwuupForwardDLEvt { 25 | return &NwuupForwardDLEvt{ 26 | Packet: packet, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/nas/nas_security/security.go: -------------------------------------------------------------------------------- 1 | package nas_security 2 | 3 | import ( 4 | "encoding/binary" 5 | ) 6 | 7 | // API for N3IWF 8 | func EncapNasMsgToEnvelope(nasPDU []byte) []byte { 9 | // According to TS 24.502 8.2.4, 10 | // in order to transport a NAS message over the non-3GPP access between the UE and the N3IWF, 11 | // the NAS message shall be framed in a NAS message envelope as defined in subclause 9.4. 12 | // According to TS 24.502 9.4, 13 | // a NAS message envelope = Length | NAS Message 14 | nasEnv := make([]byte, 2) 15 | binary.BigEndian.PutUint16(nasEnv, uint16(len(nasPDU))) 16 | nasEnv = append(nasEnv, nasPDU...) 17 | return nasEnv 18 | } 19 | -------------------------------------------------------------------------------- /internal/context/gtp.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | type GtpEventType int64 4 | 5 | // GTP Event Type 6 | const ( 7 | ForwardUL GtpEventType = iota 8 | ) 9 | 10 | type GtpEvt interface { 11 | Type() GtpEventType 12 | } 13 | 14 | type ForwardULEvt struct { 15 | GtpConnInfo *GTPConnectionInfo 16 | QFI *uint8 17 | Payload []byte 18 | } 19 | 20 | func (forwardDLEvt *ForwardULEvt) Type() GtpEventType { 21 | return ForwardUL 22 | } 23 | 24 | func NewForwardULEvt(gtpConnInfo *GTPConnectionInfo, qfi *uint8, payload []byte) *ForwardULEvt { 25 | return &ForwardULEvt{ 26 | GtpConnInfo: gtpConnInfo, 27 | QFI: qfi, 28 | Payload: payload, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/ngap/message/types.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | // Used in AN-Parameter field for IE types 4 | const ( 5 | ANParametersTypeGUAMI = 1 6 | ANParametersTypeSelectedPLMNID = 2 7 | ANParametersTypeRequestedNSSAI = 3 8 | ANParametersTypeEstablishmentCause = 4 9 | ) 10 | 11 | // Used for checking if AN-Parameter length field is legal 12 | const ( 13 | ANParametersLenGUAMI = 6 14 | ANParametersLenPLMNID = 3 15 | ANParametersLenEstCause = 1 16 | ) 17 | 18 | // Used in IE Establishment Cause field for cause types 19 | const ( 20 | EstablishmentCauseEmergency = 0 21 | EstablishmentCauseHighPriorityAccess = 1 22 | EstablishmentCauseMO_Signalling = 3 23 | EstablishmentCauseMO_Data = 4 24 | EstablishmentCauseMPS_PriorityAccess = 8 25 | EstablishmentCauseMCS_PriorityAccess = 9 26 | ) 27 | -------------------------------------------------------------------------------- /internal/context/timer.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "sync/atomic" 5 | "time" 6 | ) 7 | 8 | type Timer struct { 9 | ticker *time.Ticker 10 | done chan bool 11 | } 12 | 13 | func NewDPDPeriodicTimer(d time.Duration, maxRetryTimes int32, ikeSA *IKESecurityAssociation, 14 | cancelFunc func(), 15 | ) *Timer { 16 | t := &Timer{} 17 | t.done = make(chan bool, 1) 18 | t.ticker = time.NewTicker(d) 19 | 20 | go func(ticker *time.Ticker) { 21 | defer ticker.Stop() 22 | 23 | for { 24 | select { 25 | case <-t.done: 26 | return 27 | case <-ticker.C: 28 | atomic.AddInt32(&ikeSA.CurrentRetryTimes, 1) 29 | if atomic.LoadInt32(&ikeSA.CurrentRetryTimes) > 0 { 30 | cancelFunc() 31 | return 32 | } 33 | } 34 | } 35 | }(t.ticker) 36 | 37 | return t 38 | } 39 | 40 | func (t *Timer) Stop() { 41 | t.done <- true 42 | close(t.done) 43 | } 44 | -------------------------------------------------------------------------------- /internal/util/test/testN3iwfcfg.yaml: -------------------------------------------------------------------------------- 1 | info: 2 | version: 1.0.0 3 | description: N3IWF initial local configuration 4 | 5 | configuration: 6 | N3IWFInformation: 7 | GlobalN3IWFID: 8 | PLMNID: 9 | MCC: 10 | 208 11 | MNC: 12 | 93 13 | N3IWFID: 14 | 135 15 | Name: 16 | free5GC_N3IWF 17 | SupportedTAList: 18 | - TAC: 19 | 1 20 | BroadcastPLMNList: 21 | - PLMNID: 22 | MCC: 23 | 208 24 | MNC: 25 | 93 26 | TAISliceSupportList: 27 | - SNSSAI: 28 | SST: 29 | 1 30 | SD: 31 | 010203 32 | - SNSSAI: 33 | SST: 34 | 1 35 | SD: 36 | 112233 37 | AMFAddress: 38 | - IP: 127.0.0.1 39 | -------------------------------------------------------------------------------- /internal/util/ngap_convert.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/hex" 6 | "strings" 7 | 8 | "github.com/free5gc/aper" 9 | "github.com/free5gc/n3iwf/internal/logger" 10 | "github.com/free5gc/n3iwf/pkg/factory" 11 | "github.com/free5gc/ngap/ngapType" 12 | ) 13 | 14 | func PlmnIdToNgap(plmnId factory.PLMNID) (ngapPlmnId ngapType.PLMNIdentity) { 15 | var hexString string 16 | mcc := strings.Split(plmnId.Mcc, "") 17 | mnc := strings.Split(plmnId.Mnc, "") 18 | if len(plmnId.Mnc) == 2 { 19 | hexString = mcc[1] + mcc[0] + "f" + mcc[2] + mnc[1] + mnc[0] 20 | } else { 21 | hexString = mcc[1] + mcc[0] + mnc[0] + mcc[2] + mnc[2] + mnc[1] 22 | } 23 | var err error 24 | ngapPlmnId.Value, err = hex.DecodeString(hexString) 25 | if err != nil { 26 | logger.UtilLog.Errorf("DecodeString error: %+v", err) 27 | } 28 | return 29 | } 30 | 31 | func N3iwfIdToNgap(n3iwfId uint16) (ngapN3iwfId *aper.BitString) { 32 | ngapN3iwfId = new(aper.BitString) 33 | ngapN3iwfId.Bytes = make([]byte, 2) 34 | binary.BigEndian.PutUint16(ngapN3iwfId.Bytes, n3iwfId) 35 | ngapN3iwfId.BitLength = 16 36 | return 37 | } 38 | -------------------------------------------------------------------------------- /internal/ike/dispatcher.go: -------------------------------------------------------------------------------- 1 | package ike 2 | 3 | import ( 4 | "net" 5 | "runtime/debug" 6 | 7 | ike_message "github.com/free5gc/ike/message" 8 | n3iwf_context "github.com/free5gc/n3iwf/internal/context" 9 | "github.com/free5gc/n3iwf/internal/logger" 10 | ) 11 | 12 | func (s *Server) Dispatch( 13 | udpConn *net.UDPConn, 14 | localAddr, remoteAddr *net.UDPAddr, 15 | ikeMessage *ike_message.IKEMessage, msg []byte, 16 | ikeSA *n3iwf_context.IKESecurityAssociation, 17 | ) { 18 | ikeLog := logger.IKELog 19 | defer func() { 20 | if p := recover(); p != nil { 21 | // Print stack for panic to log. Fatalf() will let program exit. 22 | ikeLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 23 | } 24 | }() 25 | 26 | switch ikeMessage.ExchangeType { 27 | case ike_message.IKE_SA_INIT: 28 | s.HandleIKESAINIT(udpConn, localAddr, remoteAddr, ikeMessage, msg) 29 | case ike_message.IKE_AUTH: 30 | s.HandleIKEAUTH(udpConn, localAddr, remoteAddr, ikeMessage, ikeSA) 31 | case ike_message.CREATE_CHILD_SA: 32 | s.HandleCREATECHILDSA(udpConn, localAddr, remoteAddr, ikeMessage, ikeSA) 33 | case ike_message.INFORMATIONAL: 34 | s.HandleInformational(udpConn, localAddr, remoteAddr, ikeMessage, ikeSA) 35 | default: 36 | ikeLog.Warnf("Unimplemented IKE message type, exchange type: %d", ikeMessage.ExchangeType) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pkg/factory/factory.go: -------------------------------------------------------------------------------- 1 | package factory 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/asaskevich/govalidator" 8 | yaml "gopkg.in/yaml.v2" 9 | 10 | "github.com/free5gc/n3iwf/internal/logger" 11 | ) 12 | 13 | var N3iwfConfig *Config 14 | 15 | // TODO: Support configuration update from REST api 16 | func InitConfigFactory(f string, cfg *Config) error { 17 | if f == "" { 18 | // Use default config path 19 | f = N3iwfDefaultConfigPath 20 | } 21 | 22 | if content, err := os.ReadFile(f); err != nil { 23 | return fmt.Errorf("[Factory] %+v", err) 24 | } else { 25 | logger.CfgLog.Infof("Read config from [%s]", f) 26 | if yamlErr := yaml.Unmarshal(content, cfg); yamlErr != nil { 27 | return fmt.Errorf("[Factory] %+v", yamlErr) 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func ReadConfig(cfgPath string) (*Config, error) { 35 | cfg := &Config{} 36 | if err := InitConfigFactory(cfgPath, cfg); err != nil { 37 | return nil, fmt.Errorf("ReadConfig [%s] Error: %+v", cfgPath, err) 38 | } 39 | if err := cfg.Validate(); err != nil { 40 | validErrs := err.(govalidator.Errors).Errors() 41 | for _, validErr := range validErrs { 42 | logger.CfgLog.Errorf("%+v", validErr) 43 | } 44 | logger.CfgLog.Errorf("[-- PLEASE REFER TO SAMPLE CONFIG FILE COMMENTS --]") 45 | return nil, fmt.Errorf("Config validate Error") 46 | } 47 | 48 | return cfg, nil 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | branches: [ main ] 8 | pull_request: 9 | branches: [ main ] 10 | 11 | jobs: 12 | golangci: 13 | name: lint 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | go: [ '1.24' ] 18 | steps: 19 | - name: Set up Go 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version: ${{ matrix.go }} 23 | - uses: actions/checkout@v4 24 | - name: Run golangci-lint 25 | uses: golangci/golangci-lint-action@v8 26 | with: 27 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 28 | version: v2.1.6 29 | 30 | # Optional: working directory, useful for monorepos 31 | # working-directory: somedir 32 | 33 | # Optional: golangci-lint command line arguments. 34 | # args: --issues-exit-code=0 35 | 36 | # Optional: show only new issues if it's a pull request. The default value is `false`. 37 | # only-new-issues: true 38 | 39 | # Optional: if set to true then the all caching functionality will be complete disabled, 40 | # takes precedence over all other caching options. 41 | # skip-cache: true 42 | 43 | # Optional: if set to true then the action don't cache or restore ~/go/pkg. 44 | # skip-pkg-cache: true 45 | 46 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. 47 | # skip-build-cache: true 48 | -------------------------------------------------------------------------------- /internal/ngap/server_test.go: -------------------------------------------------------------------------------- 1 | package ngap 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | n3iwf_context "github.com/free5gc/n3iwf/internal/context" 8 | "github.com/free5gc/n3iwf/internal/ike" 9 | "github.com/free5gc/n3iwf/pkg/factory" 10 | "github.com/free5gc/util/safe_channel" 11 | ) 12 | 13 | type n3iwfTestApp struct { 14 | cfg *factory.Config 15 | n3iwfCtx *n3iwf_context.N3IWFContext 16 | ctx context.Context 17 | cancel context.CancelFunc 18 | wg *sync.WaitGroup 19 | 20 | ngapServer *Server 21 | ikeServer *ike.Server 22 | 23 | mockIkeEvtCh *safe_channel.SafeCh[n3iwf_context.IkeEvt] 24 | } 25 | 26 | func (a *n3iwfTestApp) Config() *factory.Config { 27 | return a.cfg 28 | } 29 | 30 | func (a *n3iwfTestApp) Context() *n3iwf_context.N3IWFContext { 31 | return a.n3iwfCtx 32 | } 33 | 34 | func (a *n3iwfTestApp) CancelContext() context.Context { 35 | return a.ctx 36 | } 37 | 38 | func (a *n3iwfTestApp) SendNgapEvt(evt n3iwf_context.NgapEvt) { 39 | a.ngapServer.SendNgapEvt(evt) 40 | } 41 | 42 | func (a *n3iwfTestApp) SendIkeEvt(evt n3iwf_context.IkeEvt) { 43 | a.mockIkeEvtCh.Send(evt) 44 | } 45 | 46 | func NewN3iwfTestApp(cfg *factory.Config) (*n3iwfTestApp, error) { 47 | var err error 48 | ctx, cancel := context.WithCancel(context.Background()) 49 | 50 | n3iwfApp := &n3iwfTestApp{ 51 | cfg: cfg, 52 | ctx: ctx, 53 | cancel: cancel, 54 | wg: &sync.WaitGroup{}, 55 | } 56 | n3iwfApp.mockIkeEvtCh = safe_channel.NewSafeCh[n3iwf_context.IkeEvt](10) 57 | n3iwfApp.n3iwfCtx, err = n3iwf_context.NewTestContext(n3iwfApp) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return n3iwfApp, err 62 | } 63 | -------------------------------------------------------------------------------- /internal/ngap/handler_test.go: -------------------------------------------------------------------------------- 1 | package ngap 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | n3iwf_context "github.com/free5gc/n3iwf/internal/context" 9 | "github.com/free5gc/n3iwf/internal/ike" 10 | "github.com/free5gc/n3iwf/pkg/factory" 11 | ) 12 | 13 | func TestReleaseIkeUeAndRanUe(t *testing.T) { 14 | n3iwf, err := NewN3iwfTestApp(&factory.Config{}) 15 | require.NoError(t, err) 16 | 17 | n3iwf.ngapServer, err = NewServer(n3iwf) 18 | require.NoError(t, err) 19 | 20 | n3iwf.ikeServer, err = ike.NewServer(n3iwf) 21 | require.NoError(t, err) 22 | 23 | n3iwfCtx := n3iwf.n3iwfCtx 24 | ranUe := &n3iwf_context.N3IWFRanUe{ 25 | RanUeSharedCtx: n3iwf_context.RanUeSharedCtx{ 26 | N3iwfCtx: n3iwfCtx, 27 | }, 28 | } 29 | 30 | ranUeNgapId := int64(0x1234567890ABCDEF) 31 | spi := uint64(123) 32 | ranUe.RanUeNgapId = ranUeNgapId 33 | n3iwfCtx.RANUePool.Store(ranUeNgapId, ranUe) 34 | n3iwfCtx.NGAPIdToIKESPI.Store(ranUeNgapId, spi) 35 | n3iwfCtx.IKESPIToNGAPId.Store(spi, ranUeNgapId) 36 | 37 | stopCh := make(chan struct{}) 38 | rcvIkeEvtCh := n3iwf.mockIkeEvtCh.GetRcvChan() 39 | 40 | go func() { 41 | for { 42 | select { 43 | case <-stopCh: 44 | return 45 | case rcvEvt := <-rcvIkeEvtCh: 46 | if rcvEvt.Type() != n3iwf_context.IKEDeleteRequest { 47 | t.Errorf("Receive Wrong Event") 48 | } 49 | } 50 | } 51 | }() 52 | 53 | err = n3iwf.ngapServer.releaseIkeUeAndRanUe(ranUe) 54 | require.NoError(t, err) 55 | 56 | _, ok := n3iwfCtx.RANUePool.Load(ranUeNgapId) 57 | if ok { 58 | t.Errorf("RanUe doesn't get remove") 59 | } 60 | 61 | stopCh <- struct{}{} 62 | n3iwf.ngapServer.Stop() 63 | } 64 | -------------------------------------------------------------------------------- /internal/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | 6 | logger_util "github.com/free5gc/util/logger" 7 | ) 8 | 9 | var ( 10 | Log *logrus.Logger 11 | N3iwfLog *logrus.Entry 12 | MainLog *logrus.Entry 13 | InitLog *logrus.Entry 14 | CfgLog *logrus.Entry 15 | CtxLog *logrus.Entry 16 | GinLog *logrus.Entry 17 | NasLog *logrus.Entry 18 | NgapLog *logrus.Entry 19 | IKELog *logrus.Entry 20 | GTPLog *logrus.Entry 21 | NWuCPLog *logrus.Entry 22 | NWuUPLog *logrus.Entry 23 | RelayLog *logrus.Entry 24 | UtilLog *logrus.Entry 25 | GmmLog *logrus.Entry 26 | ) 27 | 28 | func UpdateN3iwfLog() { 29 | N3iwfLog = Log.WithField(logger_util.FieldNF, "N3IWF") 30 | // update logs created from N3iwfLog 31 | MainLog = N3iwfLog.WithField(logger_util.FieldCategory, "Main") 32 | InitLog = N3iwfLog.WithField(logger_util.FieldCategory, "Init") 33 | CfgLog = N3iwfLog.WithField(logger_util.FieldCategory, "CFG") 34 | CtxLog = N3iwfLog.WithField(logger_util.FieldCategory, "CTX") 35 | GinLog = N3iwfLog.WithField(logger_util.FieldCategory, "GIN") 36 | NgapLog = N3iwfLog.WithField(logger_util.FieldCategory, "NGAP") 37 | IKELog = N3iwfLog.WithField(logger_util.FieldCategory, "IKE") 38 | GTPLog = N3iwfLog.WithField(logger_util.FieldCategory, "GTP") 39 | NWuCPLog = N3iwfLog.WithField(logger_util.FieldCategory, "NWuCP") 40 | NWuUPLog = N3iwfLog.WithField(logger_util.FieldCategory, "NWuUP") 41 | RelayLog = N3iwfLog.WithField(logger_util.FieldCategory, "Relay") 42 | UtilLog = N3iwfLog.WithField(logger_util.FieldCategory, "Util") 43 | } 44 | 45 | func init() { 46 | fieldsOrder := []string{ 47 | logger_util.FieldNF, 48 | logger_util.FieldCategory, 49 | } 50 | Log = logger_util.New(fieldsOrder) 51 | UpdateN3iwfLog() 52 | } 53 | -------------------------------------------------------------------------------- /internal/gre/message.go: -------------------------------------------------------------------------------- 1 | package gre 2 | 3 | import ( 4 | "encoding/binary" 5 | "math" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | // [TS 24.502] 9.3.3 GRE encapsulated user data packet 11 | const ( 12 | GREHeaderFieldLength = 8 13 | GREHeaderKeyFieldLength = 4 14 | ) 15 | 16 | // Ethertypes Specified by the IETF 17 | const ( 18 | IPv4 uint16 = 0x0800 19 | IPv6 uint16 = 0x86DD 20 | ) 21 | 22 | type GREPacket struct { 23 | flags uint8 24 | version uint8 25 | protocolType uint16 26 | key uint32 27 | payload []byte 28 | } 29 | 30 | func (p *GREPacket) Marshal() []byte { 31 | packet := make([]byte, GREHeaderFieldLength+len(p.payload)) 32 | 33 | packet[0] = p.flags 34 | packet[1] = p.version 35 | binary.BigEndian.PutUint16(packet[2:4], p.protocolType) 36 | binary.BigEndian.PutUint32(packet[4:8], p.key) 37 | copy(packet[GREHeaderFieldLength:], p.payload) 38 | return packet 39 | } 40 | 41 | func (p *GREPacket) Unmarshal(b []byte) error { 42 | p.flags = b[0] 43 | p.version = b[1] 44 | 45 | p.protocolType = binary.BigEndian.Uint16(b[2:4]) 46 | 47 | offset := 4 48 | 49 | if p.GetKeyFlag() { 50 | p.key = binary.BigEndian.Uint32(b[offset : offset+GREHeaderKeyFieldLength]) 51 | offset += GREHeaderKeyFieldLength 52 | } 53 | 54 | p.payload = append(p.payload, b[offset:]...) 55 | return nil 56 | } 57 | 58 | func (p *GREPacket) SetPayload(payload []byte, protocolType uint16) { 59 | p.payload = payload 60 | p.protocolType = protocolType 61 | } 62 | 63 | func (p *GREPacket) GetPayload() ([]byte, uint16) { 64 | return p.payload, p.protocolType 65 | } 66 | 67 | func (p *GREPacket) setKeyFlag() { 68 | p.flags |= 0x20 69 | } 70 | 71 | func (p *GREPacket) GetKeyFlag() bool { 72 | return (p.flags & 0x20) > 0 73 | } 74 | 75 | func (p *GREPacket) setQFI(qfi uint8) { 76 | p.key |= (uint32(qfi) & 0x3F) << 24 77 | } 78 | 79 | func (p *GREPacket) setRQI(rqi bool) { 80 | if rqi { 81 | p.key |= 0x80 82 | } 83 | } 84 | 85 | func (p *GREPacket) GetQFI() (uint8, error) { 86 | value := (p.key >> 24) & 0x3F 87 | 88 | if value > math.MaxUint8 { 89 | return 0, errors.Errorf("GetQFI() value exceeds uint8: %d", value) 90 | } else { 91 | return uint8(value), nil 92 | } 93 | } 94 | 95 | func (p *GREPacket) GetRQI() bool { 96 | return (p.key & 0x80) > 0 97 | } 98 | 99 | func (p *GREPacket) GetKeyField() uint32 { 100 | return p.key 101 | } 102 | 103 | func (p *GREPacket) SetQoS(qfi uint8, rqi bool) { 104 | p.setQFI(qfi) 105 | p.setRQI(rqi) 106 | p.setKeyFlag() 107 | } 108 | -------------------------------------------------------------------------------- /internal/gtp/message/message.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "encoding/hex" 5 | 6 | "github.com/pkg/errors" 7 | gtpMsg "github.com/wmnsk/go-gtp/gtpv1/message" 8 | 9 | "github.com/free5gc/n3iwf/internal/logger" 10 | ) 11 | 12 | // [TS 38.415] 5.5.2 Frame format for the PDU Session user plane protocol 13 | const ( 14 | DL_PDU_SESSION_INFORMATION_TYPE = 0x00 15 | UL_PDU_SESSION_INFORMATION_TYPE = 0x10 16 | ) 17 | 18 | type QoSTPDUPacket struct { 19 | tPDU *gtpMsg.TPDU 20 | qos bool 21 | rqi bool 22 | qfi uint8 23 | } 24 | 25 | func (p *QoSTPDUPacket) GetPayload() []byte { 26 | return p.tPDU.Payload 27 | } 28 | 29 | func (p *QoSTPDUPacket) GetTEID() uint32 { 30 | return p.tPDU.TEID() 31 | } 32 | 33 | func (p *QoSTPDUPacket) GetExtensionHeader() []*gtpMsg.ExtensionHeader { 34 | return p.tPDU.ExtensionHeaders 35 | } 36 | 37 | func (p *QoSTPDUPacket) HasQoS() bool { 38 | return p.qos 39 | } 40 | 41 | func (p *QoSTPDUPacket) GetQoSParameters() (uint8, bool) { 42 | return p.qfi, p.rqi 43 | } 44 | 45 | func (p *QoSTPDUPacket) Unmarshal(pdu *gtpMsg.TPDU) error { 46 | p.tPDU = pdu 47 | if p.tPDU.HasExtensionHeader() { 48 | if err := p.unmarshalExtensionHeader(); err != nil { 49 | return err 50 | } 51 | } 52 | 53 | return nil 54 | } 55 | 56 | // [TS 29.281] [TS 38.415] 57 | // Define GTP extension header 58 | // [TS 38.415] 59 | // Define PDU Session User Plane protocol 60 | func (p *QoSTPDUPacket) unmarshalExtensionHeader() error { 61 | gtpLog := logger.GTPLog 62 | 63 | for _, eh := range p.tPDU.ExtensionHeaders { 64 | switch eh.Type { 65 | case gtpMsg.ExtHeaderTypePDUSessionContainer: 66 | p.qos = true 67 | p.rqi = ((int(eh.Content[1]) >> 6) & 0x1) == 1 68 | p.qfi = eh.Content[1] & 0x3F 69 | gtpLog.Tracef("Parsed Extension Header: Len=%d, Next Type=%d, Content Dump:\n%s", 70 | eh.Length, eh.NextType, hex.Dump(eh.Content)) 71 | default: 72 | gtpLog.Warningf("Unsupported Extension Header Field Value: %x", eh.Type) 73 | } 74 | } 75 | 76 | if !p.qos { 77 | return errors.Errorf("unmarshalExtensionHeader err: no PDUSessionContainer in ExtensionHeaders.") 78 | } 79 | 80 | return nil 81 | } 82 | 83 | func BuildQoSGTPPacket(teid uint32, qfi uint8, payload []byte) ([]byte, error) { 84 | header := gtpMsg.NewHeader(0x34, gtpMsg.MsgTypeTPDU, teid, 0x00, payload).WithExtensionHeaders( 85 | gtpMsg.NewExtensionHeader( 86 | gtpMsg.ExtHeaderTypePDUSessionContainer, 87 | []byte{UL_PDU_SESSION_INFORMATION_TYPE, qfi}, 88 | gtpMsg.ExtHeaderTypeNoMoreExtensionHeaders, 89 | ), 90 | ) 91 | 92 | b := make([]byte, header.MarshalLen()) 93 | if err := header.MarshalTo(b); err != nil { 94 | return nil, errors.Wrapf(err, "go-gtp Marshal failed") 95 | } 96 | 97 | return b, nil 98 | } 99 | -------------------------------------------------------------------------------- /internal/context/n3iwf_ue.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/pkg/errors" 7 | 8 | "github.com/free5gc/ngap/ngapConvert" 9 | "github.com/free5gc/ngap/ngapType" 10 | ) 11 | 12 | type N3IWFRanUe struct { 13 | RanUeSharedCtx 14 | 15 | // Temporary cached NAS message 16 | // Used when NAS registration accept arrived before 17 | // UE setup NAS TCP connection with N3IWF, and 18 | // Forward pduSessionEstablishmentAccept to UE after 19 | // UE send CREATE_CHILD_SA response 20 | TemporaryCachedNASMessage []byte 21 | 22 | // NAS TCP Connection Established 23 | IsNASTCPConnEstablished bool 24 | IsNASTCPConnEstablishedComplete bool 25 | 26 | // NAS TCP Connection 27 | TCPConnection net.Conn 28 | } 29 | 30 | func (n3iwfUe *N3IWFRanUe) init(ranUeNgapId int64) { 31 | n3iwfUe.RanUeNgapId = ranUeNgapId 32 | n3iwfUe.AmfUeNgapId = AmfUeNgapIdUnspecified 33 | n3iwfUe.PduSessionList = make(map[int64]*PDUSession) 34 | n3iwfUe.TemporaryPDUSessionSetupData = new(PDUSessionSetupTemporaryData) 35 | n3iwfUe.IsNASTCPConnEstablished = false 36 | n3iwfUe.IsNASTCPConnEstablishedComplete = false 37 | } 38 | 39 | func (ranUe *N3IWFRanUe) Remove() error { 40 | // remove from AMF context 41 | ranUe.DetachAMF() 42 | 43 | // remove from RAN UE context 44 | n3iwfCtx := ranUe.N3iwfCtx 45 | n3iwfCtx.DeleteRanUe(ranUe.RanUeNgapId) 46 | 47 | for _, pduSession := range ranUe.PduSessionList { 48 | n3iwfCtx.DeleteTEID(pduSession.GTPConnInfo.IncomingTEID) 49 | } 50 | 51 | if ranUe.TCPConnection != nil { 52 | if err := ranUe.TCPConnection.Close(); err != nil { 53 | return errors.Errorf("Close TCP conn error : %v", err) 54 | } 55 | } 56 | 57 | return nil 58 | } 59 | 60 | func (n3iwfUe *N3IWFRanUe) AttachAMF(sctpAddr string) bool { 61 | if amf, ok := n3iwfUe.N3iwfCtx.AMFPoolLoad(sctpAddr); ok { 62 | amf.N3iwfRanUeList[n3iwfUe.RanUeNgapId] = n3iwfUe 63 | n3iwfUe.AMF = amf 64 | return true 65 | } else { 66 | return false 67 | } 68 | } 69 | 70 | func (n3iwfUe *N3IWFRanUe) DetachAMF() { 71 | if n3iwfUe.AMF == nil { 72 | return 73 | } 74 | delete(n3iwfUe.AMF.N3iwfRanUeList, n3iwfUe.RanUeNgapId) 75 | } 76 | 77 | // Implement RanUe interface 78 | func (n3iwfUe *N3IWFRanUe) GetUserLocationInformation() *ngapType.UserLocationInformation { 79 | userLocationInformation := new(ngapType.UserLocationInformation) 80 | 81 | userLocationInformation.Present = ngapType.UserLocationInformationPresentUserLocationInformationN3IWF 82 | userLocationInformation.UserLocationInformationN3IWF = new(ngapType.UserLocationInformationN3IWF) 83 | 84 | userLocationInfoN3IWF := userLocationInformation.UserLocationInformationN3IWF 85 | userLocationInfoN3IWF.IPAddress = ngapConvert.IPAddressToNgap(n3iwfUe.IPAddrv4, n3iwfUe.IPAddrv6) 86 | userLocationInfoN3IWF.PortNumber = ngapConvert.PortNumberToNgap(n3iwfUe.PortNumber) 87 | 88 | return userLocationInformation 89 | } 90 | -------------------------------------------------------------------------------- /internal/ngap/message/forwardIE.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "github.com/free5gc/aper" 5 | "github.com/free5gc/ngap/ngapType" 6 | ) 7 | 8 | func AppendPDUSessionResourceSetupListCxtRes( 9 | list *ngapType.PDUSessionResourceSetupListCxtRes, pduSessionID int64, transfer []byte, 10 | ) { 11 | item := ngapType.PDUSessionResourceSetupItemCxtRes{} 12 | item.PDUSessionID.Value = pduSessionID 13 | item.PDUSessionResourceSetupResponseTransfer = transfer 14 | list.List = append(list.List, item) 15 | } 16 | 17 | func AppendPDUSessionResourceFailedToSetupListCxtRes( 18 | list *ngapType.PDUSessionResourceFailedToSetupListCxtRes, pduSessionID int64, transfer []byte, 19 | ) { 20 | item := ngapType.PDUSessionResourceFailedToSetupItemCxtRes{} 21 | item.PDUSessionID.Value = pduSessionID 22 | item.PDUSessionResourceSetupUnsuccessfulTransfer = transfer 23 | list.List = append(list.List, item) 24 | } 25 | 26 | func AppendPDUSessionResourceFailedToSetupListCxtfail( 27 | list *ngapType.PDUSessionResourceFailedToSetupListCxtFail, pduSessionID int64, transfer []byte, 28 | ) { 29 | item := ngapType.PDUSessionResourceFailedToSetupItemCxtFail{} 30 | item.PDUSessionID.Value = pduSessionID 31 | item.PDUSessionResourceSetupUnsuccessfulTransfer = transfer 32 | list.List = append(list.List, item) 33 | } 34 | 35 | func AppendPDUSessionResourceSetupListSURes( 36 | list *ngapType.PDUSessionResourceSetupListSURes, pduSessionID int64, transfer []byte, 37 | ) { 38 | item := ngapType.PDUSessionResourceSetupItemSURes{} 39 | item.PDUSessionID.Value = pduSessionID 40 | item.PDUSessionResourceSetupResponseTransfer = transfer 41 | list.List = append(list.List, item) 42 | } 43 | 44 | func AppendPDUSessionResourceFailedToSetupListSURes( 45 | list *ngapType.PDUSessionResourceFailedToSetupListSURes, pduSessionID int64, transfer []byte, 46 | ) { 47 | item := ngapType.PDUSessionResourceFailedToSetupItemSURes{} 48 | item.PDUSessionID.Value = pduSessionID 49 | item.PDUSessionResourceSetupUnsuccessfulTransfer = transfer 50 | list.List = append(list.List, item) 51 | } 52 | 53 | func AppendPDUSessionResourceModifyListModRes( 54 | list *ngapType.PDUSessionResourceModifyListModRes, pduSessionID int64, transfer []byte, 55 | ) { 56 | var pduSessionResourceModifyResponseTransfer aper.OctetString = transfer 57 | item := ngapType.PDUSessionResourceModifyItemModRes{} 58 | item.PDUSessionID.Value = pduSessionID 59 | item.PDUSessionResourceModifyResponseTransfer = pduSessionResourceModifyResponseTransfer 60 | list.List = append(list.List, item) 61 | } 62 | 63 | func AppendPDUSessionResourceFailedToModifyListModRes( 64 | list *ngapType.PDUSessionResourceFailedToModifyListModRes, pduSessionID int64, transfer []byte, 65 | ) { 66 | item := ngapType.PDUSessionResourceFailedToModifyItemModRes{} 67 | item.PDUSessionID.Value = pduSessionID 68 | item.PDUSessionResourceModifyUnsuccessfulTransfer = transfer 69 | list.List = append(list.List, item) 70 | } 71 | -------------------------------------------------------------------------------- /internal/context/context_test.go: -------------------------------------------------------------------------------- 1 | package context_test 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "sync" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | 11 | n3iwf_context "github.com/free5gc/n3iwf/internal/context" 12 | "github.com/free5gc/n3iwf/pkg/factory" 13 | "github.com/free5gc/util/ippool" 14 | ) 15 | 16 | type n3iwfTestApp struct { 17 | cfg *factory.Config 18 | n3iwfCtx *n3iwf_context.N3IWFContext 19 | ctx context.Context 20 | cancel context.CancelFunc 21 | wg *sync.WaitGroup 22 | } 23 | 24 | func (a *n3iwfTestApp) Config() *factory.Config { 25 | return a.cfg 26 | } 27 | 28 | func (a *n3iwfTestApp) Context() *n3iwf_context.N3IWFContext { 29 | return a.n3iwfCtx 30 | } 31 | 32 | func (a *n3iwfTestApp) CancelContext() context.Context { 33 | return a.ctx 34 | } 35 | 36 | func NewN3iwfTestApp(cfg *factory.Config) (*n3iwfTestApp, error) { 37 | var err error 38 | ctx, cancel := context.WithCancel(context.Background()) 39 | 40 | n3iwfApp := &n3iwfTestApp{ 41 | cfg: cfg, 42 | ctx: ctx, 43 | cancel: cancel, 44 | wg: &sync.WaitGroup{}, 45 | } 46 | 47 | n3iwfApp.n3iwfCtx, err = n3iwf_context.NewTestContext(n3iwfApp) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return n3iwfApp, err 52 | } 53 | 54 | func NewTestCfg() *factory.Config { 55 | return &factory.Config{ 56 | Configuration: &factory.Configuration{ 57 | IPSecGatewayAddr: "10.0.0.1", 58 | UEIPAddressRange: "10.0.0.0/24", 59 | }, 60 | } 61 | } 62 | 63 | func TestNewInternalUEIPAddr(t *testing.T) { 64 | cfg := NewTestCfg() 65 | var app *n3iwfTestApp 66 | var err error 67 | var ip, invalidIP, invalidIP2 net.IP 68 | 69 | app, err = NewN3iwfTestApp(cfg) 70 | require.NoError(t, err) 71 | 72 | n3iwfCtx := app.n3iwfCtx 73 | 74 | invalidIP = net.ParseIP("10.0.0.0") 75 | invalidIP2 = net.ParseIP("10.0.0.255") 76 | n3iwfCtx.IPSecInnerIPPool, err = ippool.NewIPPool("10.0.0.0/24") 77 | require.NoError(t, err) 78 | 79 | for i := 1; i <= 253; i++ { 80 | ip, err = n3iwfCtx.NewIPsecInnerUEIP(&n3iwf_context.N3IWFIkeUe{}) 81 | require.NoError(t, err) 82 | require.NotEqual(t, cfg.GetIPSecGatewayAddr(), ip.String()) 83 | require.NotEqual(t, ip, invalidIP) 84 | require.NotEqual(t, ip, invalidIP2) 85 | } 86 | 87 | _, err = n3iwfCtx.NewIPsecInnerUEIP(&n3iwf_context.N3IWFIkeUe{}) 88 | require.Error(t, err) 89 | 90 | n3iwfCtx.AllocatedUEIPAddress = sync.Map{} 91 | 92 | n3iwfCtx.IPSecInnerIPPool, err = ippool.NewIPPool("10.0.0.0/16") 93 | require.NoError(t, err) 94 | 95 | invalidIP2 = net.ParseIP("10.0.255.255") 96 | for i := 1; i <= 65533; i++ { 97 | ip, err = n3iwfCtx.NewIPsecInnerUEIP(&n3iwf_context.N3IWFIkeUe{}) 98 | require.NoError(t, err) 99 | require.NotEqual(t, cfg.GetIPSecGatewayAddr(), ip.String()) 100 | require.NotEqual(t, ip, invalidIP) 101 | require.NotEqual(t, ip, invalidIP2) 102 | } 103 | 104 | _, err = n3iwfCtx.NewIPsecInnerUEIP(&n3iwf_context.N3IWFIkeUe{}) 105 | require.Error(t, err) 106 | } 107 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/free5gc/n3iwf 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d 7 | github.com/free5gc/aper v1.0.6-0.20250102035630-3ddc831eed6a 8 | github.com/free5gc/ike v1.1.1-0.20241014015325-083f89768f43 9 | github.com/free5gc/ngap v1.1.1 10 | github.com/free5gc/sctp v1.1.0 11 | github.com/free5gc/util v1.2.0 12 | github.com/gin-contrib/pprof v1.5.0 13 | github.com/gin-gonic/gin v1.10.0 14 | github.com/google/gopacket v1.1.19 15 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 16 | github.com/pkg/errors v0.9.1 17 | github.com/prometheus/client_golang v1.21.0 18 | github.com/sirupsen/logrus v1.9.3 19 | github.com/stretchr/testify v1.10.0 20 | github.com/urfave/cli/v2 v2.27.7 21 | github.com/vishvananda/netlink v1.1.0 22 | github.com/wmnsk/go-gtp v0.8.11-0.20240705144331-f53bfdd4233b 23 | golang.org/x/net v0.47.0 24 | golang.org/x/sys v0.38.0 25 | gopkg.in/yaml.v2 v2.4.0 26 | ) 27 | 28 | require ( 29 | github.com/beorn7/perks v1.0.1 // indirect 30 | github.com/bytedance/sonic v1.11.6 // indirect 31 | github.com/bytedance/sonic/loader v0.1.1 // indirect 32 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 33 | github.com/cloudwego/base64x v0.1.4 // indirect 34 | github.com/cloudwego/iasm v0.2.0 // indirect 35 | github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect 36 | github.com/davecgh/go-spew v1.1.1 // indirect 37 | github.com/free5gc/nas v1.2.1 // indirect 38 | github.com/free5gc/openapi v1.2.2 // indirect 39 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 40 | github.com/gin-contrib/sse v0.1.0 // indirect 41 | github.com/go-playground/locales v0.14.1 // indirect 42 | github.com/go-playground/universal-translator v0.18.1 // indirect 43 | github.com/go-playground/validator/v10 v10.20.0 // indirect 44 | github.com/goccy/go-json v0.10.2 // indirect 45 | github.com/golang-jwt/jwt/v5 v5.2.2 // indirect 46 | github.com/json-iterator/go v1.1.12 // indirect 47 | github.com/klauspost/compress v1.17.11 // indirect 48 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 49 | github.com/leodido/go-urn v1.4.0 // indirect 50 | github.com/mattn/go-isatty v0.0.20 // indirect 51 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 52 | github.com/modern-go/reflect2 v1.0.2 // indirect 53 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 54 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 55 | github.com/pmezard/go-difflib v1.0.0 // indirect 56 | github.com/prometheus/client_model v0.6.1 // indirect 57 | github.com/prometheus/common v0.62.0 // indirect 58 | github.com/prometheus/procfs v0.15.1 // indirect 59 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 60 | github.com/tim-ywliu/nested-logrus-formatter v1.3.2 // indirect 61 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 62 | github.com/ugorji/go/codec v1.2.12 // indirect 63 | github.com/vishvananda/netns v0.0.4 // indirect 64 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect 65 | golang.org/x/arch v0.8.0 // indirect 66 | golang.org/x/crypto v0.45.0 // indirect 67 | golang.org/x/text v0.31.0 // indirect 68 | google.golang.org/protobuf v1.36.1 // indirect 69 | gopkg.in/yaml.v3 v3.0.1 // indirect 70 | ) 71 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | concurrency: 4 4 | issues-exit-code: 1 5 | tests: true 6 | allow-parallel-runners: true 7 | linters: 8 | enable: 9 | - asciicheck 10 | - bodyclose 11 | - dogsled 12 | - godox 13 | - lll 14 | - misspell 15 | - nakedret 16 | - noctx 17 | - predeclared 18 | - unconvert 19 | - whitespace 20 | settings: 21 | staticcheck: 22 | checks: 23 | - all 24 | - -QF1008 25 | - -ST1000 26 | - -ST1003 27 | - -ST1016 28 | - -ST1020 29 | - -ST1021 30 | errcheck: 31 | check-type-assertions: false 32 | check-blank: true 33 | funlen: 34 | lines: 60 35 | statements: 40 36 | gocognit: 37 | min-complexity: 10 38 | goconst: 39 | min-len: 3 40 | min-occurrences: 3 41 | gocritic: 42 | disabled-checks: 43 | - regexpMust 44 | enabled-tags: 45 | - performance 46 | disabled-tags: 47 | - experimental 48 | settings: 49 | captLocal: 50 | paramsOnly: true 51 | rangeValCopy: 52 | sizeThreshold: 32 53 | gocyclo: 54 | min-complexity: 10 55 | godox: 56 | keywords: 57 | - FIXME 58 | - BUG 59 | - XXX 60 | govet: 61 | enable: 62 | - atomicalign 63 | disable: 64 | - shadow 65 | enable-all: false 66 | disable-all: false 67 | settings: 68 | printf: 69 | funcs: 70 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof 71 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf 72 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf 73 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf 74 | lll: 75 | line-length: 120 76 | tab-width: 1 77 | nakedret: 78 | max-func-lines: 30 79 | nestif: 80 | min-complexity: 4 81 | testpackage: 82 | skip-regexp: (export|internal)_test\.go 83 | whitespace: 84 | multi-if: false 85 | multi-func: false 86 | wsl: 87 | strict-append: true 88 | allow-assign-and-call: true 89 | allow-multiline-assign: true 90 | force-case-trailing-whitespace: 0 91 | allow-trailing-comment: true 92 | allow-separated-leading-comment: false 93 | allow-cuddle-declarations: false 94 | force-err-cuddling: false 95 | exclusions: 96 | generated: lax 97 | paths: 98 | - third_party$ 99 | - builtin$ 100 | - examples$ 101 | issues: 102 | new-from-rev: "" 103 | new: false 104 | severity: 105 | default: error 106 | rules: 107 | - linters: 108 | - mnd 109 | severity: ignore 110 | formatters: 111 | enable: 112 | - gci 113 | - gofmt 114 | - gofumpt 115 | settings: 116 | gci: 117 | sections: 118 | - standard 119 | - default 120 | - prefix(github.com/free5gc) 121 | gofmt: 122 | simplify: true 123 | goimports: 124 | local-prefixes: 125 | - github.com/org/project 126 | exclusions: 127 | generated: lax 128 | paths: 129 | - third_party$ 130 | - builtin$ 131 | - examples$ 132 | -------------------------------------------------------------------------------- /internal/ike/send.go: -------------------------------------------------------------------------------- 1 | package ike 2 | 3 | import ( 4 | "math" 5 | "net" 6 | 7 | "github.com/pkg/errors" 8 | 9 | "github.com/free5gc/ike" 10 | ike_message "github.com/free5gc/ike/message" 11 | "github.com/free5gc/ike/security" 12 | n3iwf_context "github.com/free5gc/n3iwf/internal/context" 13 | "github.com/free5gc/n3iwf/internal/logger" 14 | ) 15 | 16 | func SendIKEMessageToUE( 17 | udpConn *net.UDPConn, 18 | srcAddr, dstAddr *net.UDPAddr, 19 | message *ike_message.IKEMessage, 20 | ikeSAKey *security.IKESAKey, 21 | ) error { 22 | ikeLog := logger.IKELog 23 | ikeLog.Trace("Send IKE message to UE") 24 | ikeLog.Trace("Encoding...") 25 | pkt, err := ike.EncodeEncrypt(message, ikeSAKey, ike_message.Role_Responder) 26 | if err != nil { 27 | return errors.Wrapf(err, "SendIKEMessageToUE") 28 | } 29 | // As specified in RFC 7296 section 3.1, the IKE message send from/to UDP port 4500 30 | // should prepend a 4 bytes zero 31 | if srcAddr.Port == 4500 { 32 | prependZero := make([]byte, 4) 33 | pkt = append(prependZero, pkt...) 34 | } 35 | 36 | ikeLog.Trace("Sending...") 37 | n, err := udpConn.WriteToUDP(pkt, dstAddr) 38 | if err != nil { 39 | return errors.Wrapf(err, "SendIKEMessageToUE") 40 | } 41 | if n != len(pkt) { 42 | return errors.Errorf("SendIKEMessageToUE Not all of the data is sent. Total length: %d. Sent: %d.", 43 | len(pkt), n) 44 | } 45 | return nil 46 | } 47 | 48 | func SendUEInformationExchange( 49 | ikeSA *n3iwf_context.IKESecurityAssociation, 50 | ikeSAKey *security.IKESAKey, 51 | payload *ike_message.IKEPayloadContainer, initiator bool, 52 | response bool, messageID uint32, conn *net.UDPConn, 53 | ueAddr *net.UDPAddr, n3iwfAddr *net.UDPAddr, 54 | ) { 55 | ikeLog := logger.IKELog 56 | 57 | // Build IKE message 58 | responseIKEMessage := ike_message.NewMessage(ikeSA.RemoteSPI, ikeSA.LocalSPI, 59 | ike_message.INFORMATIONAL, response, initiator, messageID, nil) 60 | 61 | if payload != nil && len(*payload) > 0 { 62 | responseIKEMessage.Payloads = append(responseIKEMessage.Payloads, *payload...) 63 | } 64 | 65 | err := SendIKEMessageToUE(conn, n3iwfAddr, ueAddr, responseIKEMessage, ikeSAKey) 66 | if err != nil { 67 | ikeLog.Errorf("SendUEInformationExchange err: %+v", err) 68 | return 69 | } 70 | } 71 | 72 | func SendIKEDeleteRequest(n3iwfCtx *n3iwf_context.N3IWFContext, localSPI uint64) { 73 | ikeLog := logger.IKELog 74 | ikeUe, ok := n3iwfCtx.IkeUePoolLoad(localSPI) 75 | if !ok { 76 | ikeLog.Errorf("Cannot get IkeUE from SPI : %+v", localSPI) 77 | return 78 | } 79 | 80 | var deletePayload ike_message.IKEPayloadContainer 81 | deletePayload.BuildDeletePayload(ike_message.TypeIKE, 0, 0, nil) 82 | SendUEInformationExchange(ikeUe.N3IWFIKESecurityAssociation, ikeUe.N3IWFIKESecurityAssociation.IKESAKey, 83 | &deletePayload, false, false, ikeUe.N3IWFIKESecurityAssociation.ResponderMessageID, 84 | ikeUe.IKEConnection.Conn, ikeUe.IKEConnection.UEAddr, ikeUe.IKEConnection.N3IWFAddr) 85 | } 86 | 87 | func SendChildSADeleteRequest( 88 | ikeUe *n3iwf_context.N3IWFIkeUe, 89 | relaseList []int64, 90 | ) { 91 | ikeLog := logger.IKELog 92 | var deleteSPIs []uint32 93 | spiLen := uint16(0) 94 | for _, releaseItem := range relaseList { 95 | for _, childSA := range ikeUe.N3IWFChildSecurityAssociation { 96 | if childSA.PDUSessionIds[0] == releaseItem { 97 | spi := childSA.XfrmStateList[0].Spi 98 | if spi < 0 || spi > math.MaxUint32 { 99 | ikeLog.Errorf("SendChildSADeleteRequest spi out of uint32 range : %d", spi) 100 | return 101 | } 102 | deleteSPIs = append(deleteSPIs, uint32(spi)) 103 | spiLen += 1 104 | err := ikeUe.DeleteChildSA(childSA) 105 | if err != nil { 106 | ikeLog.Errorf("Delete Child SA error : %v", err) 107 | return 108 | } 109 | } 110 | } 111 | } 112 | 113 | var deletePayload ike_message.IKEPayloadContainer 114 | deletePayload.BuildDeletePayload(ike_message.TypeESP, 4, spiLen, deleteSPIs) 115 | SendUEInformationExchange(ikeUe.N3IWFIKESecurityAssociation, ikeUe.N3IWFIKESecurityAssociation.IKESAKey, 116 | &deletePayload, false, false, ikeUe.N3IWFIKESecurityAssociation.ResponderMessageID, 117 | ikeUe.IKEConnection.Conn, ikeUe.IKEConnection.UEAddr, ikeUe.IKEConnection.N3IWFAddr) 118 | } 119 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | "path/filepath" 8 | "runtime/debug" 9 | "syscall" 10 | 11 | "github.com/gin-contrib/pprof" 12 | "github.com/gin-gonic/gin" 13 | "github.com/urfave/cli/v2" 14 | 15 | "github.com/free5gc/n3iwf/internal/logger" 16 | "github.com/free5gc/n3iwf/pkg/factory" 17 | "github.com/free5gc/n3iwf/pkg/service" 18 | logger_util "github.com/free5gc/util/logger" 19 | "github.com/free5gc/util/version" 20 | ) 21 | 22 | func main() { 23 | defer func() { 24 | if p := recover(); p != nil { 25 | // Print stack for panic to log. Fatalf() will let program exit. 26 | logger.MainLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 27 | } 28 | }() 29 | 30 | app := cli.NewApp() 31 | app.Name = "n3iwf" 32 | app.Usage = "Non-3GPP Interworking Function (N3IWF)" 33 | app.Action = action 34 | app.Flags = []cli.Flag{ 35 | &cli.StringFlag{ 36 | Name: "config", 37 | Aliases: []string{"c"}, 38 | Usage: "Load configuration from `FILE`", 39 | }, 40 | &cli.StringSliceFlag{ 41 | Name: "log", 42 | Aliases: []string{"l"}, 43 | Usage: "Output NF log to `FILE`", 44 | }, 45 | &cli.BoolFlag{ 46 | Name: "nolog", 47 | Aliases: []string{"nl"}, 48 | Usage: "Disable log to stdout/stderr", 49 | }, 50 | &cli.StringFlag{ 51 | Name: "loglevel", 52 | Aliases: []string{"ll"}, 53 | Usage: "Override logger level", 54 | }, 55 | &cli.BoolFlag{ 56 | Name: "reportcaller", 57 | Aliases: []string{"rc"}, 58 | Usage: "Enable logger report caller", 59 | }, 60 | &cli.BoolFlag{ 61 | Name: "debug", 62 | Aliases: []string{"deb"}, 63 | Usage: "Enable pprof debug", 64 | }, 65 | } 66 | if err := app.Run(os.Args); err != nil { 67 | logger.MainLog.Errorf("N3IWF Run Error: %v\n", err) 68 | } 69 | } 70 | 71 | func runPProfServer() { 72 | r := gin.Default() 73 | pprof.Register(r) 74 | // Listen and Server in 0.0.0.0:6061 75 | err := r.Run(":6061") 76 | if err != nil { 77 | logger.MainLog.Errorf("runPProfServer(): %v", err) 78 | } 79 | } 80 | 81 | func action(cliCtx *cli.Context) error { 82 | debug := cliCtx.Bool("debug") 83 | if debug { 84 | go runPProfServer() 85 | } 86 | logPathSlice := cliCtx.StringSlice("log") 87 | cfgPath := cliCtx.String("config") 88 | noLog := cliCtx.Bool("nolog") 89 | logLevel := cliCtx.String("loglevel") 90 | reportCaller := cliCtx.Bool("reportcaller") 91 | 92 | tlsKeyLogPath, err := initLogFile(logPathSlice) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | logger.MainLog.Infoln("N3IWF version: ", version.GetVersion()) 98 | 99 | ctx, cancel := context.WithCancel(context.Background()) 100 | sigCh := make(chan os.Signal, 1) 101 | signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) 102 | 103 | go func() { 104 | <-sigCh // Wait for interrupt signal to gracefully shutdown 105 | cancel() // Notify each goroutine and wait them stopped 106 | }() 107 | 108 | cfg, err := factory.ReadConfig(cfgPath) 109 | if err != nil { 110 | close(sigCh) 111 | return err 112 | } 113 | factory.N3iwfConfig = cfg 114 | 115 | // Replace logger config with cli parameters 116 | if noLog { 117 | cfg.SetLogEnable(false) 118 | } 119 | if logLevel != "" { 120 | cfg.SetLogLevel(logLevel) 121 | } 122 | if reportCaller { 123 | cfg.SetLogReportCaller(true) 124 | } 125 | 126 | n3iwfApp, err := service.NewApp(ctx, cfg, tlsKeyLogPath) 127 | if err != nil { 128 | close(sigCh) 129 | return err 130 | } 131 | 132 | n3iwfApp.Start() 133 | 134 | return nil 135 | } 136 | 137 | func initLogFile(logNfPath []string) (string, error) { 138 | logTlsKeyPath := "" 139 | 140 | for _, path := range logNfPath { 141 | if err := logger_util.LogFileHook(logger.Log, path); err != nil { 142 | return "", err 143 | } 144 | 145 | if logTlsKeyPath != "" { 146 | continue 147 | } 148 | 149 | nfDir, _ := filepath.Split(path) 150 | tmpDir := filepath.Join(nfDir, "key") 151 | if err := os.MkdirAll(tmpDir, 0o775); err != nil { 152 | logger.InitLog.Errorf("Make directory %s failed: %+v", tmpDir, err) 153 | return "", err 154 | } 155 | _, name := filepath.Split(factory.N3iwfDefaultTLSKeyLogPath) 156 | logTlsKeyPath = filepath.Join(tmpDir, name) 157 | } 158 | 159 | return logTlsKeyPath, nil 160 | } 161 | -------------------------------------------------------------------------------- /internal/ngap/dispatcher.go: -------------------------------------------------------------------------------- 1 | package ngap 2 | 3 | import ( 4 | "runtime/debug" 5 | 6 | "github.com/free5gc/n3iwf/internal/logger" 7 | "github.com/free5gc/ngap" 8 | "github.com/free5gc/ngap/ngapType" 9 | "github.com/free5gc/sctp" 10 | ) 11 | 12 | func (s *Server) NGAPDispatch(conn *sctp.SCTPConn, msg []byte) { 13 | ngapLog := logger.NgapLog 14 | 15 | defer func() { 16 | if p := recover(); p != nil { 17 | // Print stack for panic to log. Fatalf() will let program exit. 18 | ngapLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 19 | } 20 | }() 21 | 22 | // AMF SCTP address 23 | sctpAddr := conn.RemoteAddr().String() 24 | // AMF context 25 | n3iwfCtx := s.Context() 26 | amf, _ := n3iwfCtx.AMFPoolLoad(sctpAddr) 27 | // Decode 28 | pdu, err := ngap.Decoder(msg) 29 | if err != nil { 30 | ngapLog.Errorf("NGAP decode error: %+v\n", err) 31 | return 32 | } 33 | 34 | switch pdu.Present { 35 | case ngapType.NGAPPDUPresentInitiatingMessage: 36 | initiatingMessage := pdu.InitiatingMessage 37 | if initiatingMessage == nil { 38 | ngapLog.Errorln("Initiating Message is nil") 39 | return 40 | } 41 | 42 | switch initiatingMessage.ProcedureCode.Value { 43 | case ngapType.ProcedureCodeNGReset: 44 | s.HandleNGReset(amf, pdu) 45 | case ngapType.ProcedureCodeInitialContextSetup: 46 | s.HandleInitialContextSetupRequest(amf, pdu) 47 | case ngapType.ProcedureCodeUEContextModification: 48 | s.HandleUEContextModificationRequest(amf, pdu) 49 | case ngapType.ProcedureCodeUEContextRelease: 50 | s.HandleUEContextReleaseCommand(amf, pdu) 51 | case ngapType.ProcedureCodeDownlinkNASTransport: 52 | s.HandleDownlinkNASTransport(amf, pdu) 53 | case ngapType.ProcedureCodePDUSessionResourceSetup: 54 | s.HandlePDUSessionResourceSetupRequest(amf, pdu) 55 | case ngapType.ProcedureCodePDUSessionResourceModify: 56 | s.HandlePDUSessionResourceModifyRequest(amf, pdu) 57 | case ngapType.ProcedureCodePDUSessionResourceRelease: 58 | s.HandlePDUSessionResourceReleaseCommand(amf, pdu) 59 | case ngapType.ProcedureCodeErrorIndication: 60 | s.HandleErrorIndication(amf, pdu) 61 | case ngapType.ProcedureCodeUERadioCapabilityCheck: 62 | s.HandleUERadioCapabilityCheckRequest(amf, pdu) 63 | case ngapType.ProcedureCodeAMFConfigurationUpdate: 64 | s.HandleAMFConfigurationUpdate(amf, pdu) 65 | case ngapType.ProcedureCodeDownlinkRANConfigurationTransfer: 66 | s.HandleDownlinkRANConfigurationTransfer(pdu) 67 | case ngapType.ProcedureCodeDownlinkRANStatusTransfer: 68 | s.HandleDownlinkRANStatusTransfer(pdu) 69 | case ngapType.ProcedureCodeAMFStatusIndication: 70 | s.HandleAMFStatusIndication(pdu) 71 | case ngapType.ProcedureCodeLocationReportingControl: 72 | s.HandleLocationReportingControl(pdu) 73 | case ngapType.ProcedureCodeUETNLABindingRelease: 74 | s.HandleUETNLAReleaseRequest(pdu) 75 | case ngapType.ProcedureCodeOverloadStart: 76 | s.HandleOverloadStart(amf, pdu) 77 | case ngapType.ProcedureCodeOverloadStop: 78 | s.HandleOverloadStop(amf, pdu) 79 | default: 80 | ngapLog.Warnf("Not implemented NGAP message(initiatingMessage), procedureCode:%d]\n", 81 | initiatingMessage.ProcedureCode.Value) 82 | } 83 | case ngapType.NGAPPDUPresentSuccessfulOutcome: 84 | successfulOutcome := pdu.SuccessfulOutcome 85 | if successfulOutcome == nil { 86 | ngapLog.Errorln("Successful Outcome is nil") 87 | return 88 | } 89 | 90 | switch successfulOutcome.ProcedureCode.Value { 91 | case ngapType.ProcedureCodeNGSetup: 92 | s.HandleNGSetupResponse(sctpAddr, conn, pdu) 93 | case ngapType.ProcedureCodeNGReset: 94 | s.HandleNGResetAcknowledge(amf, pdu) 95 | case ngapType.ProcedureCodePDUSessionResourceModifyIndication: 96 | s.HandlePDUSessionResourceModifyConfirm(amf, pdu) 97 | case ngapType.ProcedureCodeRANConfigurationUpdate: 98 | s.HandleRANConfigurationUpdateAcknowledge(amf, pdu) 99 | default: 100 | ngapLog.Warnf("Not implemented NGAP message(successfulOutcome), procedureCode:%d]\n", 101 | successfulOutcome.ProcedureCode.Value) 102 | } 103 | case ngapType.NGAPPDUPresentUnsuccessfulOutcome: 104 | unsuccessfulOutcome := pdu.UnsuccessfulOutcome 105 | if unsuccessfulOutcome == nil { 106 | ngapLog.Errorln("Unsuccessful Outcome is nil") 107 | return 108 | } 109 | 110 | switch unsuccessfulOutcome.ProcedureCode.Value { 111 | case ngapType.ProcedureCodeNGSetup: 112 | s.HandleNGSetupFailure(sctpAddr, conn, pdu) 113 | case ngapType.ProcedureCodeRANConfigurationUpdate: 114 | s.HandleRANConfigurationUpdateFailure(amf, pdu) 115 | default: 116 | ngapLog.Warnf("Not implemented NGAP message(unsuccessfulOutcome), procedureCode:%d]\n", 117 | unsuccessfulOutcome.ProcedureCode.Value) 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /internal/context/ike.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | type IkeEventType int64 4 | 5 | // IKE Event type 6 | const ( 7 | UnmarshalEAP5GDataResponse IkeEventType = iota 8 | SendEAP5GFailureMsg 9 | SendEAPNASMsg 10 | SendEAPSuccessMsg 11 | CreatePDUSession 12 | IKEDeleteRequest 13 | SendChildSADeleteRequest 14 | IKEContextUpdate 15 | GetNGAPContextResponse 16 | ) 17 | 18 | type IkeEvt interface { 19 | Type() IkeEventType 20 | } 21 | 22 | type UnmarshalEAP5GDataResponseEvt struct { 23 | LocalSPI uint64 24 | RanUeNgapId int64 25 | NasPDU []byte 26 | } 27 | 28 | func (unmarshalEAP5GDataResponseEvt *UnmarshalEAP5GDataResponseEvt) Type() IkeEventType { 29 | return UnmarshalEAP5GDataResponse 30 | } 31 | 32 | func NewUnmarshalEAP5GDataResponseEvt(localSPI uint64, ranUeNgapId int64, nasPDU []byte, 33 | ) *UnmarshalEAP5GDataResponseEvt { 34 | return &UnmarshalEAP5GDataResponseEvt{ 35 | LocalSPI: localSPI, 36 | RanUeNgapId: ranUeNgapId, 37 | NasPDU: nasPDU, 38 | } 39 | } 40 | 41 | type SendEAP5GFailureMsgEvt struct { 42 | LocalSPI uint64 43 | ErrMsg EvtError 44 | } 45 | 46 | func (sendEAP5GFailureMsgEvt *SendEAP5GFailureMsgEvt) Type() IkeEventType { 47 | return SendEAP5GFailureMsg 48 | } 49 | 50 | func NewSendEAP5GFailureMsgEvt(localSPI uint64, errMsg EvtError, 51 | ) *SendEAP5GFailureMsgEvt { 52 | return &SendEAP5GFailureMsgEvt{ 53 | LocalSPI: localSPI, 54 | ErrMsg: errMsg, 55 | } 56 | } 57 | 58 | type SendEAPNASMsgEvt struct { 59 | LocalSPI uint64 60 | NasPDU []byte 61 | } 62 | 63 | func (sendEAPNASMsgEvt *SendEAPNASMsgEvt) Type() IkeEventType { 64 | return SendEAPNASMsg 65 | } 66 | 67 | func NewSendEAPNASMsgEvt(localSPI uint64, nasPDU []byte, 68 | ) *SendEAPNASMsgEvt { 69 | return &SendEAPNASMsgEvt{ 70 | LocalSPI: localSPI, 71 | NasPDU: nasPDU, 72 | } 73 | } 74 | 75 | type SendEAPSuccessMsgEvt struct { 76 | LocalSPI uint64 77 | Kn3iwf []byte 78 | PduSessionListLen int 79 | } 80 | 81 | func (SendEAPSuccessMsgEvt *SendEAPSuccessMsgEvt) Type() IkeEventType { 82 | return SendEAPSuccessMsg 83 | } 84 | 85 | func NewSendEAPSuccessMsgEvt(localSPI uint64, kn3iwf []byte, pduSessionListLen int, 86 | ) *SendEAPSuccessMsgEvt { 87 | return &SendEAPSuccessMsgEvt{ 88 | LocalSPI: localSPI, 89 | Kn3iwf: kn3iwf, 90 | PduSessionListLen: pduSessionListLen, 91 | } 92 | } 93 | 94 | type CreatePDUSessionEvt struct { 95 | LocalSPI uint64 96 | PduSessionListLen int 97 | TempPDUSessionSetupData *PDUSessionSetupTemporaryData 98 | } 99 | 100 | func (createPDUSessionEvt *CreatePDUSessionEvt) Type() IkeEventType { 101 | return CreatePDUSession 102 | } 103 | 104 | func NewCreatePDUSessionEvt(localSPI uint64, pduSessionListLen int, 105 | tempPDUSessionSetupData *PDUSessionSetupTemporaryData, 106 | ) *CreatePDUSessionEvt { 107 | return &CreatePDUSessionEvt{ 108 | LocalSPI: localSPI, 109 | PduSessionListLen: pduSessionListLen, 110 | TempPDUSessionSetupData: tempPDUSessionSetupData, 111 | } 112 | } 113 | 114 | type IKEDeleteRequestEvt struct { 115 | LocalSPI uint64 116 | } 117 | 118 | func (ikeDeleteRequestEvt *IKEDeleteRequestEvt) Type() IkeEventType { 119 | return IKEDeleteRequest 120 | } 121 | 122 | func NewIKEDeleteRequestEvt(localSPI uint64, 123 | ) *IKEDeleteRequestEvt { 124 | return &IKEDeleteRequestEvt{ 125 | LocalSPI: localSPI, 126 | } 127 | } 128 | 129 | type SendChildSADeleteRequestEvt struct { 130 | LocalSPI uint64 131 | ReleaseIdList []int64 132 | } 133 | 134 | func (sendChildSADeleteRequestEvt *SendChildSADeleteRequestEvt) Type() IkeEventType { 135 | return SendChildSADeleteRequest 136 | } 137 | 138 | func NewSendChildSADeleteRequestEvt(localSPI uint64, releaseIdList []int64, 139 | ) *SendChildSADeleteRequestEvt { 140 | return &SendChildSADeleteRequestEvt{ 141 | LocalSPI: localSPI, 142 | ReleaseIdList: releaseIdList, 143 | } 144 | } 145 | 146 | type IKEContextUpdateEvt struct { 147 | LocalSPI uint64 148 | Kn3iwf []byte 149 | } 150 | 151 | func (ikeContextUpdateEvt *IKEContextUpdateEvt) Type() IkeEventType { 152 | return IKEContextUpdate 153 | } 154 | 155 | func NewIKEContextUpdateEvt(localSPI uint64, kn3iwf []byte, 156 | ) *IKEContextUpdateEvt { 157 | return &IKEContextUpdateEvt{ 158 | LocalSPI: localSPI, 159 | Kn3iwf: kn3iwf, 160 | } 161 | } 162 | 163 | type GetNGAPContextRepEvt struct { 164 | LocalSPI uint64 165 | NgapCxtReqNumlist []int64 166 | NgapCxt []interface{} 167 | } 168 | 169 | func (getNGAPContextRepEvt *GetNGAPContextRepEvt) Type() IkeEventType { 170 | return GetNGAPContextResponse 171 | } 172 | 173 | func NewGetNGAPContextRepEvt(localSPI uint64, ngapCxtReqNumlist []int64, 174 | ngapCxt []interface{}, 175 | ) *GetNGAPContextRepEvt { 176 | return &GetNGAPContextRepEvt{ 177 | LocalSPI: localSPI, 178 | NgapCxtReqNumlist: ngapCxtReqNumlist, 179 | NgapCxt: ngapCxt, 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /internal/context/amf.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/free5gc/aper" 8 | "github.com/free5gc/ngap/ngapConvert" 9 | "github.com/free5gc/ngap/ngapType" 10 | "github.com/free5gc/sctp" 11 | ) 12 | 13 | type N3IWFAMF struct { 14 | SCTPAddr string 15 | SCTPConn *sctp.SCTPConn 16 | AMFName *ngapType.AMFName 17 | ServedGUAMIList *ngapType.ServedGUAMIList 18 | RelativeAMFCapacity *ngapType.RelativeAMFCapacity 19 | PLMNSupportList *ngapType.PLMNSupportList 20 | AMFTNLAssociationList map[string]*AMFTNLAssociationItem // v4+v6 as key 21 | // Overload related 22 | AMFOverloadContent *AMFOverloadContent 23 | // Relative Context 24 | N3iwfRanUeList map[int64]RanUe // ranUeNgapId as key 25 | } 26 | 27 | type AMFTNLAssociationItem struct { 28 | Ipv4 string 29 | Ipv6 string 30 | TNLAssociationUsage *ngapType.TNLAssociationUsage 31 | TNLAddressWeightFactor *int64 32 | } 33 | 34 | type AMFOverloadContent struct { 35 | Action *ngapType.OverloadAction 36 | TrafficInd *int64 37 | NSSAIList []SliceOverloadItem 38 | } 39 | 40 | type SliceOverloadItem struct { 41 | SNssaiList []ngapType.SNSSAI 42 | Action *ngapType.OverloadAction 43 | TrafficInd *int64 44 | } 45 | 46 | func (amf *N3IWFAMF) init(sctpAddr string, conn *sctp.SCTPConn) { 47 | amf.SCTPAddr = sctpAddr 48 | amf.SCTPConn = conn 49 | amf.AMFTNLAssociationList = make(map[string]*AMFTNLAssociationItem) 50 | amf.N3iwfRanUeList = make(map[int64]RanUe) 51 | } 52 | 53 | func (amf *N3IWFAMF) FindUeByAmfUeNgapID(id int64) RanUe { 54 | for _, ranUe := range amf.N3iwfRanUeList { 55 | if ranUe.GetSharedCtx().AmfUeNgapId == id { 56 | return ranUe 57 | } 58 | } 59 | return nil 60 | } 61 | 62 | func (amf *N3IWFAMF) RemoveAllRelatedUe() error { 63 | for _, ranUe := range amf.N3iwfRanUeList { 64 | if err := ranUe.Remove(); err != nil { 65 | return fmt.Errorf("RemoveAllRelatedUe error : %+v", err) 66 | } 67 | } 68 | return nil 69 | } 70 | 71 | func (amf *N3IWFAMF) AddAMFTNLAssociationItem(info ngapType.CPTransportLayerInformation) *AMFTNLAssociationItem { 72 | item := &AMFTNLAssociationItem{} 73 | item.Ipv4, item.Ipv6 = ngapConvert.IPAddressToString(*info.EndpointIPAddress) 74 | amf.AMFTNLAssociationList[item.Ipv4+item.Ipv6] = item 75 | return item 76 | } 77 | 78 | func (amf *N3IWFAMF) FindAMFTNLAssociationItem(info ngapType.CPTransportLayerInformation) *AMFTNLAssociationItem { 79 | v4, v6 := ngapConvert.IPAddressToString(*info.EndpointIPAddress) 80 | return amf.AMFTNLAssociationList[v4+v6] 81 | } 82 | 83 | func (amf *N3IWFAMF) DeleteAMFTNLAssociationItem(info ngapType.CPTransportLayerInformation) { 84 | v4, v6 := ngapConvert.IPAddressToString(*info.EndpointIPAddress) 85 | delete(amf.AMFTNLAssociationList, v4+v6) 86 | } 87 | 88 | func (amf *N3IWFAMF) StartOverload( 89 | resp *ngapType.OverloadResponse, trafloadInd *ngapType.TrafficLoadReductionIndication, 90 | nssai *ngapType.OverloadStartNSSAIList, 91 | ) *AMFOverloadContent { 92 | if resp == nil && trafloadInd == nil && nssai == nil { 93 | return nil 94 | } 95 | content := AMFOverloadContent{} 96 | if resp != nil { 97 | content.Action = resp.OverloadAction 98 | } 99 | if trafloadInd != nil { 100 | content.TrafficInd = &trafloadInd.Value 101 | } 102 | if nssai != nil { 103 | for _, item := range nssai.List { 104 | sliceItem := SliceOverloadItem{} 105 | for _, item2 := range item.SliceOverloadList.List { 106 | sliceItem.SNssaiList = append(sliceItem.SNssaiList, item2.SNSSAI) 107 | } 108 | if item.SliceOverloadResponse != nil { 109 | sliceItem.Action = item.SliceOverloadResponse.OverloadAction 110 | } 111 | if item.SliceTrafficLoadReductionIndication != nil { 112 | sliceItem.TrafficInd = &item.SliceTrafficLoadReductionIndication.Value 113 | } 114 | content.NSSAIList = append(content.NSSAIList, sliceItem) 115 | } 116 | } 117 | amf.AMFOverloadContent = &content 118 | return amf.AMFOverloadContent 119 | } 120 | 121 | func (amf *N3IWFAMF) StopOverload() { 122 | amf.AMFOverloadContent = nil 123 | } 124 | 125 | // FindAvalibleAMFByCompareGUAMI compares the incoming GUAMI with AMF served GUAMI 126 | // and return if this AMF is avalible for UE 127 | func (amf *N3IWFAMF) FindAvalibleAMFByCompareGUAMI(ueSpecifiedGUAMI *ngapType.GUAMI) bool { 128 | for _, amfServedGUAMI := range amf.ServedGUAMIList.List { 129 | codedAMFServedGUAMI, err := aper.MarshalWithParams(&amfServedGUAMI.GUAMI, "valueExt") 130 | if err != nil { 131 | return false 132 | } 133 | codedUESpecifiedGUAMI, err := aper.MarshalWithParams(ueSpecifiedGUAMI, "valueExt") 134 | if err != nil { 135 | return false 136 | } 137 | if !bytes.Equal(codedAMFServedGUAMI, codedUESpecifiedGUAMI) { 138 | continue 139 | } 140 | return true 141 | } 142 | return false 143 | } 144 | 145 | func (amf *N3IWFAMF) FindAvalibleAMFByCompareSelectedPLMNId(ueSpecifiedSelectedPLMNId *ngapType.PLMNIdentity) bool { 146 | for _, amfServedPLMNId := range amf.PLMNSupportList.List { 147 | if !bytes.Equal(amfServedPLMNId.PLMNIdentity.Value, ueSpecifiedSelectedPLMNId.Value) { 148 | continue 149 | } 150 | return true 151 | } 152 | return false 153 | } 154 | -------------------------------------------------------------------------------- /internal/context/ranue.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/free5gc/ngap/ngapType" 8 | ) 9 | 10 | type UeCtxRelState bool 11 | 12 | const ( 13 | // NGAP has already received UE Context release command 14 | UeCtxRelStateNone UeCtxRelState = false 15 | UeCtxRelStateOngoing UeCtxRelState = true 16 | ) 17 | 18 | type PduSessResRelState bool 19 | 20 | const ( 21 | // NGAP has not received Pdu Session resouces release request 22 | PduSessResRelStateNone PduSessResRelState = false 23 | PduSessResRelStateOngoing PduSessResRelState = true 24 | ) 25 | 26 | type RanUe interface { 27 | // Get Attributes 28 | GetUserLocationInformation() *ngapType.UserLocationInformation 29 | GetSharedCtx() *RanUeSharedCtx 30 | 31 | // User Plane Traffic 32 | // ForwardDL(gtpQoSMsg.QoSTPDUPacket) 33 | // ForwardUL() 34 | 35 | // Others 36 | CreatePDUSession(int64, ngapType.SNSSAI) (*PDUSession, error) 37 | DeletePDUSession(int64) 38 | FindPDUSession(int64) *PDUSession 39 | Remove() error 40 | } 41 | 42 | type RanUeSharedCtx struct { 43 | // UE identity 44 | RanUeNgapId int64 45 | AmfUeNgapId int64 46 | IPAddrv4 string 47 | IPAddrv6 string 48 | PortNumber int32 49 | MaskedIMEISV *ngapType.MaskedIMEISV // TS 38.413 9.3.1.54 50 | Guti string 51 | 52 | // Relative Context 53 | N3iwfCtx *N3IWFContext 54 | AMF *N3IWFAMF 55 | 56 | // Security 57 | SecurityCapabilities *ngapType.UESecurityCapabilities // TS 38.413 9.3.1.86 58 | 59 | // PDU Session 60 | PduSessionList map[int64]*PDUSession // pduSessionId as key 61 | 62 | // PDU Session Setup Temporary Data 63 | TemporaryPDUSessionSetupData *PDUSessionSetupTemporaryData 64 | 65 | // Others 66 | Guami *ngapType.GUAMI 67 | IndexToRfsp int64 68 | Ambr *ngapType.UEAggregateMaximumBitRate 69 | AllowedNssai *ngapType.AllowedNSSAI 70 | RadioCapability *ngapType.UERadioCapability // TODO: This is for RRC, can be deleted 71 | CoreNetworkAssistanceInformation *ngapType.CoreNetworkAssistanceInformation // TS 38.413 9.3.1.15 72 | IMSVoiceSupported int32 73 | RRCEstablishmentCause int16 74 | PduSessionReleaseList ngapType.PDUSessionResourceReleasedListRelRes 75 | UeCtxRelState UeCtxRelState 76 | PduSessResRelState PduSessResRelState 77 | } 78 | 79 | type PDUSession struct { 80 | Id int64 // PDU Session ID 81 | Type *ngapType.PDUSessionType 82 | Ambr *ngapType.PDUSessionAggregateMaximumBitRate 83 | Snssai ngapType.SNSSAI 84 | NetworkInstance *ngapType.NetworkInstance 85 | SecurityCipher bool 86 | SecurityIntegrity bool 87 | MaximumIntegrityDataRateUplink *ngapType.MaximumIntegrityProtectedDataRate 88 | MaximumIntegrityDataRateDownlink *ngapType.MaximumIntegrityProtectedDataRate 89 | GTPConnInfo *GTPConnectionInfo 90 | QFIList []uint8 91 | QosFlows map[int64]*QosFlow // QosFlowIdentifier as key 92 | } 93 | 94 | type QosFlow struct { 95 | Identifier int64 96 | Parameters ngapType.QosFlowLevelQosParameters 97 | } 98 | 99 | type GTPConnectionInfo struct { 100 | UPFIPAddr string 101 | UPFUDPAddr net.Addr 102 | IncomingTEID uint32 103 | OutgoingTEID uint32 104 | } 105 | 106 | type PDUSessionSetupTemporaryData struct { 107 | // Slice of unactivated PDU session 108 | UnactivatedPDUSession []*PDUSession // PDUSession as content 109 | // NGAPProcedureCode is used to identify which type of 110 | // response shall be used 111 | NGAPProcedureCode ngapType.ProcedureCode 112 | // PDU session setup list response 113 | SetupListCxtRes *ngapType.PDUSessionResourceSetupListCxtRes 114 | FailedListCxtRes *ngapType.PDUSessionResourceFailedToSetupListCxtRes 115 | SetupListSURes *ngapType.PDUSessionResourceSetupListSURes 116 | FailedListSURes *ngapType.PDUSessionResourceFailedToSetupListSURes 117 | // List of Error for failed setup PDUSessionID 118 | FailedErrStr []EvtError // Error string as content 119 | // Current Index of UnactivatedPDUSession 120 | Index int 121 | } 122 | 123 | func (ranUe *RanUeSharedCtx) GetSharedCtx() *RanUeSharedCtx { 124 | return ranUe 125 | } 126 | 127 | func (ranUe *RanUeSharedCtx) FindPDUSession(pduSessionID int64) *PDUSession { 128 | if pduSession, ok := ranUe.PduSessionList[pduSessionID]; ok { 129 | return pduSession 130 | } else { 131 | return nil 132 | } 133 | } 134 | 135 | func (ranUe *RanUeSharedCtx) CreatePDUSession(pduSessionID int64, snssai ngapType.SNSSAI) (*PDUSession, error) { 136 | if _, exists := ranUe.PduSessionList[pduSessionID]; exists { 137 | return nil, fmt.Errorf("PDU Session[ID:%d] is already exists", pduSessionID) 138 | } 139 | pduSession := &PDUSession{ 140 | Id: pduSessionID, 141 | Snssai: snssai, 142 | QosFlows: make(map[int64]*QosFlow), 143 | } 144 | ranUe.PduSessionList[pduSessionID] = pduSession 145 | return pduSession, nil 146 | } 147 | 148 | func (ranUe *RanUeSharedCtx) DeletePDUSession(pduSessionId int64) { 149 | delete(ranUe.PduSessionList, pduSessionId) 150 | } 151 | -------------------------------------------------------------------------------- /internal/nwucp/server.go: -------------------------------------------------------------------------------- 1 | package nwucp 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "encoding/binary" 7 | "io" 8 | "net" 9 | "runtime/debug" 10 | "strings" 11 | "sync" 12 | 13 | n3iwf_context "github.com/free5gc/n3iwf/internal/context" 14 | "github.com/free5gc/n3iwf/internal/logger" 15 | "github.com/free5gc/n3iwf/internal/ngap/message" 16 | "github.com/free5gc/n3iwf/pkg/factory" 17 | ) 18 | 19 | type n3iwf interface { 20 | Config() *factory.Config 21 | Context() *n3iwf_context.N3IWFContext 22 | CancelContext() context.Context 23 | 24 | SendNgapEvt(n3iwf_context.NgapEvt) 25 | } 26 | 27 | type Server struct { 28 | n3iwf 29 | 30 | tcpListener net.Listener 31 | } 32 | 33 | func NewServer(n3iwf n3iwf) (*Server, error) { 34 | s := &Server{ 35 | n3iwf: n3iwf, 36 | } 37 | return s, nil 38 | } 39 | 40 | // Run setup N3IWF NAS for UE to forward NAS message 41 | // to AMF 42 | func (s *Server) Run(wg *sync.WaitGroup) error { 43 | cfg := s.Config() 44 | listener, err := net.Listen("tcp", cfg.GetNasTcpAddr()) 45 | if err != nil { 46 | return err 47 | } 48 | s.tcpListener = listener 49 | 50 | wg.Add(1) 51 | go s.listenAndServe(wg) 52 | 53 | return nil 54 | } 55 | 56 | // listenAndServe handle TCP listener and accept incoming 57 | // requests. It also stores accepted connection into UE 58 | // context, and finally, call serveConn() to serve the messages 59 | // received from the connection. 60 | func (s *Server) listenAndServe(wg *sync.WaitGroup) { 61 | nwucpLog := logger.NWuCPLog 62 | defer func() { 63 | if p := recover(); p != nil { 64 | // Print stack for panic to log. Fatalf() will let program exit. 65 | nwucpLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 66 | } 67 | 68 | err := s.tcpListener.Close() 69 | if err != nil { 70 | nwucpLog.Errorf("Error closing tcpListener: %+v", err) 71 | } 72 | wg.Done() 73 | }() 74 | 75 | n3iwfCtx := s.Context() 76 | for { 77 | connection, err := s.tcpListener.Accept() 78 | if err != nil { 79 | nwucpLog.Errorf("TCP server accept failed : %+v. Close the listener...", err) 80 | return 81 | } 82 | 83 | nwucpLog.Tracef("Accepted one UE from %+v", connection.RemoteAddr()) 84 | 85 | // Find UE context and store this connection in to it, then check if 86 | // there is any cached NAS message for this UE. If yes, send to it. 87 | 88 | ueIP := strings.Split(connection.RemoteAddr().String(), ":")[0] 89 | ikeUe, ok := n3iwfCtx.AllocatedUEIPAddressLoad(ueIP) 90 | if !ok { 91 | nwucpLog.Errorf("UE context not found for peer %+v", ueIP) 92 | continue 93 | } 94 | 95 | ranUe, err := n3iwfCtx.RanUeLoadFromIkeSPI(ikeUe.N3IWFIKESecurityAssociation.LocalSPI) 96 | if err != nil { 97 | nwucpLog.Errorf("RanUe context not found : %v", err) 98 | continue 99 | } 100 | 101 | n3iwfUe, ok := ranUe.(*n3iwf_context.N3IWFRanUe) 102 | if !ok { 103 | nwucpLog.Errorf("listenAndServe(): [Type Assertion] RanUe -> N3iwfUe failed") 104 | continue 105 | } 106 | 107 | // Store connection 108 | n3iwfUe.TCPConnection = connection 109 | 110 | s.SendNgapEvt(n3iwf_context.NewNASTCPConnEstablishedCompleteEvt(n3iwfUe.RanUeNgapId)) 111 | 112 | wg.Add(1) 113 | go serveConn(n3iwfUe, connection, wg) 114 | } 115 | } 116 | 117 | func (s *Server) Stop() { 118 | nwucpLog := logger.NWuCPLog 119 | nwucpLog.Infof("Close Nwucp server...") 120 | 121 | if err := s.tcpListener.Close(); err != nil { 122 | nwucpLog.Errorf("Stop nwucp server error : %+v", err) 123 | } 124 | 125 | // TODO: [Bug] TCPConnection may close twice, need to check 126 | s.Context().RANUePool.Range( 127 | func(key, value interface{}) bool { 128 | ranUe, ok := value.(*n3iwf_context.N3IWFRanUe) 129 | if ok && ranUe.TCPConnection != nil { 130 | if err := ranUe.TCPConnection.Close(); err != nil { 131 | logger.InitLog.Errorf("Stop nwucp server error : %+v", err) 132 | } 133 | } 134 | return true 135 | }, 136 | ) 137 | } 138 | 139 | // serveConn handle accepted TCP connection. It reads NAS packets 140 | // from the connection and call forward() to forward NAS messages 141 | // to AMF 142 | func serveConn(ranUe *n3iwf_context.N3IWFRanUe, connection net.Conn, wg *sync.WaitGroup) { 143 | nwucpLog := logger.NWuCPLog 144 | defer func() { 145 | if p := recover(); p != nil { 146 | // Print stack for panic to log. Fatalf() will let program exit. 147 | nwucpLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 148 | } 149 | 150 | err := connection.Close() 151 | if err != nil { 152 | nwucpLog.Errorf("Error closing connection: %+v", err) 153 | } 154 | wg.Done() 155 | }() 156 | 157 | connReader := bufio.NewReader(connection) 158 | buf := make([]byte, factory.MAX_BUF_MSG_LEN) 159 | for { 160 | // Read the length of NAS message 161 | n, err := io.ReadFull(connReader, buf[:2]) 162 | if err != nil { 163 | nwucpLog.Errorf("Read the length of NAS message failed: %+v", err) 164 | ranUe.TCPConnection = nil 165 | return 166 | } 167 | nasLen := binary.BigEndian.Uint16(buf[:n]) 168 | if uint64(nasLen) > uint64(cap(buf)) { 169 | buf = make([]byte, 0, nasLen) 170 | } 171 | 172 | // Read the NAS message 173 | n, err = io.ReadFull(connReader, buf[:nasLen]) 174 | if err != nil { 175 | nwucpLog.Errorf("Read the NAS message failed: %+v", err) 176 | ranUe.TCPConnection = nil 177 | return 178 | } 179 | fwdNas := make([]byte, n) 180 | copy(fwdNas, buf[:n]) 181 | 182 | wg.Add(1) 183 | go forward(ranUe, fwdNas, wg) 184 | } 185 | } 186 | 187 | // forward forwards NAS messages sent from UE to the 188 | // associated AMF 189 | func forward(ranUe *n3iwf_context.N3IWFRanUe, packet []byte, wg *sync.WaitGroup) { 190 | nwucpLog := logger.NWuCPLog 191 | defer func() { 192 | if p := recover(); p != nil { 193 | // Print stack for panic to log. Fatalf() will let program exit. 194 | nwucpLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 195 | } 196 | wg.Done() 197 | }() 198 | 199 | nwucpLog.Trace("Forward NWu -> N2") 200 | message.SendUplinkNASTransport(ranUe, packet) 201 | } 202 | -------------------------------------------------------------------------------- /internal/ngap/server.go: -------------------------------------------------------------------------------- 1 | package ngap 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "runtime/debug" 9 | "sync" 10 | "time" 11 | 12 | n3iwf_context "github.com/free5gc/n3iwf/internal/context" 13 | "github.com/free5gc/n3iwf/internal/logger" 14 | "github.com/free5gc/n3iwf/internal/ngap/message" 15 | "github.com/free5gc/n3iwf/pkg/factory" 16 | lib_ngap "github.com/free5gc/ngap" 17 | "github.com/free5gc/sctp" 18 | "github.com/free5gc/util/safe_channel" 19 | ) 20 | 21 | const ( 22 | RECEIVE_NGAPPACKET_CHANNEL_LEN = 512 23 | RECEIVE_NGAPEVENT_CHANNEL_LEN = 512 24 | ) 25 | 26 | type n3iwf interface { 27 | Config() *factory.Config 28 | Context() *n3iwf_context.N3IWFContext 29 | CancelContext() context.Context 30 | 31 | SendIkeEvt(n3iwf_context.IkeEvt) 32 | } 33 | 34 | type Server struct { 35 | n3iwf 36 | 37 | conn []*sctp.SCTPConn 38 | rcvPktCh *safe_channel.SafeCh[ReceiveNGAPPacket] 39 | rcvEvtCh *safe_channel.SafeCh[n3iwf_context.NgapEvt] 40 | } 41 | 42 | type ReceiveNGAPPacket struct { 43 | Conn *sctp.SCTPConn 44 | Buf []byte 45 | } 46 | 47 | func NewServer(n3iwf n3iwf) (*Server, error) { 48 | s := &Server{ 49 | n3iwf: n3iwf, 50 | } 51 | s.rcvPktCh = safe_channel.NewSafeCh[ReceiveNGAPPacket](RECEIVE_NGAPPACKET_CHANNEL_LEN) 52 | s.rcvEvtCh = safe_channel.NewSafeCh[n3iwf_context.NgapEvt](RECEIVE_NGAPEVENT_CHANNEL_LEN) 53 | return s, nil 54 | } 55 | 56 | // Run start the N3IWF SCTP process. 57 | func (s *Server) Run(wg *sync.WaitGroup) error { 58 | // n3iwf context 59 | cfg := s.Config() 60 | 61 | localAddr := cfg.GetLocalSctpAddr() 62 | 63 | for _, remoteAddr := range cfg.GetAmfSctpAddrs() { 64 | errChan := make(chan error) 65 | wg.Add(1) 66 | go s.listenAndServe(localAddr, remoteAddr, errChan, wg) 67 | if err, ok := <-errChan; ok { 68 | return err 69 | } 70 | } 71 | 72 | wg.Add(1) 73 | go s.runNgapEventHandler(wg) 74 | 75 | return nil 76 | } 77 | 78 | func (s *Server) runNgapEventHandler(wg *sync.WaitGroup) { 79 | ngapLog := logger.NgapLog 80 | defer func() { 81 | if p := recover(); p != nil { 82 | // Print stack for panic to log. Fatalf() will let program exit. 83 | ngapLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 84 | } 85 | ngapLog.Infof("NGAP server stopped") 86 | s.rcvEvtCh.Close() 87 | s.rcvPktCh.Close() 88 | wg.Done() 89 | }() 90 | 91 | rcvEvtCh := s.rcvEvtCh.GetRcvChan() 92 | rcvPktCh := s.rcvPktCh.GetRcvChan() 93 | 94 | for { 95 | select { 96 | case rcvPkt := <-rcvPktCh: 97 | if len(rcvPkt.Buf) == 0 { // receiver closed 98 | return 99 | } 100 | s.NGAPDispatch(rcvPkt.Conn, rcvPkt.Buf) 101 | case rcvEvt := <-rcvEvtCh: 102 | s.HandleEvent(rcvEvt) 103 | } 104 | } 105 | } 106 | 107 | func (s *Server) listenAndServe( 108 | localAddr, remoteAddr *sctp.SCTPAddr, 109 | errChan chan<- error, 110 | wg *sync.WaitGroup, 111 | ) { 112 | ngapLog := logger.NgapLog 113 | defer func() { 114 | if p := recover(); p != nil { 115 | // Print stack for panic to log. Fatalf() will let program exit. 116 | ngapLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 117 | } 118 | ngapLog.Infof("NGAP receiver stopped") 119 | wg.Done() 120 | }() 121 | 122 | var conn *sctp.SCTPConn 123 | var err error 124 | // Connect the session 125 | for i := 0; i < 3; i++ { 126 | conn, err = sctp.DialSCTP("sctp", localAddr, remoteAddr) 127 | if err != nil { 128 | ngapLog.Errorf("[SCTP] DialSCTP(): %+v", err) 129 | } else { 130 | break 131 | } 132 | 133 | if i != 2 { 134 | ngapLog.Info("Retry to connect AMF after 1 second...") 135 | time.Sleep(1 * time.Second) 136 | } else { 137 | ngapLog.Debugf("[SCTP] AMF SCTP address: %s", remoteAddr) 138 | errChan <- fmt.Errorf("failed to connect to AMF") 139 | return 140 | } 141 | } 142 | 143 | // Set default sender SCTP information sinfo_ppid = NGAP_PPID = 60 144 | info, err := conn.GetDefaultSentParam() 145 | if err != nil { 146 | ngapLog.Errorf("[SCTP] GetDefaultSentParam(): %+v", err) 147 | errConn := conn.Close() 148 | if errConn != nil { 149 | ngapLog.Errorf("conn close error in GetDefaultSentParam(): %+v", errConn) 150 | } 151 | errChan <- fmt.Errorf("get socket information failed") 152 | return 153 | } 154 | info.PPID = lib_ngap.PPID 155 | err = conn.SetDefaultSentParam(info) 156 | if err != nil { 157 | ngapLog.Errorf("[SCTP] SetDefaultSentParam(): %+v", err) 158 | errConn := conn.Close() 159 | if errConn != nil { 160 | ngapLog.Errorf("conn close error in SetDefaultSentParam(): %+v", errConn) 161 | } 162 | errChan <- errors.New("set socket parameter failed") 163 | return 164 | } 165 | 166 | // Subscribe receiver SCTP information 167 | err = conn.SubscribeEvents(sctp.SCTP_EVENT_DATA_IO) 168 | if err != nil { 169 | ngapLog.Errorf("[SCTP] SubscribeEvents(): %+v", err) 170 | errConn := conn.Close() 171 | if errConn != nil { 172 | ngapLog.Errorf("conn close error in SubscribeEvents(): %+v", errConn) 173 | } 174 | errChan <- errors.New("subscribe SCTP event failed") 175 | return 176 | } 177 | 178 | // Send NG setup request 179 | n3iwfCtx := s.Context() 180 | message.SendNGSetupRequest(conn, n3iwfCtx) 181 | 182 | close(errChan) 183 | 184 | s.conn = append(s.conn, conn) 185 | 186 | buf := make([]byte, factory.MAX_BUF_MSG_LEN) 187 | for { 188 | n, info, _, err := conn.SCTPRead(buf) 189 | if err != nil { 190 | ngapLog.Debugf("[SCTP] AMF SCTP address: %s", remoteAddr) 191 | if err == io.EOF || err == io.ErrUnexpectedEOF { 192 | ngapLog.Warn("[SCTP] Close connection.") 193 | errConn := conn.Close() 194 | if errConn != nil { 195 | ngapLog.Errorf("conn close error: %+v", errConn) 196 | } 197 | s.rcvPktCh.Send(ReceiveNGAPPacket{}) 198 | return 199 | } 200 | ngapLog.Errorf("[SCTP] Read from SCTP connection failed: %+v", err) 201 | return 202 | } 203 | 204 | ngapLog.Tracef("[SCTP] Successfully read %d bytes.", n) 205 | 206 | if info == nil || info.PPID != lib_ngap.PPID { 207 | ngapLog.Warn("Received SCTP PPID != 60") 208 | continue 209 | } 210 | 211 | forwardData := make([]byte, n) 212 | copy(forwardData, buf[:n]) 213 | 214 | ngapPkt := ReceiveNGAPPacket{ 215 | Conn: conn, 216 | Buf: forwardData[:n], 217 | } 218 | s.rcvPktCh.Send(ngapPkt) 219 | } 220 | } 221 | 222 | func (s *Server) SendNgapEvt(evt n3iwf_context.NgapEvt) { 223 | s.rcvEvtCh.Send(evt) 224 | } 225 | 226 | func (s *Server) Stop() { 227 | ngapLog := logger.NgapLog 228 | ngapLog.Infof("Close NGAP server....") 229 | 230 | for _, ngapServerConn := range s.conn { 231 | if err := ngapServerConn.Close(); err != nil { 232 | ngapLog.Errorf("Stop ngap server error : %+v", err) 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /internal/ike/server_test.go: -------------------------------------------------------------------------------- 1 | package ike 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "sync" 7 | "testing" 8 | 9 | "github.com/google/gopacket" 10 | "github.com/google/gopacket/layers" 11 | "github.com/stretchr/testify/require" 12 | 13 | ike_message "github.com/free5gc/ike/message" 14 | n3iwf_context "github.com/free5gc/n3iwf/internal/context" 15 | "github.com/free5gc/n3iwf/internal/ngap" 16 | "github.com/free5gc/n3iwf/pkg/factory" 17 | ) 18 | 19 | type n3iwfTestApp struct { 20 | cfg *factory.Config 21 | n3iwfCtx *n3iwf_context.N3IWFContext 22 | ngapServer *ngap.Server 23 | ikeServer *Server 24 | ctx context.Context 25 | cancel context.CancelFunc 26 | wg *sync.WaitGroup 27 | } 28 | 29 | func (a *n3iwfTestApp) Config() *factory.Config { 30 | return a.cfg 31 | } 32 | 33 | func (a *n3iwfTestApp) Context() *n3iwf_context.N3IWFContext { 34 | return a.n3iwfCtx 35 | } 36 | 37 | func (a *n3iwfTestApp) CancelContext() context.Context { 38 | return a.ctx 39 | } 40 | 41 | func (a *n3iwfTestApp) SendNgapEvt(evt n3iwf_context.NgapEvt) { 42 | a.ngapServer.SendNgapEvt(evt) 43 | } 44 | 45 | func NewN3iwfTestApp(cfg *factory.Config) (*n3iwfTestApp, error) { 46 | var err error 47 | ctx, cancel := context.WithCancel(context.Background()) 48 | 49 | n3iwfApp := &n3iwfTestApp{ 50 | cfg: cfg, 51 | ctx: ctx, 52 | cancel: cancel, 53 | wg: &sync.WaitGroup{}, 54 | } 55 | 56 | n3iwfApp.n3iwfCtx, err = n3iwf_context.NewTestContext(n3iwfApp) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return n3iwfApp, err 61 | } 62 | 63 | func NewTestCfg() *factory.Config { 64 | return &factory.Config{ 65 | Configuration: &factory.Configuration{}, 66 | } 67 | } 68 | 69 | func TestHandleNattMsg(t *testing.T) { 70 | initiatorSPI := uint64(0x123) 71 | ikeMessage := ike_message.NewMessage(initiatorSPI, 0, ike_message.IKE_SA_INIT, 72 | true, false, 0, nil) 73 | pkt, err := ikeMessage.Encode() 74 | require.NoError(t, err) 75 | 76 | NonESPPkt := append([]byte{0, 0, 0, 0}, pkt...) 77 | 78 | tests := []struct { 79 | name string 80 | conn *net.UDPConn 81 | rcvPkt []byte 82 | lAddr, rAddr *net.UDPAddr 83 | msg *ike_message.IKEMessage 84 | expectedErr bool 85 | }{ 86 | { 87 | name: "Received NAT-T Keepalive", 88 | rcvPkt: []byte{0xff}, 89 | lAddr: &net.UDPAddr{ 90 | IP: net.ParseIP("10.100.100.2"), 91 | Port: 4500, 92 | }, 93 | rAddr: &net.UDPAddr{ 94 | IP: net.ParseIP("10.100.100.1"), 95 | Port: 4500, 96 | }, 97 | expectedErr: false, 98 | }, 99 | { 100 | name: "Received NAT-T Msg is too short", 101 | rcvPkt: []byte{0x01, 0x02}, 102 | lAddr: &net.UDPAddr{ 103 | IP: net.ParseIP("10.100.100.2"), 104 | Port: 4500, 105 | }, 106 | rAddr: &net.UDPAddr{ 107 | IP: net.ParseIP("10.100.100.1"), 108 | Port: 4500, 109 | }, 110 | expectedErr: true, 111 | }, 112 | { 113 | name: "Received IKE packet from port 4500, and no need to drop", 114 | rcvPkt: NonESPPkt, 115 | lAddr: &net.UDPAddr{ 116 | IP: net.ParseIP("10.100.100.2"), 117 | Port: 4500, 118 | }, 119 | rAddr: &net.UDPAddr{ 120 | IP: net.ParseIP("10.100.100.1"), 121 | Port: 4500, 122 | }, 123 | expectedErr: false, 124 | }, 125 | { 126 | name: "Received ESP packet from port 4500", 127 | rcvPkt: []byte{0x1, 0x2, 0x3, 0x4, 0x5}, 128 | lAddr: &net.UDPAddr{ 129 | IP: net.ParseIP("10.100.100.2"), 130 | Port: 4500, 131 | }, 132 | rAddr: &net.UDPAddr{ 133 | IP: net.ParseIP("10.100.100.1"), 134 | Port: 4500, 135 | }, 136 | expectedErr: false, 137 | }, 138 | } 139 | 140 | for _, tt := range tests { 141 | t.Run(tt.name, func(t *testing.T) { 142 | _, err := handleNattMsg(tt.rcvPkt, tt.rAddr, tt.lAddr, nil) 143 | if tt.expectedErr { 144 | require.Error(t, err) 145 | } else { 146 | require.NoError(t, err) 147 | } 148 | }) 149 | } 150 | } 151 | 152 | func TestCheckIKEMessage(t *testing.T) { 153 | n3iwf, err := NewN3iwfTestApp(NewTestCfg()) 154 | require.NoError(t, err) 155 | 156 | n3iwf.ikeServer, err = NewServer(n3iwf) 157 | require.NoError(t, err) 158 | ikeServer := n3iwf.ikeServer 159 | 160 | srcIP := &net.UDPAddr{ 161 | IP: net.ParseIP("10.100.100.1"), 162 | Port: 500, 163 | } 164 | dstIP := &net.UDPAddr{ 165 | IP: net.ParseIP("10.100.100.2"), 166 | Port: 500, 167 | } 168 | 169 | mockConn, err := net.DialUDP("udp", nil, dstIP) 170 | require.NoError(t, err) 171 | 172 | initiatorSPI := uint64(0x123) 173 | nonceData := []byte("randomNonce") 174 | payload := new(ike_message.IKEPayloadContainer) 175 | payload.BuildNonce(nonceData) 176 | 177 | ikeMsg := ike_message.NewMessage(initiatorSPI, 0, ike_message.IKE_SA_INIT, 178 | true, false, 0, *payload) 179 | 180 | tests := []struct { 181 | name string 182 | conn *net.UDPConn 183 | localAddr *net.UDPAddr 184 | remoteAddr *net.UDPAddr 185 | msg *ike_message.IKEMessage 186 | expectedErr bool 187 | }{ 188 | { 189 | name: "Receive packet has IKE version error", 190 | conn: mockConn, 191 | localAddr: dstIP, 192 | remoteAddr: srcIP, 193 | msg: &ike_message.IKEMessage{ 194 | IKEHeader: &ike_message.IKEHeader{ 195 | InitiatorSPI: initiatorSPI, 196 | ExchangeType: ike_message.IKE_SA_INIT, 197 | Flags: 0, 198 | MajorVersion: 3, 199 | MinorVersion: 0, 200 | }, 201 | }, 202 | expectedErr: true, 203 | }, 204 | { 205 | name: "Decode IKE_SA_INIT msg", 206 | conn: mockConn, 207 | localAddr: dstIP, 208 | remoteAddr: srcIP, 209 | msg: ikeMsg, 210 | expectedErr: false, 211 | }, 212 | { 213 | name: "SPI not found from IKE header", 214 | conn: mockConn, 215 | localAddr: dstIP, 216 | remoteAddr: srcIP, 217 | msg: &ike_message.IKEMessage{ 218 | IKEHeader: &ike_message.IKEHeader{ 219 | InitiatorSPI: initiatorSPI, 220 | ExchangeType: ike_message.IKE_AUTH, 221 | Flags: ike_message.ResponseBitCheck, 222 | MajorVersion: 2, 223 | MinorVersion: 0, 224 | }, 225 | Payloads: ikeMsg.Payloads, 226 | }, 227 | expectedErr: true, 228 | }, 229 | } 230 | 231 | for _, tt := range tests { 232 | t.Run(tt.name, func(t *testing.T) { 233 | msg, err := tt.msg.Encode() 234 | require.NoError(t, err) 235 | 236 | _, _, err = ikeServer.checkIKEMessage( 237 | msg, tt.conn, tt.localAddr, tt.remoteAddr) 238 | if tt.expectedErr { 239 | require.Error(t, err) 240 | } else { 241 | require.NoError(t, err) 242 | } 243 | }) 244 | } 245 | } 246 | 247 | func TestConstructPacketWithESP(t *testing.T) { 248 | srcIP := &net.UDPAddr{ 249 | IP: net.IPv4(192, 168, 0, 1), 250 | } 251 | dstIP := &net.UDPAddr{ 252 | IP: net.IPv4(192, 168, 0, 2), 253 | } 254 | 255 | espPacket := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07} 256 | 257 | packet, err := constructPacketWithESP(srcIP, dstIP, espPacket) 258 | require.NoError(t, err) 259 | 260 | packetParsed := gopacket.NewPacket(packet, layers.LayerTypeIPv4, gopacket.Default) 261 | ipLayer := packetParsed.Layer(layers.LayerTypeIPv4) 262 | require.NotNil(t, ipLayer) 263 | 264 | ipv4, _ := ipLayer.(*layers.IPv4) 265 | require.Equal(t, ipv4.SrcIP.To4().String(), srcIP.IP.String()) 266 | require.Equal(t, ipv4.DstIP.To4().String(), dstIP.IP.String()) 267 | require.Equal(t, ipv4.Protocol, layers.IPProtocolESP) 268 | } 269 | -------------------------------------------------------------------------------- /internal/context/ngap.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "github.com/free5gc/ngap/ngapType" 5 | ) 6 | 7 | type NgapEventType int64 8 | 9 | // NGAP event type 10 | const ( 11 | UnmarshalEAP5GData NgapEventType = iota 12 | NASTCPConnEstablishedComplete 13 | GetNGAPContext 14 | SendInitialUEMessage 15 | SendPDUSessionResourceSetupResponse 16 | SendNASMsg 17 | StartTCPSignalNASMsg 18 | SendUEContextRelease 19 | SendUEContextReleaseRequest 20 | SendUEContextReleaseComplete 21 | SendPDUSessionResourceRelease 22 | SendPDUSessionResourceReleaseResponse 23 | SendUplinkNASTransport 24 | SendInitialContextSetupResponse 25 | ) 26 | 27 | type EvtError string 28 | 29 | func (e EvtError) Error() string { return string(e) } 30 | 31 | // NGAP IKE event error string 32 | const ( 33 | ErrNil = EvtError("Nil") 34 | ErrRadioConnWithUeLost = EvtError("RadioConnectionWithUeLost") 35 | ErrTransportResourceUnavailable = EvtError("TransportResourceUnavailable") 36 | ErrAMFSelection = EvtError("No avalible AMF for this UE") 37 | ) 38 | 39 | type NgapEvt interface { 40 | Type() NgapEventType 41 | } 42 | 43 | type UnmarshalEAP5GDataEvt struct { 44 | LocalSPI uint64 45 | EAPVendorData []byte 46 | IsInitialUE bool 47 | RanUeNgapId int64 48 | } 49 | 50 | func (unmarshalEAP5GDataEvt *UnmarshalEAP5GDataEvt) Type() NgapEventType { 51 | return UnmarshalEAP5GData 52 | } 53 | 54 | func NewUnmarshalEAP5GDataEvt(localSPI uint64, eapVendorData []byte, isInitialUE bool, 55 | ranUeNgapId int64, 56 | ) *UnmarshalEAP5GDataEvt { 57 | return &UnmarshalEAP5GDataEvt{ 58 | LocalSPI: localSPI, 59 | EAPVendorData: eapVendorData, 60 | IsInitialUE: isInitialUE, 61 | RanUeNgapId: ranUeNgapId, 62 | } 63 | } 64 | 65 | type SendInitialUEMessageEvt struct { 66 | RanUeNgapId int64 67 | IPv4Addr string 68 | IPv4Port int 69 | NasPDU []byte 70 | } 71 | 72 | func (sendInitialUEMessageEvt *SendInitialUEMessageEvt) Type() NgapEventType { 73 | return SendInitialUEMessage 74 | } 75 | 76 | func NewSendInitialUEMessageEvt(ranUeNgapId int64, ipv4Addr string, ipv4Port int, 77 | nasPDU []byte, 78 | ) *SendInitialUEMessageEvt { 79 | return &SendInitialUEMessageEvt{ 80 | RanUeNgapId: ranUeNgapId, 81 | IPv4Addr: ipv4Addr, 82 | IPv4Port: ipv4Port, 83 | NasPDU: nasPDU, 84 | } 85 | } 86 | 87 | type SendPDUSessionResourceSetupResEvt struct { 88 | RanUeNgapId int64 89 | } 90 | 91 | func (sendPDUSessionResourceSetupResEvt *SendPDUSessionResourceSetupResEvt) Type() NgapEventType { 92 | return SendPDUSessionResourceSetupResponse 93 | } 94 | 95 | func NewSendPDUSessionResourceSetupResEvt(ranUeNgapId int64) *SendPDUSessionResourceSetupResEvt { 96 | return &SendPDUSessionResourceSetupResEvt{ 97 | RanUeNgapId: ranUeNgapId, 98 | } 99 | } 100 | 101 | type SendNASMsgEvt struct { 102 | RanUeNgapId int64 103 | } 104 | 105 | func (sendNASMsgEvt *SendNASMsgEvt) Type() NgapEventType { 106 | return SendNASMsg 107 | } 108 | 109 | func NewSendNASMsgEvt(ranUeNgapId int64) *SendNASMsgEvt { 110 | return &SendNASMsgEvt{ 111 | RanUeNgapId: ranUeNgapId, 112 | } 113 | } 114 | 115 | type StartTCPSignalNASMsgEvt struct { 116 | RanUeNgapId int64 117 | } 118 | 119 | func (startTCPSignalNASMsgEvt *StartTCPSignalNASMsgEvt) Type() NgapEventType { 120 | return StartTCPSignalNASMsg 121 | } 122 | 123 | func NewStartTCPSignalNASMsgEvt(ranUeNgapId int64) *StartTCPSignalNASMsgEvt { 124 | return &StartTCPSignalNASMsgEvt{ 125 | RanUeNgapId: ranUeNgapId, 126 | } 127 | } 128 | 129 | type NASTCPConnEstablishedCompleteEvt struct { 130 | RanUeNgapId int64 131 | } 132 | 133 | func (nasTCPConnEstablishedCompleteEvt *NASTCPConnEstablishedCompleteEvt) Type() NgapEventType { 134 | return NASTCPConnEstablishedComplete 135 | } 136 | 137 | func NewNASTCPConnEstablishedCompleteEvt(ranUeNgapId int64) *NASTCPConnEstablishedCompleteEvt { 138 | return &NASTCPConnEstablishedCompleteEvt{ 139 | RanUeNgapId: ranUeNgapId, 140 | } 141 | } 142 | 143 | type SendUEContextReleaseRequestEvt struct { 144 | RanUeNgapId int64 145 | ErrMsg EvtError 146 | } 147 | 148 | func (sendUEContextReleaseRequestEvt *SendUEContextReleaseRequestEvt) Type() NgapEventType { 149 | return SendUEContextReleaseRequest 150 | } 151 | 152 | func NewSendUEContextReleaseRequestEvt(ranUeNgapId int64, errMsg EvtError, 153 | ) *SendUEContextReleaseRequestEvt { 154 | return &SendUEContextReleaseRequestEvt{ 155 | RanUeNgapId: ranUeNgapId, 156 | ErrMsg: errMsg, 157 | } 158 | } 159 | 160 | type SendUEContextReleaseCompleteEvt struct { 161 | RanUeNgapId int64 162 | } 163 | 164 | func (sendUEContextReleaseCompleteEvt *SendUEContextReleaseCompleteEvt) Type() NgapEventType { 165 | return SendUEContextReleaseComplete 166 | } 167 | 168 | func NewSendUEContextReleaseCompleteEvt(ranUeNgapId int64) *SendUEContextReleaseCompleteEvt { 169 | return &SendUEContextReleaseCompleteEvt{ 170 | RanUeNgapId: ranUeNgapId, 171 | } 172 | } 173 | 174 | type SendPDUSessionResourceReleaseResEvt struct { 175 | RanUeNgapId int64 176 | } 177 | 178 | func (sendPDUSessionResourceReleaseResEvt *SendPDUSessionResourceReleaseResEvt) Type() NgapEventType { 179 | return SendPDUSessionResourceReleaseResponse 180 | } 181 | 182 | func NewSendPDUSessionResourceReleaseResEvt(ranUeNgapId int64) *SendPDUSessionResourceReleaseResEvt { 183 | return &SendPDUSessionResourceReleaseResEvt{ 184 | RanUeNgapId: ranUeNgapId, 185 | } 186 | } 187 | 188 | // Ngap context 189 | const ( 190 | CxtTempPDUSessionSetupData int64 = iota 191 | ) 192 | 193 | type GetNGAPContextEvt struct { 194 | RanUeNgapId int64 195 | NgapCxtReqNumlist []int64 196 | } 197 | 198 | func (getNGAPContextEvt *GetNGAPContextEvt) Type() NgapEventType { 199 | return GetNGAPContext 200 | } 201 | 202 | func NewGetNGAPContextEvt(ranUeNgapId int64, ngapCxtReqNumlist []int64) *GetNGAPContextEvt { 203 | return &GetNGAPContextEvt{ 204 | RanUeNgapId: ranUeNgapId, 205 | NgapCxtReqNumlist: ngapCxtReqNumlist, 206 | } 207 | } 208 | 209 | type SendUplinkNASTransportEvt struct { 210 | RanUeNgapId int64 211 | Pdu []byte 212 | } 213 | 214 | func (e *SendUplinkNASTransportEvt) Type() NgapEventType { 215 | return SendUplinkNASTransport 216 | } 217 | 218 | func NewSendUplinkNASTransportEvt(ranUeNgapId int64, pdu []byte) *SendUplinkNASTransportEvt { 219 | return &SendUplinkNASTransportEvt{ 220 | RanUeNgapId: ranUeNgapId, 221 | Pdu: pdu, 222 | } 223 | } 224 | 225 | type SendInitialContextSetupRespEvt struct { 226 | RanUeNgapId int64 227 | ResponseList *ngapType.PDUSessionResourceSetupListCxtRes 228 | FailedList *ngapType.PDUSessionResourceFailedToSetupListCxtRes 229 | CriticalityDiagnostics *ngapType.CriticalityDiagnostics 230 | } 231 | 232 | func (e *SendInitialContextSetupRespEvt) Type() NgapEventType { 233 | return SendInitialContextSetupResponse 234 | } 235 | 236 | func NewSendInitialContextSetupRespEvt( 237 | ranUeNgapId int64, 238 | responseList *ngapType.PDUSessionResourceSetupListCxtRes, 239 | failedList *ngapType.PDUSessionResourceFailedToSetupListCxtRes, 240 | criticalityDiagnostics *ngapType.CriticalityDiagnostics, 241 | ) *SendInitialContextSetupRespEvt { 242 | return &SendInitialContextSetupRespEvt{ 243 | RanUeNgapId: ranUeNgapId, 244 | ResponseList: responseList, 245 | FailedList: failedList, 246 | CriticalityDiagnostics: criticalityDiagnostics, 247 | } 248 | } 249 | 250 | type SendUEContextReleaseEvt struct { 251 | RanUeNgapId int64 252 | } 253 | 254 | func (e *SendUEContextReleaseEvt) Type() NgapEventType { 255 | return SendUEContextRelease 256 | } 257 | 258 | func NewSendUEContextReleaseEvt(ranUeNgapId int64) *SendUEContextReleaseEvt { 259 | return &SendUEContextReleaseEvt{ 260 | RanUeNgapId: ranUeNgapId, 261 | } 262 | } 263 | 264 | type SendPDUSessionResourceReleaseEvt struct { 265 | RanUeNgapId int64 266 | DeletPduIds []int64 267 | } 268 | 269 | func (e *SendPDUSessionResourceReleaseEvt) Type() NgapEventType { 270 | return SendPDUSessionResourceRelease 271 | } 272 | 273 | func NewendPDUSessionResourceReleaseEvt(ranUeNgapId int64, deletPduIds []int64) *SendPDUSessionResourceReleaseEvt { 274 | return &SendPDUSessionResourceReleaseEvt{ 275 | RanUeNgapId: ranUeNgapId, 276 | DeletPduIds: deletPduIds, 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /internal/ike/xfrm/xfrm.go: -------------------------------------------------------------------------------- 1 | package xfrm 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/pkg/errors" 8 | "github.com/vishvananda/netlink" 9 | 10 | "github.com/free5gc/ike/message" 11 | "github.com/free5gc/n3iwf/internal/context" 12 | "github.com/free5gc/n3iwf/internal/logger" 13 | ) 14 | 15 | type XFRMEncryptionAlgorithmType uint16 16 | 17 | func (xfrmEncryptionAlgorithmType XFRMEncryptionAlgorithmType) String() string { 18 | switch xfrmEncryptionAlgorithmType { 19 | case message.ENCR_DES: 20 | return "cbc(des)" 21 | case message.ENCR_3DES: 22 | return "cbc(des3_ede)" 23 | case message.ENCR_CAST: 24 | return "cbc(cast5)" 25 | case message.ENCR_BLOWFISH: 26 | return "cbc(blowfish)" 27 | case message.ENCR_NULL: 28 | return "ecb(cipher_null)" 29 | case message.ENCR_AES_CBC: 30 | return "cbc(aes)" 31 | case message.ENCR_AES_CTR: 32 | return "rfc3686(ctr(aes))" 33 | default: 34 | return "" 35 | } 36 | } 37 | 38 | type XFRMIntegrityAlgorithmType uint16 39 | 40 | func (xfrmIntegrityAlgorithmType XFRMIntegrityAlgorithmType) String() string { 41 | switch xfrmIntegrityAlgorithmType { 42 | case message.AUTH_HMAC_MD5_96: 43 | return "hmac(md5)" 44 | case message.AUTH_HMAC_SHA1_96: 45 | return "hmac(sha1)" 46 | case message.AUTH_AES_XCBC_96: 47 | return "xcbc(aes)" 48 | case message.AUTH_HMAC_SHA2_256_128: 49 | return "hmac(sha256)" 50 | default: 51 | return "" 52 | } 53 | } 54 | 55 | func ApplyXFRMRule(n3iwf_is_initiator bool, xfrmiId uint32, 56 | childSecurityAssociation *context.ChildSecurityAssociation, 57 | ) error { 58 | // Build XFRM information data structure for incoming traffic. 59 | 60 | // Direction: {private_network} -> this_server 61 | // State 62 | var xfrmEncryptionAlgorithm, xfrmIntegrityAlgorithm *netlink.XfrmStateAlgo 63 | if n3iwf_is_initiator { 64 | xfrmEncryptionAlgorithm = &netlink.XfrmStateAlgo{ 65 | Name: XFRMEncryptionAlgorithmType(childSecurityAssociation.EncrKInfo.TransformID()).String(), 66 | Key: childSecurityAssociation.ResponderToInitiatorEncryptionKey, 67 | } 68 | if childSecurityAssociation.IntegKInfo != nil { 69 | xfrmIntegrityAlgorithm = &netlink.XfrmStateAlgo{ 70 | Name: XFRMIntegrityAlgorithmType(childSecurityAssociation.IntegKInfo.TransformID()).String(), 71 | Key: childSecurityAssociation.ResponderToInitiatorIntegrityKey, 72 | TruncateLen: getTruncateLength(childSecurityAssociation.IntegKInfo.TransformID()), 73 | } 74 | } 75 | } else { 76 | xfrmEncryptionAlgorithm = &netlink.XfrmStateAlgo{ 77 | Name: XFRMEncryptionAlgorithmType(childSecurityAssociation.EncrKInfo.TransformID()).String(), 78 | Key: childSecurityAssociation.InitiatorToResponderEncryptionKey, 79 | } 80 | if childSecurityAssociation.IntegKInfo != nil { 81 | xfrmIntegrityAlgorithm = &netlink.XfrmStateAlgo{ 82 | Name: XFRMIntegrityAlgorithmType(childSecurityAssociation.IntegKInfo.TransformID()).String(), 83 | Key: childSecurityAssociation.InitiatorToResponderIntegrityKey, 84 | TruncateLen: getTruncateLength(childSecurityAssociation.IntegKInfo.TransformID()), 85 | } 86 | } 87 | } 88 | 89 | xfrmState := new(netlink.XfrmState) 90 | 91 | xfrmState.Src = childSecurityAssociation.PeerPublicIPAddr 92 | xfrmState.Dst = childSecurityAssociation.LocalPublicIPAddr 93 | xfrmState.Proto = netlink.XFRM_PROTO_ESP 94 | xfrmState.Mode = netlink.XFRM_MODE_TUNNEL 95 | xfrmState.Spi = int(childSecurityAssociation.InboundSPI) 96 | xfrmState.Ifid = int(xfrmiId) 97 | xfrmState.Auth = xfrmIntegrityAlgorithm 98 | xfrmState.Crypt = xfrmEncryptionAlgorithm 99 | xfrmState.ESN = childSecurityAssociation.EsnInfo.GetNeedESN() 100 | 101 | // Commit xfrm state to netlink 102 | var err error 103 | if err = netlink.XfrmStateAdd(xfrmState); err != nil { 104 | return errors.Wrapf(err, "Add XFRM state") 105 | } 106 | 107 | childSecurityAssociation.XfrmStateList = append(childSecurityAssociation.XfrmStateList, *xfrmState) 108 | 109 | // Policy 110 | xfrmPolicyTemplate := netlink.XfrmPolicyTmpl{ 111 | Src: xfrmState.Src, 112 | Dst: xfrmState.Dst, 113 | Proto: xfrmState.Proto, 114 | Mode: xfrmState.Mode, 115 | Spi: xfrmState.Spi, 116 | } 117 | 118 | xfrmPolicy := new(netlink.XfrmPolicy) 119 | 120 | xfrmPolicy.Src = &childSecurityAssociation.TrafficSelectorRemote 121 | xfrmPolicy.Dst = &childSecurityAssociation.TrafficSelectorLocal 122 | xfrmPolicy.Proto = netlink.Proto(childSecurityAssociation.SelectedIPProtocol) 123 | xfrmPolicy.Dir = netlink.XFRM_DIR_IN 124 | xfrmPolicy.Ifid = int(xfrmiId) 125 | xfrmPolicy.Tmpls = []netlink.XfrmPolicyTmpl{ 126 | xfrmPolicyTemplate, 127 | } 128 | 129 | // Commit xfrm policy to netlink 130 | if err = netlink.XfrmPolicyAdd(xfrmPolicy); err != nil { 131 | return errors.Wrapf(err, "Add XFRM policy") 132 | } 133 | 134 | childSecurityAssociation.XfrmPolicyList = append(childSecurityAssociation.XfrmPolicyList, *xfrmPolicy) 135 | 136 | // Direction: this_server -> {private_network} 137 | // State 138 | if n3iwf_is_initiator { 139 | xfrmEncryptionAlgorithm.Key = childSecurityAssociation.InitiatorToResponderEncryptionKey 140 | if childSecurityAssociation.IntegKInfo != nil { 141 | xfrmIntegrityAlgorithm.Key = childSecurityAssociation.InitiatorToResponderIntegrityKey 142 | } 143 | } else { 144 | xfrmEncryptionAlgorithm.Key = childSecurityAssociation.ResponderToInitiatorEncryptionKey 145 | if childSecurityAssociation.IntegKInfo != nil { 146 | xfrmIntegrityAlgorithm.Key = childSecurityAssociation.ResponderToInitiatorIntegrityKey 147 | } 148 | } 149 | 150 | xfrmState.Spi = int(childSecurityAssociation.OutboundSPI) 151 | xfrmState.Src, xfrmState.Dst = xfrmState.Dst, xfrmState.Src 152 | 153 | if childSecurityAssociation.EnableEncapsulate { 154 | xfrmState.Encap = &netlink.XfrmStateEncap{ 155 | Type: netlink.XFRM_ENCAP_ESPINUDP, 156 | SrcPort: childSecurityAssociation.NATPort, 157 | DstPort: childSecurityAssociation.N3IWFPort, 158 | } 159 | } 160 | 161 | if xfrmState.Encap != nil { 162 | xfrmState.Encap.SrcPort, xfrmState.Encap.DstPort = xfrmState.Encap.DstPort, xfrmState.Encap.SrcPort 163 | } 164 | 165 | // Commit xfrm state to netlink 166 | if err = netlink.XfrmStateAdd(xfrmState); err != nil { 167 | return errors.Wrapf(err, "Add XFRM state") 168 | } 169 | 170 | childSecurityAssociation.XfrmStateList = append(childSecurityAssociation.XfrmStateList, *xfrmState) 171 | 172 | // Policy 173 | xfrmPolicyTemplate.Spi = int(childSecurityAssociation.OutboundSPI) 174 | xfrmPolicyTemplate.Src, xfrmPolicyTemplate.Dst = xfrmPolicyTemplate.Dst, xfrmPolicyTemplate.Src 175 | 176 | xfrmPolicy.Src, xfrmPolicy.Dst = xfrmPolicy.Dst, xfrmPolicy.Src 177 | xfrmPolicy.Dir = netlink.XFRM_DIR_OUT 178 | xfrmPolicy.Tmpls = []netlink.XfrmPolicyTmpl{ 179 | xfrmPolicyTemplate, 180 | } 181 | 182 | // Commit xfrm policy to netlink 183 | if err = netlink.XfrmPolicyAdd(xfrmPolicy); err != nil { 184 | return errors.Wrapf(err, "Add XFRM policy") 185 | } 186 | 187 | childSecurityAssociation.XfrmPolicyList = append(childSecurityAssociation.XfrmPolicyList, *xfrmPolicy) 188 | return nil 189 | } 190 | 191 | func SetupIPsecXfrmi(xfrmIfaceName, parentIfaceName string, xfrmIfaceId uint32, 192 | xfrmIfaceAddr net.IPNet, 193 | ) (netlink.Link, error) { 194 | ikeLog := logger.IKELog 195 | var ( 196 | xfrmi, parent netlink.Link 197 | err error 198 | ) 199 | 200 | if parent, err = netlink.LinkByName(parentIfaceName); err != nil { 201 | return nil, fmt.Errorf("cannot find parent interface %s by name: %+v", parentIfaceName, err) 202 | } 203 | 204 | // ip link add type xfrm dev if_id 205 | link := &netlink.Xfrmi{ 206 | LinkAttrs: netlink.LinkAttrs{ 207 | Name: xfrmIfaceName, 208 | ParentIndex: parent.Attrs().Index, 209 | }, 210 | Ifid: xfrmIfaceId, 211 | } 212 | 213 | if err = netlink.LinkAdd(link); err != nil { 214 | return nil, err 215 | } 216 | 217 | if xfrmi, err = netlink.LinkByName(xfrmIfaceName); err != nil { 218 | return nil, err 219 | } 220 | 221 | ikeLog.Debugf("XFRM interface %s index is %d", xfrmIfaceName, xfrmi.Attrs().Index) 222 | 223 | // ip addr add xfrmIfaceAddr dev 224 | linkIPSecAddr := &netlink.Addr{ 225 | IPNet: &xfrmIfaceAddr, 226 | } 227 | 228 | if err := netlink.AddrAdd(xfrmi, linkIPSecAddr); err != nil { 229 | return nil, err 230 | } 231 | 232 | // ip link set up 233 | if err := netlink.LinkSetUp(xfrmi); err != nil { 234 | return nil, err 235 | } 236 | 237 | return xfrmi, nil 238 | } 239 | 240 | func getTruncateLength(transformID uint16) int { 241 | switch transformID { 242 | case message.AUTH_HMAC_MD5_96: 243 | return 96 244 | case message.AUTH_HMAC_SHA1_96: 245 | return 96 246 | case message.AUTH_HMAC_SHA2_256_128: 247 | return 128 248 | default: 249 | return 96 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /pkg/service/init.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net" 8 | "os" 9 | "runtime/debug" 10 | "sync" 11 | 12 | "github.com/pkg/errors" 13 | "github.com/prometheus/client_golang/prometheus" 14 | "github.com/sirupsen/logrus" 15 | "github.com/vishvananda/netlink" 16 | 17 | n3iwf_context "github.com/free5gc/n3iwf/internal/context" 18 | "github.com/free5gc/n3iwf/internal/ike" 19 | "github.com/free5gc/n3iwf/internal/ike/xfrm" 20 | "github.com/free5gc/n3iwf/internal/logger" 21 | "github.com/free5gc/n3iwf/internal/ngap" 22 | "github.com/free5gc/n3iwf/internal/nwucp" 23 | "github.com/free5gc/n3iwf/internal/nwuup" 24 | "github.com/free5gc/n3iwf/pkg/app" 25 | "github.com/free5gc/n3iwf/pkg/factory" 26 | "github.com/free5gc/util/metrics" 27 | "github.com/free5gc/util/metrics/utils" 28 | ) 29 | 30 | var N3IWF *N3iwfApp 31 | 32 | var _ app.App = &N3iwfApp{} 33 | 34 | type N3iwfApp struct { 35 | n3iwfCtx *n3iwf_context.N3IWFContext 36 | cfg *factory.Config 37 | ngapServer *ngap.Server 38 | nwucpServer *nwucp.Server 39 | nwuupServer *nwuup.Server 40 | ikeServer *ike.Server 41 | metricsServer *metrics.Server 42 | ctx context.Context 43 | cancel context.CancelFunc 44 | wg sync.WaitGroup 45 | } 46 | 47 | func NewApp( 48 | ctx context.Context, 49 | cfg *factory.Config, 50 | tlsKeyLogPath string, 51 | ) (*N3iwfApp, error) { 52 | var err error 53 | n3iwf := &N3iwfApp{ 54 | cfg: cfg, 55 | wg: sync.WaitGroup{}, 56 | } 57 | n3iwf.ctx, n3iwf.cancel = context.WithCancel(ctx) 58 | 59 | n3iwf.SetLogEnable(cfg.GetLogEnable()) 60 | n3iwf.SetLogLevel(cfg.GetLogLevel()) 61 | n3iwf.SetReportCaller(cfg.GetLogReportCaller()) 62 | 63 | if n3iwf.n3iwfCtx, err = n3iwf_context.NewContext(n3iwf); err != nil { 64 | return nil, errors.Wrap(err, "NewApp()") 65 | } 66 | if n3iwf.ngapServer, err = ngap.NewServer(n3iwf); err != nil { 67 | return nil, errors.Wrap(err, "NewApp()") 68 | } 69 | if n3iwf.nwucpServer, err = nwucp.NewServer(n3iwf); err != nil { 70 | return nil, errors.Wrap(err, "NewApp()") 71 | } 72 | if n3iwf.nwuupServer, err = nwuup.NewServer(n3iwf); err != nil { 73 | return nil, errors.Wrap(err, "NewApp()") 74 | } 75 | if n3iwf.ikeServer, err = ike.NewServer(n3iwf); err != nil { 76 | return nil, errors.Wrap(err, "NewApp()") 77 | } 78 | 79 | features := map[utils.MetricTypeEnabled]bool{utils.NGAP: true} 80 | customMetrics := make(map[utils.MetricTypeEnabled][]prometheus.Collector) 81 | if cfg.AreMetricsEnabled() { 82 | if n3iwf.metricsServer, err = metrics.NewServer( 83 | getInitMetrics(cfg, features, customMetrics), tlsKeyLogPath, logger.InitLog); err != nil { 84 | return nil, err 85 | } 86 | } 87 | 88 | N3IWF = n3iwf 89 | return n3iwf, nil 90 | } 91 | 92 | func getInitMetrics( 93 | cfg *factory.Config, 94 | features map[utils.MetricTypeEnabled]bool, 95 | customMetrics map[utils.MetricTypeEnabled][]prometheus.Collector, 96 | ) metrics.InitMetrics { 97 | metricsInfo := metrics.Metrics{ 98 | BindingIPv4: cfg.GetMetricsBindingAddr(), 99 | Scheme: cfg.GetMetricsScheme(), 100 | Namespace: cfg.GetMetricsNamespace(), 101 | Port: cfg.GetMetricsPort(), 102 | Tls: metrics.Tls{ 103 | Key: cfg.GetMetricsCertKeyPath(), 104 | Pem: cfg.GetMetricsCertPemPath(), 105 | }, 106 | } 107 | 108 | return metrics.NewInitMetrics(metricsInfo, "N3IWF", features, customMetrics) 109 | } 110 | 111 | func (a *N3iwfApp) CancelContext() context.Context { 112 | return a.ctx 113 | } 114 | 115 | func (a *N3iwfApp) Context() *n3iwf_context.N3IWFContext { 116 | return a.n3iwfCtx 117 | } 118 | 119 | func (a *N3iwfApp) Config() *factory.Config { 120 | return a.cfg 121 | } 122 | 123 | func (a *N3iwfApp) SetLogEnable(enable bool) { 124 | logger.MainLog.Infof("Log enable is set to [%v]", enable) 125 | if enable && logger.Log.Out == os.Stderr { 126 | return 127 | } else if !enable && logger.Log.Out == io.Discard { 128 | return 129 | } 130 | 131 | a.cfg.SetLogEnable(enable) 132 | if enable { 133 | logger.Log.SetOutput(os.Stderr) 134 | } else { 135 | logger.Log.SetOutput(io.Discard) 136 | } 137 | } 138 | 139 | func (a *N3iwfApp) SetLogLevel(level string) { 140 | lvl, err := logrus.ParseLevel(level) 141 | mainLog := logger.MainLog 142 | if err != nil { 143 | mainLog.Warnf("Log level [%s] is invalid", level) 144 | return 145 | } 146 | 147 | mainLog.Infof("Log level is set to [%s]", level) 148 | if lvl == logger.Log.GetLevel() { 149 | return 150 | } 151 | 152 | a.cfg.SetLogLevel(level) 153 | logger.Log.SetLevel(lvl) 154 | } 155 | 156 | func (a *N3iwfApp) SetReportCaller(reportCaller bool) { 157 | logger.MainLog.Infof("Report Caller is set to [%v]", reportCaller) 158 | if reportCaller == logger.Log.ReportCaller { 159 | return 160 | } 161 | 162 | a.cfg.SetLogReportCaller(reportCaller) 163 | logger.Log.SetReportCaller(reportCaller) 164 | } 165 | 166 | func (a *N3iwfApp) Run() error { 167 | if err := a.initDefaultXfrmInterface(); err != nil { 168 | return err 169 | } 170 | mainLog := logger.MainLog 171 | 172 | a.wg.Add(1) 173 | go a.listenShutdownEvent() 174 | 175 | // NGAP 176 | if err := a.ngapServer.Run(&a.wg); err != nil { 177 | return errors.Wrapf(err, "Run()") 178 | } 179 | mainLog.Infof("NGAP service running.") 180 | 181 | // Relay listeners 182 | // Control plane 183 | if err := a.nwucpServer.Run(&a.wg); err != nil { 184 | return errors.Wrapf(err, "Listen NWu control plane traffic failed") 185 | } 186 | mainLog.Infof("NAS TCP server successfully started.") 187 | 188 | // User plane of N3IWF 189 | if err := a.nwuupServer.Run(&a.wg); err != nil { 190 | return errors.Wrapf(err, "Listen NWu user plane traffic failed") 191 | } 192 | mainLog.Infof("Listening NWu user plane traffic") 193 | 194 | // IKE 195 | if err := a.ikeServer.Run(&a.wg); err != nil { 196 | return errors.Wrapf(err, "Start IKE service failed") 197 | } 198 | mainLog.Infof("IKE service running") 199 | 200 | // Metrics server 201 | if a.cfg.AreMetricsEnabled() && a.metricsServer != nil { 202 | go func() { 203 | a.metricsServer.Run(&a.wg) 204 | }() 205 | } 206 | 207 | mainLog.Infof("N3IWF started") 208 | 209 | a.WaitRoutineStopped() 210 | return nil 211 | } 212 | 213 | func (a *N3iwfApp) Start() { 214 | if err := a.Run(); err != nil { 215 | logger.MainLog.Errorf("N3IWF Run err: %v", err) 216 | } 217 | } 218 | 219 | func (a *N3iwfApp) listenShutdownEvent() { 220 | defer func() { 221 | if p := recover(); p != nil { 222 | // Print stack for panic to log. Fatalf() will let program exit. 223 | logger.MainLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 224 | } 225 | a.wg.Done() 226 | }() 227 | 228 | <-a.ctx.Done() 229 | a.terminateProcedure() 230 | } 231 | 232 | func (a *N3iwfApp) WaitRoutineStopped() { 233 | a.wg.Wait() 234 | // Waiting for negotiatioon with netlink for deleting interfaces 235 | a.removeIPsecInterfaces() 236 | logger.MainLog.Infof("N3IWF App is terminated") 237 | } 238 | 239 | func (a *N3iwfApp) initDefaultXfrmInterface() error { 240 | // Setup default IPsec interface for Control Plane 241 | var linkIPSec netlink.Link 242 | var err error 243 | n3iwfCtx := a.n3iwfCtx 244 | cfg := a.Config() 245 | mainLog := logger.MainLog 246 | n3iwfIPAddr := net.ParseIP(cfg.GetIPSecGatewayAddr()).To4() 247 | n3iwfIPAddrAndSubnet := net.IPNet{IP: n3iwfIPAddr, Mask: n3iwfCtx.IPSecInnerIPPool.IPSubnet.Mask} 248 | newXfrmiName := fmt.Sprintf("%s-default", cfg.GetXfrmIfaceName()) 249 | 250 | if linkIPSec, err = xfrm.SetupIPsecXfrmi(newXfrmiName, n3iwfCtx.XfrmParentIfaceName, 251 | cfg.GetXfrmIfaceId(), n3iwfIPAddrAndSubnet); err != nil { 252 | mainLog.Errorf("Setup XFRM interface %s fail: %+v", newXfrmiName, err) 253 | return err 254 | } 255 | 256 | route := &netlink.Route{ 257 | LinkIndex: linkIPSec.Attrs().Index, 258 | Dst: n3iwfCtx.IPSecInnerIPPool.IPSubnet, 259 | } 260 | 261 | if err := netlink.RouteAdd(route); err != nil { 262 | mainLog.Warnf("netlink.RouteAdd: %+v", err) 263 | } 264 | 265 | mainLog.Infof("Setup XFRM interface %s ", newXfrmiName) 266 | 267 | n3iwfCtx.XfrmIfaces.LoadOrStore(cfg.GetXfrmIfaceId(), linkIPSec) 268 | n3iwfCtx.XfrmIfaceIdOffsetForUP = 1 269 | 270 | return nil 271 | } 272 | 273 | func (a *N3iwfApp) removeIPsecInterfaces() { 274 | mainLog := logger.MainLog 275 | a.n3iwfCtx.XfrmIfaces.Range( 276 | func(key, value interface{}) bool { 277 | iface := value.(netlink.Link) 278 | if err := netlink.LinkDel(iface); err != nil { 279 | mainLog.Errorf("Delete interface %s fail: %+v", iface.Attrs().Name, err) 280 | } else { 281 | mainLog.Infof("Delete interface: %s", iface.Attrs().Name) 282 | } 283 | return true 284 | }, 285 | ) 286 | } 287 | 288 | func (a *N3iwfApp) Terminate() { 289 | a.cancel() 290 | } 291 | 292 | func (a *N3iwfApp) terminateProcedure() { 293 | logger.MainLog.Info("Stopping service created by N3IWF") 294 | 295 | a.ngapServer.Stop() 296 | a.nwucpServer.Stop() 297 | a.nwuupServer.Stop() 298 | a.ikeServer.Stop() 299 | if a.metricsServer != nil { 300 | a.metricsServer.Stop() 301 | logger.MainLog.Infof("N3IWF Metrics Server terminated") 302 | } 303 | } 304 | 305 | func (a *N3iwfApp) SendNgapEvt(evt n3iwf_context.NgapEvt) { 306 | a.ngapServer.SendNgapEvt(evt) 307 | } 308 | 309 | func (a *N3iwfApp) SendIkeEvt(evt n3iwf_context.IkeEvt) { 310 | a.ikeServer.SendIkeEvt(evt) 311 | } 312 | -------------------------------------------------------------------------------- /internal/ike/handler_test.go: -------------------------------------------------------------------------------- 1 | package ike 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | 9 | ike_message "github.com/free5gc/ike/message" 10 | n3iwf_context "github.com/free5gc/n3iwf/internal/context" 11 | "github.com/free5gc/n3iwf/pkg/factory" 12 | "github.com/free5gc/util/ippool" 13 | ) 14 | 15 | func TestRemoveIkeUe(t *testing.T) { 16 | n3iwf, err := NewN3iwfTestApp(&factory.Config{}) 17 | require.NoError(t, err) 18 | 19 | n3iwf.ikeServer, err = NewServer(n3iwf) 20 | require.NoError(t, err) 21 | 22 | n3iwfCtx := n3iwf.n3iwfCtx 23 | ikeSA := n3iwfCtx.NewIKESecurityAssociation() 24 | ikeUe := n3iwfCtx.NewN3iwfIkeUe(ikeSA.LocalSPI) 25 | ikeUe.N3IWFIKESecurityAssociation = ikeSA 26 | ikeUe.IPSecInnerIP = net.ParseIP("10.0.0.1") 27 | ikeSA.IsUseDPD = false 28 | 29 | n3iwfCtx.IPSecInnerIPPool, err = ippool.NewIPPool("10.0.0.0/24") 30 | require.NoError(t, err) 31 | _, err = n3iwfCtx.IPSecInnerIPPool.Allocate(nil) 32 | require.NoError(t, err) 33 | 34 | ikeUe.CreateHalfChildSA(1, 123, 1) 35 | 36 | ikeAuth := &ike_message.SecurityAssociation{} 37 | 38 | proposal := ikeAuth.Proposals.BuildProposal(1, 1, []byte{0, 1, 2, 3}) 39 | var attributeType uint16 = ike_message.AttributeTypeKeyLength 40 | var attributeValue uint16 = 256 41 | proposal.EncryptionAlgorithm.BuildTransform(ike_message.TypeEncryptionAlgorithm, 42 | ike_message.ENCR_AES_CBC, &attributeType, &attributeValue, nil) 43 | 44 | proposal.IntegrityAlgorithm.BuildTransform(ike_message.TypeIntegrityAlgorithm, 45 | ike_message.AUTH_HMAC_SHA1_96, nil, nil, nil) 46 | 47 | proposal.ExtendedSequenceNumbers.BuildTransform( 48 | ike_message.TypeExtendedSequenceNumbers, ike_message.ESN_DISABLE, nil, nil, nil) 49 | 50 | childSA, err := ikeUe.CompleteChildSA(1, 456, ikeAuth) 51 | require.NoError(t, err) 52 | 53 | err = n3iwf.ikeServer.removeIkeUe(ikeSA.LocalSPI) 54 | require.NoError(t, err) 55 | 56 | _, ok := n3iwfCtx.IkeUePoolLoad(ikeSA.LocalSPI) 57 | require.False(t, ok) 58 | 59 | _, ok = n3iwfCtx.IKESALoad(ikeSA.LocalSPI) 60 | require.False(t, ok) 61 | 62 | _, ok = ikeUe.N3IWFChildSecurityAssociation[childSA.InboundSPI] 63 | require.False(t, ok) 64 | } 65 | 66 | func TestGenerateNATDetectHash(t *testing.T) { 67 | n3iwf, err := NewN3iwfTestApp(&factory.Config{}) 68 | require.NoError(t, err) 69 | 70 | n3iwf.ikeServer, err = NewServer(n3iwf) 71 | require.NoError(t, err) 72 | 73 | tests := []struct { 74 | name string 75 | initiatorSPI uint64 76 | responderSPI uint64 77 | Addr net.UDPAddr 78 | expectedData []byte 79 | }{ 80 | { 81 | name: "Generate NAT-D hash", 82 | initiatorSPI: 0x1122334455667788, 83 | responderSPI: 0xaabbeeddeeff1122, 84 | Addr: net.UDPAddr{ 85 | IP: net.ParseIP("192.168.1.1"), 86 | Port: 4500, 87 | }, 88 | expectedData: []byte{ 89 | 0xd2, 0xee, 0x40, 0x2d, 0x5d, 0x53, 0xe4, 0x4a, 90 | 0x01, 0x2d, 0x44, 0x2a, 0x90, 0x05, 0xc1, 0xea, 91 | 0x38, 0x8a, 0x81, 0x7e, 92 | }, 93 | }, 94 | } 95 | 96 | for i := range tests { 97 | tt := tests[i] 98 | t.Run(tt.name, func(t *testing.T) { 99 | data, err := n3iwf.ikeServer.generateNATDetectHash(tt.initiatorSPI, tt.responderSPI, &tt.Addr) 100 | require.NoError(t, err) 101 | 102 | require.Equal(t, tt.expectedData, data) 103 | }) 104 | } 105 | } 106 | 107 | func TestBuildNATDetectMsg(t *testing.T) { 108 | n3iwf, err := NewN3iwfTestApp(&factory.Config{}) 109 | require.NoError(t, err) 110 | 111 | n3iwf.ikeServer, err = NewServer(n3iwf) 112 | require.NoError(t, err) 113 | 114 | remoteSPI := uint64(0x1234567890abcdef) 115 | localSPI := uint64(0xfedcba0987654321) 116 | ikeSA := &n3iwf_context.IKESecurityAssociation{ 117 | LocalSPI: localSPI, 118 | RemoteSPI: remoteSPI, 119 | } 120 | payload := &ike_message.IKEPayloadContainer{} 121 | 122 | ueAddr := net.UDPAddr{ 123 | IP: net.ParseIP("192.168.1.1"), 124 | Port: 4500, 125 | } 126 | n3iwfAddr := net.UDPAddr{ 127 | IP: net.ParseIP("192.168.1.2"), 128 | Port: 4500, 129 | } 130 | 131 | err = n3iwf.ikeServer.buildNATDetectNotifPayload(ikeSA, payload, &ueAddr, &n3iwfAddr) 132 | require.NoError(t, err) 133 | 134 | var notifications []*ike_message.Notification 135 | for _, ikePayload := range *payload { 136 | switch ikePayload.Type() { 137 | case ike_message.TypeN: 138 | notifications = append(notifications, ikePayload.(*ike_message.Notification)) 139 | default: 140 | require.Fail(t, "Get unexpected IKE payload type : %v", ikePayload.Type()) 141 | } 142 | } 143 | 144 | for _, notification := range notifications { 145 | switch notification.NotifyMessageType { 146 | case ike_message.NAT_DETECTION_SOURCE_IP: 147 | expectedData := []byte{ 148 | 0x13, 0xd8, 0x9e, 0xdc, 0xfa, 0x39, 0xe4, 0xc0, 149 | 0x06, 0x80, 0x5f, 0xde, 0x11, 0x62, 0xd8, 0x76, 150 | 0xee, 0xe8, 0xf2, 0x00, 151 | } 152 | require.Equal(t, expectedData, notification.NotificationData) 153 | case ike_message.NAT_DETECTION_DESTINATION_IP: 154 | expectedData := []byte{ 155 | 0x0d, 0x36, 0x26, 0x71, 0xaf, 0x7f, 0x0b, 0x19, 156 | 0x32, 0xec, 0xf8, 0xf3, 0xe1, 0x84, 0x87, 0xf0, 157 | 0x47, 0x76, 0x83, 0x04, 158 | } 159 | require.Equal(t, expectedData, notification.NotificationData) 160 | } 161 | } 162 | } 163 | 164 | func TestHandleNATDetect(t *testing.T) { 165 | n3iwf, err := NewN3iwfTestApp(&factory.Config{}) 166 | require.NoError(t, err) 167 | 168 | n3iwf.ikeServer, err = NewServer(n3iwf) 169 | require.NoError(t, err) 170 | 171 | tests := []struct { 172 | name string 173 | initiatorSPI uint64 174 | responderSPI uint64 175 | notification []*ike_message.Notification 176 | ueAddr net.UDPAddr 177 | n3iwfAddr net.UDPAddr 178 | expectedUeBehindNAT bool 179 | expectedN3iwfBehindNAT bool 180 | }{ 181 | { 182 | name: "UE and N3IWF is not behind NAT", 183 | initiatorSPI: 0x1234567890abcdef, 184 | responderSPI: 0xfedcba0987654321, 185 | notification: []*ike_message.Notification{ 186 | { 187 | NotifyMessageType: ike_message.NAT_DETECTION_SOURCE_IP, 188 | NotificationData: []byte{ 189 | 0x0d, 0x36, 0x26, 0x71, 0xaf, 0x7f, 0x0b, 0x19, 190 | 0x32, 0xec, 0xf8, 0xf3, 0xe1, 0x84, 0x87, 0xf0, 191 | 0x47, 0x76, 0x83, 0x04, 192 | }, 193 | }, 194 | { 195 | NotifyMessageType: ike_message.NAT_DETECTION_DESTINATION_IP, 196 | NotificationData: []byte{ 197 | 0x13, 0xd8, 0x9e, 0xdc, 0xfa, 0x39, 0xe4, 0xc0, 198 | 0x06, 0x80, 0x5f, 0xde, 0x11, 0x62, 0xd8, 0x76, 199 | 0xee, 0xe8, 0xf2, 0x00, 200 | }, 201 | }, 202 | }, 203 | ueAddr: net.UDPAddr{ 204 | IP: net.ParseIP("192.168.1.1"), 205 | Port: 4500, 206 | }, 207 | n3iwfAddr: net.UDPAddr{ 208 | IP: net.ParseIP("192.168.1.2"), 209 | Port: 4500, 210 | }, 211 | expectedUeBehindNAT: false, 212 | expectedN3iwfBehindNAT: false, 213 | }, 214 | { 215 | name: "UE is behind NAT and N3IWF is not behind NAT", 216 | initiatorSPI: 0x1234567890abcdef, 217 | responderSPI: 0xfedcba0987654321, 218 | notification: []*ike_message.Notification{ 219 | { 220 | NotifyMessageType: ike_message.NAT_DETECTION_SOURCE_IP, 221 | NotificationData: []byte{ 222 | 0x0b, 0x17, 0x2d, 0x42, 0xaf, 0x7f, 0x0b, 0x19, 223 | 0x32, 0xec, 0xf8, 0xf3, 0xe1, 0x84, 0x87, 0xf0, 224 | 0x47, 0x76, 0x83, 0x04, 225 | }, 226 | }, 227 | { 228 | NotifyMessageType: ike_message.NAT_DETECTION_DESTINATION_IP, 229 | NotificationData: []byte{ 230 | 0x13, 0xd8, 0x9e, 0xdc, 0xfa, 0x39, 0xe4, 0xc0, 231 | 0x06, 0x80, 0x5f, 0xde, 0x11, 0x62, 0xd8, 0x76, 232 | 0xee, 0xe8, 0xf2, 0x00, 233 | }, 234 | }, 235 | }, 236 | ueAddr: net.UDPAddr{ 237 | IP: net.ParseIP("192.168.1.1"), 238 | Port: 4500, 239 | }, 240 | n3iwfAddr: net.UDPAddr{ 241 | IP: net.ParseIP("192.168.1.2"), 242 | Port: 4500, 243 | }, 244 | expectedUeBehindNAT: true, 245 | expectedN3iwfBehindNAT: false, 246 | }, 247 | { 248 | name: "UE and N3IWF is behind NAT", 249 | initiatorSPI: 0x1234567890abcdef, 250 | responderSPI: 0xfedcba0987654321, 251 | notification: []*ike_message.Notification{ 252 | { 253 | NotifyMessageType: ike_message.NAT_DETECTION_SOURCE_IP, 254 | NotificationData: []byte{ 255 | 0x0b, 0x16, 0x26, 0x71, 0xaf, 0x7f, 0x0b, 0x19, 256 | 0x32, 0xec, 0xf8, 0xf3, 0xe1, 0x84, 0x87, 0xf0, 257 | 0x47, 0x76, 0x83, 0x04, 258 | }, 259 | }, 260 | { 261 | NotifyMessageType: ike_message.NAT_DETECTION_DESTINATION_IP, 262 | NotificationData: []byte{ 263 | 0x0f, 0xd9, 0x9e, 0xdc, 0xfa, 0x39, 0xe4, 0xc0, 264 | 0x06, 0x80, 0x5f, 0xde, 0x11, 0x62, 0xd8, 0x76, 265 | 0xee, 0xe8, 0xf2, 0x00, 266 | }, 267 | }, 268 | }, 269 | ueAddr: net.UDPAddr{ 270 | IP: net.ParseIP("192.168.1.1"), 271 | Port: 4500, 272 | }, 273 | n3iwfAddr: net.UDPAddr{ 274 | IP: net.ParseIP("192.168.1.2"), 275 | Port: 4500, 276 | }, 277 | expectedUeBehindNAT: true, 278 | expectedN3iwfBehindNAT: true, 279 | }, 280 | } 281 | 282 | for i := range tests { 283 | tt := tests[i] 284 | t.Run(tt.name, func(t *testing.T) { 285 | ueBehindNAT, n3iwfBehindNAT, err := n3iwf.ikeServer.handleNATDetect( 286 | tt.initiatorSPI, tt.responderSPI, 287 | tt.notification, &tt.ueAddr, &tt.n3iwfAddr) 288 | require.NoError(t, err) 289 | 290 | require.Equal(t, tt.expectedUeBehindNAT, ueBehindNAT) 291 | require.Equal(t, tt.expectedN3iwfBehindNAT, n3iwfBehindNAT) 292 | }) 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /internal/nwuup/server.go: -------------------------------------------------------------------------------- 1 | package nwuup 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "runtime/debug" 7 | "sync" 8 | 9 | "github.com/pkg/errors" 10 | "github.com/sirupsen/logrus" 11 | "github.com/wmnsk/go-gtp/gtpv1" 12 | gtpMsg "github.com/wmnsk/go-gtp/gtpv1/message" 13 | "golang.org/x/net/ipv4" 14 | 15 | n3iwf_context "github.com/free5gc/n3iwf/internal/context" 16 | "github.com/free5gc/n3iwf/internal/gre" 17 | gtpQoSMsg "github.com/free5gc/n3iwf/internal/gtp/message" 18 | "github.com/free5gc/n3iwf/internal/logger" 19 | "github.com/free5gc/n3iwf/pkg/factory" 20 | ) 21 | 22 | type n3iwf interface { 23 | Config() *factory.Config 24 | Context() *n3iwf_context.N3IWFContext 25 | CancelContext() context.Context 26 | } 27 | 28 | type Server struct { 29 | n3iwf 30 | 31 | greConn *ipv4.PacketConn 32 | gtpuConn *gtpv1.UPlaneConn 33 | log *logrus.Entry 34 | } 35 | 36 | func NewServer(n3iwf n3iwf) (*Server, error) { 37 | s := &Server{ 38 | n3iwf: n3iwf, 39 | log: logger.NWuUPLog, 40 | } 41 | return s, nil 42 | } 43 | 44 | // Run bind and listen IPv4 packet connection on N3IWF NWu interface 45 | // with UP_IP_ADDRESS, catching GRE encapsulated packets and forward 46 | // to N3 interface. 47 | func (s *Server) Run(wg *sync.WaitGroup) error { 48 | err := s.newGreConn() 49 | if err != nil { 50 | return err 51 | } 52 | 53 | err = s.newGtpuConn() 54 | if err != nil { 55 | return err 56 | } 57 | 58 | wg.Add(1) 59 | go s.greListenAndServe(wg) 60 | 61 | wg.Add(1) 62 | go s.gtpuListenAndServe(wg) 63 | 64 | return nil 65 | } 66 | 67 | func (s *Server) newGreConn() error { 68 | listenAddr := s.Config().GetIPSecGatewayAddr() 69 | 70 | // Setup IPv4 packet connection socket 71 | // This socket will only capture GRE encapsulated packet 72 | connection, err := net.ListenPacket("ip4:gre", listenAddr) 73 | if err != nil { 74 | return errors.Wrapf(err, "Error setting GRE listen socket on %s", listenAddr) 75 | } 76 | s.greConn = ipv4.NewPacketConn(connection) 77 | if s.greConn == nil { 78 | return errors.Wrapf(err, "Error opening GRE IPv4 packet connection socket on %s", listenAddr) 79 | } 80 | return nil 81 | } 82 | 83 | func (s *Server) newGtpuConn() error { 84 | gtpuAddr := s.Config().GetN3iwfGtpBindAddress() + gtpv1.GTPUPort 85 | 86 | laddr, err := net.ResolveUDPAddr("udp", gtpuAddr) 87 | if err != nil { 88 | return errors.Wrapf(err, "Resolve GTP-U address %s Failed", gtpuAddr) 89 | } 90 | 91 | upConn := gtpv1.NewUPlaneConn(laddr) 92 | // Overwrite T-PDU handler for supporting extension header containing QoS parameters 93 | upConn.AddHandler(gtpMsg.MsgTypeTPDU, s.handleQoSTPDU) 94 | s.gtpuConn = upConn 95 | return nil 96 | } 97 | 98 | // listenAndServe read from socket and call forward() to 99 | // forward packet. 100 | func (s *Server) greListenAndServe(wg *sync.WaitGroup) { 101 | nwuupLog := s.log 102 | defer func() { 103 | if p := recover(); p != nil { 104 | // Print stack for panic to log. Fatalf() will let program exit. 105 | nwuupLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 106 | } 107 | 108 | err := s.greConn.Close() 109 | if err != nil { 110 | nwuupLog.Errorf("Error closing raw socket: %+v", err) 111 | } 112 | wg.Done() 113 | }() 114 | 115 | buf := make([]byte, factory.MAX_BUF_MSG_LEN) 116 | 117 | err := s.greConn.SetControlMessage(ipv4.FlagInterface|ipv4.FlagTTL, true) 118 | if err != nil { 119 | nwuupLog.Errorf("Set control message visibility for IPv4 packet connection fail: %+v", err) 120 | return 121 | } 122 | 123 | for { 124 | n, cm, src, err := s.greConn.ReadFrom(buf) 125 | nwuupLog.Tracef("Read %d bytes, %s", n, cm) 126 | if err != nil { 127 | nwuupLog.Errorf("Error read from IPv4 packet connection: %+v", err) 128 | return 129 | } 130 | 131 | forwardData := make([]byte, n) 132 | copy(forwardData, buf) 133 | 134 | wg.Add(1) 135 | go s.forwardUL(src.String(), cm.IfIndex, forwardData, wg) 136 | } 137 | } 138 | 139 | func (s *Server) gtpuListenAndServe(wg *sync.WaitGroup) { 140 | nwuupLog := s.log 141 | defer func() { 142 | if p := recover(); p != nil { 143 | // Print stack for panic to log. Fatalf() will let program exit. 144 | nwuupLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 145 | } 146 | 147 | wg.Done() 148 | }() 149 | 150 | if err := s.gtpuConn.ListenAndServe(context.Background()); err != nil { 151 | nwuupLog.Errorf("GTP-U server err: %v", err) 152 | } 153 | } 154 | 155 | // forward forwards user plane packets from NWu to UPF 156 | // with GTP header encapsulated 157 | func (s *Server) forwardUL(ueInnerIP string, ifIndex int, rawData []byte, wg *sync.WaitGroup) { 158 | nwuupLog := s.log 159 | defer func() { 160 | if p := recover(); p != nil { 161 | // Print stack for panic to log. Fatalf() will let program exit. 162 | nwuupLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 163 | } 164 | wg.Done() 165 | }() 166 | 167 | // Find UE information 168 | n3iwfCtx := s.Context() 169 | ikeUe, ok := n3iwfCtx.AllocatedUEIPAddressLoad(ueInnerIP) 170 | if !ok { 171 | nwuupLog.Error("Ike UE context not found") 172 | return 173 | } 174 | 175 | ranUe, err := n3iwfCtx.RanUeLoadFromIkeSPI(ikeUe.N3IWFIKESecurityAssociation.LocalSPI) 176 | if err != nil { 177 | nwuupLog.Error("ranUe not found") 178 | return 179 | } 180 | 181 | var pduSession *n3iwf_context.PDUSession 182 | 183 | for _, childSA := range ikeUe.N3IWFChildSecurityAssociation { 184 | // Check which child SA the packet come from with interface index, 185 | // and find the corresponding PDU session 186 | if childSA.XfrmIface != nil && childSA.XfrmIface.Attrs().Index == ifIndex { 187 | pduSession = ranUe.GetSharedCtx().PduSessionList[childSA.PDUSessionIds[0]] 188 | break 189 | } 190 | } 191 | 192 | if pduSession == nil { 193 | nwuupLog.Error("This UE doesn't have any available PDU session") 194 | return 195 | } 196 | 197 | gtpConnection := pduSession.GTPConnInfo 198 | 199 | // Decapsulate GRE header and extract QoS Parameters if exist 200 | grePacket := gre.GREPacket{} 201 | if err := grePacket.Unmarshal(rawData); err != nil { 202 | nwuupLog.Errorf("gre Unmarshal err: %+v", err) 203 | return 204 | } 205 | 206 | var ( 207 | n int 208 | writeErr error 209 | ) 210 | 211 | payload, _ := grePacket.GetPayload() 212 | 213 | // Encapsulate UL PDU SESSION INFORMATION with extension header if the QoS parameters exist 214 | if grePacket.GetKeyFlag() { 215 | qfi, err := grePacket.GetQFI() 216 | if err != nil { 217 | nwuupLog.Errorf("forwardUL err: %+v", err) 218 | return 219 | } 220 | gtpPacket, err := gtpQoSMsg.BuildQoSGTPPacket(gtpConnection.OutgoingTEID, qfi, payload) 221 | if err != nil { 222 | nwuupLog.Errorf("buildQoSGTPPacket err: %+v", err) 223 | return 224 | } 225 | 226 | n, writeErr = s.gtpuConn.WriteTo(gtpPacket, gtpConnection.UPFUDPAddr) 227 | } else { 228 | nwuupLog.Warnf("Receive GRE header without key field specifying QFI and RQI.") 229 | n, writeErr = s.gtpuConn.WriteToGTP(gtpConnection.OutgoingTEID, payload, gtpConnection.UPFUDPAddr) 230 | } 231 | 232 | if writeErr != nil { 233 | nwuupLog.Errorf("Write to UPF failed: %+v", writeErr) 234 | if writeErr == gtpv1.ErrConnNotOpened { 235 | nwuupLog.Error("The connection has been closed") 236 | // TODO: Release the GTP resource 237 | } 238 | return 239 | } 240 | nwuupLog.Trace("Forward NWu -> N3") 241 | nwuupLog.Tracef("Wrote %d bytes", n) 242 | } 243 | 244 | func (s *Server) Stop() { 245 | nwuupLog := s.log 246 | nwuupLog.Infof("Close Nwuup server...") 247 | 248 | if err := s.greConn.Close(); err != nil { 249 | nwuupLog.Errorf("Stop nwuup greConn error : %v", err) 250 | } 251 | 252 | if err := s.gtpuConn.Close(); err != nil { 253 | nwuupLog.Errorf("Stop nwuup gtpuConn error : %v", err) 254 | } 255 | } 256 | 257 | // Parse the fields not supported by go-gtp and forward data to UE. 258 | func (s *Server) handleQoSTPDU(c gtpv1.Conn, senderAddr net.Addr, msg gtpMsg.Message) error { 259 | pdu := gtpQoSMsg.QoSTPDUPacket{} 260 | err := pdu.Unmarshal(msg.(*gtpMsg.TPDU)) 261 | if err != nil { 262 | return err 263 | } 264 | 265 | s.forwardDL(pdu) 266 | return nil 267 | } 268 | 269 | // Forward user plane packets from N3 to UE with GRE header and new IP header encapsulated 270 | func (s *Server) forwardDL(packet gtpQoSMsg.QoSTPDUPacket) { 271 | nwuupLog := s.log 272 | 273 | defer func() { 274 | if p := recover(); p != nil { 275 | // Print stack for panic to log. Fatalf() will let program exit. 276 | nwuupLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 277 | } 278 | }() 279 | 280 | n3iwfCtx := s.Context() 281 | pktTEID := packet.GetTEID() 282 | nwuupLog.Tracef("pkt teid : %d", pktTEID) 283 | 284 | // Find UE information 285 | ranUe, ok := n3iwfCtx.AllocatedUETEIDLoad(pktTEID) 286 | if !ok { 287 | nwuupLog.Errorf("Cannot find RanUE context from QosPacket TEID : %+v", pktTEID) 288 | return 289 | } 290 | ranUeNgapID := ranUe.GetSharedCtx().RanUeNgapId 291 | 292 | ikeUe, err := n3iwfCtx.IkeUeLoadFromNgapId(ranUeNgapID) 293 | if err != nil { 294 | nwuupLog.Errorf("Cannot find IkeUe context from RanUe , NgapID : %+v", ranUeNgapID) 295 | return 296 | } 297 | 298 | // UE inner IP in IPSec 299 | ueInnerIPAddr := ikeUe.IPSecInnerIPAddr 300 | 301 | var cm *ipv4.ControlMessage 302 | for _, childSA := range ikeUe.N3IWFChildSecurityAssociation { 303 | pdusession := ranUe.FindPDUSession(childSA.PDUSessionIds[0]) 304 | if pdusession != nil && pdusession.GTPConnInfo.IncomingTEID == pktTEID { 305 | nwuupLog.Tracef("forwarding IPSec xfrm interfaceid : %d", childSA.XfrmIface.Attrs().Index) 306 | cm = &ipv4.ControlMessage{ 307 | IfIndex: childSA.XfrmIface.Attrs().Index, 308 | } 309 | break 310 | } 311 | } 312 | if cm == nil { 313 | nwuupLog.Warnf("forwardDL(): Cannot match TEID(%d) to ChildSA", pktTEID) 314 | return 315 | } 316 | 317 | var ( 318 | qfi uint8 319 | rqi bool 320 | ) 321 | 322 | // QoS Related Parameter 323 | if packet.HasQoS() { 324 | qfi, rqi = packet.GetQoSParameters() 325 | nwuupLog.Tracef("QFI: %v, RQI: %v", qfi, rqi) 326 | } 327 | 328 | // Encasulate IPv4 packet with GRE header before forward to UE through IPsec 329 | grePacket := gre.GREPacket{} 330 | 331 | // TODO:[24.502(v15.7) 9.3.3 ] The Protocol Type field should be set to zero 332 | grePacket.SetPayload(packet.GetPayload(), gre.IPv4) 333 | grePacket.SetQoS(qfi, rqi) 334 | forwardData := grePacket.Marshal() 335 | 336 | // Send to UE through Nwu 337 | if n, err := s.greConn.WriteTo(forwardData, cm, ueInnerIPAddr); err != nil { 338 | nwuupLog.Errorf("Write to UE failed: %+v", err) 339 | return 340 | } else { 341 | nwuupLog.Trace("Forward NWu <- N3") 342 | nwuupLog.Tracef("Wrote %d bytes", n) 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /internal/ngap/eap5g.go: -------------------------------------------------------------------------------- 1 | package ngap 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | 7 | "github.com/free5gc/aper" 8 | "github.com/free5gc/n3iwf/internal/logger" 9 | "github.com/free5gc/n3iwf/internal/ngap/message" 10 | "github.com/free5gc/ngap/ngapType" 11 | ) 12 | 13 | // 3GPP specified EAP-5G 14 | 15 | // Access Network Parameters 16 | type ANParameters struct { 17 | GUAMI *ngapType.GUAMI 18 | SelectedPLMNID *ngapType.PLMNIdentity 19 | RequestedNSSAI *ngapType.AllowedNSSAI 20 | EstablishmentCause *ngapType.RRCEstablishmentCause 21 | } 22 | 23 | func UnmarshalEAP5GData( 24 | codedData []byte, 25 | ) ( 26 | anParameters *ANParameters, 27 | nasPDU []byte, 28 | err error, 29 | ) { 30 | ngapLog := logger.NgapLog 31 | if len(codedData) < 2 { 32 | return nil, nil, errors.New("no data to decode") 33 | } 34 | 35 | ngapLog.Debug("===== Unmarshal EAP5G Data (Ref: TS24.502 Fig. 9.3.2.2.2-1) =====") 36 | 37 | codedData = codedData[2:] 38 | 39 | // [TS 24.502 f30] 9.3.2.2.2.3 40 | // AN-parameter value field in GUAMI, PLMN ID and NSSAI is coded as value part 41 | // Therefore, IEI of AN-parameter is not needed to be included. 42 | // anParameter = AN-parameter Type | AN-parameter Length | Value part of IE 43 | 44 | if len(codedData) < 2 { 45 | ngapLog.Error("No AN-Parameter type or length specified") 46 | return nil, nil, errors.New("error formatting") 47 | } 48 | 49 | // Length of the AN-Parameter field 50 | anParameterLength := binary.BigEndian.Uint16(codedData[:2]) 51 | ngapLog.Debugf("AN-parameters length: %d", anParameterLength) 52 | 53 | if anParameterLength != 0 { 54 | anParameterField := codedData[2:] 55 | 56 | // Bound checking 57 | if len(anParameterField) < int(anParameterLength) { 58 | ngapLog.Error("Packet contained error length of value") 59 | return nil, nil, errors.New("error formatting") 60 | } 61 | anParameterField = anParameterField[:anParameterLength] 62 | 63 | ngapLog.Debugf("Parsing AN-parameters...: % v", anParameterField) 64 | 65 | anParameters = new(ANParameters) 66 | 67 | // Parse AN-Parameters 68 | for len(anParameterField) >= 2 { 69 | parameterType := anParameterField[0] 70 | // The AN-parameter length field indicates the length of the AN-parameter value field. 71 | parameterLength := anParameterField[1] 72 | 73 | switch parameterType { 74 | case message.ANParametersTypeGUAMI: 75 | ngapLog.Debugf("-> Parameter type: GUAMI") 76 | if parameterLength != 0 { 77 | parameterValue := anParameterField[2:] 78 | 79 | if len(parameterValue) < int(parameterLength) { 80 | return nil, nil, errors.New("error formatting") 81 | } 82 | parameterValue = parameterValue[:parameterLength] 83 | 84 | if len(parameterValue) != message.ANParametersLenGUAMI { 85 | return nil, nil, errors.New("unmatched GUAMI length") 86 | } 87 | 88 | guamiField := make([]byte, 1) 89 | guamiField = append(guamiField, parameterValue...) 90 | // Decode GUAMI using aper 91 | ngapGUAMI := new(ngapType.GUAMI) 92 | err = aper.UnmarshalWithParams(guamiField, ngapGUAMI, "valueExt") 93 | if err != nil { 94 | ngapLog.Errorf("APER unmarshal with parameter failed: %+v", err) 95 | return nil, nil, errors.New("unmarshal failed when decoding GUAMI") 96 | } 97 | anParameters.GUAMI = ngapGUAMI 98 | ngapLog.Debugf("Unmarshal GUAMI: % x", guamiField) 99 | ngapLog.Debugf("\tGUAMI: PLMNIdentity[% x], "+ 100 | "AMFRegionID[% x], AMFSetID[% x], AMFPointer[% x]", 101 | anParameters.GUAMI.PLMNIdentity, anParameters.GUAMI.AMFRegionID, 102 | anParameters.GUAMI.AMFSetID, anParameters.GUAMI.AMFPointer) 103 | } else { 104 | ngapLog.Warn("AN-Parameter GUAMI field empty") 105 | } 106 | case message.ANParametersTypeSelectedPLMNID: 107 | ngapLog.Debugf("-> Parameter type: ANParametersTypeSelectedPLMNID") 108 | if parameterLength != 0 { 109 | parameterValue := anParameterField[2:] 110 | 111 | if len(parameterValue) < int(parameterLength) { 112 | return nil, nil, errors.New("error formatting") 113 | } 114 | parameterValue = parameterValue[:parameterLength] 115 | 116 | if len(parameterValue) != message.ANParametersLenPLMNID { 117 | return nil, nil, errors.New("unmatched PLMN ID length") 118 | } 119 | 120 | plmnField := make([]byte, 1) 121 | plmnField = append(plmnField, parameterValue...) 122 | // Decode PLMN using aper 123 | ngapPLMN := new(ngapType.PLMNIdentity) 124 | err = aper.UnmarshalWithParams(plmnField, ngapPLMN, "valueExt") 125 | if err != nil { 126 | ngapLog.Errorf("APER unmarshal with parameter failed: %v", err) 127 | return nil, nil, errors.New("unmarshal failed when decoding PLMN") 128 | } 129 | anParameters.SelectedPLMNID = ngapPLMN 130 | ngapLog.Debugf("Unmarshal SelectedPLMNID: % x", plmnField) 131 | ngapLog.Debugf("\tSelectedPLMNID: % x", anParameters.SelectedPLMNID.Value) 132 | } else { 133 | ngapLog.Warn("AN-Parameter PLMN field empty") 134 | } 135 | case message.ANParametersTypeRequestedNSSAI: 136 | ngapLog.Debugf("-> Parameter type: ANParametersTypeRequestedNSSAI") 137 | if parameterLength != 0 { 138 | parameterValue := anParameterField[2:] 139 | 140 | if len(parameterValue) < int(parameterLength) { 141 | return nil, nil, errors.New("error formatting") 142 | } 143 | parameterValue = parameterValue[:parameterLength] 144 | 145 | ngapNSSAI := new(ngapType.AllowedNSSAI) 146 | 147 | // [TS 24501 f30] 9.11.2.8 S-NSSAI 148 | // s-nssai(LV) consists of 149 | // len(1 byte) | SST(1) | SD(3,opt) | Mapped HPLMN SST (1,opt) | Mapped HPLMN SD (3,opt) 150 | // The length of minimum s-nssai comprised of a length and a SST is 2 bytes. 151 | 152 | for len(parameterValue) >= 2 { 153 | snssaiLength := parameterValue[0] 154 | snssaiValue := parameterValue[1:] 155 | 156 | if len(snssaiValue) < int(snssaiLength) { 157 | ngapLog.Error("SNSSAI length error") 158 | return nil, nil, errors.New("error formatting") 159 | } 160 | snssaiValue = snssaiValue[:snssaiLength] 161 | 162 | ngapSNSSAIItem := ngapType.AllowedNSSAIItem{} 163 | 164 | if len(snssaiValue) == 1 { 165 | ngapSNSSAIItem.SNSSAI = ngapType.SNSSAI{ 166 | SST: ngapType.SST{ 167 | Value: []byte{snssaiValue[0]}, 168 | }, 169 | } 170 | } else if len(snssaiValue) == 4 { 171 | ngapSNSSAIItem.SNSSAI = ngapType.SNSSAI{ 172 | SST: ngapType.SST{ 173 | Value: []byte{snssaiValue[0]}, 174 | }, 175 | SD: &ngapType.SD{ 176 | Value: []byte{snssaiValue[1], snssaiValue[2], snssaiValue[3]}, 177 | }, 178 | } 179 | } else { 180 | ngapLog.Error("Empty SNSSAI value") 181 | return nil, nil, errors.New("error formatting") 182 | } 183 | 184 | ngapNSSAI.List = append(ngapNSSAI.List, ngapSNSSAIItem) 185 | 186 | ngapLog.Debugf("Unmarshal SNSSAI: % x", parameterValue[:1+snssaiLength]) 187 | ngapLog.Debugf("\t\t\tSST: % x", ngapSNSSAIItem.SNSSAI.SST.Value) 188 | sd := ngapSNSSAIItem.SNSSAI.SD 189 | if sd == nil { 190 | ngapLog.Debugf("\t\t\tSD: nil") 191 | } else { 192 | ngapLog.Debugf("\t\t\tSD: % x", sd.Value) 193 | } 194 | 195 | // shift parameterValue for parsing next s-nssai 196 | parameterValue = parameterValue[1+snssaiLength:] 197 | } 198 | anParameters.RequestedNSSAI = ngapNSSAI 199 | } else { 200 | ngapLog.Warn("AN-Parameter NSSAI is empty") 201 | } 202 | case message.ANParametersTypeEstablishmentCause: 203 | ngapLog.Debugf("-> Parameter type: ANParametersTypeEstablishmentCause") 204 | if parameterLength != 0 { 205 | parameterValue := anParameterField[2:] 206 | 207 | if len(parameterValue) < int(parameterLength) { 208 | return nil, nil, errors.New("error formatting") 209 | } 210 | parameterValue = parameterValue[:parameterLength] 211 | 212 | if len(parameterValue) != message.ANParametersLenEstCause { 213 | return nil, nil, errors.New("unmatched Establishment Cause length") 214 | } 215 | 216 | ngapLog.Debugf("Unmarshal ANParametersTypeEstablishmentCause: % x", parameterValue) 217 | 218 | establishmentCause := parameterValue[0] & 0x0f 219 | switch establishmentCause { 220 | case message.EstablishmentCauseEmergency: 221 | ngapLog.Trace("AN-Parameter establishment cause: Emergency") 222 | case message.EstablishmentCauseHighPriorityAccess: 223 | ngapLog.Trace("AN-Parameter establishment cause: High Priority Access") 224 | case message.EstablishmentCauseMO_Signalling: 225 | ngapLog.Trace("AN-Parameter establishment cause: MO Signalling") 226 | case message.EstablishmentCauseMO_Data: 227 | ngapLog.Trace("AN-Parameter establishment cause: MO Data") 228 | case message.EstablishmentCauseMPS_PriorityAccess: 229 | ngapLog.Trace("AN-Parameter establishment cause: MPS Priority Access") 230 | case message.EstablishmentCauseMCS_PriorityAccess: 231 | ngapLog.Trace("AN-Parameter establishment cause: MCS Priority Access") 232 | default: 233 | ngapLog.Trace("AN-Parameter establishment cause: Unknown. Treat as mo-Data") 234 | establishmentCause = message.EstablishmentCauseMO_Data 235 | } 236 | 237 | ngapEstablishmentCause := new(ngapType.RRCEstablishmentCause) 238 | ngapEstablishmentCause.Value = aper.Enumerated(establishmentCause) 239 | 240 | anParameters.EstablishmentCause = ngapEstablishmentCause 241 | } else { 242 | ngapLog.Warn("AN-Parameter establishment cause field empty") 243 | } 244 | default: 245 | ngapLog.Warn("Unsopprted AN-Parameter. Ignore.") 246 | } 247 | 248 | // shift anParameterField 249 | anParameterField = anParameterField[2+parameterLength:] 250 | } 251 | } 252 | 253 | // shift codedData 254 | codedData = codedData[2+anParameterLength:] 255 | 256 | if len(codedData) < 2 { 257 | ngapLog.Error("No NASPDU length specified") 258 | return nil, nil, errors.New("error formatting") 259 | } 260 | // Length of the NASPDU field 261 | nasPDULength := binary.BigEndian.Uint16(codedData[:2]) 262 | ngapLog.Debugf("nasPDULength: %d", nasPDULength) 263 | 264 | if nasPDULength == 0 { 265 | ngapLog.Error("No NAS PDU included in EAP-5G packet") 266 | return nil, nil, errors.New("no NAS PDU") 267 | } 268 | 269 | nasPDUField := codedData[2:] 270 | // Bound checking 271 | if len(nasPDUField) < int(nasPDULength) { 272 | return nil, nil, errors.New("error formatting") 273 | } else { 274 | nasPDUField = nasPDUField[:nasPDULength] 275 | } 276 | ngapLog.Debugf("nasPDUField: % v", nasPDUField) 277 | 278 | nasPDU = append(nasPDU, nasPDUField...) 279 | return anParameters, nasPDU, err 280 | } 281 | -------------------------------------------------------------------------------- /internal/context/ikeue.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "net" 7 | 8 | "github.com/pkg/errors" 9 | "github.com/vishvananda/netlink" 10 | 11 | ike_message "github.com/free5gc/ike/message" 12 | ike_security "github.com/free5gc/ike/security" 13 | ) 14 | 15 | const ( 16 | AmfUeNgapIdUnspecified int64 = 0xffffffffff 17 | ) 18 | 19 | type N3IWFIkeUe struct { 20 | N3iwfCtx *N3IWFContext 21 | 22 | // UE identity 23 | IPSecInnerIP net.IP 24 | IPSecInnerIPAddr *net.IPAddr // Used to send UP packets to UE 25 | 26 | // IKE Security Association 27 | N3IWFIKESecurityAssociation *IKESecurityAssociation 28 | N3IWFChildSecurityAssociation map[uint32]*ChildSecurityAssociation // inbound SPI as key 29 | 30 | // Temporary Mapping of two SPIs 31 | // Exchange Message ID(including a SPI) and ChildSA(including a SPI) 32 | // Mapping of Message ID of exchange in IKE and Child SA when creating new child SA 33 | TemporaryExchangeMsgIDChildSAMapping map[uint32]*ChildSecurityAssociation // Message ID as a key 34 | 35 | // Security 36 | Kn3iwf []uint8 // 32 bytes (256 bits), value is from NGAP IE "Security Key" 37 | 38 | // NAS IKE Connection 39 | IKEConnection *UDPSocketInfo 40 | 41 | // Length of PDU Session List 42 | PduSessionListLen int 43 | } 44 | 45 | type IkeMsgTemporaryData struct { 46 | SecurityAssociation *ike_message.SecurityAssociation 47 | TrafficSelectorInitiator *ike_message.TrafficSelectorInitiator 48 | TrafficSelectorResponder *ike_message.TrafficSelectorResponder 49 | } 50 | 51 | type IKESecurityAssociation struct { 52 | *ike_security.IKESAKey 53 | // SPI 54 | RemoteSPI uint64 55 | LocalSPI uint64 56 | 57 | // Message ID 58 | InitiatorMessageID uint32 59 | ResponderMessageID uint32 60 | 61 | // Used for key generating 62 | ConcatenatedNonce []byte 63 | 64 | // State for IKE_AUTH 65 | State uint8 66 | 67 | // Temporary data stored for the use in later exchange 68 | InitiatorID *ike_message.IdentificationInitiator 69 | InitiatorCertificate *ike_message.Certificate 70 | IKEAuthResponseSA *ike_message.SecurityAssociation 71 | TrafficSelectorInitiator *ike_message.TrafficSelectorInitiator 72 | TrafficSelectorResponder *ike_message.TrafficSelectorResponder 73 | LastEAPIdentifier uint8 74 | 75 | // UDP Connection 76 | IKEConnection *UDPSocketInfo 77 | 78 | // Authentication data 79 | ResponderSignedOctets []byte 80 | InitiatorSignedOctets []byte 81 | 82 | // NAT detection 83 | UeBehindNAT bool // If true, N3IWF should enable NAT traversal and 84 | N3iwfBehindNAT bool // TODO: If true, N3IWF should send UDP keepalive periodically 85 | 86 | // IKE UE context 87 | IkeUE *N3IWFIkeUe 88 | 89 | // Temporary store the receive ike message 90 | TemporaryIkeMsg *IkeMsgTemporaryData 91 | 92 | DPDReqRetransTimer *Timer // The time from sending the DPD request to receiving the response 93 | CurrentRetryTimes int32 // Accumulate the number of times the DPD response wasn't received 94 | IKESAClosedCh chan struct{} 95 | IsUseDPD bool 96 | } 97 | 98 | func (ikeSA *IKESecurityAssociation) String() string { 99 | return "====== IKE Security Association Info =====" + 100 | "\nInitiator's SPI: " + fmt.Sprintf("%016x", ikeSA.RemoteSPI) + 101 | "\nResponder's SPI: " + fmt.Sprintf("%016x", ikeSA.LocalSPI) + 102 | "\nIKESAKey: " + ikeSA.IKESAKey.String() 103 | } 104 | 105 | // Temporary State Data Args 106 | const ( 107 | ArgsUEUDPConn string = "UE UDP Socket Info" 108 | ) 109 | 110 | type ChildSecurityAssociation struct { 111 | // SPI 112 | InboundSPI uint32 // N3IWF Specify 113 | OutboundSPI uint32 // Non-3GPP UE Specify 114 | 115 | // Associated XFRM interface 116 | XfrmIface netlink.Link 117 | 118 | XfrmStateList []netlink.XfrmState 119 | XfrmPolicyList []netlink.XfrmPolicy 120 | 121 | // IP address 122 | PeerPublicIPAddr net.IP 123 | LocalPublicIPAddr net.IP 124 | 125 | // Traffic selector 126 | SelectedIPProtocol uint8 127 | TrafficSelectorLocal net.IPNet 128 | TrafficSelectorRemote net.IPNet 129 | 130 | // Security 131 | *ike_security.ChildSAKey 132 | 133 | // Encapsulate 134 | EnableEncapsulate bool 135 | N3IWFPort int 136 | NATPort int 137 | 138 | // PDU Session IDs associated with this child SA 139 | PDUSessionIds []int64 140 | 141 | // IKE UE context 142 | IkeUE *N3IWFIkeUe 143 | 144 | LocalIsInitiator bool 145 | } 146 | 147 | func (childSA *ChildSecurityAssociation) String(xfrmiId uint32) string { 148 | var inboundEncryptionKey, inboundIntegrityKey, outboundEncryptionKey, outboundIntegrityKey []byte 149 | 150 | if childSA.LocalIsInitiator { 151 | inboundEncryptionKey = childSA.ResponderToInitiatorEncryptionKey 152 | inboundIntegrityKey = childSA.ResponderToInitiatorIntegrityKey 153 | outboundEncryptionKey = childSA.InitiatorToResponderEncryptionKey 154 | outboundIntegrityKey = childSA.InitiatorToResponderIntegrityKey 155 | } else { 156 | inboundEncryptionKey = childSA.InitiatorToResponderEncryptionKey 157 | inboundIntegrityKey = childSA.InitiatorToResponderIntegrityKey 158 | outboundEncryptionKey = childSA.ResponderToInitiatorEncryptionKey 159 | outboundIntegrityKey = childSA.ResponderToInitiatorIntegrityKey 160 | } 161 | 162 | return fmt.Sprintf("====== IPSec/Child SA Info ======"+ 163 | "\n====== Inbound ======"+ 164 | "\nXFRM interface if_id: %d"+ 165 | "\nIPSec Inbound SPI: 0x%08x"+ 166 | "\n[UE:%+v] -> [N3IWF:%+v]"+ 167 | "\nIPSec Encryption Algorithm: %d"+ 168 | "\nIPSec Encryption Key: 0x%x"+ 169 | "\nIPSec Integrity Algorithm: %d"+ 170 | "\nIPSec Integrity Key: 0x%x"+ 171 | "\n====== IPSec/Child SA Info ======"+ 172 | "\n====== Outbound ======"+ 173 | "\nXFRM interface if_id: %d"+ 174 | "\nIPSec Outbound SPI: 0x%08x"+ 175 | "\n[N3IWF:%+v] -> [UE:%+v]"+ 176 | "\nIPSec Encryption Algorithm: %d"+ 177 | "\nIPSec Encryption Key: 0x%x"+ 178 | "\nIPSec Integrity Algorithm: %d"+ 179 | "\nIPSec Integrity Key: 0x%x", 180 | xfrmiId, 181 | childSA.InboundSPI, 182 | childSA.PeerPublicIPAddr, 183 | childSA.LocalPublicIPAddr, 184 | childSA.EncrKInfo.TransformID(), 185 | inboundEncryptionKey, 186 | childSA.IntegKInfo.TransformID(), 187 | inboundIntegrityKey, 188 | xfrmiId, 189 | childSA.OutboundSPI, 190 | childSA.LocalPublicIPAddr, 191 | childSA.PeerPublicIPAddr, 192 | childSA.EncrKInfo.TransformID(), 193 | outboundEncryptionKey, 194 | childSA.IntegKInfo.TransformID(), 195 | outboundIntegrityKey, 196 | ) 197 | } 198 | 199 | type UDPSocketInfo struct { 200 | Conn *net.UDPConn 201 | N3IWFAddr *net.UDPAddr 202 | UEAddr *net.UDPAddr 203 | } 204 | 205 | func (ikeUe *N3IWFIkeUe) init() { 206 | ikeUe.N3IWFChildSecurityAssociation = make(map[uint32]*ChildSecurityAssociation) 207 | ikeUe.TemporaryExchangeMsgIDChildSAMapping = make(map[uint32]*ChildSecurityAssociation) 208 | } 209 | 210 | func (ikeUe *N3IWFIkeUe) Remove() error { 211 | if ikeUe.N3IWFIKESecurityAssociation.IsUseDPD { 212 | ikeUe.N3IWFIKESecurityAssociation.IKESAClosedCh <- struct{}{} 213 | } 214 | 215 | // remove from IKE UE context 216 | n3iwfCtx := ikeUe.N3iwfCtx 217 | n3iwfCtx.DeleteIKESecurityAssociation(ikeUe.N3IWFIKESecurityAssociation.LocalSPI) 218 | n3iwfCtx.DeleteInternalUEIPAddr(ikeUe.IPSecInnerIP.String()) 219 | 220 | err := n3iwfCtx.IPSecInnerIPPool.Release(net.ParseIP(ikeUe.IPSecInnerIP.String()).To4()) 221 | if err != nil { 222 | return errors.Wrapf(err, "N3IWFIkeUe Remove()") 223 | } 224 | 225 | for _, childSA := range ikeUe.N3IWFChildSecurityAssociation { 226 | if err := ikeUe.DeleteChildSA(childSA); err != nil { 227 | return err 228 | } 229 | } 230 | n3iwfCtx.DeleteIKEUe(ikeUe.N3IWFIKESecurityAssociation.LocalSPI) 231 | 232 | return nil 233 | } 234 | 235 | func (ikeUe *N3IWFIkeUe) DeleteChildSAXfrm(childSA *ChildSecurityAssociation) error { 236 | n3iwfCtx := ikeUe.N3iwfCtx 237 | iface := childSA.XfrmIface 238 | 239 | // Delete child SA xfrmState 240 | for idx := range childSA.XfrmStateList { 241 | xfrmState := childSA.XfrmStateList[idx] 242 | if err := netlink.XfrmStateDel(&xfrmState); err != nil { 243 | return errors.Wrapf(err, "Delete xfrmstate") 244 | } 245 | } 246 | // Delete child SA xfrmPolicy 247 | for idx := range childSA.XfrmPolicyList { 248 | xfrmPolicy := childSA.XfrmPolicyList[idx] 249 | if err := netlink.XfrmPolicyDel(&xfrmPolicy); err != nil { 250 | return errors.Wrapf(err, "Delete xfrmPolicy") 251 | } 252 | } 253 | 254 | if iface == nil || iface.Attrs().Name == "xfrmi-default" { 255 | } else if err := netlink.LinkDel(iface); err != nil { 256 | return errors.Wrapf(err, "Delete interface[%s]", iface.Attrs().Name) 257 | } else { 258 | ifId := childSA.XfrmStateList[0].Ifid 259 | if ifId < 0 || ifId > math.MaxUint32 { 260 | return errors.Errorf("DeleteChildSAXfrm Ifid has out of uint32 range value: %d", ifId) 261 | } 262 | n3iwfCtx.XfrmIfaces.Delete(uint32(ifId)) 263 | } 264 | 265 | childSA.XfrmStateList = nil 266 | childSA.XfrmPolicyList = nil 267 | 268 | return nil 269 | } 270 | 271 | func (ikeUe *N3IWFIkeUe) DeleteChildSA(childSA *ChildSecurityAssociation) error { 272 | if err := ikeUe.DeleteChildSAXfrm(childSA); err != nil { 273 | return err 274 | } 275 | 276 | delete(ikeUe.N3IWFChildSecurityAssociation, childSA.InboundSPI) 277 | 278 | return nil 279 | } 280 | 281 | // When N3IWF send CREATE_CHILD_SA request to N3UE, the inbound SPI of childSA will be only stored first until 282 | // receive response and call CompleteChildSAWithProposal to fill the all data of childSA 283 | func (ikeUe *N3IWFIkeUe) CreateHalfChildSA(msgID, inboundSPI uint32, pduSessionID int64) { 284 | childSA := new(ChildSecurityAssociation) 285 | childSA.InboundSPI = inboundSPI 286 | childSA.PDUSessionIds = append(childSA.PDUSessionIds, pduSessionID) 287 | // Link UE context 288 | childSA.IkeUE = ikeUe 289 | // Map Exchange Message ID and Child SA data until get paired response 290 | ikeUe.TemporaryExchangeMsgIDChildSAMapping[msgID] = childSA 291 | } 292 | 293 | func (ikeUe *N3IWFIkeUe) CompleteChildSA(msgID uint32, outboundSPI uint32, 294 | chosenSecurityAssociation *ike_message.SecurityAssociation, 295 | ) (*ChildSecurityAssociation, error) { 296 | childSA, ok := ikeUe.TemporaryExchangeMsgIDChildSAMapping[msgID] 297 | 298 | if !ok { 299 | return nil, fmt.Errorf("there's not a half child SA created by the exchange with message ID %d", msgID) 300 | } 301 | 302 | // Remove mapping of exchange msg ID and child SA 303 | delete(ikeUe.TemporaryExchangeMsgIDChildSAMapping, msgID) 304 | 305 | if chosenSecurityAssociation == nil { 306 | return nil, errors.New("chosenSecurityAssociation is nil") 307 | } 308 | 309 | if len(chosenSecurityAssociation.Proposals) == 0 { 310 | return nil, errors.New("no proposal") 311 | } 312 | 313 | childSA.OutboundSPI = outboundSPI 314 | 315 | var err error 316 | childSA.ChildSAKey, err = ike_security.NewChildSAKeyByProposal(chosenSecurityAssociation.Proposals[0]) 317 | if err != nil { 318 | return nil, errors.Wrapf(err, "CompleteChildSA") 319 | } 320 | 321 | // Record to UE context with inbound SPI as key 322 | ikeUe.N3IWFChildSecurityAssociation[childSA.InboundSPI] = childSA 323 | // Record to N3IWF context with inbound SPI as key 324 | ikeUe.N3iwfCtx.ChildSA.Store(childSA.InboundSPI, childSA) 325 | 326 | return childSA, nil 327 | } 328 | -------------------------------------------------------------------------------- /internal/ike/server.go: -------------------------------------------------------------------------------- 1 | package ike 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/hex" 7 | "fmt" 8 | "net" 9 | "runtime/debug" 10 | "sync" 11 | "syscall" 12 | 13 | "github.com/google/gopacket" 14 | "github.com/google/gopacket/layers" 15 | "github.com/pkg/errors" 16 | 17 | "github.com/free5gc/ike" 18 | ike_message "github.com/free5gc/ike/message" 19 | n3iwf_context "github.com/free5gc/n3iwf/internal/context" 20 | "github.com/free5gc/n3iwf/internal/logger" 21 | "github.com/free5gc/n3iwf/pkg/factory" 22 | "github.com/free5gc/util/safe_channel" 23 | ) 24 | 25 | const ( 26 | RECEIVE_IKEPACKET_CHANNEL_LEN = 512 27 | RECEIVE_IKEEVENT_CHANNEL_LEN = 512 28 | 29 | DEFAULT_IKE_PORT = 500 30 | DEFAULT_NATT_PORT = 4500 31 | ) 32 | 33 | type n3iwf interface { 34 | Config() *factory.Config 35 | Context() *n3iwf_context.N3IWFContext 36 | CancelContext() context.Context 37 | 38 | SendNgapEvt(n3iwf_context.NgapEvt) 39 | } 40 | 41 | type EspHandler func(srcIP, dstIP *net.UDPAddr, espPkt []byte) error 42 | 43 | type Server struct { 44 | n3iwf 45 | 46 | Listener map[int]*net.UDPConn 47 | StopServer chan struct{} 48 | rcvPktCh *safe_channel.SafeCh[IkeReceivePacket] 49 | rcvEvtCh *safe_channel.SafeCh[n3iwf_context.IkeEvt] 50 | } 51 | 52 | type IkeReceivePacket struct { 53 | Listener net.UDPConn 54 | LocalAddr net.UDPAddr 55 | RemoteAddr net.UDPAddr 56 | Msg []byte 57 | } 58 | 59 | func NewServer(n3iwf n3iwf) (*Server, error) { 60 | s := &Server{ 61 | n3iwf: n3iwf, 62 | Listener: make(map[int]*net.UDPConn), 63 | StopServer: make(chan struct{}), 64 | } 65 | s.rcvPktCh = safe_channel.NewSafeCh[IkeReceivePacket](RECEIVE_IKEPACKET_CHANNEL_LEN) 66 | s.rcvEvtCh = safe_channel.NewSafeCh[n3iwf_context.IkeEvt](RECEIVE_IKEEVENT_CHANNEL_LEN) 67 | return s, nil 68 | } 69 | 70 | func (s *Server) Run(wg *sync.WaitGroup) error { 71 | cfg := s.Config() 72 | 73 | // Resolve UDP addresses 74 | ip := cfg.GetIKEBindAddr() 75 | ikeAddrPort, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ip, DEFAULT_IKE_PORT)) 76 | if err != nil { 77 | return err 78 | } 79 | nattAddrPort, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ip, DEFAULT_NATT_PORT)) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | // Listen and serve 85 | var errChan chan error 86 | 87 | wg.Add(1) 88 | errChan = make(chan error) 89 | go s.receiver(ikeAddrPort, errChan, wg) 90 | if err, ok := <-errChan; ok { 91 | return errors.Wrapf(err, "ikeAddrPort") 92 | } 93 | 94 | wg.Add(1) 95 | errChan = make(chan error) 96 | go s.receiver(nattAddrPort, errChan, wg) 97 | if err, ok := <-errChan; ok { 98 | return errors.Wrapf(err, "nattAddrPort") 99 | } 100 | 101 | wg.Add(1) 102 | go s.server(wg) 103 | 104 | return nil 105 | } 106 | 107 | func (s *Server) server(wg *sync.WaitGroup) { 108 | ikeLog := logger.IKELog 109 | defer func() { 110 | if p := recover(); p != nil { 111 | // Print stack for panic to log. Fatalf() will let program exit. 112 | ikeLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 113 | } 114 | ikeLog.Infof("Ike server stopped") 115 | s.rcvPktCh.Close() 116 | s.rcvEvtCh.Close() 117 | close(s.StopServer) 118 | wg.Done() 119 | }() 120 | 121 | rcvEvtCh := s.rcvEvtCh.GetRcvChan() 122 | rcvPktCh := s.rcvPktCh.GetRcvChan() 123 | 124 | for { 125 | select { 126 | case rcvPkt := <-rcvPktCh: 127 | ikeMsg, ikeSA, err := s.checkIKEMessage( 128 | rcvPkt.Msg, &rcvPkt.Listener, &rcvPkt.LocalAddr, &rcvPkt.RemoteAddr) 129 | if err != nil { 130 | ikeLog.Warnln(err) 131 | continue 132 | } 133 | s.Dispatch(&rcvPkt.Listener, &rcvPkt.LocalAddr, &rcvPkt.RemoteAddr, 134 | ikeMsg, rcvPkt.Msg, ikeSA) 135 | case rcvIkeEvent := <-rcvEvtCh: 136 | s.HandleEvent(rcvIkeEvent) 137 | case <-s.StopServer: 138 | return 139 | } 140 | } 141 | } 142 | 143 | func (s *Server) receiver( 144 | localAddr *net.UDPAddr, 145 | errChan chan<- error, 146 | wg *sync.WaitGroup, 147 | ) { 148 | ikeLog := logger.IKELog 149 | defer func() { 150 | if p := recover(); p != nil { 151 | // Print stack for panic to log. Fatalf() will let program exit. 152 | ikeLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 153 | } 154 | ikeLog.Infof("Ike receiver stopped") 155 | wg.Done() 156 | }() 157 | 158 | listener, err := net.ListenUDP("udp", localAddr) 159 | if err != nil { 160 | ikeLog.Errorf("Listen UDP failed: %+v", err) 161 | errChan <- errors.New("listenAndServe failed") 162 | return 163 | } 164 | 165 | close(errChan) 166 | 167 | s.Listener[localAddr.Port] = listener 168 | 169 | buf := make([]byte, factory.MAX_BUF_MSG_LEN) 170 | 171 | for { 172 | n, remoteAddr, err := listener.ReadFromUDP(buf) 173 | if err != nil { 174 | ikeLog.Errorf("ReadFromUDP failed: %+v", err) 175 | return 176 | } 177 | 178 | msgBuf := make([]byte, n) 179 | copy(msgBuf, buf) 180 | ikeLog.Tracef("recv from port(%d):\n%s", localAddr.Port, hex.Dump(msgBuf)) 181 | 182 | // As specified in RFC 7296 section 3.1, the IKE message send from/to UDP port 4500 183 | // should prepend a 4 bytes zero 184 | if localAddr.Port == DEFAULT_NATT_PORT { 185 | msgBuf, err = handleNattMsg(msgBuf, remoteAddr, localAddr, handleESPPacket) 186 | if err != nil { 187 | ikeLog.Errorf("Handle NATT msg: %v", err) 188 | continue 189 | } 190 | if msgBuf == nil { 191 | continue 192 | } 193 | } 194 | 195 | if len(msgBuf) < ike_message.IKE_HEADER_LEN { 196 | ikeLog.Warnf("Received IKE msg is too short from %s", remoteAddr) 197 | continue 198 | } 199 | 200 | ikePkt := IkeReceivePacket{ 201 | RemoteAddr: *remoteAddr, 202 | Listener: *listener, 203 | LocalAddr: *localAddr, 204 | Msg: msgBuf, 205 | } 206 | s.rcvPktCh.Send(ikePkt) 207 | } 208 | } 209 | 210 | func handleNattMsg( 211 | msgBuf []byte, 212 | rAddr, lAddr *net.UDPAddr, 213 | espHandler EspHandler, 214 | ) ([]byte, error) { 215 | if len(msgBuf) == 1 && msgBuf[0] == 0xff { 216 | // skip NAT-T Keepalive 217 | return nil, nil 218 | } 219 | 220 | nonEspMarker := []byte{0, 0, 0, 0} // Non-ESP Marker 221 | nonEspMarkerLen := len(nonEspMarker) 222 | if len(msgBuf) < nonEspMarkerLen { 223 | return nil, errors.Errorf("Received msg is too short") 224 | } 225 | if !bytes.Equal(msgBuf[:nonEspMarkerLen], nonEspMarker) { 226 | // ESP packet 227 | if espHandler != nil { 228 | err := espHandler(rAddr, lAddr, msgBuf) 229 | if err != nil { 230 | return nil, errors.Wrapf(err, "Handle ESP") 231 | } 232 | } 233 | return nil, nil 234 | } 235 | 236 | // IKE message: skip Non-ESP Marker 237 | msgBuf = msgBuf[nonEspMarkerLen:] 238 | return msgBuf, nil 239 | } 240 | 241 | func (s *Server) SendIkeEvt(evt n3iwf_context.IkeEvt) { 242 | s.rcvEvtCh.Send(evt) 243 | } 244 | 245 | func (s *Server) Stop() { 246 | ikeLog := logger.IKELog 247 | ikeLog.Infof("Close Ike server...") 248 | 249 | for _, ikeServerListener := range s.Listener { 250 | if err := ikeServerListener.Close(); err != nil { 251 | ikeLog.Errorf("Stop ike server : %s error : %+v", err, ikeServerListener.LocalAddr().String()) 252 | } 253 | } 254 | 255 | s.StopServer <- struct{}{} 256 | } 257 | 258 | func (s *Server) checkIKEMessage( 259 | msg []byte, udpConn *net.UDPConn, 260 | localAddr, remoteAddr *net.UDPAddr, 261 | ) (*ike_message.IKEMessage, 262 | *n3iwf_context.IKESecurityAssociation, error, 263 | ) { 264 | var ikeHeader *ike_message.IKEHeader 265 | var ikeMessage *ike_message.IKEMessage 266 | var ikeSA *n3iwf_context.IKESecurityAssociation 267 | var err error 268 | 269 | // parse IKE header and setup IKE context 270 | ikeHeader, err = ike_message.ParseHeader(msg) 271 | if err != nil { 272 | return nil, nil, errors.Wrapf(err, "IKE msg decode header") 273 | } 274 | 275 | // check major version 276 | if ikeHeader.MajorVersion > 2 { 277 | // send INFORMATIONAL type message with INVALID_MAJOR_VERSION Notify payload 278 | // For response or needed data 279 | payload := new(ike_message.IKEPayloadContainer) 280 | payload.BuildNotification(ike_message.TypeNone, 281 | ike_message.INVALID_MAJOR_VERSION, nil, nil) 282 | responseIKEMessage := ike_message.NewMessage(ikeHeader.InitiatorSPI, ikeHeader.ResponderSPI, 283 | ike_message.INFORMATIONAL, true, false, ikeHeader.MessageID, *payload) 284 | 285 | err = SendIKEMessageToUE(udpConn, localAddr, remoteAddr, responseIKEMessage, nil) 286 | if err != nil { 287 | return nil, nil, errors.Wrapf(err, "Received an IKE message with higher major version "+ 288 | "(%d>2)", ikeHeader.MajorVersion) 289 | } 290 | return nil, nil, errors.Errorf("Received an IKE message with higher major version (%d>2)", ikeHeader.MajorVersion) 291 | } 292 | 293 | if ikeHeader.ExchangeType == ike_message.IKE_SA_INIT { 294 | ikeMessage, err = ike.DecodeDecrypt(msg, ikeHeader, 295 | nil, ike_message.Role_Responder) 296 | if err != nil { 297 | return nil, nil, errors.Wrapf(err, "Decrypt IkeMsg error") 298 | } 299 | } else if ikeHeader.ExchangeType != ike_message.IKE_SA_INIT { 300 | localSPI := ikeHeader.ResponderSPI 301 | var ok bool 302 | n3iwfCtx := s.Context() 303 | 304 | ikeSA, ok = n3iwfCtx.IKESALoad(localSPI) 305 | if !ok { 306 | payload := new(ike_message.IKEPayloadContainer) 307 | // send INFORMATIONAL type message with INVALID_IKE_SPI Notify payload ( OUTSIDE IKE SA ) 308 | payload.BuildNotification(ike_message.TypeNone, ike_message.INVALID_IKE_SPI, nil, nil) 309 | responseIKEMessage := ike_message.NewMessage(ikeHeader.InitiatorSPI, ikeHeader.ResponderSPI, 310 | ike_message.INFORMATIONAL, true, false, ikeHeader.MessageID, *payload) 311 | 312 | err = SendIKEMessageToUE(udpConn, localAddr, remoteAddr, responseIKEMessage, nil) 313 | if err != nil { 314 | return nil, nil, errors.Wrapf(err, "checkIKEMessage():") 315 | } 316 | return nil, nil, errors.Errorf("Received an unrecognized SPI message: %d", localSPI) 317 | } 318 | 319 | ikeMessage, err = ike.DecodeDecrypt(msg, ikeHeader, 320 | ikeSA.IKESAKey, ike_message.Role_Responder) 321 | if err != nil { 322 | return nil, nil, errors.Wrapf(err, "Decrypt IkeMsg error") 323 | } 324 | } 325 | 326 | return ikeMessage, ikeSA, nil 327 | } 328 | 329 | func constructPacketWithESP(srcIP, dstIP *net.UDPAddr, espPacket []byte) ([]byte, error) { 330 | ipLayer := &layers.IPv4{ 331 | SrcIP: srcIP.IP, 332 | DstIP: dstIP.IP, 333 | Version: 4, 334 | TTL: 64, 335 | Protocol: layers.IPProtocolESP, 336 | } 337 | 338 | buffer := gopacket.NewSerializeBuffer() 339 | options := gopacket.SerializeOptions{ 340 | ComputeChecksums: true, 341 | FixLengths: true, 342 | } 343 | 344 | err := gopacket.SerializeLayers(buffer, 345 | options, 346 | ipLayer, 347 | gopacket.Payload(espPacket), 348 | ) 349 | if err != nil { 350 | return nil, errors.Errorf("Error serializing layers: %v", err) 351 | } 352 | 353 | packetData := buffer.Bytes() 354 | return packetData, nil 355 | } 356 | 357 | func handleESPPacket(srcIP, dstIP *net.UDPAddr, espPacket []byte) error { 358 | ikeLog := logger.IKELog 359 | ikeLog.Tracef("Handle ESPPacket") 360 | 361 | fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW) 362 | if err != nil { 363 | return errors.Errorf("socket error: %v", err) 364 | } 365 | 366 | defer func() { 367 | if err = syscall.Close(fd); err != nil { 368 | ikeLog.Errorf("Close fd error : %v", err) 369 | } 370 | }() 371 | 372 | ipPacket, err := constructPacketWithESP(srcIP, dstIP, espPacket) 373 | if err != nil { 374 | return err 375 | } 376 | 377 | addr := syscall.SockaddrInet4{ 378 | Addr: [4]byte(dstIP.IP), 379 | Port: dstIP.Port, 380 | } 381 | 382 | err = syscall.Sendto(fd, ipPacket, 0, &addr) 383 | if err != nil { 384 | return errors.Errorf("sendto error: %v", err) 385 | } 386 | 387 | return nil 388 | } 389 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2019 Communication Service/Software Laboratory, National Chiao Tung University 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /internal/context/context.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/sha1" // #nosec G505 8 | "crypto/x509" 9 | "encoding/pem" 10 | "fmt" 11 | "math" 12 | "math/big" 13 | "net" 14 | "os" 15 | "path/filepath" 16 | "strings" 17 | "sync" 18 | 19 | "github.com/pkg/errors" 20 | gtpv1 "github.com/wmnsk/go-gtp/gtpv1" 21 | 22 | "github.com/free5gc/n3iwf/internal/logger" 23 | "github.com/free5gc/n3iwf/pkg/factory" 24 | "github.com/free5gc/ngap/ngapType" 25 | "github.com/free5gc/sctp" 26 | "github.com/free5gc/util/idgenerator" 27 | "github.com/free5gc/util/ippool" 28 | ) 29 | 30 | type n3iwf interface { 31 | Config() *factory.Config 32 | CancelContext() context.Context 33 | } 34 | 35 | type N3IWFContext struct { 36 | n3iwf 37 | 38 | // ID generator 39 | RANUENGAPIDGenerator *idgenerator.IDGenerator 40 | TEIDGenerator *idgenerator.IDGenerator 41 | 42 | // Pools 43 | AMFPool sync.Map // map[string]*N3IWFAMF, SCTPAddr as key 44 | AMFReInitAvailableList sync.Map // map[string]bool, SCTPAddr as key 45 | IKESA sync.Map // map[uint64]*IKESecurityAssociation, SPI as key 46 | ChildSA sync.Map // map[uint32]*ChildSecurityAssociation, inboundSPI as key 47 | GTPConnectionWithUPF sync.Map // map[string]*gtpv1.UPlaneConn, UPF address as key 48 | AllocatedUEIPAddress sync.Map // map[string]*N3IWFIkeUe, IPAddr as key 49 | AllocatedUETEID sync.Map // map[uint32]*RanUe, TEID as key 50 | IKEUePool sync.Map // map[uint64]*N3IWFIkeUe, SPI as key 51 | RANUePool sync.Map // map[int64]*RanUe, RanUeNgapID as key 52 | IKESPIToNGAPId sync.Map // map[uint64]RanUeNgapID, SPI as key 53 | NGAPIdToIKESPI sync.Map // map[uint64]SPI, RanUeNgapID as key 54 | 55 | // Security data 56 | CertificateAuthority []byte 57 | N3IWFCertificate []byte 58 | N3IWFPrivateKey *rsa.PrivateKey 59 | 60 | IPSecInnerIPPool *ippool.IPPool 61 | // TODO: [TWIF] TwifUe may has its own IP address pool 62 | 63 | // XFRM interface 64 | XfrmIfaces sync.Map // map[uint32]*netlink.Link, XfrmIfaceId as key 65 | XfrmParentIfaceName string 66 | // Every UE's first UP IPsec will use default XFRM interface, additoinal UP IPsec will offset its XFRM id 67 | XfrmIfaceIdOffsetForUP uint32 68 | } 69 | 70 | func NewContext(n3iwf n3iwf) (*N3IWFContext, error) { 71 | n := &N3IWFContext{ 72 | n3iwf: n3iwf, 73 | RANUENGAPIDGenerator: idgenerator.NewGenerator(0, math.MaxInt64), 74 | TEIDGenerator: idgenerator.NewGenerator(1, math.MaxUint32), 75 | } 76 | cfg := n3iwf.Config() 77 | 78 | // Private key 79 | block, _, err := decodePEM(cfg.GetIKECertKeyPath()) 80 | if err != nil { 81 | return nil, errors.Wrapf(err, "IKE PrivKey") 82 | } 83 | key, err := x509.ParsePKCS8PrivateKey(block.Bytes) 84 | if err != nil { 85 | logger.CtxLog.Warnf("Parse PKCS8 private key failed: %v", err) 86 | logger.CtxLog.Info("Parse using PKCS1...") 87 | 88 | key, err = x509.ParsePKCS1PrivateKey(block.Bytes) 89 | if err != nil { 90 | return nil, errors.Errorf("Parse PKCS1 pricate key failed: %v", err) 91 | } 92 | } 93 | rsaKey, ok := key.(*rsa.PrivateKey) 94 | if !ok { 95 | return nil, errors.Errorf("Private key is not an rsa private key") 96 | } 97 | n.N3IWFPrivateKey = rsaKey 98 | 99 | // Certificate authority 100 | block, _, err = decodePEM(cfg.GetIKECAPemPath()) 101 | if err != nil { 102 | return nil, errors.Wrapf(err, "IKE CA") 103 | } 104 | cert, err := x509.ParseCertificate(block.Bytes) 105 | if err != nil { 106 | return nil, errors.Errorf("Parse certificate authority failed: %v", err) 107 | } 108 | // Get sha1 hash of subject public key info 109 | sha1Hash := sha1.New() // #nosec G401 110 | _, err = sha1Hash.Write(cert.RawSubjectPublicKeyInfo) 111 | if err != nil { 112 | return nil, errors.Errorf("Hash function writing failed: %+v", err) 113 | } 114 | n.CertificateAuthority = sha1Hash.Sum(nil) 115 | 116 | // Certificate 117 | block, _, err = decodePEM(cfg.GetIKECertPemPath()) 118 | if err != nil { 119 | return nil, errors.Wrapf(err, "IKE Cert") 120 | } 121 | n.N3IWFCertificate = block.Bytes 122 | 123 | // UE IP address range 124 | ueIPPool, err := ippool.NewIPPool(cfg.GetUEIPAddrRange()) 125 | if err != nil { 126 | return nil, errors.Errorf("NewContext(): %+v", err) 127 | } 128 | n.IPSecInnerIPPool = ueIPPool 129 | 130 | // XFRM related 131 | ikeBindIfaceName, err := getInterfaceName(cfg.GetIKEBindAddr()) 132 | if err != nil { 133 | return nil, err 134 | } 135 | n.XfrmParentIfaceName = ikeBindIfaceName 136 | 137 | return n, nil 138 | } 139 | 140 | func decodePEM(path string) (*pem.Block, []byte, error) { 141 | content, err := os.ReadFile(filepath.Clean(path)) 142 | if err != nil { 143 | return nil, nil, errors.Wrapf(err, "Cannot read file(%s)", path) 144 | } 145 | p, rest := pem.Decode(content) 146 | if p == nil { 147 | return nil, nil, errors.Errorf("Decode pem failed") 148 | } 149 | return p, rest, nil 150 | } 151 | 152 | func getInterfaceName(IPAddress string) (interfaceName string, err error) { 153 | interfaces, err := net.Interfaces() 154 | if err != nil { 155 | return "nil", err 156 | } 157 | 158 | res, err := net.ResolveIPAddr("ip4", IPAddress) 159 | if err != nil { 160 | return "", fmt.Errorf("Error resolving address [%s]: %v", IPAddress, err) 161 | } 162 | IPAddress = res.String() 163 | 164 | for _, inter := range interfaces { 165 | addrs, err := inter.Addrs() 166 | if err != nil { 167 | return "nil", err 168 | } 169 | for _, addr := range addrs { 170 | if IPAddress == addr.String()[0:strings.Index(addr.String(), "/")] { 171 | return inter.Name, nil 172 | } 173 | } 174 | } 175 | return "", fmt.Errorf("cannot find interface name for IP[%s]", IPAddress) 176 | } 177 | 178 | func (c *N3IWFContext) NewN3iwfIkeUe(spi uint64) *N3IWFIkeUe { 179 | n3iwfIkeUe := &N3IWFIkeUe{ 180 | N3iwfCtx: c, 181 | } 182 | n3iwfIkeUe.init() 183 | c.IKEUePool.Store(spi, n3iwfIkeUe) 184 | return n3iwfIkeUe 185 | } 186 | 187 | func (c *N3IWFContext) NewN3iwfRanUe() *N3IWFRanUe { 188 | ranUeNgapId, err := c.RANUENGAPIDGenerator.Allocate() 189 | if err != nil { 190 | logger.CtxLog.Errorf("New N3IWF UE failed: %+v", err) 191 | return nil 192 | } 193 | n3iwfRanUe := &N3IWFRanUe{ 194 | RanUeSharedCtx: RanUeSharedCtx{ 195 | N3iwfCtx: c, 196 | }, 197 | } 198 | n3iwfRanUe.init(ranUeNgapId) 199 | c.RANUePool.Store(ranUeNgapId, n3iwfRanUe) 200 | 201 | return n3iwfRanUe 202 | } 203 | 204 | func (c *N3IWFContext) DeleteRanUe(ranUeNgapId int64) { 205 | c.RANUePool.Delete(ranUeNgapId) 206 | c.DeleteIkeSPIFromNgapId(ranUeNgapId) 207 | } 208 | 209 | func (c *N3IWFContext) DeleteIKEUe(spi uint64) { 210 | c.IKEUePool.Delete(spi) 211 | c.DeleteNgapIdFromIkeSPI(spi) 212 | } 213 | 214 | func (c *N3IWFContext) IkeUePoolLoad(spi uint64) (*N3IWFIkeUe, bool) { 215 | ikeUe, ok := c.IKEUePool.Load(spi) 216 | if ok { 217 | return ikeUe.(*N3IWFIkeUe), ok 218 | } else { 219 | return nil, ok 220 | } 221 | } 222 | 223 | func (c *N3IWFContext) RanUePoolLoad(id interface{}) (RanUe, bool) { 224 | var ranUeNgapId int64 225 | 226 | cfgLog := logger.CfgLog 227 | switch id := id.(type) { 228 | case int64: 229 | ranUeNgapId = id 230 | default: 231 | cfgLog.Warnf("RanUePoolLoad unhandle type: %t", id) 232 | return nil, false 233 | } 234 | 235 | ranUe, ok := c.RANUePool.Load(ranUeNgapId) 236 | if ok { 237 | return ranUe.(RanUe), ok 238 | } else { 239 | return nil, ok 240 | } 241 | } 242 | 243 | func (c *N3IWFContext) IkeSpiNgapIdMapping(spi uint64, ranUeNgapId int64) { 244 | c.IKESPIToNGAPId.Store(spi, ranUeNgapId) 245 | c.NGAPIdToIKESPI.Store(ranUeNgapId, spi) 246 | } 247 | 248 | func (c *N3IWFContext) IkeSpiLoad(ranUeNgapId int64) (uint64, bool) { 249 | spi, ok := c.NGAPIdToIKESPI.Load(ranUeNgapId) 250 | if ok { 251 | return spi.(uint64), ok 252 | } 253 | return 0, false 254 | } 255 | 256 | func (c *N3IWFContext) NgapIdLoad(spi uint64) (int64, bool) { 257 | ranNgapId, ok := c.IKESPIToNGAPId.Load(spi) 258 | if ok { 259 | return ranNgapId.(int64), ok 260 | } 261 | return 0, false 262 | } 263 | 264 | func (c *N3IWFContext) DeleteNgapIdFromIkeSPI(spi uint64) { 265 | c.IKESPIToNGAPId.Delete(spi) 266 | } 267 | 268 | func (c *N3IWFContext) DeleteIkeSPIFromNgapId(ranUeNgapId int64) { 269 | c.NGAPIdToIKESPI.Delete(ranUeNgapId) 270 | } 271 | 272 | func (c *N3IWFContext) RanUeLoadFromIkeSPI(spi uint64) (RanUe, error) { 273 | ranNgapId, ok := c.IKESPIToNGAPId.Load(spi) 274 | if ok { 275 | ranUe, err := c.RanUePoolLoad(ranNgapId.(int64)) 276 | if !err { 277 | return nil, fmt.Errorf("cannot find RanUE from RanNgapId : %+v", ranNgapId) 278 | } 279 | return ranUe, nil 280 | } 281 | return nil, fmt.Errorf("cannot find RanNgapId from IkeUe SPI : %+v", spi) 282 | } 283 | 284 | func (c *N3IWFContext) IkeUeLoadFromNgapId(ranUeNgapId int64) (*N3IWFIkeUe, error) { 285 | spi, ok := c.NGAPIdToIKESPI.Load(ranUeNgapId) 286 | if ok { 287 | ikeUe, err := c.IkeUePoolLoad(spi.(uint64)) 288 | if !err { 289 | return nil, fmt.Errorf("cannot find IkeUe from spi : %+v", spi) 290 | } 291 | return ikeUe, nil 292 | } 293 | return nil, fmt.Errorf("cannot find SPI from NgapId : %+v", ranUeNgapId) 294 | } 295 | 296 | func (c *N3IWFContext) NewN3iwfAmf(sctpAddr string, conn *sctp.SCTPConn) *N3IWFAMF { 297 | amf := new(N3IWFAMF) 298 | amf.init(sctpAddr, conn) 299 | item, loaded := c.AMFPool.LoadOrStore(sctpAddr, amf) 300 | if loaded { 301 | logger.CtxLog.Warn("[Context] NewN3iwfAmf(): AMF entry already exists.") 302 | return item.(*N3IWFAMF) 303 | } 304 | return amf 305 | } 306 | 307 | func (c *N3IWFContext) DeleteN3iwfAmf(sctpAddr string) { 308 | c.AMFPool.Delete(sctpAddr) 309 | } 310 | 311 | func (c *N3IWFContext) AMFPoolLoad(sctpAddr string) (*N3IWFAMF, bool) { 312 | amf, ok := c.AMFPool.Load(sctpAddr) 313 | if ok { 314 | return amf.(*N3IWFAMF), ok 315 | } 316 | return nil, false 317 | } 318 | 319 | func (c *N3IWFContext) DeleteAMFReInitAvailableFlag(sctpAddr string) { 320 | c.AMFReInitAvailableList.Delete(sctpAddr) 321 | } 322 | 323 | func (c *N3IWFContext) AMFReInitAvailableListLoad(sctpAddr string) (bool, bool) { 324 | flag, ok := c.AMFReInitAvailableList.Load(sctpAddr) 325 | if ok { 326 | return flag.(bool), ok 327 | } 328 | return true, false 329 | } 330 | 331 | func (c *N3IWFContext) AMFReInitAvailableListStore(sctpAddr string, flag bool) { 332 | c.AMFReInitAvailableList.Store(sctpAddr, flag) 333 | } 334 | 335 | func (c *N3IWFContext) NewIKESecurityAssociation() *IKESecurityAssociation { 336 | ikeSecurityAssociation := new(IKESecurityAssociation) 337 | 338 | maxSPI := new(big.Int).SetUint64(math.MaxUint64) 339 | var localSPIuint64 uint64 340 | 341 | for { 342 | localSPI, err := rand.Int(rand.Reader, maxSPI) 343 | if err != nil { 344 | logger.CtxLog.Error("[Context] Error occurs when generate new IKE SPI") 345 | return nil 346 | } 347 | localSPIuint64 = localSPI.Uint64() 348 | _, duplicate := c.IKESA.LoadOrStore(localSPIuint64, ikeSecurityAssociation) 349 | if !duplicate { 350 | break 351 | } 352 | } 353 | 354 | ikeSecurityAssociation.LocalSPI = localSPIuint64 355 | 356 | return ikeSecurityAssociation 357 | } 358 | 359 | func (c *N3IWFContext) DeleteIKESecurityAssociation(spi uint64) { 360 | c.IKESA.Delete(spi) 361 | } 362 | 363 | func (c *N3IWFContext) IKESALoad(spi uint64) (*IKESecurityAssociation, bool) { 364 | securityAssociation, ok := c.IKESA.Load(spi) 365 | if ok { 366 | return securityAssociation.(*IKESecurityAssociation), ok 367 | } 368 | return nil, false 369 | } 370 | 371 | func (c *N3IWFContext) DeleteGTPConnection(upfAddr string) { 372 | c.GTPConnectionWithUPF.Delete(upfAddr) 373 | } 374 | 375 | func (c *N3IWFContext) GTPConnectionWithUPFLoad(upfAddr string) (*gtpv1.UPlaneConn, bool) { 376 | conn, ok := c.GTPConnectionWithUPF.Load(upfAddr) 377 | if ok { 378 | return conn.(*gtpv1.UPlaneConn), ok 379 | } 380 | return nil, false 381 | } 382 | 383 | func (c *N3IWFContext) GTPConnectionWithUPFStore(upfAddr string, conn *gtpv1.UPlaneConn) { 384 | c.GTPConnectionWithUPF.Store(upfAddr, conn) 385 | } 386 | 387 | func (c *N3IWFContext) NewIPsecInnerUEIP(ikeUe *N3IWFIkeUe) (net.IP, error) { 388 | var ueIPAddr net.IP 389 | var err error 390 | cfg := c.Config() 391 | ipsecGwAddr := cfg.GetIPSecGatewayAddr() 392 | 393 | for { 394 | ueIPAddr, err = c.IPSecInnerIPPool.Allocate(nil) 395 | if err != nil { 396 | return nil, errors.Wrapf(err, "NewIPsecInnerUEIP()") 397 | } 398 | if ueIPAddr.String() == ipsecGwAddr { 399 | continue 400 | } 401 | _, ok := c.AllocatedUEIPAddress.LoadOrStore(ueIPAddr.String(), ikeUe) 402 | if ok { 403 | logger.CtxLog.Warnf("NewIPsecInnerUEIP(): IP(%v) is used by other IkeUE", 404 | ueIPAddr.String()) 405 | } else { 406 | break 407 | } 408 | } 409 | 410 | return ueIPAddr, nil 411 | } 412 | 413 | func (c *N3IWFContext) DeleteInternalUEIPAddr(ipAddr string) { 414 | c.AllocatedUEIPAddress.Delete(ipAddr) 415 | } 416 | 417 | func (c *N3IWFContext) AllocatedUEIPAddressLoad(ipAddr string) (*N3IWFIkeUe, bool) { 418 | ikeUe, ok := c.AllocatedUEIPAddress.Load(ipAddr) 419 | if ok { 420 | return ikeUe.(*N3IWFIkeUe), ok 421 | } 422 | return nil, false 423 | } 424 | 425 | func (c *N3IWFContext) NewTEID(ranUe RanUe) uint32 { 426 | teid64, err := c.TEIDGenerator.Allocate() 427 | if err != nil { 428 | logger.CtxLog.Errorf("New TEID failed: %+v", err) 429 | return 0 430 | } 431 | if teid64 < 0 || teid64 > math.MaxUint32 { 432 | logger.CtxLog.Warnf("NewTEID teid64 out of uint32 range: %d, use maxUint32", teid64) 433 | return 0 434 | } 435 | teid32 := uint32(teid64) 436 | 437 | c.AllocatedUETEID.Store(teid32, ranUe) 438 | 439 | return teid32 440 | } 441 | 442 | func (c *N3IWFContext) DeleteTEID(teid uint32) { 443 | c.TEIDGenerator.FreeID(int64(teid)) 444 | c.AllocatedUETEID.Delete(teid) 445 | } 446 | 447 | func (c *N3IWFContext) AllocatedUETEIDLoad(teid uint32) (RanUe, bool) { 448 | ranUe, ok := c.AllocatedUETEID.Load(teid) 449 | if ok { 450 | return ranUe.(RanUe), ok 451 | } 452 | return nil, false 453 | } 454 | 455 | func (c *N3IWFContext) AMFSelection( 456 | ueSpecifiedGUAMI *ngapType.GUAMI, 457 | ueSpecifiedPLMNId *ngapType.PLMNIdentity, 458 | ) *N3IWFAMF { 459 | var availableAMF, defaultAMF *N3IWFAMF 460 | c.AMFPool.Range(func(key, value interface{}) bool { 461 | amf := value.(*N3IWFAMF) 462 | if defaultAMF == nil { 463 | defaultAMF = amf 464 | } 465 | if amf.FindAvalibleAMFByCompareGUAMI(ueSpecifiedGUAMI) { 466 | availableAMF = amf 467 | return false 468 | } else { 469 | // Fail to find through GUAMI served by UE. 470 | // Try again using SelectedPLMNId 471 | if amf.FindAvalibleAMFByCompareSelectedPLMNId(ueSpecifiedPLMNId) { 472 | availableAMF = amf 473 | return false 474 | } 475 | return true 476 | } 477 | }) 478 | if availableAMF == nil && 479 | defaultAMF != nil { 480 | availableAMF = defaultAMF 481 | } 482 | return availableAMF 483 | } 484 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= 2 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 3 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 4 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 5 | github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= 6 | github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= 7 | github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= 8 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 9 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 10 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 11 | github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= 12 | github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 13 | github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= 14 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 15 | github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= 16 | github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 17 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/free5gc/aper v1.0.6-0.20250102035630-3ddc831eed6a h1:PRapMdjqo96T7y8PHShUlirRDI0EaJNVrmdfQaHxOKY= 21 | github.com/free5gc/aper v1.0.6-0.20250102035630-3ddc831eed6a/go.mod h1:L4d0BJpHVOgiOC49aCz44mF0Vja3l+bcVSAs7i3CZ8s= 22 | github.com/free5gc/ike v1.1.1-0.20241014015325-083f89768f43 h1:cgpG06umqWTAwYy/bLXXcdNg+k7+qkinsElCVZzuOSI= 23 | github.com/free5gc/ike v1.1.1-0.20241014015325-083f89768f43/go.mod h1:57Ujd9Xjva02mt3OVfepYKiheFHO5Y0YCQyBgB1p1Qs= 24 | github.com/free5gc/nas v1.2.1 h1:jIXffRFXCuAEQs5j5NHlS+ugQBbReNe6q3NPxfrTZVo= 25 | github.com/free5gc/nas v1.2.1/go.mod h1:K14TWrOk7UXdLmZkP5uGCSf/0EYxrUOzpi/hlfJm40w= 26 | github.com/free5gc/ngap v1.1.1 h1:MBpB4bSYOE79T0XXBLY+lNKz77R5nCboLgiBKnBx4SY= 27 | github.com/free5gc/ngap v1.1.1/go.mod h1:L0FjR4QPpLrPyur9kiep8RSxeahK7HIBm4x9dybt1bA= 28 | github.com/free5gc/openapi v1.2.2 h1:SoWkuI/QOWA22UgNuqtkeoKeLEjThziJYGQFh/1gxSQ= 29 | github.com/free5gc/openapi v1.2.2/go.mod h1:5HbgGqlhaTBwcOrXQLCOmpbQoX/ogKSg6+TAsR1VxUM= 30 | github.com/free5gc/sctp v1.1.0 h1:erxOTR49VOc8cc/fuqi8sscnCpl2/EvWG4CrFsbRoVc= 31 | github.com/free5gc/sctp v1.1.0/go.mod h1:3pNCIh4roncSgvzIcBhSSsvZGRrMZNiqAVW3qzSE+cw= 32 | github.com/free5gc/util v1.2.0 h1:qA6EwiM7uAaE2SUSZDkUaMEa1DVEBoMN+wEWhRj8cYs= 33 | github.com/free5gc/util v1.2.0/go.mod h1:eFZg7JCMlG1G5eCBbw+Gfs0SOBSIZv1OeuB1Sh0H+zE= 34 | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= 35 | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= 36 | github.com/gin-contrib/pprof v1.5.0 h1:E/Oy7g+kNw94KfdCy3bZxQFtyDnAX2V7axRS7sNYVrU= 37 | github.com/gin-contrib/pprof v1.5.0/go.mod h1:GqFL6LerKoCQ/RSWnkYczkTJ+tOAUVN/8sbnEtaqOKs= 38 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 39 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 40 | github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= 41 | github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 42 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 43 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 44 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 45 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 46 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 47 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 48 | github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= 49 | github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 50 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 51 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 52 | github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= 53 | github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 54 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 55 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 56 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 57 | github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= 58 | github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= 59 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 60 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 61 | github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= 62 | github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 63 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 64 | github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 65 | github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 66 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 67 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 68 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 69 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 70 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 71 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 72 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 73 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 74 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 75 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 76 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 77 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 78 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 79 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 80 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 81 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 82 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= 83 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 84 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 85 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 86 | github.com/pascaldekloe/goe v0.1.1 h1:Ah6WQ56rZONR3RW3qWa2NCZ6JAVvSpUcoLBaOmYFt9Q= 87 | github.com/pascaldekloe/goe v0.1.1/go.mod h1:KSyfaxQOh0HZPjDP1FL/kFtbqYqrALJTaMafFUIccqU= 88 | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= 89 | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 90 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 91 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 92 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 93 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 94 | github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= 95 | github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= 96 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 97 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 98 | github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= 99 | github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= 100 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 101 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 102 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 103 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 104 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 105 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 106 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 107 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 108 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 109 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 110 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 111 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 112 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 113 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 114 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 115 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 116 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 117 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 118 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 119 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 120 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 121 | github.com/tim-ywliu/nested-logrus-formatter v1.3.2 h1:jugNJ2/CNCI79SxOJCOhwUHeN3O7/7/bj+ZRGOFlCSw= 122 | github.com/tim-ywliu/nested-logrus-formatter v1.3.2/go.mod h1:oGPmcxZB65j9Wo7mCnQKSrKEJtVDqyjD666SGmyStXI= 123 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 124 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 125 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 126 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 127 | github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= 128 | github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= 129 | github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= 130 | github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= 131 | github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= 132 | github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= 133 | github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 134 | github.com/wmnsk/go-gtp v0.8.11-0.20240705144331-f53bfdd4233b h1:wMTb2vJX7btPS+HTP1fnSUjbSiTkULLDu8ccAvR6+lo= 135 | github.com/wmnsk/go-gtp v0.8.11-0.20240705144331-f53bfdd4233b/go.mod h1:pXocxsDkxGn1VAAjqd68hYDSm52hwMx13kl+e7Xqoyo= 136 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= 137 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= 138 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 139 | golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= 140 | golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 141 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 142 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 143 | golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= 144 | golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= 145 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 146 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 147 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 148 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 149 | golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= 150 | golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= 151 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 152 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 153 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 154 | golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 155 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 156 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 157 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 158 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 159 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 160 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 161 | golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= 162 | golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 163 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 164 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 165 | google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= 166 | google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 167 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 168 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 169 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 170 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 171 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 172 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 173 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 174 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 175 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 176 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 177 | -------------------------------------------------------------------------------- /pkg/factory/config.go: -------------------------------------------------------------------------------- 1 | package factory 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "os" 8 | "strconv" 9 | "sync" 10 | "time" 11 | 12 | "github.com/asaskevich/govalidator" 13 | "github.com/mohae/deepcopy" 14 | 15 | "github.com/free5gc/n3iwf/internal/logger" 16 | "github.com/free5gc/sctp" 17 | ) 18 | 19 | const ( 20 | N3iwfDefaultTLSKeyLogPath string = "./log/n3iwfsslkey.log" 21 | N3iwfDefaultCertPemPath string = "./cert/n3iwf.pem" 22 | N3iwfDefaultCertKeyPath string = "./cert/n3iwf.key" 23 | N3iwfDefaultConfigPath string = "./config/n3iwfcfg.yaml" 24 | N3iwfDefaultXfrmIfaceName string = "ipsec" 25 | N3iwfDefaultXfrmIfaceId uint32 = 7 26 | 27 | N3iwfMetricsDefaultEnabled = false 28 | N3iwfMetricsDefaultPort = 9091 29 | N3iwfMetricsDefaultScheme = "https" 30 | N3iwfMetricsDefaultNamespace = "free5gc" 31 | 32 | MAX_BUF_MSG_LEN int = 65535 33 | ) 34 | 35 | type N3IWFNFInfo struct { 36 | GlobalN3IWFID *GlobalN3IWFID `yaml:"globalN3IWFID" valid:"required"` 37 | RanNodeName string `yaml:"name,omitempty" valid:"optional"` 38 | SupportedTAList []SupportedTAItem `yaml:"supportedTAList" valid:"required"` 39 | } 40 | 41 | type GlobalN3IWFID struct { 42 | PLMNID *PLMNID `yaml:"plmnID" valid:"required"` 43 | N3IWFID uint16 `yaml:"n3iwfID" valid:"range(0|65535),required"` // with length 2 bytes 44 | } 45 | 46 | type SupportedTAItem struct { 47 | TAC string `yaml:"tac" valid:"hexadecimal,stringlength(6|6),required"` 48 | BroadcastPLMNList []BroadcastPLMNItem `yaml:"broadcastPlmnList" valid:"required"` 49 | } 50 | 51 | type BroadcastPLMNItem struct { 52 | PLMNID *PLMNID `yaml:"plmnID" valid:"required"` 53 | TAISliceSupportList []SliceSupportItem `yaml:"taiSliceSupportList" valid:"required"` 54 | } 55 | 56 | type PLMNID struct { 57 | Mcc string `yaml:"mcc" valid:"numeric,stringlength(3|3),required"` 58 | Mnc string `yaml:"mnc" valid:"numeric,stringlength(2|3),required"` 59 | } 60 | 61 | type SliceSupportItem struct { 62 | SNSSAI SNSSAIItem `yaml:"snssai" valid:"required"` 63 | } 64 | 65 | type SNSSAIItem struct { 66 | SST int32 `yaml:"sst" valid:"required"` 67 | SD string `yaml:"sd,omitempty" valid:"required,hexadecimal,stringlength(6|6)"` 68 | } 69 | 70 | type AMFSCTPAddresses struct { 71 | IPAddresses []string `yaml:"ip" valid:"required"` 72 | Port int `yaml:"port,omitempty" valid:"port,optional"` // Default port is 38412 if not defined. 73 | } 74 | 75 | func (a *AMFSCTPAddresses) validate() error { 76 | var errs govalidator.Errors 77 | 78 | for _, IPAddress := range a.IPAddresses { 79 | if !govalidator.IsHost(IPAddress) { 80 | err := errors.New("Invalid AMFSCTPAddresses.IP: " + IPAddress + ", does not validate as IP") 81 | errs = append(errs, err) 82 | } 83 | } 84 | 85 | if len(errs) > 0 { 86 | return errs 87 | } 88 | 89 | return nil 90 | } 91 | 92 | type Config struct { 93 | Info *Info `yaml:"info" valid:"required"` 94 | Configuration *Configuration `yaml:"configuration" valid:"required"` 95 | Logger *Logger `yaml:"logger" valid:"required"` 96 | sync.RWMutex 97 | } 98 | 99 | func (c *Config) Validate() error { 100 | govalidator.TagMap["scheme"] = func(str string) bool { 101 | return str == "https" || str == "http" 102 | } 103 | 104 | if configuration := c.Configuration; configuration != nil { 105 | for _, amfSCTPAddress := range configuration.AMFSCTPAddresses { 106 | if err := amfSCTPAddress.validate(); err != nil { 107 | return err 108 | } 109 | } 110 | 111 | if configuration.Metrics != nil { 112 | if _, err := configuration.Metrics.validate(); err != nil { 113 | return err 114 | } 115 | } 116 | } 117 | 118 | govalidator.TagMap["cidr"] = govalidator.Validator(func(str string) bool { 119 | return govalidator.IsCIDR(str) 120 | }) 121 | 122 | _, err := govalidator.ValidateStruct(c) 123 | return appendInvalid(err) 124 | } 125 | 126 | type Info struct { 127 | Version string `yaml:"version,omitempty" valid:"required,in(1.0.5)"` 128 | Description string `yaml:"description,omitempty" valid:"optional"` 129 | } 130 | 131 | type Configuration struct { 132 | N3IWFInfo *N3IWFNFInfo `yaml:"n3iwfInformation" valid:"required"` 133 | LocalSctpAddr string `yaml:"localSctpAddr,omitempty" valid:"optional,host"` 134 | AMFSCTPAddresses []AMFSCTPAddresses `yaml:"amfSCTPAddresses" valid:"required"` 135 | 136 | Metrics *Metrics `yaml:"metrics,omitempty" valid:"optional"` 137 | 138 | TCPPort int `yaml:"nasTcpPort" valid:"required,port"` 139 | IKEBindAddr string `yaml:"ikeBindAddress" valid:"required,host"` 140 | UEIPAddressRange string `yaml:"ueIpAddressRange" valid:"required,cidr"` // e.g. 10.0.1.0/24 141 | IPSecGatewayAddr string `yaml:"ipSecTunnelAddress" valid:"required,host"` 142 | XfrmIfaceName string `yaml:"xfrmInterfaceName" valid:"optional,stringlength(1|10)"` // must != 0 143 | XfrmIfaceId uint32 `yaml:"xfrmInterfaceID" valid:"optional"` // must != 0 144 | N3IWFGTPBindAddress string `yaml:"n3iwfGtpBindAddress" valid:"required,host"` 145 | FQDN string `yaml:"fqdn" valid:"required,host"` // e.g. n3iwf.Saviah.com 146 | PrivateKey string `yaml:"privateKey" valid:"optional"` 147 | CertificateAuthority string `yaml:"certificateAuthority" valid:"optional"` 148 | Certificate string `yaml:"certificate" valid:"optional"` 149 | LivenessCheck *TimerValue `yaml:"livenessCheck" valid:"required"` 150 | } 151 | 152 | type Logger struct { 153 | Enable bool `yaml:"enable" valid:"type(bool)"` 154 | Level string `yaml:"level" valid:"required,in(trace|debug|info|warn|error|fatal|panic)"` 155 | ReportCaller bool `yaml:"reportCaller" valid:"type(bool)"` 156 | } 157 | 158 | type TimerValue struct { 159 | Enable bool `yaml:"enable" valid:"optional"` 160 | TransFreq time.Duration `yaml:"transFreq" valid:"required"` 161 | MaxRetryTimes int32 `yaml:"maxRetryTimes" valid:"optional"` 162 | } 163 | 164 | func appendInvalid(err error) error { 165 | var errs govalidator.Errors 166 | 167 | if err == nil { 168 | return nil 169 | } 170 | 171 | es := err.(govalidator.Errors).Errors() 172 | for _, e := range es { 173 | errs = append(errs, fmt.Errorf("invalid %w", e)) 174 | } 175 | 176 | return error(errs) 177 | } 178 | 179 | func (c *Config) GetVersion() string { 180 | c.RLock() 181 | defer c.RUnlock() 182 | 183 | if c.Info.Version != "" { 184 | return c.Info.Version 185 | } 186 | return "" 187 | } 188 | 189 | func (c *Config) SetLogEnable(enable bool) { 190 | c.Lock() 191 | defer c.Unlock() 192 | 193 | if c.Logger == nil { 194 | logger.CfgLog.Warnf("Logger should not be nil") 195 | c.Logger = &Logger{ 196 | Enable: enable, 197 | Level: "info", 198 | } 199 | } else { 200 | c.Logger.Enable = enable 201 | } 202 | } 203 | 204 | func (c *Config) SetLogLevel(level string) { 205 | c.Lock() 206 | defer c.Unlock() 207 | 208 | if c.Logger == nil { 209 | logger.CfgLog.Warnf("Logger should not be nil") 210 | c.Logger = &Logger{ 211 | Level: level, 212 | } 213 | } else { 214 | c.Logger.Level = level 215 | } 216 | } 217 | 218 | func (c *Config) SetLogReportCaller(reportCaller bool) { 219 | c.Lock() 220 | defer c.Unlock() 221 | 222 | if c.Logger == nil { 223 | logger.CfgLog.Warnf("Logger should not be nil") 224 | c.Logger = &Logger{ 225 | Level: "info", 226 | ReportCaller: reportCaller, 227 | } 228 | } else { 229 | c.Logger.ReportCaller = reportCaller 230 | } 231 | } 232 | 233 | func (c *Config) GetLogEnable() bool { 234 | c.RLock() 235 | defer c.RUnlock() 236 | if c.Logger == nil { 237 | logger.CfgLog.Warnf("Logger should not be nil") 238 | return false 239 | } 240 | return c.Logger.Enable 241 | } 242 | 243 | func (c *Config) GetLogLevel() string { 244 | c.RLock() 245 | defer c.RUnlock() 246 | if c.Logger == nil { 247 | logger.CfgLog.Warnf("Logger should not be nil") 248 | return "info" 249 | } 250 | return c.Logger.Level 251 | } 252 | 253 | func (c *Config) GetLogReportCaller() bool { 254 | c.RLock() 255 | defer c.RUnlock() 256 | if c.Logger == nil { 257 | logger.CfgLog.Warnf("Logger should not be nil") 258 | return false 259 | } 260 | return c.Logger.ReportCaller 261 | } 262 | 263 | func (c *Config) GetGlobalN3iwfId() *GlobalN3IWFID { 264 | c.RLock() 265 | defer c.RUnlock() 266 | return deepcopy.Copy(c.Configuration.N3IWFInfo.GlobalN3IWFID).(*GlobalN3IWFID) 267 | } 268 | 269 | func (c *Config) GetRanNodeName() string { 270 | c.RLock() 271 | defer c.RUnlock() 272 | return c.Configuration.N3IWFInfo.RanNodeName 273 | } 274 | 275 | func (c *Config) GetLocalSctpAddr() *sctp.SCTPAddr { 276 | c.RLock() 277 | defer c.RUnlock() 278 | 279 | sctpAddr := new(sctp.SCTPAddr) 280 | localAddr := c.Configuration.LocalSctpAddr 281 | if localAddr != "" { 282 | ipAddr, err := net.ResolveIPAddr("ip", localAddr) 283 | if err == nil { 284 | sctpAddr = &sctp.SCTPAddr{ 285 | IPAddrs: []net.IPAddr{*ipAddr}, 286 | } 287 | } 288 | } 289 | return sctpAddr 290 | } 291 | 292 | func (c *Config) GetAmfSctpAddrs() []*sctp.SCTPAddr { 293 | c.RLock() 294 | defer c.RUnlock() 295 | 296 | var addrs []*sctp.SCTPAddr 297 | for _, amfAddr := range c.Configuration.AMFSCTPAddresses { 298 | sctpAddr := new(sctp.SCTPAddr) 299 | for _, ipStr := range amfAddr.IPAddresses { 300 | ipAddr, err := net.ResolveIPAddr("ip", ipStr) 301 | if err != nil { 302 | continue 303 | } 304 | sctpAddr.IPAddrs = append(sctpAddr.IPAddrs, *ipAddr) 305 | } 306 | if amfAddr.Port == 0 { 307 | sctpAddr.Port = 38412 308 | } else { 309 | sctpAddr.Port = amfAddr.Port 310 | } 311 | addrs = append(addrs, sctpAddr) 312 | } 313 | return addrs 314 | } 315 | 316 | func (c *Config) GetSupportedTAList() []SupportedTAItem { 317 | c.RLock() 318 | defer c.RUnlock() 319 | if len(c.Configuration.N3IWFInfo.SupportedTAList) > 0 { 320 | return deepcopy.Copy(c.Configuration.N3IWFInfo.SupportedTAList).([]SupportedTAItem) 321 | } 322 | return nil 323 | } 324 | 325 | func (c *Config) GetIKEBindAddr() string { 326 | c.RLock() 327 | defer c.RUnlock() 328 | return c.Configuration.IKEBindAddr 329 | } 330 | 331 | func (c *Config) GetIPSecGatewayAddr() string { 332 | c.RLock() 333 | defer c.RUnlock() 334 | return c.Configuration.IPSecGatewayAddr 335 | } 336 | 337 | func (c *Config) GetN3iwfGtpBindAddress() string { 338 | c.RLock() 339 | defer c.RUnlock() 340 | return c.Configuration.N3IWFGTPBindAddress 341 | } 342 | 343 | func (c *Config) GetNasTcpAddr() string { 344 | c.RLock() 345 | defer c.RUnlock() 346 | return c.Configuration.IPSecGatewayAddr + ":" + strconv.Itoa(c.Configuration.TCPPort) 347 | } 348 | 349 | func (c *Config) GetNasTcpPort() uint16 { 350 | c.RLock() 351 | defer c.RUnlock() 352 | return uint16(c.Configuration.TCPPort) // #nosec G115 353 | } 354 | 355 | func (c *Config) GetFQDN() string { 356 | c.RLock() 357 | defer c.RUnlock() 358 | return c.Configuration.FQDN 359 | } 360 | 361 | func (c *Config) GetIKECAPemPath() string { 362 | c.RLock() 363 | defer c.RUnlock() 364 | if c.Configuration.CertificateAuthority != "" { 365 | return c.Configuration.CertificateAuthority 366 | } 367 | return N3iwfDefaultCertPemPath 368 | } 369 | 370 | func (c *Config) GetIKECertPemPath() string { 371 | c.RLock() 372 | defer c.RUnlock() 373 | if c.Configuration.Certificate != "" { 374 | return c.Configuration.Certificate 375 | } 376 | return N3iwfDefaultCertPemPath 377 | } 378 | 379 | func (c *Config) GetIKECertKeyPath() string { 380 | c.RLock() 381 | defer c.RUnlock() 382 | if c.Configuration.PrivateKey != "" { 383 | return c.Configuration.PrivateKey 384 | } 385 | return N3iwfDefaultCertKeyPath 386 | } 387 | 388 | func (c *Config) GetUEIPAddrRange() string { 389 | c.RLock() 390 | defer c.RUnlock() 391 | return c.Configuration.UEIPAddressRange 392 | } 393 | 394 | func (c *Config) GetXfrmIfaceName() string { 395 | c.RLock() 396 | defer c.RUnlock() 397 | if c.Configuration.XfrmIfaceName != "" { 398 | return c.Configuration.XfrmIfaceName 399 | } 400 | return N3iwfDefaultXfrmIfaceName 401 | } 402 | 403 | func (c *Config) GetXfrmIfaceId() uint32 { 404 | c.RLock() 405 | defer c.RUnlock() 406 | if c.Configuration.XfrmIfaceId != 0 { 407 | return c.Configuration.XfrmIfaceId 408 | } 409 | return N3iwfDefaultXfrmIfaceId 410 | } 411 | 412 | func (c *Config) GetLivenessCheck() TimerValue { 413 | c.RLock() 414 | defer c.RUnlock() 415 | return *c.Configuration.LivenessCheck 416 | } 417 | 418 | type Tls struct { 419 | Pem string `yaml:"pem,omitempty" valid:"type(string),minstringlength(1),required"` 420 | Key string `yaml:"key,omitempty" valid:"type(string),minstringlength(1),required"` 421 | } 422 | 423 | func (c *Tls) validate() (bool, error) { 424 | result, err := govalidator.ValidateStruct(c) 425 | return result, appendInvalid(err) 426 | } 427 | 428 | type Metrics struct { 429 | Enable bool `yaml:"enable" valid:"optional"` 430 | Scheme string `yaml:"scheme" valid:"required,scheme"` 431 | BindingIPv4 string `yaml:"bindingIPv4,omitempty" valid:"required,host"` // IP used to run the server in the node. 432 | Port int `yaml:"port,omitempty" valid:"optional,port"` 433 | Tls *Tls `yaml:"tls,omitempty" valid:"optional"` 434 | Namespace string `yaml:"namespace" valid:"optional"` 435 | } 436 | 437 | // This function is the mirror of the SBI one, I decided not to factor the code as it could in the future diverge. 438 | // And it will reduce the cognitive overload when reading the function by not hiding the logic elsewhere. 439 | func (m *Metrics) validate() (bool, error) { 440 | var errs govalidator.Errors 441 | 442 | if tls := m.Tls; tls != nil { 443 | if _, err := tls.validate(); err != nil { 444 | errs = append(errs, err) 445 | } 446 | } 447 | 448 | if _, err := govalidator.ValidateStruct(m); err != nil { 449 | errs = append(errs, err) 450 | } 451 | 452 | if len(errs) > 0 { 453 | return false, error(errs) 454 | } 455 | return true, nil 456 | } 457 | 458 | func (c *Config) AreMetricsEnabled() bool { 459 | c.RLock() 460 | defer c.RUnlock() 461 | if c.Configuration != nil && c.Configuration.Metrics != nil { 462 | return c.Configuration.Metrics.Enable 463 | } 464 | return N3iwfMetricsDefaultEnabled 465 | } 466 | 467 | func (c *Config) GetMetricsScheme() string { 468 | c.RLock() 469 | defer c.RUnlock() 470 | if c.Configuration != nil && c.Configuration.Metrics != nil && c.Configuration.Metrics.Scheme != "" { 471 | return c.Configuration.Metrics.Scheme 472 | } 473 | return N3iwfMetricsDefaultScheme 474 | } 475 | 476 | func (c *Config) GetMetricsPort() int { 477 | c.RLock() 478 | defer c.RUnlock() 479 | if c.Configuration != nil && c.Configuration.Metrics != nil && c.Configuration.Metrics.Port != 0 { 480 | return c.Configuration.Metrics.Port 481 | } 482 | return N3iwfMetricsDefaultPort 483 | } 484 | 485 | func (c *Config) GetMetricsBindingIP() string { 486 | c.RLock() 487 | defer c.RUnlock() 488 | bindIP := "0.0.0.0" 489 | 490 | if c.Configuration == nil || c.Configuration.Metrics == nil { 491 | return bindIP 492 | } 493 | 494 | if c.Configuration.Metrics.BindingIPv4 != "" { 495 | if bindIP = os.Getenv(c.Configuration.Metrics.BindingIPv4); bindIP != "" { 496 | logger.CfgLog.Infof("Parsing ServerIPv4 [%s] from ENV Variable", bindIP) 497 | } else { 498 | bindIP = c.Configuration.Metrics.BindingIPv4 499 | } 500 | } 501 | return bindIP 502 | } 503 | 504 | func (c *Config) GetMetricsBindingAddr() string { 505 | c.RLock() 506 | defer c.RUnlock() 507 | return c.GetMetricsBindingIP() + ":" + strconv.Itoa(c.GetMetricsPort()) 508 | } 509 | 510 | func (c *Config) GetMetricsCertPemPath() string { 511 | // We can see if there is a benefit to factor this tls key/pem with the sbi ones 512 | c.RLock() 513 | defer c.RUnlock() 514 | 515 | if c.Configuration.Metrics != nil && c.Configuration.Metrics.Tls != nil { 516 | return c.Configuration.Metrics.Tls.Pem 517 | } 518 | return "" 519 | } 520 | 521 | func (c *Config) GetMetricsCertKeyPath() string { 522 | c.RLock() 523 | defer c.RUnlock() 524 | 525 | if c.Configuration.Metrics != nil && c.Configuration.Metrics.Tls != nil { 526 | return c.Configuration.Metrics.Tls.Key 527 | } 528 | return "" 529 | } 530 | 531 | func (c *Config) GetMetricsNamespace() string { 532 | c.RLock() 533 | defer c.RUnlock() 534 | if c.Configuration.Metrics != nil && c.Configuration.Metrics.Namespace != "" { 535 | return c.Configuration.Metrics.Namespace 536 | } 537 | return N3iwfMetricsDefaultNamespace 538 | } 539 | --------------------------------------------------------------------------------