├── .github └── workflows │ ├── go.yml │ └── golangci-lint.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── README.md ├── cmd └── main.go ├── go.mod ├── go.sum ├── internal ├── forwarder │ ├── buffnetlink │ │ ├── server.go │ │ └── server_test.go │ ├── driver.go │ ├── empty.go │ ├── flowdesc.go │ ├── flowdesc_test.go │ ├── gtp5g.go │ ├── gtp5g_test.go │ ├── gtp5glink.go │ └── perio │ │ ├── server.go │ │ └── server_test.go ├── gtpv1 │ ├── msg.go │ └── msg_test.go ├── logger │ └── logger.go ├── pfcp │ ├── association.go │ ├── dispacher.go │ ├── heartbeat.go │ ├── node.go │ ├── node_test.go │ ├── pfcp.go │ ├── pfcp_test.go │ ├── report.go │ ├── session.go │ └── transaction.go └── report │ ├── handler.go │ ├── report.go │ └── report_test.go ├── pkg ├── app │ ├── app.go │ └── app_test.go └── factory │ ├── config.go │ └── factory.go └── testtools └── upftest └── main.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | go: [ '1.21' ] 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Set up Go 18 | uses: actions/setup-go@v3 19 | with: 20 | go-version: ${{ matrix.go }} 21 | 22 | - name: Build 23 | run: go build -v ./... 24 | 25 | - name: Test 26 | run: go test -v -short ./... 27 | -------------------------------------------------------------------------------- /.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 | 10 | jobs: 11 | golangci: 12 | name: lint 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | go: [ '1.21' ] 17 | steps: 18 | - name: Set up Go 19 | uses: actions/setup-go@v3 20 | with: 21 | go-version: ${{ matrix.go }} 22 | - uses: actions/checkout@v4 23 | - name: Run golangci-lint 24 | uses: golangci/golangci-lint-action@v4 25 | with: 26 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 27 | version: v1.57.2 28 | 29 | # Optional: working directory, useful for monorepos 30 | # working-directory: somedir 31 | 32 | # Optional: golangci-lint command line arguments. 33 | # args: --issues-exit-code=0 34 | 35 | # Optional: show only new issues if it's a pull request. The default value is `false`. 36 | # only-new-issues: true 37 | 38 | # Optional: if set to true then the all caching functionality will be complete disabled, 39 | # takes precedence over all other caching options. 40 | # skip-cache: true 41 | 42 | # Optional: if set to true then the action don't cache or restore ~/go/pkg. 43 | # skip-pkg-cache: true 44 | 45 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. 46 | # skip-build-cache: true 47 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # This file contains all available configuration options 2 | # with their default values. 3 | # options for analysis running 4 | run: 5 | # default concurrency is a available CPU number 6 | concurrency: 4 7 | # timeout for analysis, e.g. 30s, 5m, default is 1m 8 | timeout: 3m 9 | # exit code when at least one issue was found, default is 1 10 | issues-exit-code: 1 11 | # include test files or not, default is true 12 | tests: true 13 | # list of build tags, all linters use it. Default is empty list. 14 | build-tags: 15 | # which dirs to skip: issues from them won't be reported; 16 | # can use regexp here: generated.*, regexp is applied on full path; 17 | # default value is empty list, but default dirs are skipped independently 18 | # from this option's value (see skip-dirs-use-default). 19 | # "/" will be replaced by current OS file path separator to properly work 20 | # on Windows. 21 | skip-dirs: 22 | # default is true. Enables skipping of directories: 23 | # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ 24 | skip-dirs-use-default: true 25 | # which files to skip: they will be analyzed, but issues from them 26 | # won't be reported. Default value is empty list, but there is 27 | # no need to include all autogenerated files, we confidently recognize 28 | # autogenerated files. If it's not please let us know. 29 | # "/" will be replaced by current OS file path separator to properly work 30 | # on Windows. 31 | skip-files: 32 | # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": 33 | # If invoked with -mod=readonly, the go command is disallowed from the implicit 34 | # automatic updating of go.mod described above. Instead, it fails when any changes 35 | # to go.mod are needed. This setting is most useful to check that go.mod does 36 | # not need updates, such as in a continuous integration and testing system. 37 | # If invoked with -mod=vendor, the go command assumes that the vendor 38 | # directory holds the correct copies of dependencies and ignores 39 | # the dependency descriptions in go.mod. 40 | #modules-download-mode: readonly|release|vendor 41 | # Allow multiple parallel golangci-lint instances running. 42 | # If false (default) - golangci-lint acquires file lock on start. 43 | allow-parallel-runners: true 44 | # output configuration options 45 | output: 46 | # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" 47 | formats: colored-line-number 48 | # print lines of code with issue, default is true 49 | print-issued-lines: true 50 | # print linter name in the end of issue text, default is true 51 | print-linter-name: true 52 | # make issues output unique by line, default is true 53 | uniq-by-line: true 54 | # all available settings of specific linters 55 | linters-settings: 56 | errcheck: 57 | # report about not checking of errors in type assertions: `a := b.(MyStruct)`; 58 | # default is false: such cases aren't reported by default. 59 | check-type-assertions: false 60 | # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; 61 | # default is false: such cases aren't reported by default. 62 | check-blank: true 63 | # [deprecated] comma-separated list of pairs of the form pkg:regex 64 | # the regex is used to ignore names within pkg. (default "fmt:.*"). 65 | # see https://github.com/kisielk/errcheck#the-deprecated-method for details 66 | #ignore: fmt:.*,io/ioutil:^Read.* 67 | # path to a file containing a list of functions to exclude from checking 68 | # see https://github.com/kisielk/errcheck#excluding-functions for details 69 | #exclude: /path/to/file.txt 70 | funlen: 71 | lines: 60 72 | statements: 40 73 | gocognit: 74 | # minimal code complexity to report, 30 by default (but we recommend 10-20) 75 | min-complexity: 10 76 | nestif: 77 | # minimal complexity of if statements to report, 5 by default 78 | min-complexity: 4 79 | goconst: 80 | # minimal length of string constant, 3 by default 81 | min-len: 3 82 | # minimal occurrences count to trigger, 3 by default 83 | min-occurrences: 3 84 | gocritic: 85 | # Which checks should be enabled; can't be combined with 'disabled-checks'; 86 | # See https://go-critic.github.io/overview#checks-overview 87 | # To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run` 88 | # By default list of stable checks is used. 89 | enabled-checks: 90 | #- rangeValCopy 91 | # Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty 92 | disabled-checks: 93 | - regexpMust 94 | # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks. 95 | # Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags". 96 | enabled-tags: 97 | - performance 98 | disabled-tags: 99 | - experimental 100 | settings: # settings passed to gocritic 101 | captLocal: # must be valid enabled check name 102 | paramsOnly: true 103 | rangeValCopy: 104 | sizeThreshold: 32 105 | gocyclo: 106 | # minimal code complexity to report, 30 by default (but we recommend 10-20) 107 | min-complexity: 10 108 | godox: 109 | # report any comments starting with keywords, this is useful for TODO or FIXME comments that 110 | # might be left in the code accidentally and should be resolved before merging 111 | keywords: # default keywords are TODO, BUG, and FIXME, these can be overwritten by this setting 112 | #- TODO 113 | - FIXME 114 | - BUG 115 | #- NOTE 116 | #- OPTIMIZE # marks code that should be optimized before merging 117 | #- HACK # marks hack-arounds that should be removed before merging 118 | - XXX # Fatal! Important problem 119 | gofmt: 120 | # simplify code: gofmt with `-s` option, true by default 121 | simplify: true 122 | goimports: 123 | # put imports beginning with prefix after 3rd-party packages; 124 | # it's a comma-separated list of prefixes 125 | local-prefixes: github.com/org/project 126 | golint: 127 | # minimal confidence for issues, default is 0.8 128 | min-confidence: 0.8 129 | gomnd: 130 | checks: argument,case,condition,operation,return,assign,call,parameter 131 | gomodguard: 132 | allowed: 133 | modules: # List of allowed modules 134 | # - gopkg.in/yaml.v2 135 | domains: # List of allowed module domains 136 | # - golang.org 137 | blocked: 138 | modules: # List of blocked modules 139 | # - github.com/uudashr/go-module: # Blocked module 140 | # recommendations: # Recommended modules that should be used instead (Optional) 141 | # - golang.org/x/mod 142 | # reason: "`mod` is the official go.mod parser library." # Reason why the recommended module should be used (Optional) 143 | versions: # List of blocked module version constraints 144 | # - github.com/mitchellh/go-homedir: # Blocked module with version constraint 145 | # version: "< 1.1.0" # Version constraint, see https://github.com/Masterminds/semver#basic-comparisons 146 | # reason: "testing if blocked version constraint works." # Reason why the version constraint exists. (Optional) 147 | govet: 148 | # report about shadowed variables 149 | # settings per analyzer 150 | settings: 151 | printf: # analyzer name, run `go tool vet help` to see all analyzers 152 | funcs: # run `go tool vet help printf` to see available settings for `printf` analyzer 153 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof 154 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf 155 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf 156 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf 157 | # enable or disable analyzers by name 158 | enable: 159 | - atomicalign 160 | - shadow 161 | enable-all: false 162 | disable-all: false 163 | depguard: 164 | list-type: blacklist 165 | include-go-root: false 166 | packages: 167 | - github.com/sirupsen/logrus 168 | packages-with-error-message: 169 | # specify an error message to output when a blacklisted package is used 170 | - github.com/sirupsen/logrus: "logging is allowed only by logutils.Log" 171 | lll: 172 | # max line length, lines longer will be reported. Default is 120. 173 | # '\t' is counted as 1 character by default, and can be changed with the tab-width option 174 | line-length: 120 175 | # tab width in spaces. Default to 1. 176 | tab-width: 1 177 | maligned: 178 | # print struct with more effective memory layout or not, false by default 179 | suggest-new: true 180 | nakedret: 181 | # make an issue if func has more lines of code than this setting and it has naked returns; default is 30 182 | max-func-lines: 30 183 | testpackage: 184 | # regexp pattern to skip files 185 | skip-regexp: (export|internal)_test\.go 186 | unused: 187 | # treat code as a program (not a library) and report unused exported identifiers; default is false. 188 | # XXX: if you enable this setting, unused will report a lot of false-positives in text editors: 189 | # if it's called for subdir of a project it can't find funcs usages. All text editor integrations 190 | # with golangci-lint call it on a directory with the changed file. 191 | check-exported: false 192 | whitespace: 193 | multi-if: false # Enforces newlines (or comments) after every multi-line if statement 194 | multi-func: false # Enforces newlines (or comments) after every multi-line function signature 195 | gci: 196 | sections: 197 | - standard 198 | - default 199 | - prefix(github.com/free5gc) 200 | section-separators: 201 | - newLine 202 | misspell: 203 | #locale: US 204 | ignore-words: 205 | wsl: 206 | # If true append is only allowed to be cuddled if appending value is 207 | # matching variables, fields or types on line above. Default is true. 208 | strict-append: true 209 | # Allow calls and assignments to be cuddled as long as the lines have any 210 | # matching variables, fields or types. Default is true. 211 | allow-assign-and-call: true 212 | # Allow multiline assignments to be cuddled. Default is true. 213 | allow-multiline-assign: true 214 | # Allow declarations (var) to be cuddled. 215 | allow-cuddle-declarations: false 216 | # Allow trailing comments in ending of blocks 217 | allow-trailing-comment: true 218 | # Force newlines in end of case at this limit (0 = never). 219 | force-case-trailing-whitespace: 0 220 | # Force cuddling of err checks with err var assignment 221 | force-err-cuddling: false 222 | # Allow leading comments to be separated with empty liens 223 | allow-separated-leading-comment: false 224 | custom: 225 | # Each custom linter should have a unique name. 226 | 227 | linters: 228 | enable: 229 | - gofmt 230 | - govet 231 | - errcheck 232 | - staticcheck 233 | - unused 234 | - gosimple 235 | - ineffassign 236 | - typecheck 237 | # Additional 238 | - lll 239 | - godox 240 | # - gomnd 241 | # - goconst 242 | # - gocognit 243 | # - maligned 244 | # - nestif 245 | # - gomodguard 246 | - nakedret 247 | # - golint 248 | - gci 249 | - misspell 250 | - gofumpt 251 | - whitespace 252 | - unconvert 253 | - predeclared 254 | - noctx 255 | - dogsled 256 | - bodyclose 257 | - asciicheck 258 | # - stylecheck 259 | # - unparam 260 | # - wsl 261 | 262 | #disable-all: false 263 | fast: true 264 | issues: 265 | # List of regexps of issue texts to exclude, empty list by default. 266 | # But independently from this option we use default exclude patterns, 267 | # it can be disabled by `exclude-use-default: false`. To list all 268 | # excluded by default patterns execute `golangci-lint run --help` 269 | exclude: 270 | # Excluding configuration per-path, per-linter, per-text and per-source 271 | exclude-rules: 272 | # Exclude some linters from running on tests files. 273 | # Independently from option `exclude` we use default exclude patterns, 274 | # it can be disabled by this option. To list all 275 | # excluded by default patterns execute `golangci-lint run --help`. 276 | # Default value for this option is true. 277 | exclude-use-default: false 278 | # The default value is false. If set to true exclude and exclude-rules 279 | # regular expressions become case sensitive. 280 | exclude-case-sensitive: false 281 | # The list of ids of default excludes to include or disable. By default it's empty. 282 | include: 283 | #- EXC0002 # disable excluding of issues about comments from golint 284 | # Maximum issues count per one linter. Set to 0 to disable. Default is 50. 285 | #max-issues-per-linter: 0 286 | # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. 287 | #max-same-issues: 0 288 | # Show only new issues: if there are unstaged changes or untracked files, 289 | # only those changes are analyzed, else only changes in HEAD~ are analyzed. 290 | # It's a super-useful option for integration of golangci-lint into existing 291 | # large codebase. It's not practical to fix all existing issues at the moment 292 | # of integration: much better don't allow issues in new code. 293 | # Default is false. 294 | new: false 295 | # Show only new issues created after git revision `REV` 296 | new-from-rev: "" 297 | # Show only new issues created in git patch with set file path. 298 | #new-from-patch: path/to/patch/file 299 | severity: 300 | # Default value is empty string. 301 | # Set the default severity for issues. If severity rules are defined and the issues 302 | # do not match or no severity is provided to the rule this will be the default 303 | # severity applied. Severities should match the supported severity names of the 304 | # selected out format. 305 | # - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity 306 | # - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#severity 307 | # - Github: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message 308 | default-severity: error 309 | # The default value is false. 310 | # If set to true severity-rules regular expressions become case sensitive. 311 | case-sensitive: false 312 | # Default value is empty list. 313 | # When a list of severity rules are provided, severity information will be added to lint 314 | # issues. Severity rules have the same filtering capability as exclude rules except you 315 | # are allowed to specify one matcher per severity rule. 316 | # Only affects out formats that support setting severity information. 317 | rules: 318 | - linters: 319 | - gomnd 320 | severity: ignore -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-upf 2 | ### The specific version 3 | **Note:** Please make sure to check your UPF version and use a compatible gtp5g version according to the table below. 4 | 5 | |free5GC Version| UPF Version | Compatible gtp5g Versions | 6 | |-----------------|-----------------|--------------------------| 7 | |v3.4.4| v1.2.4 | >= 0.9.5 and < 0.10.0 | 8 | |v3.4.3| v1.2.3 | >= 0.8.6 and < 0.9.0 | 9 | |v3.4.2| v1.2.3 | >= 0.8.6 and < 0.9.0 | 10 | |v3.4.1| v1.2.2 | >= 0.8.6 and < 0.9.0 | 11 | |v3.4.0| v1.2.1 | >= 0.8.6 and < 0.9.0 | 12 | |v3.3.0| v1.2.0 | >= 0.8.1 and < 0.9.0 | 13 | |v3.2.1| v1.1.0 | >= 0.7.0 and < 0.7.0 | 14 | |v3.2.0| v1.1.0 | >= 0.7.0 and < 0.7.0 | 15 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math/rand" 5 | "os" 6 | "runtime/debug" 7 | "time" 8 | 9 | "github.com/urfave/cli" 10 | 11 | "github.com/free5gc/go-upf/internal/logger" 12 | upfapp "github.com/free5gc/go-upf/pkg/app" 13 | "github.com/free5gc/go-upf/pkg/factory" 14 | logger_util "github.com/free5gc/util/logger" 15 | "github.com/free5gc/util/version" 16 | ) 17 | 18 | func main() { 19 | defer func() { 20 | if p := recover(); p != nil { 21 | // Print stack for panic to log. Fatalf() will let program exit. 22 | logger.MainLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 23 | } 24 | }() 25 | 26 | app := cli.NewApp() 27 | app.Name = "upf" 28 | app.Usage = "5G User Plane Function (UPF)" 29 | app.Action = action 30 | app.Flags = []cli.Flag{ 31 | cli.StringFlag{ 32 | Name: "config, c", 33 | Usage: "Load configuration from `FILE`", 34 | }, 35 | cli.StringSliceFlag{ 36 | Name: "log, l", 37 | Usage: "Output NF log to `FILE`", 38 | }, 39 | } 40 | 41 | // rand.Seed(time.Now().UnixNano()) // rand.Seed has been deprecated 42 | randSeed := rand.New(rand.NewSource(time.Now().UnixNano())) 43 | randSeed.Uint64() 44 | 45 | if err := app.Run(os.Args); err != nil { 46 | logger.MainLog.Errorf("UPF Cli Run Error: %v", err) 47 | } 48 | } 49 | 50 | func action(cliCtx *cli.Context) error { 51 | err := initLogFile(cliCtx.StringSlice("log")) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | logger.MainLog.Infoln("UPF version: ", version.GetVersion()) 57 | 58 | cfg, err := factory.ReadConfig(cliCtx.String("config")) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | upf, err := upfapp.NewApp(cfg) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | if err := upf.Run(); err != nil { 69 | return err 70 | } 71 | 72 | return nil 73 | } 74 | 75 | func initLogFile(logNfPath []string) error { 76 | for _, path := range logNfPath { 77 | if err := logger_util.LogFileHook(logger.Log, path); err != nil { 78 | return err 79 | } 80 | } 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/free5gc/go-upf 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d 7 | github.com/davecgh/go-spew v1.1.1 8 | github.com/free5gc/go-gtp5gnl v1.4.7-0.20241008130314-a3088e4cb7fa 9 | github.com/free5gc/util v1.0.6 10 | github.com/hashicorp/go-version v1.6.0 11 | github.com/khirono/go-genl v1.0.1 12 | github.com/khirono/go-nl v1.0.5 13 | github.com/khirono/go-rtnllink v1.1.1 14 | github.com/khirono/go-rtnlroute v1.0.1 15 | github.com/pkg/errors v0.9.1 16 | github.com/sirupsen/logrus v1.9.3 17 | github.com/stretchr/testify v1.9.0 18 | github.com/urfave/cli v1.22.5 19 | github.com/wmnsk/go-pfcp v0.0.23-0.20231009074152-d5a9c1f47114 20 | gopkg.in/yaml.v2 v2.4.0 21 | ) 22 | 23 | require ( 24 | github.com/bytedance/sonic v1.9.1 // indirect 25 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 26 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect 27 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 28 | github.com/gin-contrib/sse v0.1.0 // indirect 29 | github.com/gin-gonic/gin v1.9.1 // indirect 30 | github.com/go-playground/locales v0.14.1 // indirect 31 | github.com/go-playground/universal-translator v0.18.1 // indirect 32 | github.com/go-playground/validator/v10 v10.14.0 // indirect 33 | github.com/goccy/go-json v0.10.2 // indirect 34 | github.com/google/go-cmp v0.6.0 // indirect 35 | github.com/json-iterator/go v1.1.12 // indirect 36 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 37 | github.com/leodido/go-urn v1.2.4 // indirect 38 | github.com/mattn/go-isatty v0.0.19 // indirect 39 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 40 | github.com/modern-go/reflect2 v1.0.2 // indirect 41 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 42 | github.com/pmezard/go-difflib v1.0.0 // indirect 43 | github.com/russross/blackfriday/v2 v2.0.1 // indirect 44 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect 45 | github.com/tim-ywliu/nested-logrus-formatter v1.3.2 // indirect 46 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 47 | github.com/ugorji/go/codec v1.2.11 // indirect 48 | golang.org/x/arch v0.3.0 // indirect 49 | golang.org/x/crypto v0.31.0 // indirect 50 | golang.org/x/net v0.33.0 // indirect 51 | golang.org/x/sys v0.28.0 // indirect 52 | golang.org/x/text v0.21.0 // indirect 53 | google.golang.org/protobuf v1.33.0 // indirect 54 | gopkg.in/yaml.v3 v3.0.1 // indirect 55 | ) 56 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= 3 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 4 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 5 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 6 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 7 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 8 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 9 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 10 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= 11 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/free5gc/go-gtp5gnl v1.4.7-0.20241008130314-a3088e4cb7fa h1:D5OzFSttS6WY2XRspxtPKoHyCVkRLH9kqteQ1bGfOg0= 16 | github.com/free5gc/go-gtp5gnl v1.4.7-0.20241008130314-a3088e4cb7fa/go.mod h1:TT5aXB90NuSPMehuIK9lV2yJFnq6Qjw37ZqNB1QAKh0= 17 | github.com/free5gc/util v1.0.6 h1:dBt9drcXtYKE/cY5XuQcuffgsYclPIpIArhSeS6M+DQ= 18 | github.com/free5gc/util v1.0.6/go.mod h1:eSGN7POUM8LNTvg/E591XR6447a6/w1jFWGKNZPHcXw= 19 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 20 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 21 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 22 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 23 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 24 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 25 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 26 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 27 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 28 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 29 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 30 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 31 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 32 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 33 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 34 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 35 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 36 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 37 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 38 | github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= 39 | github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 40 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 41 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 42 | github.com/khirono/go-genl v1.0.1 h1:9/7V52C/lkifC0zjZZaAN6GghQSLZkuNz87vxemyzeY= 43 | github.com/khirono/go-genl v1.0.1/go.mod h1:EQ4XpG8LUJTtozdrgcsAgJm++r3l94/LwCia4Rq4Scs= 44 | github.com/khirono/go-nl v1.0.4/go.mod h1:PzYeSjD38fzV7mX5DuaCZvnwx2kD/o7XgHyzuJxoi7U= 45 | github.com/khirono/go-nl v1.0.5 h1:ekOJy/0n/rwlPG732VEZvj4ogmVSLRBxKzb6ah5iwzU= 46 | github.com/khirono/go-nl v1.0.5/go.mod h1:PzYeSjD38fzV7mX5DuaCZvnwx2kD/o7XgHyzuJxoi7U= 47 | github.com/khirono/go-rtnllink v1.1.1 h1:VsJbbW2HbqIZ62qit5FsehxR0gnrfDp7GE9fSTqHUDY= 48 | github.com/khirono/go-rtnllink v1.1.1/go.mod h1:FqrOS6/iGjmK30oNB3snEtXXd0JRT9nJ/98/605IiO0= 49 | github.com/khirono/go-rtnlroute v1.0.1 h1:YgR085h06LTnQ0fekNyN3rA6dn7HUFv7dBpf41nEFeM= 50 | github.com/khirono/go-rtnlroute v1.0.1/go.mod h1:6GOI/cznMHo/kxpGZjwk0rseeVIcGmHNBHfbH3qhLWM= 51 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 52 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 53 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 54 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 55 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 56 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 57 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 58 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 59 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 60 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 61 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 62 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 63 | github.com/pascaldekloe/goe v0.1.1 h1:Ah6WQ56rZONR3RW3qWa2NCZ6JAVvSpUcoLBaOmYFt9Q= 64 | github.com/pascaldekloe/goe v0.1.1/go.mod h1:KSyfaxQOh0HZPjDP1FL/kFtbqYqrALJTaMafFUIccqU= 65 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 66 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 67 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 68 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 69 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 70 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 71 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= 72 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 73 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 74 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 75 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 76 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 77 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 78 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 79 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 80 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 81 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 82 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 83 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 84 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 85 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 86 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 87 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 88 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 89 | github.com/tim-ywliu/nested-logrus-formatter v1.3.2 h1:jugNJ2/CNCI79SxOJCOhwUHeN3O7/7/bj+ZRGOFlCSw= 90 | github.com/tim-ywliu/nested-logrus-formatter v1.3.2/go.mod h1:oGPmcxZB65j9Wo7mCnQKSrKEJtVDqyjD666SGmyStXI= 91 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 92 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 93 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 94 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 95 | github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= 96 | github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 97 | github.com/wmnsk/go-pfcp v0.0.23-0.20231009074152-d5a9c1f47114 h1:8y4Hd1l92YQajB5s53suWTTbRRClaZWyDyYm9fl1RX0= 98 | github.com/wmnsk/go-pfcp v0.0.23-0.20231009074152-d5a9c1f47114/go.mod h1:C93sXfS7NGT9niMpb6K+pf/2GQTMBPFqZ5K2fmAtoA8= 99 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 100 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 101 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 102 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 103 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 104 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 105 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 106 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 107 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 108 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 109 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 110 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 111 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 112 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 113 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 114 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 115 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 116 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 117 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 118 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 119 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 120 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 121 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 122 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 123 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 124 | -------------------------------------------------------------------------------- /internal/forwarder/buffnetlink/server.go: -------------------------------------------------------------------------------- 1 | package buffnetlink 2 | 3 | import ( 4 | "encoding/binary" 5 | "sync" 6 | "syscall" 7 | 8 | "github.com/khirono/go-genl" 9 | "github.com/khirono/go-nl" 10 | "github.com/pkg/errors" 11 | 12 | "github.com/free5gc/go-gtp5gnl" 13 | "github.com/free5gc/go-upf/internal/logger" 14 | "github.com/free5gc/go-upf/internal/report" 15 | ) 16 | 17 | type Server struct { 18 | client *nl.Client 19 | mux *nl.Mux 20 | conn *nl.Conn 21 | handler report.Handler 22 | } 23 | 24 | var native binary.ByteOrder = gtp5gnl.NativeEndian() 25 | 26 | func OpenServer(wg *sync.WaitGroup, client *nl.Client, mux *nl.Mux) (*Server, error) { 27 | s := &Server{ 28 | client: client, 29 | mux: mux, 30 | } 31 | 32 | f, err := genl.GetFamily(s.client, "gtp5g") 33 | if err != nil { 34 | return nil, errors.Wrap(err, "get family") 35 | } 36 | 37 | s.conn, err = nl.Open(syscall.NETLINK_GENERIC, int(f.Groups[gtp5gnl.GENL_MCGRP].ID)) 38 | if err != nil { 39 | return nil, errors.Wrap(err, "open netlink") 40 | } 41 | 42 | err = s.mux.PushHandler(s.conn, s) 43 | if err != nil { 44 | return nil, errors.Wrap(err, "push handler") 45 | } 46 | 47 | logger.BuffLog.Infof("buff netlink server started") 48 | 49 | // wg.Add(1) 50 | return s, nil 51 | } 52 | 53 | func (s *Server) Close() { 54 | s.mux.PopHandler(s.conn) 55 | s.conn.Close() 56 | } 57 | 58 | func (s *Server) Handle(handler report.Handler) { 59 | s.handler = handler 60 | } 61 | 62 | func decodbuffer(b []byte) (uint64, uint16, uint16, []byte, error) { 63 | var pkt []byte 64 | var seid uint64 65 | var pdrid uint16 66 | var action uint16 67 | for len(b) > 0 { 68 | hdr, n, err := nl.DecodeAttrHdr(b) 69 | if err != nil { 70 | return 0, 0, 0, nil, err 71 | } 72 | switch hdr.MaskedType() { 73 | case gtp5gnl.BUFFER_ID: 74 | pdrid = native.Uint16(b[n:]) 75 | case gtp5gnl.BUFFER_ACTION: 76 | action = native.Uint16(b[n:]) 77 | case gtp5gnl.BUFFER_SEID: 78 | seid = native.Uint64(b[n:]) 79 | case gtp5gnl.BUFFER_PACKET: 80 | pkt = b[n:int(hdr.Len)] 81 | } 82 | 83 | b = b[hdr.Len.Align():] 84 | } 85 | 86 | return seid, pdrid, action, pkt, nil 87 | } 88 | 89 | func (s *Server) ServeMsg(msg *nl.Msg) bool { 90 | b := msg.Body[genl.SizeofHeader:] 91 | var usars map[uint64][]report.USAReport 92 | 93 | hdr, n, err := nl.DecodeAttrHdr(b) 94 | if err != nil { 95 | return false 96 | } 97 | switch hdr.MaskedType() { 98 | case gtp5gnl.BUFFER: 99 | seid, pdrid, action, pkt, err := decodbuffer(b[n:]) 100 | if err != nil { 101 | return false 102 | } 103 | 104 | if s.handler != nil && pkt != nil { 105 | dldr := report.DLDReport{ 106 | PDRID: pdrid, 107 | Action: action, 108 | BufPkt: pkt, 109 | } 110 | s.handler.NotifySessReport( 111 | report.SessReport{ 112 | SEID: seid, 113 | Reports: []report.Report{dldr}, 114 | }, 115 | ) 116 | } 117 | case gtp5gnl.REPORT: 118 | rs, err := gtp5gnl.DecodeAllUSAReports(b[n:]) 119 | if err != nil { 120 | return false 121 | } 122 | if rs == nil { 123 | return false 124 | } 125 | 126 | usars = make(map[uint64][]report.USAReport) 127 | for _, r := range rs { 128 | usar := report.USAReport{ 129 | URRID: r.URRID, 130 | QueryUrrRef: r.QueryUrrRef, 131 | StartTime: r.StartTime, 132 | EndTime: r.EndTime, 133 | } 134 | 135 | usar.USARTrigger.SetReportingTrigger(r.USARTrigger) 136 | 137 | usar.VolumMeasure = report.VolumeMeasure{ 138 | TotalVolume: r.VolMeasurement.TotalVolume, 139 | UplinkVolume: r.VolMeasurement.UplinkVolume, 140 | DownlinkVolume: r.VolMeasurement.DownlinkVolume, 141 | TotalPktNum: r.VolMeasurement.TotalPktNum, 142 | UplinkPktNum: r.VolMeasurement.UplinkPktNum, 143 | DownlinkPktNum: r.VolMeasurement.DownlinkPktNum, 144 | } 145 | usars[r.SEID] = append(usars[r.SEID], usar) 146 | } 147 | 148 | for seid, rs := range usars { 149 | var usars []report.Report 150 | for _, r := range rs { 151 | usars = append(usars, r) 152 | } 153 | s.handler.NotifySessReport( 154 | report.SessReport{ 155 | SEID: seid, 156 | Reports: usars, 157 | }, 158 | ) 159 | } 160 | default: 161 | return false 162 | } 163 | 164 | return true 165 | } 166 | 167 | func (s *Server) Pop(seid uint64, pdrid uint16) ([]byte, bool) { 168 | return s.handler.PopBufPkt(seid, pdrid) 169 | } 170 | -------------------------------------------------------------------------------- /internal/forwarder/buffnetlink/server_test.go: -------------------------------------------------------------------------------- 1 | package buffnetlink 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "os" 8 | "sync" 9 | "syscall" 10 | "testing" 11 | "time" 12 | 13 | "github.com/khirono/go-genl" 14 | "github.com/khirono/go-nl" 15 | 16 | "github.com/free5gc/go-upf/internal/report" 17 | ) 18 | 19 | type testHandler struct { 20 | q map[uint64]map[uint16]chan []byte 21 | } 22 | 23 | func NewTestHandler() *testHandler { 24 | return &testHandler{q: make(map[uint64]map[uint16]chan []byte)} 25 | } 26 | 27 | func (h *testHandler) Close() { 28 | for _, s := range h.q { 29 | for _, q := range s { 30 | close(q) 31 | } 32 | } 33 | } 34 | 35 | func (h *testHandler) NotifySessReport(sr report.SessReport) { 36 | s, ok := h.q[sr.SEID] 37 | if !ok { 38 | return 39 | } 40 | for _, rep := range sr.Reports { 41 | switch r := rep.(type) { 42 | case report.DLDReport: 43 | if r.Action&report.APPLY_ACT_BUFF != 0 && len(r.BufPkt) > 0 { 44 | q, ok := s[r.PDRID] 45 | if !ok { 46 | qlen := 10 47 | s[r.PDRID] = make(chan []byte, qlen) 48 | q = s[r.PDRID] 49 | } 50 | q <- r.BufPkt 51 | } 52 | default: 53 | } 54 | } 55 | } 56 | 57 | func (h *testHandler) PopBufPkt(seid uint64, pdrid uint16) ([]byte, bool) { 58 | s, ok := h.q[seid] 59 | if !ok { 60 | return nil, false 61 | } 62 | q, ok := s[pdrid] 63 | if !ok { 64 | return nil, false 65 | } 66 | select { 67 | case pkt := <-q: 68 | return pkt, true 69 | default: 70 | return nil, false 71 | } 72 | } 73 | 74 | func TestServer(t *testing.T) { 75 | if testing.Short() { 76 | t.Skip("skipping testing in short mode") 77 | } 78 | 79 | var wg sync.WaitGroup 80 | 81 | mux, err := nl.NewMux() 82 | if err != nil { 83 | t.Fatal(err) 84 | } 85 | wg.Add(1) 86 | go func() { 87 | defer wg.Done() 88 | err = mux.Serve() 89 | if err != nil { 90 | fmt.Fprintf(os.Stderr, "%v", err) 91 | } 92 | }() 93 | 94 | conn, err := nl.Open(syscall.NETLINK_GENERIC) 95 | if err != nil { 96 | t.Fatal(err) 97 | } 98 | 99 | c := nl.NewClient(conn, mux) 100 | s, err := OpenServer(&wg, c, mux) 101 | if err != nil { 102 | t.Fatal(err) 103 | } 104 | 105 | f, err := genl.GetFamily(c, "gtp5g") 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | conn.Close() 110 | 111 | h := NewTestHandler() 112 | defer func() { 113 | h.Close() 114 | s.Close() 115 | mux.Close() 116 | wg.Wait() 117 | }() 118 | 119 | fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW|syscall.SOCK_CLOEXEC, syscall.NETLINK_GENERIC) 120 | if err != nil { 121 | t.Fatal(err) 122 | } 123 | defer func() { 124 | errClose := syscall.Close(fd) 125 | if errClose != nil { 126 | t.Fatal(errClose) 127 | } 128 | }() 129 | 130 | seid := uint64(6) 131 | h.q[seid] = make(map[uint16]chan []byte) 132 | s.Handle(h) 133 | 134 | pkt := []byte{ 135 | 0x00, 0x00, 0x00, 0x00, 136 | 0x00, 0x00, 137 | 0x00, 0x00, 138 | 0x00, 0x00, 0x00, 0x00, 139 | 0x00, 0x00, 0x00, 0x00, 140 | 0x10, 141 | 0x00, 142 | 0x00, 0x00, 143 | 0x0c, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x03, 0x00, 144 | 0x06, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00, 145 | 0x06, 0x00, 0x07, 0x00, 0x0c, 0x00, 0x00, 0x00, 146 | 0x08, 0x00, 0x04, 0x00, 147 | 0xee, 0xbb, 148 | 0xdd, 0xcc, 149 | } 150 | 151 | binary.LittleEndian.PutUint16(pkt[4:6], f.ID) 152 | binary.LittleEndian.PutUint32(pkt[0:4], uint32(len(pkt))) 153 | 154 | N := 10 155 | for i := 0; i < N; i++ { 156 | addr := syscall.SockaddrNetlink{ 157 | Family: syscall.AF_NETLINK, 158 | Groups: 1 << (f.Groups[0].ID - 1), 159 | } 160 | err = syscall.Sendmsg(fd, pkt, nil, &addr, 0) 161 | if err != nil { 162 | t.Fatal(err) 163 | } 164 | time.Sleep(100 * time.Millisecond) 165 | 166 | pdrid := uint16(3) 167 | pkt, ok := s.Pop(seid, pdrid) 168 | if !ok { 169 | t.Fatal("not found") 170 | } 171 | 172 | want := []byte{0xee, 0xbb, 0xdd, 0xcc} 173 | if !bytes.Equal(pkt, want) { 174 | t.Errorf("want %x; but got %x\n", want, pkt) 175 | } 176 | 177 | _, ok = s.Pop(seid, pdrid) 178 | if ok { 179 | t.Fatal("found") 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /internal/forwarder/driver.go: -------------------------------------------------------------------------------- 1 | package forwarder 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "sync" 7 | 8 | "github.com/pkg/errors" 9 | "github.com/wmnsk/go-pfcp/ie" 10 | 11 | "github.com/free5gc/go-upf/internal/logger" 12 | "github.com/free5gc/go-upf/internal/report" 13 | "github.com/free5gc/go-upf/pkg/factory" 14 | ) 15 | 16 | type Driver interface { 17 | Close() 18 | 19 | CreatePDR(uint64, *ie.IE) error 20 | UpdatePDR(uint64, *ie.IE) error 21 | RemovePDR(uint64, *ie.IE) error 22 | 23 | CreateFAR(uint64, *ie.IE) error 24 | UpdateFAR(uint64, *ie.IE) error 25 | RemoveFAR(uint64, *ie.IE) error 26 | 27 | CreateQER(uint64, *ie.IE) error 28 | UpdateQER(uint64, *ie.IE) error 29 | RemoveQER(uint64, *ie.IE) error 30 | 31 | CreateURR(uint64, *ie.IE) error 32 | UpdateURR(uint64, *ie.IE) ([]report.USAReport, error) 33 | RemoveURR(uint64, *ie.IE) ([]report.USAReport, error) 34 | QueryURR(uint64, uint32) ([]report.USAReport, error) 35 | 36 | CreateBAR(uint64, *ie.IE) error 37 | UpdateBAR(uint64, *ie.IE) error 38 | RemoveBAR(uint64, *ie.IE) error 39 | 40 | HandleReport(report.Handler) 41 | } 42 | 43 | func NewDriver(wg *sync.WaitGroup, cfg *factory.Config) (Driver, error) { 44 | cfgGtpu := cfg.Gtpu 45 | if cfgGtpu == nil { 46 | return nil, errors.Errorf("no Gtpu config") 47 | } 48 | 49 | logger.MainLog.Infof("starting Gtpu Forwarder [%s]", cfgGtpu.Forwarder) 50 | if cfgGtpu.Forwarder == "gtp5g" { 51 | var gtpuAddr string 52 | var mtu uint32 53 | for _, ifInfo := range cfgGtpu.IfList { 54 | mtu = ifInfo.MTU 55 | gtpuAddr = fmt.Sprintf("%s:%d", ifInfo.Addr, factory.UpfGtpDefaultPort) 56 | logger.MainLog.Infof("GTP Address: %q", gtpuAddr) 57 | break 58 | } 59 | if gtpuAddr == "" { 60 | return nil, errors.Errorf("not found GTP address") 61 | } 62 | driver, err := OpenGtp5g(wg, gtpuAddr, mtu) 63 | if err != nil { 64 | return nil, errors.Wrap(err, "open Gtp5g") 65 | } 66 | 67 | link := driver.Link() 68 | for _, dnn := range cfg.DnnList { 69 | _, dst, err := net.ParseCIDR(dnn.Cidr) 70 | if err != nil { 71 | logger.MainLog.Errorln(err) 72 | continue 73 | } 74 | err = link.RouteAdd(dst) 75 | if err != nil { 76 | driver.Close() 77 | return nil, err 78 | } 79 | } 80 | return driver, nil 81 | } 82 | return nil, errors.Errorf("not support forwarder:%q", cfgGtpu.Forwarder) 83 | } 84 | -------------------------------------------------------------------------------- /internal/forwarder/empty.go: -------------------------------------------------------------------------------- 1 | package forwarder 2 | 3 | import ( 4 | "github.com/wmnsk/go-pfcp/ie" 5 | 6 | "github.com/free5gc/go-upf/internal/report" 7 | ) 8 | 9 | type Empty struct{} 10 | 11 | func (Empty) Close() { 12 | } 13 | 14 | func (Empty) CreatePDR(uint64, *ie.IE) error { 15 | return nil 16 | } 17 | 18 | func (Empty) UpdatePDR(uint64, *ie.IE) error { 19 | return nil 20 | } 21 | 22 | func (Empty) RemovePDR(uint64, *ie.IE) error { 23 | return nil 24 | } 25 | 26 | func (Empty) CreateFAR(uint64, *ie.IE) error { 27 | return nil 28 | } 29 | 30 | func (Empty) UpdateFAR(uint64, *ie.IE) error { 31 | return nil 32 | } 33 | 34 | func (Empty) RemoveFAR(uint64, *ie.IE) error { 35 | return nil 36 | } 37 | 38 | func (Empty) CreateQER(uint64, *ie.IE) error { 39 | return nil 40 | } 41 | 42 | func (Empty) UpdateQER(uint64, *ie.IE) error { 43 | return nil 44 | } 45 | 46 | func (Empty) RemoveQER(uint64, *ie.IE) error { 47 | return nil 48 | } 49 | 50 | func (Empty) CreateURR(uint64, *ie.IE) error { 51 | return nil 52 | } 53 | 54 | func (Empty) UpdateURR(uint64, *ie.IE) ([]report.USAReport, error) { 55 | return nil, nil 56 | } 57 | 58 | func (Empty) RemoveURR(uint64, *ie.IE) ([]report.USAReport, error) { 59 | return nil, nil 60 | } 61 | 62 | func (Empty) CreateBAR(uint64, *ie.IE) error { 63 | return nil 64 | } 65 | 66 | func (Empty) UpdateBAR(uint64, *ie.IE) error { 67 | return nil 68 | } 69 | 70 | func (Empty) RemoveBAR(uint64, *ie.IE) error { 71 | return nil 72 | } 73 | 74 | func (Empty) QueryURR(uint64, uint32) ([]report.USAReport, error) { 75 | return nil, nil 76 | } 77 | 78 | func (Empty) HandleReport(report.Handler) { 79 | } 80 | -------------------------------------------------------------------------------- /internal/forwarder/flowdesc.go: -------------------------------------------------------------------------------- 1 | package forwarder 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // IPFilterRule <- action s dir s proto s 'from' s src 'to' s dst 11 | // action <- 'permit' / 'deny' 12 | // dir <- 'in' / 'out' 13 | // proto <- 'ip' / digit 14 | // src <- addr s ports? 15 | // dst <- addr s ports? 16 | // addr <- 'any' / 'assigned' / cidr 17 | // cidr <- ipv4addr ('/' digit)? 18 | // ipv4addr <- digit ('.' digit){3} 19 | // ports <- port (',' port)* 20 | // port <- (digit '-' digit) / digit 21 | // digit <- [1-9][0-9]+ 22 | // s <- ' '+ 23 | 24 | type FlowDesc struct { 25 | Action string 26 | Dir string 27 | Proto uint8 28 | Src *net.IPNet 29 | Dst *net.IPNet 30 | SrcPorts [][]uint16 31 | DstPorts [][]uint16 32 | } 33 | 34 | func ParseFlowDesc(s string) (*FlowDesc, error) { 35 | fd := new(FlowDesc) 36 | token := strings.Fields(s) 37 | pos := 0 38 | 39 | if pos >= len(token) { 40 | return nil, fmt.Errorf("too few fields %v", len(token)) 41 | } 42 | switch token[pos] { 43 | case "permit": 44 | fd.Action = token[pos] 45 | default: 46 | return nil, fmt.Errorf("unknown action %v", token[pos]) 47 | } 48 | pos++ 49 | 50 | if pos >= len(token) { 51 | return nil, fmt.Errorf("too few fields %v", len(token)) 52 | } 53 | switch token[pos] { 54 | case "in", "out": 55 | fd.Dir = token[pos] 56 | default: 57 | return nil, fmt.Errorf("unknown direction %v", token[pos]) 58 | } 59 | pos++ 60 | 61 | if pos >= len(token) { 62 | return nil, fmt.Errorf("too few fields %v", len(token)) 63 | } 64 | switch token[pos] { 65 | case "ip": 66 | fd.Proto = 0xff 67 | default: 68 | v, err := strconv.ParseUint(token[pos], 10, 8) 69 | if err != nil { 70 | return nil, err 71 | } 72 | fd.Proto = uint8(v) 73 | } 74 | pos++ 75 | 76 | if pos >= len(token) { 77 | return nil, fmt.Errorf("too few fields %v", len(token)) 78 | } 79 | if token[pos] != "from" { 80 | return nil, fmt.Errorf("not match 'from'") 81 | } 82 | pos++ 83 | 84 | if pos >= len(token) { 85 | return nil, fmt.Errorf("too few fields %v", len(token)) 86 | } 87 | src, err := ParseFlowDescIPNet(token[pos]) 88 | if err != nil { 89 | return nil, err 90 | } 91 | pos++ 92 | fd.Src = src 93 | 94 | if pos >= len(token) { 95 | return nil, fmt.Errorf("too few fields %v", len(token)) 96 | } 97 | sports, err := ParseFlowDescPorts(token[pos]) 98 | if err == nil { 99 | fd.SrcPorts = sports 100 | pos++ 101 | } 102 | 103 | if pos >= len(token) { 104 | return nil, fmt.Errorf("too few fields %v", len(token)) 105 | } 106 | if token[pos] != "to" { 107 | return nil, fmt.Errorf("not match 'to'") 108 | } 109 | pos++ 110 | 111 | if pos >= len(token) { 112 | return nil, fmt.Errorf("too few fields %v", len(token)) 113 | } 114 | dst, err := ParseFlowDescIPNet(token[pos]) 115 | if err != nil { 116 | return nil, err 117 | } 118 | pos++ 119 | fd.Dst = dst 120 | 121 | if pos < len(token) { 122 | dports, err := ParseFlowDescPorts(token[pos]) 123 | if err == nil { 124 | fd.DstPorts = dports 125 | } 126 | } 127 | 128 | return fd, nil 129 | } 130 | 131 | func ParseFlowDescIPNet(s string) (*net.IPNet, error) { 132 | if s == "any" || s == "assigned" { 133 | return &net.IPNet{ 134 | IP: net.IPv6zero, 135 | Mask: net.CIDRMask(0, 128), 136 | }, nil 137 | } 138 | _, ipnet, err := net.ParseCIDR(s) 139 | if err == nil { 140 | return ipnet, nil 141 | } 142 | ip := net.ParseIP(s) 143 | if ip == nil { 144 | return nil, fmt.Errorf("invalid address %v", s) 145 | } 146 | v4 := ip.To4() 147 | if v4 != nil { 148 | ip = v4 149 | } 150 | n := len(ip) * 8 151 | return &net.IPNet{ 152 | IP: ip, 153 | Mask: net.CIDRMask(n, n), 154 | }, nil 155 | } 156 | 157 | func ParseFlowDescPorts(s string) ([][]uint16, error) { 158 | var vals [][]uint16 159 | for _, port := range strings.Split(s, ",") { 160 | digit := strings.SplitN(port, "-", 2) 161 | switch len(digit) { 162 | case 1: 163 | v, err := strconv.ParseUint(digit[0], 10, 16) 164 | if err != nil { 165 | return nil, err 166 | } 167 | vals = append(vals, []uint16{uint16(v)}) 168 | case 2: 169 | start, err := strconv.ParseUint(digit[0], 10, 16) 170 | if err != nil { 171 | return nil, err 172 | } 173 | end, err := strconv.ParseUint(digit[1], 10, 16) 174 | if err != nil { 175 | return nil, err 176 | } 177 | vals = append(vals, []uint16{uint16(start), uint16(end)}) 178 | default: 179 | return nil, fmt.Errorf("invalid port: %q", port) 180 | } 181 | } 182 | return vals, nil 183 | } 184 | -------------------------------------------------------------------------------- /internal/forwarder/flowdesc_test.go: -------------------------------------------------------------------------------- 1 | package forwarder 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestParseFlowDesc(t *testing.T) { 11 | cases := []struct { 12 | name string 13 | s string 14 | fd FlowDesc 15 | err error 16 | }{ 17 | { 18 | name: "host addr", 19 | s: "permit out ip from 10.20.30.40 to 50.60.70.80", 20 | fd: FlowDesc{ 21 | Action: "permit", 22 | Dir: "out", 23 | Proto: 0xff, 24 | Src: &net.IPNet{ 25 | IP: net.IPv4(10, 20, 30, 40).To4(), 26 | Mask: net.CIDRMask(32, 32), 27 | }, 28 | Dst: &net.IPNet{ 29 | IP: net.IPv4(50, 60, 70, 80).To4(), 30 | Mask: net.CIDRMask(32, 32), 31 | }, 32 | }, 33 | }, 34 | { 35 | name: "proto", 36 | s: "permit out 210 from 10.20.30.40 to 50.60.70.80", 37 | fd: FlowDesc{ 38 | Action: "permit", 39 | Dir: "out", 40 | Proto: 210, 41 | Src: &net.IPNet{ 42 | IP: net.IPv4(10, 20, 30, 40).To4(), 43 | Mask: net.CIDRMask(32, 32), 44 | }, 45 | Dst: &net.IPNet{ 46 | IP: net.IPv4(50, 60, 70, 80).To4(), 47 | Mask: net.CIDRMask(32, 32), 48 | }, 49 | }, 50 | }, 51 | { 52 | name: "network addr", 53 | s: "permit out ip from 10.20.30.40/24 to 50.60.70.80/16", 54 | fd: FlowDesc{ 55 | Action: "permit", 56 | Dir: "out", 57 | Proto: 0xff, 58 | Src: &net.IPNet{ 59 | IP: net.IPv4(10, 20, 30, 0).To4(), 60 | Mask: net.CIDRMask(24, 32), 61 | }, 62 | Dst: &net.IPNet{ 63 | IP: net.IPv4(50, 60, 0, 0).To4(), 64 | Mask: net.CIDRMask(16, 32), 65 | }, 66 | }, 67 | }, 68 | { 69 | name: "source port", 70 | s: "permit out ip from 10.20.30.0/24 345,789-792,1023-1026 to 50.60.0.0/16", 71 | fd: FlowDesc{ 72 | Action: "permit", 73 | Dir: "out", 74 | Proto: 0xff, 75 | Src: &net.IPNet{ 76 | IP: net.IPv4(10, 20, 30, 0).To4(), 77 | Mask: net.CIDRMask(24, 32), 78 | }, 79 | Dst: &net.IPNet{ 80 | IP: net.IPv4(50, 60, 0, 0).To4(), 81 | Mask: net.CIDRMask(16, 32), 82 | }, 83 | SrcPorts: [][]uint16{ 84 | { 85 | 345, 86 | }, 87 | { 88 | 789, 89 | 792, 90 | }, 91 | { 92 | 1023, 93 | 1026, 94 | }, 95 | }, 96 | }, 97 | }, 98 | { 99 | name: "dst port", 100 | s: "permit out ip from 10.20.30.0/24 to 50.60.0.0/16 345,789-792,1023-1026", 101 | fd: FlowDesc{ 102 | Action: "permit", 103 | Dir: "out", 104 | Proto: 0xff, 105 | Src: &net.IPNet{ 106 | IP: net.IPv4(10, 20, 30, 0).To4(), 107 | Mask: net.CIDRMask(24, 32), 108 | }, 109 | Dst: &net.IPNet{ 110 | IP: net.IPv4(50, 60, 0, 0).To4(), 111 | Mask: net.CIDRMask(16, 32), 112 | }, 113 | DstPorts: [][]uint16{ 114 | { 115 | 345, 116 | }, 117 | { 118 | 789, 119 | 792, 120 | }, 121 | { 122 | 1023, 123 | 1026, 124 | }, 125 | }, 126 | }, 127 | }, 128 | { 129 | name: "any to assign", 130 | s: "permit out ip from any to assigned", 131 | fd: FlowDesc{ 132 | Action: "permit", 133 | Dir: "out", 134 | Proto: 0xff, 135 | Src: &net.IPNet{ 136 | IP: net.IPv6zero, 137 | Mask: net.CIDRMask(0, 128), 138 | }, 139 | Dst: &net.IPNet{ 140 | IP: net.IPv6zero, 141 | Mask: net.CIDRMask(0, 128), 142 | }, 143 | }, 144 | }, 145 | } 146 | for _, tt := range cases { 147 | t.Run(tt.name, func(t *testing.T) { 148 | fd, err := ParseFlowDesc(tt.s) 149 | if tt.err == nil { 150 | if err != nil { 151 | t.Fatal(err) 152 | } 153 | assert.Equal(t, &tt.fd, fd) 154 | } else if err != tt.err { 155 | t.Errorf("wantErr %v; but got %v", tt.err, err) 156 | } 157 | }) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /internal/forwarder/gtp5g_test.go: -------------------------------------------------------------------------------- 1 | package forwarder 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | "strconv" 7 | "sync" 8 | "testing" 9 | "time" 10 | 11 | "github.com/khirono/go-nl" 12 | "github.com/stretchr/testify/assert" 13 | "github.com/stretchr/testify/require" 14 | "github.com/wmnsk/go-pfcp/ie" 15 | 16 | "github.com/free5gc/go-gtp5gnl" 17 | "github.com/free5gc/go-upf/internal/report" 18 | "github.com/free5gc/go-upf/pkg/factory" 19 | ) 20 | 21 | func Test_convertSlice(t *testing.T) { 22 | t.Run("convert slices", func(t *testing.T) { 23 | b := convertSlice([][]uint16{{1}, {2, 4}}) 24 | want := []byte{0x01, 0x00, 0x01, 0x00, 0x04, 0x00, 0x02, 0x00} 25 | if !bytes.Equal(b, want) { 26 | t.Errorf("want %x; but got %x\n", want, b) 27 | } 28 | }) 29 | } 30 | 31 | type testHandler struct{} 32 | 33 | var testSessRpts map[uint64]*report.SessReport // key: SEID 34 | 35 | func (h *testHandler) NotifySessReport(sessRpt report.SessReport) { 36 | testSessRpts[sessRpt.SEID] = &sessRpt 37 | } 38 | 39 | func (h *testHandler) PopBufPkt(lSeid uint64, pdrid uint16) ([]byte, bool) { 40 | return nil, true 41 | } 42 | 43 | func TestGtp5g_CreateRules(t *testing.T) { 44 | if testing.Short() { 45 | t.Skip("skipping testing in short mode") 46 | } 47 | 48 | var wg sync.WaitGroup 49 | g, err := OpenGtp5g(&wg, ":"+strconv.Itoa(factory.UpfGtpDefaultPort), 1400) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | defer g.Close() 54 | 55 | testSessRpts = make(map[uint64]*report.SessReport) 56 | g.HandleReport(&testHandler{}) 57 | 58 | lSeid := uint64(1) 59 | t.Run("create rules", func(t *testing.T) { 60 | far := ie.NewCreateFAR( 61 | ie.NewFARID(2), 62 | ie.NewApplyAction(0x2), 63 | ie.NewForwardingParameters( 64 | ie.NewDestinationInterface(ie.DstInterfaceSGiLANN6LAN), 65 | ie.NewNetworkInstance("internet"), 66 | ), 67 | ) 68 | 69 | err = g.CreateFAR(lSeid, far) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | far = ie.NewCreateFAR( 75 | ie.NewFARID(4), 76 | ie.NewApplyAction(0x2), 77 | ) 78 | 79 | err = g.CreateFAR(lSeid, far) 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | 84 | qer := ie.NewCreateQER( 85 | ie.NewQERID(1), 86 | ie.NewGateStatus(ie.GateStatusOpen, ie.GateStatusOpen), 87 | ie.NewMBR(200000, 100000), 88 | ie.NewQFI(10), 89 | ) 90 | 91 | err = g.CreateQER(lSeid, qer) 92 | if err != nil { 93 | t.Fatal(err) 94 | } 95 | 96 | rptTrig := report.ReportingTrigger{ 97 | Flags: report.RPT_TRIG_PERIO, 98 | } 99 | 100 | urr := ie.NewCreateURR( 101 | ie.NewURRID(1), 102 | ie.NewMeasurementPeriod(1*time.Second), 103 | ie.NewMeasurementMethod(0, 1, 0), 104 | rptTrig.IE(), 105 | ie.NewMeasurementInformation(4), 106 | ) 107 | err = g.CreateURR(lSeid, urr) 108 | if err != nil { 109 | t.Fatal(err) 110 | } 111 | 112 | rptTrig.Flags = report.RPT_TRIG_VOLTH | report.RPT_TRIG_VOLQU 113 | urr = ie.NewCreateURR( 114 | ie.NewURRID(2), 115 | ie.NewMeasurementMethod(0, 1, 0), 116 | rptTrig.IE(), 117 | ie.NewMeasurementInformation(4), 118 | ie.NewVolumeThreshold(7, 10000, 20000, 30000), 119 | ie.NewVolumeQuota(7, 40000, 50000, 60000), 120 | ) 121 | err = g.CreateURR(lSeid, urr) 122 | if err != nil { 123 | t.Fatal(err) 124 | } 125 | 126 | pdr := ie.NewCreatePDR( 127 | ie.NewPDRID(1), 128 | ie.NewPrecedence(255), 129 | ie.NewPDI( 130 | ie.NewSourceInterface(ie.SrcInterfaceAccess), 131 | ie.NewFTEID( 132 | 0x01, 133 | 1, 134 | net.ParseIP("30.30.30.2"), 135 | nil, 136 | 0, 137 | ), 138 | ie.NewNetworkInstance(""), 139 | ie.NewUEIPAddress( 140 | 0x02, 141 | "60.60.0.1", 142 | "", 143 | 0, 144 | 0, 145 | ), 146 | ), 147 | ie.NewOuterHeaderRemoval(0, 0), 148 | ie.NewFARID(2), 149 | ie.NewQERID(1), 150 | ie.NewURRID(1), 151 | ie.NewURRID(2), 152 | ) 153 | 154 | err = g.CreatePDR(lSeid, pdr) 155 | if err != nil { 156 | t.Fatal(err) 157 | } 158 | 159 | pdr = ie.NewCreatePDR( 160 | ie.NewPDRID(3), 161 | ie.NewPrecedence(255), 162 | ie.NewPDI( 163 | ie.NewSourceInterface(ie.SrcInterfaceCore), 164 | ie.NewNetworkInstance("internet"), 165 | ie.NewUEIPAddress( 166 | 0x02, 167 | "60.60.0.1", 168 | "", 169 | 0, 170 | 0, 171 | ), 172 | ), 173 | ie.NewFARID(4), 174 | ie.NewQERID(1), 175 | ie.NewURRID(1), 176 | ) 177 | 178 | err = g.CreatePDR(lSeid, pdr) 179 | if err != nil { 180 | t.Fatal(err) 181 | } 182 | 183 | time.Sleep(1100 * time.Millisecond) 184 | 185 | require.Contains(t, testSessRpts, lSeid) 186 | require.Equal(t, len(testSessRpts[lSeid].Reports), 1) 187 | require.Equal(t, testSessRpts[lSeid].Reports[0].(report.USAReport).URRID, uint32(1)) 188 | }) 189 | 190 | t.Run("update rules", func(t *testing.T) { 191 | rpt := report.ReportingTrigger{ 192 | Flags: report.RPT_TRIG_PERIO, 193 | } 194 | 195 | urr := ie.NewUpdateURR( 196 | ie.NewURRID(1), 197 | ie.NewMeasurementPeriod(2*time.Second), 198 | rpt.IE(), 199 | ) 200 | rs, err := g.UpdateURR(lSeid, urr) 201 | if err != nil { 202 | t.Fatal(err) 203 | } 204 | 205 | // TODO: should apply PERIO updateURR and receive final report from old URR 206 | require.Nil(t, rs) 207 | // require.NotNil(t, r) 208 | // require.Equal(t, r.URRID, uint32(1)) 209 | 210 | far := ie.NewUpdateFAR( 211 | ie.NewFARID(4), 212 | ie.NewApplyAction(0x2), 213 | ie.NewUpdateForwardingParameters( 214 | ie.NewDestinationInterface(ie.DstInterfaceAccess), 215 | ie.NewNetworkInstance("internet"), 216 | ie.NewOuterHeaderCreation( 217 | 0x0100, 218 | 1, 219 | "30.30.30.1", 220 | "", 221 | 0, 222 | 0, 223 | 0, 224 | ), 225 | ), 226 | ) 227 | 228 | err = g.UpdateFAR(lSeid, far) 229 | if err != nil { 230 | t.Fatal(err) 231 | } 232 | 233 | pdr := ie.NewUpdatePDR( 234 | ie.NewPDRID(3), 235 | ie.NewPrecedence(255), 236 | ie.NewPDI( 237 | ie.NewSourceInterface(ie.SrcInterfaceCore), 238 | ie.NewNetworkInstance("internet"), 239 | ie.NewUEIPAddress( 240 | 0x02, 241 | "60.60.0.1", 242 | "", 243 | 0, 244 | 0, 245 | ), 246 | ), 247 | ie.NewFARID(4), 248 | ) 249 | 250 | err = g.UpdatePDR(lSeid, pdr) 251 | if err != nil { 252 | t.Fatal(err) 253 | } 254 | }) 255 | 256 | t.Run("remove rules", func(t *testing.T) { 257 | urr := ie.NewRemoveURR( 258 | ie.NewURRID(1), 259 | ) 260 | 261 | rs, err1 := g.RemoveURR(lSeid, urr) 262 | if err1 != nil { 263 | t.Fatal(err1) 264 | } 265 | g.log.Infof("Receive final report from URR(%d), rpts: %+v", rs[0].URRID, rs) 266 | 267 | require.NotNil(t, rs) 268 | g.log.Infof("Receive final report from URR(%d)", rs[0].URRID) 269 | 270 | urr = ie.NewRemoveURR( 271 | ie.NewURRID(2), 272 | ) 273 | rs, err1 = g.RemoveURR(lSeid, urr) 274 | if err1 != nil { 275 | t.Fatal(err1) 276 | } 277 | 278 | g.log.Infof("Receive final report from URR(%d), rpts: %+v", rs[0].URRID, rs) 279 | 280 | require.NotNil(t, rs) 281 | g.log.Infof("Receive final reports from URR(%d)", rs[0].URRID) 282 | }) 283 | } 284 | 285 | func TestNewFlowDesc(t *testing.T) { 286 | if testing.Short() { 287 | t.Skip("skipping testing in short mode") 288 | } 289 | 290 | var wg sync.WaitGroup 291 | g, err := OpenGtp5g(&wg, ":"+strconv.Itoa(factory.UpfGtpDefaultPort), 1400) 292 | if err != nil { 293 | t.Fatal(err) 294 | } 295 | defer func() { 296 | g.Close() 297 | wg.Wait() 298 | }() 299 | 300 | cases := []struct { 301 | name string 302 | s string 303 | swapSrcDst bool 304 | attrs nl.AttrList 305 | err error 306 | }{ 307 | { 308 | name: "permit out any to assigned", 309 | s: "permit out ip from any to assigned", 310 | swapSrcDst: false, 311 | attrs: nl.AttrList{ 312 | nl.Attr{ 313 | Type: gtp5gnl.FLOW_DESCRIPTION_ACTION, 314 | Value: nl.AttrU8(gtp5gnl.SDF_FILTER_PERMIT), 315 | }, 316 | nl.Attr{ 317 | Type: gtp5gnl.FLOW_DESCRIPTION_DIRECTION, 318 | Value: nl.AttrU8(gtp5gnl.SDF_FILTER_OUT), 319 | }, 320 | }, 321 | err: nil, 322 | }, 323 | { 324 | name: "network addr (UL)", 325 | s: "permit out ip from 10.20.30.40/24 to 50.60.70.80/16", 326 | swapSrcDst: false, 327 | attrs: nl.AttrList{ 328 | nl.Attr{ 329 | Type: gtp5gnl.FLOW_DESCRIPTION_ACTION, 330 | Value: nl.AttrU8(gtp5gnl.SDF_FILTER_PERMIT), 331 | }, 332 | nl.Attr{ 333 | Type: gtp5gnl.FLOW_DESCRIPTION_DIRECTION, 334 | Value: nl.AttrU8(gtp5gnl.SDF_FILTER_OUT), 335 | }, 336 | nl.Attr{ 337 | Type: gtp5gnl.FLOW_DESCRIPTION_SRC_IPV4, 338 | Value: nl.AttrBytes(net.IPv4(10, 20, 30, 0).To4()), 339 | }, 340 | nl.Attr{ 341 | Type: gtp5gnl.FLOW_DESCRIPTION_DEST_IPV4, 342 | Value: nl.AttrBytes(net.IPv4(50, 60, 0, 0).To4()), 343 | }, 344 | }, 345 | err: nil, 346 | }, 347 | { 348 | name: "network addr (DL)", 349 | s: "permit out ip from 10.20.30.40/24 to 50.60.70.80/16", 350 | swapSrcDst: true, 351 | attrs: nl.AttrList{ 352 | nl.Attr{ 353 | Type: gtp5gnl.FLOW_DESCRIPTION_ACTION, 354 | Value: nl.AttrU8(gtp5gnl.SDF_FILTER_PERMIT), 355 | }, 356 | nl.Attr{ 357 | Type: gtp5gnl.FLOW_DESCRIPTION_DIRECTION, 358 | Value: nl.AttrU8(gtp5gnl.SDF_FILTER_OUT), 359 | }, 360 | nl.Attr{ 361 | Type: gtp5gnl.FLOW_DESCRIPTION_SRC_IPV4, 362 | Value: nl.AttrBytes(net.IPv4(50, 60, 0, 0).To4()), 363 | }, 364 | nl.Attr{ 365 | Type: gtp5gnl.FLOW_DESCRIPTION_DEST_IPV4, 366 | Value: nl.AttrBytes(net.IPv4(10, 20, 30, 0).To4()), 367 | }, 368 | }, 369 | err: nil, 370 | }, 371 | { 372 | name: "source port (DL)", 373 | s: "permit out ip from 10.20.30.40/24 345,789-792,1023-1026 to 50.60.70.80/16 456-458,1088,1089", 374 | swapSrcDst: false, 375 | attrs: nl.AttrList{ 376 | nl.Attr{ 377 | Type: gtp5gnl.FLOW_DESCRIPTION_ACTION, 378 | Value: nl.AttrU8(gtp5gnl.SDF_FILTER_PERMIT), 379 | }, 380 | nl.Attr{ 381 | Type: gtp5gnl.FLOW_DESCRIPTION_DIRECTION, 382 | Value: nl.AttrU8(gtp5gnl.SDF_FILTER_OUT), 383 | }, 384 | nl.Attr{ 385 | Type: gtp5gnl.FLOW_DESCRIPTION_SRC_IPV4, 386 | Value: nl.AttrBytes(net.IPv4(10, 20, 30, 0).To4()), 387 | }, 388 | nl.Attr{ 389 | Type: gtp5gnl.FLOW_DESCRIPTION_DEST_IPV4, 390 | Value: nl.AttrBytes(net.IPv4(50, 60, 0, 0).To4()), 391 | }, 392 | nl.Attr{ 393 | Type: gtp5gnl.FLOW_DESCRIPTION_SRC_PORT, 394 | Value: nl.AttrBytes(convertSlice([][]uint16{ 395 | {345}, 396 | {789, 792}, 397 | {1023, 1026}, 398 | })), 399 | }, 400 | nl.Attr{ 401 | Type: gtp5gnl.FLOW_DESCRIPTION_DEST_PORT, 402 | Value: nl.AttrBytes(convertSlice([][]uint16{ 403 | {456, 458}, 404 | {1088}, 405 | {1089}, 406 | })), 407 | }, 408 | }, 409 | err: nil, 410 | }, 411 | { 412 | name: "source port (UL)", 413 | s: "permit out ip from 10.20.30.40/24 345,789-792,1023-1026 to 50.60.70.80/16 456-458,1088,1089", 414 | swapSrcDst: true, 415 | attrs: nl.AttrList{ 416 | nl.Attr{ 417 | Type: gtp5gnl.FLOW_DESCRIPTION_ACTION, 418 | Value: nl.AttrU8(gtp5gnl.SDF_FILTER_PERMIT), 419 | }, 420 | nl.Attr{ 421 | Type: gtp5gnl.FLOW_DESCRIPTION_DIRECTION, 422 | Value: nl.AttrU8(gtp5gnl.SDF_FILTER_OUT), 423 | }, 424 | nl.Attr{ 425 | Type: gtp5gnl.FLOW_DESCRIPTION_SRC_IPV4, 426 | Value: nl.AttrBytes(net.IPv4(50, 60, 0, 0).To4()), 427 | }, 428 | nl.Attr{ 429 | Type: gtp5gnl.FLOW_DESCRIPTION_DEST_IPV4, 430 | Value: nl.AttrBytes(net.IPv4(10, 20, 30, 0).To4()), 431 | }, 432 | nl.Attr{ 433 | Type: gtp5gnl.FLOW_DESCRIPTION_SRC_PORT, 434 | Value: nl.AttrBytes(convertSlice([][]uint16{ 435 | {456, 458}, 436 | {1088}, 437 | {1089}, 438 | })), 439 | }, 440 | nl.Attr{ 441 | Type: gtp5gnl.FLOW_DESCRIPTION_DEST_PORT, 442 | Value: nl.AttrBytes(convertSlice([][]uint16{ 443 | {345}, 444 | {789, 792}, 445 | {1023, 1026}, 446 | })), 447 | }, 448 | }, 449 | err: nil, 450 | }, 451 | } 452 | 453 | for _, tt := range cases { 454 | t.Run(tt.name, func(t *testing.T) { 455 | attrs, err := g.newFlowDesc(tt.s, tt.swapSrcDst) 456 | if tt.err == nil { 457 | if err != nil { 458 | t.Fatal(err) 459 | } 460 | assert.Subset(t, attrs, tt.attrs) 461 | } else if err != tt.err { 462 | t.Errorf("wantErr %v; but got %v", tt.err, err) 463 | } 464 | }) 465 | } 466 | } 467 | 468 | // TODO 469 | // Test on newSdfFilter() 470 | -------------------------------------------------------------------------------- /internal/forwarder/gtp5glink.go: -------------------------------------------------------------------------------- 1 | package forwarder 2 | 3 | import ( 4 | "net" 5 | "os" 6 | "syscall" 7 | 8 | "github.com/khirono/go-nl" 9 | "github.com/khirono/go-rtnllink" 10 | "github.com/khirono/go-rtnlroute" 11 | "github.com/pkg/errors" 12 | "github.com/sirupsen/logrus" 13 | 14 | "github.com/free5gc/go-gtp5gnl" 15 | ) 16 | 17 | type Gtp5gLink struct { 18 | mux *nl.Mux 19 | rtconn *nl.Conn 20 | client *nl.Client 21 | link *gtp5gnl.Link 22 | conn *net.UDPConn 23 | f *os.File 24 | log *logrus.Entry 25 | } 26 | 27 | func OpenGtp5gLink(mux *nl.Mux, addr string, mtu uint32, log *logrus.Entry) (*Gtp5gLink, error) { 28 | g := &Gtp5gLink{ 29 | log: log, 30 | } 31 | 32 | g.mux = mux 33 | 34 | rtconn, err := nl.Open(syscall.NETLINK_ROUTE) 35 | if err != nil { 36 | return nil, errors.Wrap(err, "open") 37 | } 38 | g.rtconn = rtconn 39 | g.client = nl.NewClient(rtconn, mux) 40 | 41 | laddr, err := net.ResolveUDPAddr("udp4", addr) 42 | if err != nil { 43 | g.Close() 44 | return nil, errors.Wrap(err, "resolve addr") 45 | } 46 | conn, err := net.ListenUDP("udp4", laddr) 47 | if err != nil { 48 | g.Close() 49 | return nil, errors.Wrap(err, "listen") 50 | } 51 | g.conn = conn 52 | 53 | // TODO: Duplicate fd 54 | f, err := conn.File() 55 | if err != nil { 56 | g.Close() 57 | return nil, errors.Wrap(err, "file") 58 | } 59 | g.f = f 60 | 61 | linkinfo := &nl.Attr{ 62 | Type: syscall.IFLA_LINKINFO, 63 | Value: nl.AttrList{ 64 | { 65 | Type: rtnllink.IFLA_INFO_KIND, 66 | Value: nl.AttrString("gtp5g"), 67 | }, 68 | { 69 | Type: rtnllink.IFLA_INFO_DATA, 70 | Value: nl.AttrList{ 71 | { 72 | Type: gtp5gnl.IFLA_FD1, 73 | Value: nl.AttrU32(f.Fd()), 74 | }, 75 | { 76 | Type: gtp5gnl.IFLA_HASHSIZE, 77 | Value: nl.AttrU32(131072), 78 | }, 79 | }, 80 | }, 81 | }, 82 | } 83 | attrs := []*nl.Attr{linkinfo} 84 | 85 | if mtu != 0 { 86 | attrs = append(attrs, &nl.Attr{ 87 | Type: syscall.IFLA_MTU, 88 | Value: nl.AttrU32(mtu), 89 | }) 90 | } 91 | 92 | err = rtnllink.Create(g.client, "upfgtp", attrs...) 93 | if err != nil { 94 | g.Close() 95 | return nil, errors.Wrap(err, "create") 96 | } 97 | err = rtnllink.Up(g.client, "upfgtp") 98 | if err != nil { 99 | g.Close() 100 | return nil, errors.Wrap(err, "up") 101 | } 102 | link, err := gtp5gnl.GetLink("upfgtp") 103 | if err != nil { 104 | g.Close() 105 | return nil, errors.Wrap(err, "get link") 106 | } 107 | g.link = link 108 | return g, nil 109 | } 110 | 111 | func (g *Gtp5gLink) Close() { 112 | if g.f != nil { 113 | err := g.f.Close() 114 | if err != nil { 115 | g.log.Warnf("file close err: %+v", err) 116 | } 117 | } 118 | if g.conn != nil { 119 | err := g.conn.Close() 120 | if err != nil { 121 | g.log.Warnf("conn close err: %+v", err) 122 | } 123 | } 124 | if g.link != nil { 125 | err := rtnllink.Remove(g.client, "upfgtp") 126 | if err != nil { 127 | g.log.Warnf("rtnllink remove err: %+v", err) 128 | } 129 | } 130 | if g.rtconn != nil { 131 | g.rtconn.Close() 132 | } 133 | } 134 | 135 | func (g *Gtp5gLink) RouteAdd(dst *net.IPNet) error { 136 | r := &rtnlroute.Request{ 137 | Header: rtnlroute.Header{ 138 | Table: syscall.RT_TABLE_MAIN, 139 | Scope: syscall.RT_SCOPE_UNIVERSE, 140 | Protocol: syscall.RTPROT_STATIC, 141 | Type: syscall.RTN_UNICAST, 142 | }, 143 | } 144 | err := r.AddDst(dst) 145 | if err != nil { 146 | return err 147 | } 148 | err = r.AddIfName(g.link.Name) 149 | if err != nil { 150 | return err 151 | } 152 | return rtnlroute.Create(g.client, r) 153 | } 154 | 155 | func (g *Gtp5gLink) WriteTo(b []byte, addr net.Addr) (int, error) { 156 | return g.conn.WriteTo(b, addr) 157 | } 158 | -------------------------------------------------------------------------------- /internal/forwarder/perio/server.go: -------------------------------------------------------------------------------- 1 | package perio 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/pkg/errors" 8 | 9 | "github.com/free5gc/go-upf/internal/logger" 10 | "github.com/free5gc/go-upf/internal/report" 11 | ) 12 | 13 | const ( 14 | EVENT_CHANNEL_LEN = 512 15 | ) 16 | 17 | type EventType uint8 18 | 19 | const ( 20 | TYPE_PERIO_ADD EventType = iota + 1 21 | TYPE_PERIO_DEL 22 | TYPE_PERIO_TIMEOUT 23 | TYPE_SERVER_CLOSE 24 | ) 25 | 26 | func (t EventType) String() string { 27 | s := []string{ 28 | "", "TYPE_PERIO_ADD", "TYPE_PERIO_DEL", 29 | "TYPE_PERIO_TIMEOUT", "TYPE_SERVER_CLOSE", 30 | } 31 | return s[t] 32 | } 33 | 34 | type Event struct { 35 | eType EventType 36 | lSeid uint64 37 | urrid uint32 38 | period time.Duration 39 | } 40 | 41 | type PERIOGroup struct { 42 | urrids map[uint64]map[uint32]struct{} 43 | period time.Duration 44 | ticker *time.Ticker 45 | stopCh chan struct{} 46 | } 47 | 48 | func (pg *PERIOGroup) newTicker(wg *sync.WaitGroup, evtCh chan Event) error { 49 | if pg.ticker != nil { 50 | return errors.Errorf("ticker not nil") 51 | } 52 | logger.PerioLog.Infof("new ticker [%+v]", pg.period) 53 | 54 | pg.ticker = time.NewTicker(pg.period) 55 | pg.stopCh = make(chan struct{}) 56 | 57 | wg.Add(1) 58 | go func(ticker *time.Ticker, period time.Duration, evtCh chan Event) { 59 | defer func() { 60 | ticker.Stop() 61 | wg.Done() 62 | }() 63 | 64 | for { 65 | select { 66 | case <-ticker.C: 67 | logger.PerioLog.Debugf("ticker[%v] timeout", period) 68 | // If the UPF had terminating, the evtCh would be nil 69 | if evtCh != nil { 70 | evtCh <- Event{ 71 | eType: TYPE_PERIO_TIMEOUT, 72 | period: period, 73 | } 74 | } 75 | case <-pg.stopCh: 76 | logger.PerioLog.Infof("ticker[%v] Stopped", period) 77 | return 78 | } 79 | } 80 | }(pg.ticker, pg.period, evtCh) 81 | 82 | return nil 83 | } 84 | 85 | func (pg *PERIOGroup) stopTicker() { 86 | logger.PerioLog.Debugf("stopTicker: [%+v]", pg.period) 87 | pg.stopCh <- struct{}{} 88 | close(pg.stopCh) 89 | } 90 | 91 | type Server struct { 92 | evtCh chan Event 93 | perioList map[time.Duration]*PERIOGroup // key: period 94 | 95 | handler report.Handler 96 | queryURR func(map[uint64][]uint32) (map[uint64][]report.USAReport, error) 97 | } 98 | 99 | func OpenServer(wg *sync.WaitGroup) (*Server, error) { 100 | s := &Server{ 101 | evtCh: make(chan Event, EVENT_CHANNEL_LEN), 102 | perioList: make(map[time.Duration]*PERIOGroup), 103 | } 104 | 105 | wg.Add(1) 106 | go s.Serve(wg) 107 | 108 | return s, nil 109 | } 110 | 111 | func (s *Server) Close() { 112 | s.evtCh <- Event{eType: TYPE_SERVER_CLOSE} 113 | } 114 | 115 | func (s *Server) Handle( 116 | handler report.Handler, 117 | queryURR func(map[uint64][]uint32) (map[uint64][]report.USAReport, error), 118 | ) { 119 | s.handler = handler 120 | s.queryURR = queryURR 121 | } 122 | 123 | func (s *Server) Serve(wg *sync.WaitGroup) { 124 | logger.PerioLog.Infof("perio server started") 125 | defer func() { 126 | logger.PerioLog.Infof("perio server stopped") 127 | close(s.evtCh) 128 | wg.Done() 129 | }() 130 | 131 | for e := range s.evtCh { 132 | logger.PerioLog.Infof("recv event[%s][%+v]", e.eType, e) 133 | switch e.eType { 134 | case TYPE_PERIO_ADD: 135 | perioGroup, ok := s.perioList[e.period] 136 | if !ok { 137 | // New ticker if no this period ticker found 138 | perioGroup = &PERIOGroup{ 139 | urrids: make(map[uint64]map[uint32]struct{}), 140 | period: e.period, 141 | } 142 | err := perioGroup.newTicker(wg, s.evtCh) 143 | if err != nil { 144 | logger.PerioLog.Errorln(err) 145 | continue 146 | } 147 | s.perioList[e.period] = perioGroup 148 | } 149 | 150 | urrids := perioGroup.urrids[e.lSeid] 151 | if urrids == nil { 152 | perioGroup.urrids[e.lSeid] = make(map[uint32]struct{}) 153 | perioGroup.urrids[e.lSeid][e.urrid] = struct{}{} 154 | } else { 155 | _, ok := perioGroup.urrids[e.lSeid][e.urrid] 156 | if !ok { 157 | perioGroup.urrids[e.lSeid][e.urrid] = struct{}{} 158 | } 159 | } 160 | case TYPE_PERIO_DEL: 161 | for period, perioGroup := range s.perioList { 162 | _, ok := perioGroup.urrids[e.lSeid][e.urrid] 163 | if ok { 164 | // Stop ticker if no more PERIO URR 165 | delete(perioGroup.urrids[e.lSeid], e.urrid) 166 | if len(perioGroup.urrids[e.lSeid]) == 0 { 167 | delete(perioGroup.urrids, e.lSeid) 168 | if len(perioGroup.urrids) == 0 { 169 | // If no urr for the ticker, this ticker could be stop and delete 170 | perioGroup.stopTicker() 171 | delete(s.perioList, period) 172 | } 173 | } 174 | break 175 | } 176 | } 177 | case TYPE_PERIO_TIMEOUT: 178 | var lSeidUrridsMap map[uint64][]uint32 179 | 180 | perioGroup, ok := s.perioList[e.period] 181 | if !ok { 182 | logger.PerioLog.Warnf("no periodGroup found for period[%v]", e.period) 183 | break 184 | } 185 | 186 | lSeidUrridsMap = make(map[uint64][]uint32) 187 | for lSeid, urrIds := range perioGroup.urrids { 188 | for urrId := range urrIds { 189 | lSeidUrridsMap[lSeid] = append(lSeidUrridsMap[lSeid], urrId) 190 | } 191 | } 192 | 193 | seidUsars, err := s.queryURR(lSeidUrridsMap) 194 | if err != nil { 195 | logger.PerioLog.Warnf("get Multiple USAReports error: %v", err) 196 | break 197 | } 198 | if len(seidUsars) == 0 { 199 | logger.PerioLog.Warnf("no PERIO USAReport") 200 | break 201 | } 202 | 203 | for seid, usars := range seidUsars { 204 | var rpts []report.Report 205 | 206 | for i := range usars { 207 | usars[i].USARTrigger.Flags |= report.USAR_TRIG_PERIO 208 | rpts = append(rpts, usars[i]) 209 | } 210 | 211 | s.handler.NotifySessReport( 212 | report.SessReport{ 213 | SEID: seid, 214 | Reports: rpts, 215 | }) 216 | } 217 | case TYPE_SERVER_CLOSE: 218 | for period, perioGroup := range s.perioList { 219 | perioGroup.stopTicker() 220 | delete(s.perioList, period) 221 | } 222 | return 223 | } 224 | } 225 | } 226 | 227 | func (s *Server) AddPeriodReportTimer(lSeid uint64, urrid uint32, period time.Duration) { 228 | s.evtCh <- Event{ 229 | eType: TYPE_PERIO_ADD, 230 | lSeid: lSeid, 231 | urrid: urrid, 232 | period: period, 233 | } 234 | } 235 | 236 | func (s *Server) DelPeriodReportTimer(lSeid uint64, urrid uint32) { 237 | s.evtCh <- Event{ 238 | eType: TYPE_PERIO_DEL, 239 | lSeid: lSeid, 240 | urrid: urrid, 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /internal/forwarder/perio/server_test.go: -------------------------------------------------------------------------------- 1 | package perio 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/require" 9 | 10 | "github.com/free5gc/go-upf/internal/report" 11 | ) 12 | 13 | type testHandler struct{} 14 | 15 | func NewTestHandler() *testHandler { 16 | return &testHandler{} 17 | } 18 | 19 | var testSessRpts map[uint64]*report.SessReport // key: SEID 20 | 21 | func (h *testHandler) NotifySessReport(sessRpt report.SessReport) { 22 | testSessRpts[sessRpt.SEID] = &sessRpt 23 | } 24 | 25 | func (h *testHandler) PopBufPkt(lSeid uint64, pdrid uint16) ([]byte, bool) { 26 | return nil, true 27 | } 28 | 29 | func testGetUSAReport(lSeidUrridsMap map[uint64][]uint32) (map[uint64][]report.USAReport, error) { 30 | sessUsars := make(map[uint64][]report.USAReport) 31 | 32 | v := report.VolumeMeasure{ 33 | UplinkVolume: 10, 34 | DownlinkVolume: 20, 35 | TotalVolume: 30, 36 | } 37 | 38 | for lSeid, urrids := range lSeidUrridsMap { 39 | for _, urrid := range urrids { 40 | sessUsars[lSeid] = append(sessUsars[lSeid], report.USAReport{ 41 | URRID: urrid, 42 | USARTrigger: report.UsageReportTrigger{Flags: report.USAR_TRIG_PERIO}, 43 | VolumMeasure: v, 44 | }) 45 | } 46 | } 47 | 48 | return sessUsars, nil 49 | } 50 | 51 | func TestServer(t *testing.T) { 52 | var wg sync.WaitGroup 53 | s, err := OpenServer(&wg) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | defer func() { 58 | s.Close() 59 | wg.Wait() 60 | }() 61 | 62 | testSessRpts = make(map[uint64]*report.SessReport) 63 | h := NewTestHandler() 64 | s.Handle(h, testGetUSAReport) 65 | 66 | // 1. Add 3 PERIO URRs 67 | s.AddPeriodReportTimer( 68 | 1, // lSeid 69 | 1, // urrid 70 | 1*time.Second) 71 | s.AddPeriodReportTimer( 72 | 1, // lSeid 73 | 2, // urrid 74 | 1*time.Second) 75 | s.AddPeriodReportTimer( 76 | 2, // lSeid 77 | 1, // urrid 78 | 2*time.Second) 79 | 80 | time.Sleep(2100 * time.Millisecond) 81 | 82 | expectedSessRpts := map[uint64]*report.SessReport{ 83 | 1: { 84 | SEID: 1, 85 | Reports: []report.Report{ 86 | report.USAReport{ 87 | URRID: 1, 88 | USARTrigger: report.UsageReportTrigger{ 89 | Flags: report.USAR_TRIG_PERIO, 90 | }, 91 | VolumMeasure: report.VolumeMeasure{ 92 | TotalVolume: 30, 93 | UplinkVolume: 10, 94 | DownlinkVolume: 20, 95 | }, 96 | }, 97 | report.USAReport{ 98 | URRID: 2, 99 | USARTrigger: report.UsageReportTrigger{ 100 | Flags: report.USAR_TRIG_PERIO, 101 | }, 102 | VolumMeasure: report.VolumeMeasure{ 103 | TotalVolume: 30, 104 | UplinkVolume: 10, 105 | DownlinkVolume: 20, 106 | }, 107 | }, 108 | }, 109 | }, 110 | 2: { 111 | SEID: 2, 112 | Reports: []report.Report{ 113 | report.USAReport{ 114 | URRID: 1, 115 | USARTrigger: report.UsageReportTrigger{ 116 | Flags: report.USAR_TRIG_PERIO, 117 | }, 118 | VolumMeasure: report.VolumeMeasure{ 119 | TotalVolume: 30, 120 | UplinkVolume: 10, 121 | DownlinkVolume: 20, 122 | }, 123 | }, 124 | }, 125 | }, 126 | } 127 | 128 | // Check the reports 129 | require.Contains(t, testSessRpts, uint64(1)) 130 | require.Contains(t, testSessRpts, uint64(2)) 131 | require.ElementsMatch(t, testSessRpts[1].Reports, expectedSessRpts[1].Reports) 132 | require.ElementsMatch(t, testSessRpts[2].Reports, expectedSessRpts[2].Reports) 133 | 134 | testSessRpts = make(map[uint64]*report.SessReport) 135 | expectedSessRpts2 := map[uint64]*report.SessReport{ 136 | 1: { 137 | SEID: 1, 138 | Reports: expectedSessRpts[1].Reports[1:2], 139 | }, 140 | } 141 | 142 | // 2. Delete 2 PERIO URRs 143 | s.DelPeriodReportTimer( 144 | 1, // lSeid 145 | 1, // urrid 146 | ) 147 | s.DelPeriodReportTimer( 148 | 2, // lSeid 149 | 1, // urrid 150 | ) 151 | 152 | time.Sleep(1100 * time.Millisecond) 153 | 154 | // Check the reports 155 | require.Contains(t, testSessRpts, uint64(1)) 156 | require.ElementsMatch(t, testSessRpts[1].Reports, expectedSessRpts2[1].Reports) 157 | 158 | // 3. Make sure SEID(2) PERIO timer not launched 159 | testSessRpts = make(map[uint64]*report.SessReport) 160 | time.Sleep(1100 * time.Millisecond) 161 | 162 | require.Contains(t, testSessRpts, uint64(1)) 163 | require.NotContains(t, testSessRpts, uint64(2)) 164 | require.ElementsMatch(t, testSessRpts[1].Reports, expectedSessRpts2[1].Reports) 165 | } 166 | -------------------------------------------------------------------------------- /internal/gtpv1/msg.go: -------------------------------------------------------------------------------- 1 | package gtpv1 2 | 3 | import ( 4 | "encoding/binary" 5 | ) 6 | 7 | type Encoder interface { 8 | Len() int 9 | Encode([]byte) (int, error) 10 | } 11 | 12 | // Message Type definitions. 13 | const ( 14 | MsgTypeTPDU uint8 = 255 15 | ) 16 | 17 | type Message struct { 18 | Flags uint8 19 | Type uint8 20 | TEID uint32 21 | SequenceNumber uint16 22 | NPDUNumber uint8 23 | Exts []Encoder 24 | Payload []byte 25 | } 26 | 27 | func (m Message) HasSequence() bool { 28 | return m.Flags&0x2 != 0 29 | } 30 | 31 | func (m Message) HasNPDUNumber() bool { 32 | return m.Flags&0x1 != 0 33 | } 34 | 35 | func (m Message) Len() int { 36 | l := 8 37 | if m.HasSequence() { 38 | l += 2 39 | } 40 | if m.HasNPDUNumber() { 41 | l++ 42 | } 43 | l = ((l + 4) &^ 0x3) - 1 44 | for _, e := range m.Exts { 45 | l += e.Len() 46 | } 47 | l++ 48 | l += len(m.Payload) 49 | return l 50 | } 51 | 52 | func (m Message) Encode(b []byte) (int, error) { 53 | b[0] = m.Flags 54 | b[1] = m.Type 55 | l := m.Len() - 8 56 | binary.BigEndian.PutUint16(b[2:4], uint16(l)) 57 | binary.BigEndian.PutUint32(b[4:8], m.TEID) 58 | pos := 8 59 | if m.HasSequence() { 60 | binary.BigEndian.PutUint16(b[pos:pos+2], m.SequenceNumber) 61 | pos += 2 62 | } 63 | if m.HasNPDUNumber() { 64 | b[pos] = m.NPDUNumber 65 | pos++ 66 | } 67 | // alignment 68 | pos = ((pos + 4) &^ 0x3) - 1 69 | for _, e := range m.Exts { 70 | n, err := e.Encode(b[pos:]) 71 | if err != nil { 72 | return n, err 73 | } 74 | pos += n 75 | } 76 | // No more extension headers 77 | b[pos] = 0 78 | pos++ 79 | copy(b[pos:], m.Payload) 80 | return m.Len(), nil 81 | } 82 | 83 | type PDUSessionContainer struct { 84 | PDUType uint8 85 | QoSFlowID uint8 86 | } 87 | 88 | func (e PDUSessionContainer) Len() int { 89 | return 4 90 | } 91 | 92 | func (e PDUSessionContainer) Encode(b []byte) (int, error) { 93 | b[0] = 0x85 94 | b[1] = 1 95 | b[2] = e.PDUType << 4 96 | b[3] = e.QoSFlowID & 0xf 97 | return e.Len(), nil 98 | } 99 | -------------------------------------------------------------------------------- /internal/gtpv1/msg_test.go: -------------------------------------------------------------------------------- 1 | package gtpv1 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestMessage(t *testing.T) { 9 | pkt := []byte{ 10 | 0x34, 0xff, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 11 | 0x00, 0x00, 0x00, 0x85, 0x01, 0x10, 0x09, 0x00, 12 | 0xde, 0xad, 0xbe, 0xef, 13 | } 14 | msg := Message{ 15 | Flags: 0x34, 16 | Type: MsgTypeTPDU, 17 | TEID: 1, 18 | Exts: []Encoder{ 19 | PDUSessionContainer{ 20 | PDUType: 1, 21 | QoSFlowID: 9, 22 | }, 23 | }, 24 | Payload: []byte{0xde, 0xad, 0xbe, 0xef}, 25 | } 26 | l := msg.Len() 27 | b := make([]byte, l) 28 | n, err := msg.Encode(b) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | if n != len(pkt) { 33 | t.Errorf("want %v; but got %v\n", len(pkt), n) 34 | } 35 | if !bytes.Equal(b, pkt) { 36 | t.Errorf("want %x; but got %x\n", pkt, b) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /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 | NfLog *logrus.Entry 12 | MainLog *logrus.Entry 13 | CfgLog *logrus.Entry 14 | PfcpLog *logrus.Entry 15 | BuffLog *logrus.Entry 16 | PerioLog *logrus.Entry 17 | FwderLog *logrus.Entry 18 | ) 19 | 20 | func init() { 21 | fieldsOrder := []string{ 22 | logger_util.FieldNF, 23 | logger_util.FieldCategory, 24 | logger_util.FieldListenAddr, 25 | logger_util.FieldPFCPTxTransaction, 26 | logger_util.FieldPFCPRxTransaction, 27 | logger_util.FieldControlPlaneNodeID, 28 | logger_util.FieldControlPlaneSEID, 29 | logger_util.FieldUserPlaneSEID, 30 | } 31 | Log = logger_util.New(fieldsOrder) 32 | NfLog = Log.WithField(logger_util.FieldNF, "UPF") 33 | MainLog = NfLog.WithField(logger_util.FieldCategory, "Main") 34 | CfgLog = NfLog.WithField(logger_util.FieldCategory, "CFG") 35 | PfcpLog = NfLog.WithField(logger_util.FieldCategory, "PFCP") 36 | BuffLog = NfLog.WithField(logger_util.FieldCategory, "BUFF") 37 | PerioLog = NfLog.WithField(logger_util.FieldCategory, "Perio") 38 | FwderLog = NfLog.WithField(logger_util.FieldCategory, "FWD") 39 | } 40 | -------------------------------------------------------------------------------- /internal/pfcp/association.go: -------------------------------------------------------------------------------- 1 | package pfcp 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/wmnsk/go-pfcp/ie" 7 | "github.com/wmnsk/go-pfcp/message" 8 | ) 9 | 10 | func (s *PfcpServer) handleAssociationSetupRequest( 11 | req *message.AssociationSetupRequest, 12 | addr net.Addr, 13 | ) { 14 | s.log.Infoln("handleAssociationSetupRequest") 15 | 16 | if req.NodeID == nil { 17 | s.log.Errorln("not found NodeID") 18 | return 19 | } 20 | rnodeid, err := req.NodeID.NodeID() 21 | if err != nil { 22 | s.log.Errorln(err) 23 | return 24 | } 25 | s.log.Debugf("remote nodeid: %v\n", rnodeid) 26 | 27 | // deleting the existing PFCP association and associated PFCP sessions, 28 | // if a PFCP association was already established for the Node ID 29 | // received in the request, regardless of the Recovery Timestamp 30 | // received in the request. 31 | if node, ok := s.rnodes[rnodeid]; ok { 32 | s.log.Infof("delete node: %#+v\n", node) 33 | node.Reset() 34 | delete(s.rnodes, rnodeid) 35 | } 36 | node := s.NewNode(rnodeid, addr, s.driver) 37 | s.rnodes[rnodeid] = node 38 | 39 | rsp := message.NewAssociationSetupResponse( 40 | req.Header.SequenceNumber, 41 | newIeNodeID(s.nodeID), 42 | ie.NewCause(ie.CauseRequestAccepted), 43 | ie.NewRecoveryTimeStamp(s.recoveryTime), 44 | // TODO: 45 | // ie.NewUPFunctionFeatures(), 46 | ) 47 | 48 | err = s.sendRspTo(rsp, addr) 49 | if err != nil { 50 | s.log.Errorln(err) 51 | return 52 | } 53 | } 54 | 55 | func (s *PfcpServer) handleAssociationUpdateRequest( 56 | req *message.AssociationUpdateRequest, 57 | addr net.Addr, 58 | ) { 59 | s.log.Infoln("handleAssociationUpdateRequest not supported") 60 | } 61 | 62 | func (s *PfcpServer) handleAssociationReleaseRequest( 63 | req *message.AssociationReleaseRequest, 64 | addr net.Addr, 65 | ) { 66 | s.log.Infoln("handleAssociationReleaseRequest not supported") 67 | } 68 | 69 | func newIeNodeID(nodeID string) *ie.IE { 70 | ip := net.ParseIP(nodeID) 71 | if ip != nil { 72 | if ip.To4() != nil { 73 | return ie.NewNodeID(nodeID, "", "") 74 | } 75 | return ie.NewNodeID("", nodeID, "") 76 | } 77 | return ie.NewNodeID("", "", nodeID) 78 | } 79 | -------------------------------------------------------------------------------- /internal/pfcp/dispacher.go: -------------------------------------------------------------------------------- 1 | package pfcp 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/pkg/errors" 7 | "github.com/wmnsk/go-pfcp/message" 8 | ) 9 | 10 | func (s *PfcpServer) reqDispacher(msg message.Message, addr net.Addr) error { 11 | switch req := msg.(type) { 12 | case *message.HeartbeatRequest: 13 | s.handleHeartbeatRequest(req, addr) 14 | case *message.AssociationSetupRequest: 15 | s.handleAssociationSetupRequest(req, addr) 16 | case *message.AssociationUpdateRequest: 17 | s.handleAssociationUpdateRequest(req, addr) 18 | case *message.AssociationReleaseRequest: 19 | s.handleAssociationReleaseRequest(req, addr) 20 | case *message.SessionEstablishmentRequest: 21 | s.handleSessionEstablishmentRequest(req, addr) 22 | case *message.SessionModificationRequest: 23 | s.handleSessionModificationRequest(req, addr) 24 | case *message.SessionDeletionRequest: 25 | s.handleSessionDeletionRequest(req, addr) 26 | default: 27 | return errors.Errorf("pfcp reqDispacher unknown msg type: %d", msg.MessageType()) 28 | } 29 | return nil 30 | } 31 | 32 | func (s *PfcpServer) rspDispacher(msg message.Message, addr net.Addr, req message.Message) error { 33 | switch rsp := msg.(type) { 34 | case *message.SessionReportResponse: 35 | s.handleSessionReportResponse(rsp, addr, req) 36 | default: 37 | return errors.Errorf("pfcp rspDispacher unknown msg type: %d", msg.MessageType()) 38 | } 39 | return nil 40 | } 41 | 42 | func (s *PfcpServer) txtoDispacher(msg message.Message, addr net.Addr) error { 43 | switch req := msg.(type) { 44 | case *message.SessionReportRequest: 45 | s.handleSessionReportRequestTimeout(req, addr) 46 | default: 47 | return errors.Errorf("pfcp txtoDispacher unknown msg type: %d", msg.MessageType()) 48 | } 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /internal/pfcp/heartbeat.go: -------------------------------------------------------------------------------- 1 | package pfcp 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/wmnsk/go-pfcp/ie" 7 | "github.com/wmnsk/go-pfcp/message" 8 | ) 9 | 10 | func (s *PfcpServer) handleHeartbeatRequest(req *message.HeartbeatRequest, addr net.Addr) { 11 | s.log.Infoln("handleHeartbeatRequest") 12 | 13 | rsp := message.NewHeartbeatResponse( 14 | req.Header.SequenceNumber, 15 | ie.NewRecoveryTimeStamp(s.recoveryTime), 16 | ) 17 | 18 | err := s.sendRspTo(rsp, addr) 19 | if err != nil { 20 | s.log.Errorln(err) 21 | return 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /internal/pfcp/node.go: -------------------------------------------------------------------------------- 1 | package pfcp 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/pkg/errors" 8 | "github.com/sirupsen/logrus" 9 | "github.com/wmnsk/go-pfcp/ie" 10 | 11 | "github.com/free5gc/go-upf/internal/forwarder" 12 | "github.com/free5gc/go-upf/internal/report" 13 | logger_util "github.com/free5gc/util/logger" 14 | ) 15 | 16 | const ( 17 | BUFFQ_LEN = 512 18 | ) 19 | 20 | type PDRInfo struct { 21 | RelatedURRIDs map[uint32]struct{} 22 | } 23 | 24 | type URRInfo struct { 25 | removed bool 26 | SEQN uint32 27 | report.MeasureMethod 28 | report.MeasureInformation 29 | refPdrNum uint16 30 | } 31 | 32 | type Sess struct { 33 | rnode *RemoteNode 34 | LocalID uint64 35 | RemoteID uint64 36 | PDRIDs map[uint16]*PDRInfo // key: PDR_ID 37 | FARIDs map[uint32]struct{} // key: FAR_ID 38 | QERIDs map[uint32]struct{} // key: QER_ID 39 | URRIDs map[uint32]*URRInfo // key: URR_ID 40 | BARIDs map[uint8]struct{} // key: BAR_ID 41 | q map[uint16]chan []byte // key: PDR_ID 42 | qlen int 43 | log *logrus.Entry 44 | } 45 | 46 | func (s *Sess) Close() []report.USAReport { 47 | for id := range s.FARIDs { 48 | i := ie.NewRemoveFAR(ie.NewFARID(id)) 49 | err := s.RemoveFAR(i) 50 | if err != nil { 51 | s.log.Errorf("Remove FAR err: %+v", err) 52 | } 53 | } 54 | for id := range s.QERIDs { 55 | i := ie.NewRemoveQER(ie.NewQERID(id)) 56 | err := s.RemoveQER(i) 57 | if err != nil { 58 | s.log.Errorf("Remove QER err: %+v", err) 59 | } 60 | } 61 | 62 | var usars []report.USAReport 63 | for id := range s.URRIDs { 64 | i := ie.NewRemoveURR(ie.NewURRID(id)) 65 | rs, err := s.RemoveURR(i) 66 | if err != nil { 67 | s.log.Errorf("Remove URR err: %+v", err) 68 | continue 69 | } 70 | if rs != nil { 71 | usars = append(usars, rs...) 72 | } 73 | } 74 | for id := range s.BARIDs { 75 | i := ie.NewRemoveBAR(ie.NewBARID(id)) 76 | err := s.RemoveBAR(i) 77 | if err != nil { 78 | s.log.Errorf("Remove BAR err: %+v", err) 79 | } 80 | } 81 | for id := range s.PDRIDs { 82 | i := ie.NewRemovePDR(ie.NewPDRID(id)) 83 | rs, err := s.RemovePDR(i) 84 | if err != nil { 85 | s.log.Errorf("remove PDR err: %+v", err) 86 | } 87 | if rs != nil { 88 | usars = append(usars, rs...) 89 | } 90 | } 91 | for _, q := range s.q { 92 | close(q) 93 | } 94 | return usars 95 | } 96 | 97 | func (s *Sess) CreatePDR(req *ie.IE) error { 98 | ies, err := req.CreatePDR() 99 | if err != nil { 100 | return err 101 | } 102 | 103 | var pdrid uint16 104 | urrids := make(map[uint32]struct{}) 105 | for _, i := range ies { 106 | switch i.Type { 107 | case ie.PDRID: 108 | v, err1 := i.PDRID() 109 | if err1 != nil { 110 | break 111 | } 112 | pdrid = v 113 | case ie.URRID: 114 | v, err1 := i.URRID() 115 | if err1 != nil { 116 | break 117 | } 118 | urrids[v] = struct{}{} 119 | urrInfo, ok := s.URRIDs[v] 120 | if ok { 121 | urrInfo.refPdrNum++ 122 | } 123 | } 124 | } 125 | 126 | s.PDRIDs[pdrid] = &PDRInfo{ 127 | RelatedURRIDs: urrids, 128 | } 129 | 130 | err = s.rnode.driver.CreatePDR(s.LocalID, req) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | return nil 136 | } 137 | 138 | func (s *Sess) diassociateURR(urrid uint32) []report.USAReport { 139 | urrInfo, ok := s.URRIDs[urrid] 140 | if !ok { 141 | return nil 142 | } 143 | 144 | if urrInfo.refPdrNum > 0 { 145 | urrInfo.refPdrNum-- 146 | if urrInfo.refPdrNum == 0 { 147 | // indicates usage report being reported for a URR due to dissociated from the last PDR 148 | usars, err := s.rnode.driver.QueryURR(s.LocalID, urrid) 149 | if err != nil { 150 | return nil 151 | } 152 | for i := range usars { 153 | usars[i].USARTrigger.Flags |= report.USAR_TRIG_TERMR 154 | } 155 | return usars 156 | } 157 | } else { 158 | s.log.Warnf("diassociateURR: wrong refPdrNum(%d)", urrInfo.refPdrNum) 159 | } 160 | return nil 161 | } 162 | 163 | func (s *Sess) UpdatePDR(req *ie.IE) ([]report.USAReport, error) { 164 | ies, err := req.UpdatePDR() 165 | if err != nil { 166 | return nil, err 167 | } 168 | 169 | var pdrid uint16 170 | newUrrids := make(map[uint32]struct{}) 171 | for _, i := range ies { 172 | switch i.Type { 173 | case ie.PDRID: 174 | v, err1 := i.PDRID() 175 | if err1 != nil { 176 | break 177 | } 178 | pdrid = v 179 | case ie.URRID: 180 | v, err1 := i.URRID() 181 | if err1 != nil { 182 | break 183 | } 184 | newUrrids[v] = struct{}{} 185 | } 186 | } 187 | 188 | pdrInfo, ok := s.PDRIDs[pdrid] 189 | if !ok { 190 | return nil, errors.Errorf("UpdatePDR: PDR(%#x) not found", pdrid) 191 | } 192 | 193 | err = s.rnode.driver.UpdatePDR(s.LocalID, req) 194 | if err != nil { 195 | return nil, err 196 | } 197 | 198 | var usars []report.USAReport 199 | for urrid := range pdrInfo.RelatedURRIDs { 200 | _, ok = newUrrids[urrid] 201 | if !ok { 202 | usar := s.diassociateURR(urrid) 203 | if len(usar) > 0 { 204 | usars = append(usars, usar...) 205 | } 206 | } 207 | } 208 | pdrInfo.RelatedURRIDs = newUrrids 209 | 210 | return usars, err 211 | } 212 | 213 | func (s *Sess) RemovePDR(req *ie.IE) ([]report.USAReport, error) { 214 | pdrid, err := req.PDRID() 215 | if err != nil { 216 | return nil, err 217 | } 218 | 219 | pdrInfo, ok := s.PDRIDs[pdrid] 220 | if !ok { 221 | return nil, errors.Errorf("RemovePDR: PDR(%#x) not found", pdrid) 222 | } 223 | 224 | err = s.rnode.driver.RemovePDR(s.LocalID, req) 225 | if err != nil { 226 | return nil, err 227 | } 228 | 229 | var usars []report.USAReport 230 | for urrid := range pdrInfo.RelatedURRIDs { 231 | usar := s.diassociateURR(urrid) 232 | if len(usar) > 0 { 233 | usars = append(usars, usar...) 234 | } 235 | } 236 | delete(s.PDRIDs, pdrid) 237 | return usars, nil 238 | } 239 | 240 | func (s *Sess) CreateFAR(req *ie.IE) error { 241 | id, err := req.FARID() 242 | if err != nil { 243 | return err 244 | } 245 | s.FARIDs[id] = struct{}{} 246 | 247 | err = s.rnode.driver.CreateFAR(s.LocalID, req) 248 | if err != nil { 249 | return err 250 | } 251 | return nil 252 | } 253 | 254 | func (s *Sess) UpdateFAR(req *ie.IE) error { 255 | id, err := req.FARID() 256 | if err != nil { 257 | return err 258 | } 259 | 260 | _, ok := s.FARIDs[id] 261 | if !ok { 262 | return errors.Errorf("UpdateFAR: FAR(%#x) not found", id) 263 | } 264 | return s.rnode.driver.UpdateFAR(s.LocalID, req) 265 | } 266 | 267 | func (s *Sess) RemoveFAR(req *ie.IE) error { 268 | id, err := req.FARID() 269 | if err != nil { 270 | return err 271 | } 272 | 273 | _, ok := s.FARIDs[id] 274 | if !ok { 275 | return errors.Errorf("RemoveFAR: FAR(%#x) not found", id) 276 | } 277 | 278 | err = s.rnode.driver.RemoveFAR(s.LocalID, req) 279 | if err != nil { 280 | return err 281 | } 282 | 283 | delete(s.FARIDs, id) 284 | return nil 285 | } 286 | 287 | func (s *Sess) CreateQER(req *ie.IE) error { 288 | id, err := req.QERID() 289 | if err != nil { 290 | return err 291 | } 292 | s.QERIDs[id] = struct{}{} 293 | 294 | err = s.rnode.driver.CreateQER(s.LocalID, req) 295 | if err != nil { 296 | return err 297 | } 298 | return nil 299 | } 300 | 301 | func (s *Sess) UpdateQER(req *ie.IE) error { 302 | id, err := req.QERID() 303 | if err != nil { 304 | return err 305 | } 306 | 307 | _, ok := s.QERIDs[id] 308 | if !ok { 309 | return errors.Errorf("UpdateQER: QER(%#x) not found", id) 310 | } 311 | return s.rnode.driver.UpdateQER(s.LocalID, req) 312 | } 313 | 314 | func (s *Sess) RemoveQER(req *ie.IE) error { 315 | id, err := req.QERID() 316 | if err != nil { 317 | return err 318 | } 319 | 320 | _, ok := s.QERIDs[id] 321 | if !ok { 322 | return errors.Errorf("RemoveQER: QER(%#x) not found", id) 323 | } 324 | 325 | err = s.rnode.driver.RemoveQER(s.LocalID, req) 326 | if err != nil { 327 | return err 328 | } 329 | 330 | delete(s.QERIDs, id) 331 | return nil 332 | } 333 | 334 | func (s *Sess) CreateURR(req *ie.IE) error { 335 | id, err := req.URRID() 336 | if err != nil { 337 | return err 338 | } 339 | 340 | mInfo := &ie.IE{} 341 | for _, x := range req.ChildIEs { 342 | if x.Type == ie.MeasurementInformation { 343 | mInfo = x 344 | break 345 | } 346 | } 347 | s.URRIDs[id] = &URRInfo{ 348 | MeasureMethod: report.MeasureMethod{ 349 | DURAT: req.HasDURAT(), 350 | VOLUM: req.HasVOLUM(), 351 | EVENT: req.HasEVENT(), 352 | }, 353 | MeasureInformation: report.MeasureInformation{ 354 | MBQE: mInfo.HasMBQE(), 355 | INAM: mInfo.HasINAM(), 356 | RADI: mInfo.HasRADI(), 357 | ISTM: mInfo.HasISTM(), 358 | MNOP: mInfo.HasMNOP(), 359 | }, 360 | } 361 | 362 | err = s.rnode.driver.CreateURR(s.LocalID, req) 363 | if err != nil { 364 | return err 365 | } 366 | return nil 367 | } 368 | 369 | func (s *Sess) UpdateURR(req *ie.IE) ([]report.USAReport, error) { 370 | id, err := req.URRID() 371 | if err != nil { 372 | return nil, err 373 | } 374 | 375 | urrInfo, ok := s.URRIDs[id] 376 | if !ok { 377 | return nil, errors.Errorf("UpdateURR: URR[%#x] not found", id) 378 | } 379 | for _, x := range req.ChildIEs { 380 | switch x.Type { 381 | case ie.MeasurementMethod: 382 | urrInfo.DURAT = x.HasDURAT() 383 | urrInfo.VOLUM = x.HasVOLUM() 384 | urrInfo.EVENT = x.HasEVENT() 385 | case ie.MeasurementInformation: 386 | urrInfo.MBQE = x.HasMBQE() 387 | urrInfo.INAM = x.HasINAM() 388 | urrInfo.RADI = x.HasRADI() 389 | urrInfo.ISTM = x.HasISTM() 390 | urrInfo.MNOP = x.HasMNOP() 391 | } 392 | } 393 | 394 | usars, err := s.rnode.driver.UpdateURR(s.LocalID, req) 395 | if err != nil { 396 | return nil, err 397 | } 398 | return usars, nil 399 | } 400 | 401 | func (s *Sess) RemoveURR(req *ie.IE) ([]report.USAReport, error) { 402 | id, err := req.URRID() 403 | if err != nil { 404 | return nil, err 405 | } 406 | 407 | info, ok := s.URRIDs[id] 408 | if !ok { 409 | return nil, errors.Errorf("RemoveURR: URR[%#x] not found", id) 410 | } 411 | info.removed = true // remove URRInfo later 412 | 413 | usars, err := s.rnode.driver.RemoveURR(s.LocalID, req) 414 | if err != nil { 415 | return nil, err 416 | } 417 | 418 | // indicates usage report being reported for a URR due to the removal of the URR 419 | for i := range usars { 420 | usars[i].USARTrigger.Flags |= report.USAR_TRIG_TERMR 421 | } 422 | return usars, nil 423 | } 424 | 425 | func (s *Sess) QueryURR(req *ie.IE) ([]report.USAReport, error) { 426 | id, err := req.URRID() 427 | if err != nil { 428 | return nil, err 429 | } 430 | 431 | _, ok := s.URRIDs[id] 432 | if !ok { 433 | return nil, errors.Errorf("QueryURR: URR[%#x] not found", id) 434 | } 435 | 436 | usars, err := s.rnode.driver.QueryURR(s.LocalID, id) 437 | if err != nil { 438 | return nil, err 439 | } 440 | 441 | // indicates an immediate report reported on CP function demand 442 | for i := range usars { 443 | usars[i].USARTrigger.Flags |= report.USAR_TRIG_IMMER 444 | } 445 | return usars, nil 446 | } 447 | 448 | func (s *Sess) CreateBAR(req *ie.IE) error { 449 | id, err := req.BARID() 450 | if err != nil { 451 | return err 452 | } 453 | s.BARIDs[id] = struct{}{} 454 | 455 | err = s.rnode.driver.CreateBAR(s.LocalID, req) 456 | if err != nil { 457 | return err 458 | } 459 | return nil 460 | } 461 | 462 | func (s *Sess) UpdateBAR(req *ie.IE) error { 463 | id, err := req.BARID() 464 | if err != nil { 465 | return err 466 | } 467 | 468 | _, ok := s.BARIDs[id] 469 | if !ok { 470 | return errors.Errorf("UpdateBAR: BAR(%#x) not found", id) 471 | } 472 | return s.rnode.driver.UpdateBAR(s.LocalID, req) 473 | } 474 | 475 | func (s *Sess) RemoveBAR(req *ie.IE) error { 476 | id, err := req.BARID() 477 | if err != nil { 478 | return err 479 | } 480 | 481 | _, ok := s.BARIDs[id] 482 | if !ok { 483 | return errors.Errorf("RemoveBAR: BAR(%#x) not found", id) 484 | } 485 | 486 | err = s.rnode.driver.RemoveBAR(s.LocalID, req) 487 | if err != nil { 488 | return err 489 | } 490 | 491 | delete(s.BARIDs, id) 492 | return nil 493 | } 494 | 495 | func (s *Sess) Push(pdrid uint16, p []byte) { 496 | pkt := make([]byte, len(p)) 497 | copy(pkt, p) 498 | q, ok := s.q[pdrid] 499 | if !ok { 500 | s.q[pdrid] = make(chan []byte, s.qlen) 501 | q = s.q[pdrid] 502 | } 503 | 504 | select { 505 | case q <- pkt: 506 | s.log.Debugf("Push bufPkt to q[%d](len:%d)", pdrid, len(q)) 507 | default: 508 | s.log.Debugf("q[%d](len:%d) is full, drop it", pdrid, len(q)) 509 | } 510 | } 511 | 512 | func (s *Sess) Len(pdrid uint16) int { 513 | q, ok := s.q[pdrid] 514 | if !ok { 515 | return 0 516 | } 517 | return len(q) 518 | } 519 | 520 | func (s *Sess) Pop(pdrid uint16) ([]byte, bool) { 521 | q, ok := s.q[pdrid] 522 | if !ok { 523 | return nil, ok 524 | } 525 | select { 526 | case pkt := <-q: 527 | s.log.Debugf("Pop bufPkt from q[%d](len:%d)", pdrid, len(q)) 528 | return pkt, true 529 | default: 530 | return nil, false 531 | } 532 | } 533 | 534 | func (s *Sess) URRSeq(urrid uint32) uint32 { 535 | info, ok := s.URRIDs[urrid] 536 | if !ok { 537 | return 0 538 | } 539 | seq := info.SEQN 540 | info.SEQN++ 541 | return seq 542 | } 543 | 544 | type RemoteNode struct { 545 | ID string 546 | addr net.Addr 547 | local *LocalNode 548 | sess map[uint64]struct{} // key: Local SEID 549 | driver forwarder.Driver 550 | log *logrus.Entry 551 | } 552 | 553 | func NewRemoteNode( 554 | id string, 555 | addr net.Addr, 556 | local *LocalNode, 557 | driver forwarder.Driver, 558 | log *logrus.Entry, 559 | ) *RemoteNode { 560 | n := new(RemoteNode) 561 | n.ID = id 562 | n.addr = addr 563 | n.local = local 564 | n.sess = make(map[uint64]struct{}) 565 | n.driver = driver 566 | n.log = log 567 | return n 568 | } 569 | 570 | func (n *RemoteNode) Reset() { 571 | for id := range n.sess { 572 | n.DeleteSess(id) 573 | } 574 | n.sess = make(map[uint64]struct{}) 575 | } 576 | 577 | func (n *RemoteNode) Sess(lSeid uint64) (*Sess, error) { 578 | _, ok := n.sess[lSeid] 579 | if !ok { 580 | return nil, errors.Errorf("Sess: sess not found (lSeid:%#x)", lSeid) 581 | } 582 | return n.local.Sess(lSeid) 583 | } 584 | 585 | func (n *RemoteNode) NewSess(rSeid uint64) *Sess { 586 | s := n.local.NewSess(rSeid, BUFFQ_LEN) 587 | n.sess[s.LocalID] = struct{}{} 588 | s.rnode = n 589 | s.log = n.log.WithFields( 590 | logrus.Fields{ 591 | logger_util.FieldUserPlaneSEID: fmt.Sprintf("%#x", s.LocalID), 592 | logger_util.FieldControlPlaneSEID: fmt.Sprintf("%#x", rSeid), 593 | }) 594 | s.log.Infoln("New session") 595 | return s 596 | } 597 | 598 | func (n *RemoteNode) DeleteSess(lSeid uint64) []report.USAReport { 599 | _, ok := n.sess[lSeid] 600 | if !ok { 601 | return nil 602 | } 603 | delete(n.sess, lSeid) 604 | usars, err := n.local.DeleteSess(lSeid) 605 | if err != nil { 606 | n.log.Warnln(err) 607 | return nil 608 | } 609 | return usars 610 | } 611 | 612 | type LocalNode struct { 613 | sess []*Sess 614 | free []uint64 615 | } 616 | 617 | func (n *LocalNode) Reset() { 618 | for _, sess := range n.sess { 619 | if sess != nil { 620 | sess.Close() 621 | } 622 | } 623 | n.sess = []*Sess{} 624 | n.free = []uint64{} 625 | } 626 | 627 | func (n *LocalNode) Sess(lSeid uint64) (*Sess, error) { 628 | if lSeid == 0 { 629 | return nil, errors.New("Sess: invalid lSeid:0") 630 | } 631 | i := int(lSeid) - 1 632 | if i >= len(n.sess) { 633 | return nil, errors.Errorf("Sess: sess not found (lSeid:%#x)", lSeid) 634 | } 635 | sess := n.sess[i] 636 | if sess == nil { 637 | return nil, errors.Errorf("Sess: sess not found (lSeid:%#x)", lSeid) 638 | } 639 | return sess, nil 640 | } 641 | 642 | func (n *LocalNode) RemoteSess(rSeid uint64, addr net.Addr) (*Sess, error) { 643 | for _, s := range n.sess { 644 | if s.RemoteID == rSeid && s.rnode.addr.String() == addr.String() { 645 | return s, nil 646 | } 647 | } 648 | return nil, errors.Errorf("RemoteSess: invalid rSeid:%#x, addr:%s ", rSeid, addr) 649 | } 650 | 651 | func (n *LocalNode) NewSess(rSeid uint64, qlen int) *Sess { 652 | s := &Sess{ 653 | RemoteID: rSeid, 654 | PDRIDs: make(map[uint16]*PDRInfo), 655 | FARIDs: make(map[uint32]struct{}), 656 | QERIDs: make(map[uint32]struct{}), 657 | URRIDs: make(map[uint32]*URRInfo), 658 | BARIDs: make(map[uint8]struct{}), 659 | q: make(map[uint16]chan []byte), 660 | qlen: qlen, 661 | } 662 | last := len(n.free) - 1 663 | if last >= 0 { 664 | s.LocalID = n.free[last] 665 | n.free = n.free[:last] 666 | n.sess[s.LocalID-1] = s 667 | } else { 668 | n.sess = append(n.sess, s) 669 | s.LocalID = uint64(len(n.sess)) 670 | } 671 | return s 672 | } 673 | 674 | func (n *LocalNode) DeleteSess(lSeid uint64) ([]report.USAReport, error) { 675 | if lSeid == 0 { 676 | return nil, errors.New("DeleteSess: invalid lSeid:0") 677 | } 678 | i := int(lSeid) - 1 679 | if i >= len(n.sess) { 680 | return nil, errors.Errorf("DeleteSess: sess not found (lSeid:%#x)", lSeid) 681 | } 682 | if n.sess[i] == nil { 683 | return nil, errors.Errorf("DeleteSess: sess not found (lSeid:%#x)", lSeid) 684 | } 685 | n.sess[i].log.Infoln("sess deleted") 686 | usars := n.sess[i].Close() 687 | n.sess[i] = nil 688 | n.free = append(n.free, lSeid) 689 | return usars, nil 690 | } 691 | -------------------------------------------------------------------------------- /internal/pfcp/node_test.go: -------------------------------------------------------------------------------- 1 | package pfcp 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/free5gc/go-upf/internal/forwarder" 9 | "github.com/free5gc/go-upf/internal/logger" 10 | logger_util "github.com/free5gc/util/logger" 11 | ) 12 | 13 | func TestRemoteNode(t *testing.T) { 14 | t.Run("sess is not found before create", func(t *testing.T) { 15 | n := NewRemoteNode( 16 | "smf1", 17 | nil, 18 | &LocalNode{}, 19 | forwarder.Empty{}, 20 | logger.PfcpLog.WithField(logger_util.FieldControlPlaneNodeID, "smf1"), 21 | ) 22 | for i := 0; i < 3; i++ { 23 | _, err := n.Sess(uint64(i)) 24 | assert.NotNil(t, err) 25 | } 26 | }) 27 | 28 | t.Run("new multiple session", func(t *testing.T) { 29 | n := NewRemoteNode( 30 | "smf1", 31 | nil, 32 | &LocalNode{}, 33 | forwarder.Empty{}, 34 | logger.PfcpLog.WithField(logger_util.FieldControlPlaneNodeID, "smf1"), 35 | ) 36 | 37 | testcases := []struct { 38 | localID uint64 39 | remoteID uint64 40 | }{ 41 | {1, 10}, {2, 20}, {3, 30}, 42 | } 43 | 44 | for _, tc := range testcases { 45 | sess := n.NewSess(tc.remoteID) 46 | assert.Equal(t, tc.localID, sess.LocalID) 47 | assert.Equal(t, tc.remoteID, sess.RemoteID) 48 | } 49 | 50 | // assure the session stored in the node 51 | for _, tc := range testcases { 52 | sess, err := n.Sess(tc.localID) 53 | assert.Nil(t, err) 54 | assert.Equal(t, tc.localID, sess.LocalID) 55 | assert.Equal(t, tc.remoteID, sess.RemoteID) 56 | } 57 | }) 58 | 59 | t.Run("delete 0 no effect before create", func(t *testing.T) { 60 | n := NewRemoteNode( 61 | "smf1", 62 | nil, 63 | &LocalNode{}, 64 | forwarder.Empty{}, 65 | logger.PfcpLog.WithField(logger_util.FieldControlPlaneNodeID, "smf1"), 66 | ) 67 | report := n.DeleteSess(0) 68 | assert.Nil(t, report) 69 | }) 70 | t.Run("delete should success after create", func(t *testing.T) { 71 | n := NewRemoteNode( 72 | "smf1", 73 | nil, 74 | &LocalNode{}, 75 | forwarder.Empty{}, 76 | logger.PfcpLog.WithField(logger_util.FieldControlPlaneNodeID, "smf1"), 77 | ) 78 | 79 | testcases := []struct { 80 | localID uint64 81 | remoteID uint64 82 | }{ 83 | {1, 10}, {2, 20}, {3, 30}, 84 | } 85 | 86 | for _, tc := range testcases { 87 | n.NewSess(tc.remoteID) 88 | } 89 | 90 | for _, tc := range testcases { 91 | n.DeleteSess(tc.localID) 92 | } 93 | 94 | // assure the session is deleted 95 | for _, tc := range testcases { 96 | _, err := n.Sess(tc.localID) 97 | assert.NotNil(t, err) 98 | } 99 | 100 | // delete again should have no effect 101 | for _, tc := range testcases { 102 | report := n.DeleteSess(tc.localID) 103 | assert.Nil(t, report) 104 | } 105 | }) 106 | } 107 | 108 | func TestRemoteNode_multipleSMF(t *testing.T) { 109 | var lnode LocalNode 110 | n1 := NewRemoteNode( 111 | "smf1", 112 | nil, 113 | &lnode, 114 | forwarder.Empty{}, 115 | logger.PfcpLog.WithField(logger_util.FieldControlPlaneNodeID, "smf1"), 116 | ) 117 | n2 := NewRemoteNode( 118 | "smf2", 119 | nil, 120 | &lnode, 121 | forwarder.Empty{}, 122 | logger.PfcpLog.WithField(logger_util.FieldControlPlaneNodeID, "smf2"), 123 | ) 124 | t.Run("new smf1 r-SEID=10", func(t *testing.T) { 125 | sess := n1.NewSess(10) 126 | if sess.LocalID != 1 { 127 | t.Errorf("want 1; but got %v\n", sess.LocalID) 128 | } 129 | if sess.RemoteID != 10 { 130 | t.Errorf("want 10; but got %v\n", sess.RemoteID) 131 | } 132 | }) 133 | t.Run("new smf2 r-SEID=10", func(t *testing.T) { 134 | sess := n2.NewSess(10) 135 | if sess.LocalID != 2 { 136 | t.Errorf("want 2; but got %v\n", sess.LocalID) 137 | } 138 | if sess.RemoteID != 10 { 139 | t.Errorf("want 10; but got %v\n", sess.RemoteID) 140 | } 141 | }) 142 | t.Run("get smf1 l-SEID=1", func(t *testing.T) { 143 | sess, err := n1.Sess(1) 144 | if err != nil { 145 | t.Fatal(err) 146 | } 147 | if sess.LocalID != 1 { 148 | t.Errorf("want 1; but got %v\n", sess.LocalID) 149 | } 150 | if sess.RemoteID != 10 { 151 | t.Errorf("want 10; but got %v\n", sess.RemoteID) 152 | } 153 | }) 154 | t.Run("get smf2 l-SEID=2", func(t *testing.T) { 155 | sess, err := n2.Sess(2) 156 | if err != nil { 157 | t.Fatal(err) 158 | } 159 | if sess.LocalID != 2 { 160 | t.Errorf("want 2; but got %v\n", sess.LocalID) 161 | } 162 | if sess.RemoteID != 10 { 163 | t.Errorf("want 10; but got %v\n", sess.RemoteID) 164 | } 165 | }) 166 | t.Run("get smf1 l-SEID=2", func(t *testing.T) { 167 | _, err := n1.Sess(2) 168 | if err == nil { 169 | t.Errorf("want error; but not error") 170 | } 171 | }) 172 | t.Run("get smf2 l-SEID=1", func(t *testing.T) { 173 | _, err := n2.Sess(1) 174 | if err == nil { 175 | t.Errorf("want error; but not error") 176 | } 177 | }) 178 | t.Run("new smf1:20", func(t *testing.T) { 179 | sess := n1.NewSess(20) 180 | if sess.LocalID != 3 { 181 | t.Errorf("want 3; but got %v\n", sess.LocalID) 182 | } 183 | if sess.RemoteID != 20 { 184 | t.Errorf("want 20; but got %v\n", sess.RemoteID) 185 | } 186 | }) 187 | t.Run("get smf2 l-SEID=3", func(t *testing.T) { 188 | _, err := n2.Sess(3) 189 | if err == nil { 190 | t.Errorf("want error; but not error") 191 | } 192 | }) 193 | t.Run("reset smf1", func(t *testing.T) { 194 | n1.Reset() 195 | }) 196 | t.Run("get smf1 l-SEID=1", func(t *testing.T) { 197 | _, err := n1.Sess(1) 198 | if err == nil { 199 | t.Errorf("want error; but not error") 200 | } 201 | }) 202 | t.Run("get smf1 l-SEID=3", func(t *testing.T) { 203 | _, err := n1.Sess(3) 204 | if err == nil { 205 | t.Errorf("want error; but not error") 206 | } 207 | }) 208 | t.Run("get smf2 l-SEID=2", func(t *testing.T) { 209 | sess, err := n2.Sess(2) 210 | if err != nil { 211 | t.Fatal(err) 212 | } 213 | if sess.LocalID != 2 { 214 | t.Errorf("want 2; but got %v\n", sess.LocalID) 215 | } 216 | if sess.RemoteID != 10 { 217 | t.Errorf("want 10; but got %v\n", sess.RemoteID) 218 | } 219 | }) 220 | } 221 | 222 | func TestLocalNode(t *testing.T) { 223 | t.Run("new session", func(t *testing.T) { 224 | lnode := LocalNode{} 225 | sess := lnode.NewSess(10, BUFFQ_LEN) 226 | assert.Equal(t, uint64(1), sess.LocalID) 227 | assert.Equal(t, uint64(10), sess.RemoteID) 228 | }) 229 | 230 | t.Run("recycle LocalID", func(t *testing.T) { 231 | lnode := LocalNode{ 232 | sess: []*Sess{}, 233 | free: []uint64{}, 234 | } 235 | sess := lnode.NewSess(10, BUFFQ_LEN) 236 | recycleLocalID := 1 237 | assert.Equal(t, uint64(recycleLocalID), sess.LocalID) 238 | assert.Equal(t, uint64(10), sess.RemoteID) 239 | }) 240 | } 241 | -------------------------------------------------------------------------------- /internal/pfcp/pfcp.go: -------------------------------------------------------------------------------- 1 | package pfcp 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "net" 7 | "runtime/debug" 8 | "sync" 9 | "time" 10 | 11 | "github.com/pkg/errors" 12 | "github.com/sirupsen/logrus" 13 | "github.com/wmnsk/go-pfcp/message" 14 | 15 | "github.com/free5gc/go-upf/internal/forwarder" 16 | "github.com/free5gc/go-upf/internal/logger" 17 | "github.com/free5gc/go-upf/internal/report" 18 | "github.com/free5gc/go-upf/pkg/factory" 19 | logger_util "github.com/free5gc/util/logger" 20 | ) 21 | 22 | const ( 23 | RECEIVE_CHANNEL_LEN = 512 24 | REPORT_CHANNEL_LEN = 128 25 | TRANS_TIMEOUT_CHANNEL_LEN = 64 26 | MAX_PFCP_MSG_LEN = 65536 27 | ) 28 | 29 | type ReceivePacket struct { 30 | RemoteAddr net.Addr 31 | Buf []byte 32 | } 33 | 34 | type TransType int 35 | 36 | const ( 37 | TX TransType = iota 38 | RX 39 | ) 40 | 41 | type TransactionTimeout struct { 42 | TrType TransType 43 | TrID string 44 | } 45 | 46 | type PfcpServer struct { 47 | cfg *factory.Config 48 | listen string 49 | nodeID string 50 | rcvCh chan ReceivePacket 51 | srCh chan report.SessReport 52 | trToCh chan TransactionTimeout 53 | conn *net.UDPConn 54 | recoveryTime time.Time 55 | driver forwarder.Driver 56 | lnode LocalNode 57 | rnodes map[string]*RemoteNode 58 | txTrans map[string]*TxTransaction // key: RemoteAddr-Sequence 59 | rxTrans map[string]*RxTransaction // key: RemoteAddr-Sequence 60 | txSeq uint32 61 | log *logrus.Entry 62 | } 63 | 64 | func NewPfcpServer(cfg *factory.Config, driver forwarder.Driver) *PfcpServer { 65 | listen := fmt.Sprintf("%s:%d", cfg.Pfcp.Addr, factory.UpfPfcpDefaultPort) 66 | return &PfcpServer{ 67 | cfg: cfg, 68 | listen: listen, 69 | nodeID: cfg.Pfcp.NodeID, 70 | rcvCh: make(chan ReceivePacket, RECEIVE_CHANNEL_LEN), 71 | srCh: make(chan report.SessReport, REPORT_CHANNEL_LEN), 72 | trToCh: make(chan TransactionTimeout, TRANS_TIMEOUT_CHANNEL_LEN), 73 | recoveryTime: time.Now(), 74 | driver: driver, 75 | rnodes: make(map[string]*RemoteNode), 76 | txTrans: make(map[string]*TxTransaction), 77 | rxTrans: make(map[string]*RxTransaction), 78 | log: logger.PfcpLog.WithField(logger_util.FieldListenAddr, listen), 79 | } 80 | } 81 | 82 | func (s *PfcpServer) main(wg *sync.WaitGroup) { 83 | defer func() { 84 | if p := recover(); p != nil { 85 | // Print stack for panic to log. Fatalf() will let program exit. 86 | s.log.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 87 | } 88 | 89 | s.log.Infoln("pfcp server stopped") 90 | s.stopTrTimers() 91 | close(s.rcvCh) 92 | close(s.srCh) 93 | close(s.trToCh) 94 | wg.Done() 95 | }() 96 | 97 | var err error 98 | laddr, err := net.ResolveUDPAddr("udp4", s.listen) 99 | if err != nil { 100 | s.log.Errorf("Resolve err: %+v", err) 101 | return 102 | } 103 | 104 | conn, err := net.ListenUDP("udp4", laddr) 105 | if err != nil { 106 | s.log.Errorf("Listen err: %+v", err) 107 | return 108 | } 109 | s.conn = conn 110 | 111 | wg.Add(1) 112 | go s.receiver(wg) 113 | 114 | for { 115 | select { 116 | case sr := <-s.srCh: 117 | s.log.Tracef("receive SessReport from srCh") 118 | s.ServeReport(&sr) 119 | case rcvPkt := <-s.rcvCh: 120 | s.log.Tracef("receive buf(len=%d) from rcvCh", len(rcvPkt.Buf)) 121 | if len(rcvPkt.Buf) == 0 { 122 | // receiver closed 123 | return 124 | } 125 | msg, err := message.Parse(rcvPkt.Buf) 126 | if err != nil { 127 | s.log.Errorln(err) 128 | s.log.Tracef("ignored undecodable message:\n%+v", hex.Dump(rcvPkt.Buf)) 129 | continue 130 | } 131 | 132 | trID := fmt.Sprintf("%s-%d", rcvPkt.RemoteAddr, msg.Sequence()) 133 | if isRequest(msg) { 134 | s.log.Tracef("receive req pkt from %s", trID) 135 | rx, ok := s.rxTrans[trID] 136 | if !ok { 137 | rx = NewRxTransaction(s, rcvPkt.RemoteAddr, msg.Sequence()) 138 | s.rxTrans[trID] = rx 139 | } 140 | needDispatch, err1 := rx.recv(msg, ok) 141 | if err1 != nil { 142 | s.log.Warnf("rcvCh: %v", err1) 143 | continue 144 | } else if !needDispatch { 145 | s.log.Debugf("rcvCh: rxtr[%s] req no need to dispatch", trID) 146 | continue 147 | } 148 | err = s.reqDispacher(msg, rcvPkt.RemoteAddr) 149 | if err != nil { 150 | s.log.Errorln(err) 151 | s.log.Tracef("ignored undecodable message:\n%+v", hex.Dump(rcvPkt.Buf)) 152 | } 153 | } else if isResponse(msg) { 154 | s.log.Tracef("receive rsp pkt from %s", trID) 155 | tx, ok := s.txTrans[trID] 156 | if !ok { 157 | s.log.Debugf("rcvCh: No txtr[%s] found for rsp", trID) 158 | continue 159 | } 160 | req := tx.recv(msg) 161 | err = s.rspDispacher(msg, rcvPkt.RemoteAddr, req) 162 | if err != nil { 163 | s.log.Errorln(err) 164 | s.log.Tracef("ignored undecodable message:\n%+v", hex.Dump(rcvPkt.Buf)) 165 | } 166 | } 167 | case trTo := <-s.trToCh: 168 | s.log.Tracef("receive tr timeout (%v) from trToCh", trTo) 169 | if trTo.TrType == TX { 170 | tx, ok := s.txTrans[trTo.TrID] 171 | if !ok { 172 | s.log.Warnf("trToCh: txtr[%s] not found", trTo.TrID) 173 | continue 174 | } 175 | tx.handleTimeout() 176 | } else { // RX 177 | rx, ok := s.rxTrans[trTo.TrID] 178 | if !ok { 179 | s.log.Warnf("trToCh: rxtr[%s] not found", trTo.TrID) 180 | continue 181 | } 182 | rx.handleTimeout() 183 | } 184 | } 185 | } 186 | } 187 | 188 | func (s *PfcpServer) receiver(wg *sync.WaitGroup) { 189 | defer func() { 190 | if p := recover(); p != nil { 191 | // Print stack for panic to log. Fatalf() will let program exit. 192 | s.log.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 193 | } 194 | 195 | s.log.Infoln("pfcp reciver stopped") 196 | wg.Done() 197 | }() 198 | 199 | buf := make([]byte, MAX_PFCP_MSG_LEN) 200 | for { 201 | s.log.Tracef("receiver starts to read...") 202 | n, addr, err := s.conn.ReadFrom(buf) 203 | if err != nil { 204 | s.log.Errorf("%+v", err) 205 | s.rcvCh <- ReceivePacket{} 206 | break 207 | } 208 | 209 | s.log.Tracef("receiver reads message(len=%d)", n) 210 | msgBuf := make([]byte, n) 211 | copy(msgBuf, buf) 212 | s.rcvCh <- ReceivePacket{ 213 | RemoteAddr: addr, 214 | Buf: msgBuf, 215 | } 216 | } 217 | } 218 | 219 | func (s *PfcpServer) Start(wg *sync.WaitGroup) { 220 | s.log.Infoln("starting pfcp server") 221 | wg.Add(1) 222 | go s.main(wg) 223 | s.log.Infoln("pfcp server started") 224 | } 225 | 226 | func (s *PfcpServer) Stop() { 227 | s.log.Infoln("Stopping pfcp server") 228 | if s.conn != nil { 229 | err := s.conn.Close() 230 | if err != nil { 231 | s.log.Errorf("Stop pfcp server err: %+v", err) 232 | } 233 | } 234 | } 235 | 236 | func (s *PfcpServer) NewNode(id string, addr net.Addr, driver forwarder.Driver) *RemoteNode { 237 | n := NewRemoteNode( 238 | id, 239 | addr, 240 | &s.lnode, 241 | driver, 242 | s.log.WithField(logger_util.FieldControlPlaneNodeID, id), 243 | ) 244 | n.log.Infoln("New node") 245 | return n 246 | } 247 | 248 | func (s *PfcpServer) UpdateNodeID(n *RemoteNode, newId string) { 249 | s.log.Infof("Update nodeId %q to %q", n.ID, newId) 250 | delete(s.rnodes, n.ID) 251 | n.ID = newId 252 | n.log = s.log.WithField(logger_util.FieldControlPlaneNodeID, newId) 253 | s.rnodes[newId] = n 254 | } 255 | 256 | func (s *PfcpServer) NotifySessReport(sr report.SessReport) { 257 | s.srCh <- sr 258 | } 259 | 260 | func (s *PfcpServer) NotifyTransTimeout(trType TransType, trID string) { 261 | s.trToCh <- TransactionTimeout{TrType: trType, TrID: trID} 262 | } 263 | 264 | func (s *PfcpServer) PopBufPkt(seid uint64, pdrid uint16) ([]byte, bool) { 265 | sess, err := s.lnode.Sess(seid) 266 | if err != nil { 267 | s.log.Errorln(err) 268 | return nil, false 269 | } 270 | return sess.Pop(pdrid) 271 | } 272 | 273 | func (s *PfcpServer) sendReqTo(msg message.Message, addr net.Addr) error { 274 | if !isRequest(msg) { 275 | return errors.Errorf("sendReqTo: invalid req type(%d)", msg.MessageType()) 276 | } 277 | 278 | txtr := NewTxTransaction(s, addr, s.txSeq) 279 | s.txSeq++ 280 | s.txTrans[txtr.id] = txtr 281 | 282 | return txtr.send(msg) 283 | } 284 | 285 | func (s *PfcpServer) sendRspTo(msg message.Message, addr net.Addr) error { 286 | if !isResponse(msg) { 287 | return errors.Errorf("sendRspTo: invalid rsp type(%d)", msg.MessageType()) 288 | } 289 | 290 | // find transaction 291 | trID := fmt.Sprintf("%s-%d", addr, msg.Sequence()) 292 | rxtr, ok := s.rxTrans[trID] 293 | if !ok { 294 | return errors.Errorf("sendRspTo: rxtr(%s) not found", trID) 295 | } 296 | 297 | return rxtr.send(msg) 298 | } 299 | 300 | func (s *PfcpServer) stopTrTimers() { 301 | for _, tx := range s.txTrans { 302 | if tx.timer == nil { 303 | continue 304 | } 305 | tx.timer.Stop() 306 | tx.timer = nil 307 | } 308 | for _, rx := range s.rxTrans { 309 | if rx.timer == nil { 310 | continue 311 | } 312 | rx.timer.Stop() 313 | rx.timer = nil 314 | } 315 | } 316 | 317 | func isRequest(msg message.Message) bool { 318 | switch msg.MessageType() { 319 | case message.MsgTypeHeartbeatRequest: 320 | return true 321 | case message.MsgTypePFDManagementRequest: 322 | return true 323 | case message.MsgTypeAssociationSetupRequest: 324 | return true 325 | case message.MsgTypeAssociationUpdateRequest: 326 | return true 327 | case message.MsgTypeAssociationReleaseRequest: 328 | return true 329 | case message.MsgTypeNodeReportRequest: 330 | return true 331 | case message.MsgTypeSessionSetDeletionRequest: 332 | return true 333 | case message.MsgTypeSessionEstablishmentRequest: 334 | return true 335 | case message.MsgTypeSessionModificationRequest: 336 | return true 337 | case message.MsgTypeSessionDeletionRequest: 338 | return true 339 | case message.MsgTypeSessionReportRequest: 340 | return true 341 | default: 342 | } 343 | return false 344 | } 345 | 346 | func isResponse(msg message.Message) bool { 347 | switch msg.MessageType() { 348 | case message.MsgTypeHeartbeatResponse: 349 | return true 350 | case message.MsgTypePFDManagementResponse: 351 | return true 352 | case message.MsgTypeAssociationSetupResponse: 353 | return true 354 | case message.MsgTypeAssociationUpdateResponse: 355 | return true 356 | case message.MsgTypeAssociationReleaseResponse: 357 | return true 358 | case message.MsgTypeNodeReportResponse: 359 | return true 360 | case message.MsgTypeSessionSetDeletionResponse: 361 | return true 362 | case message.MsgTypeSessionEstablishmentResponse: 363 | return true 364 | case message.MsgTypeSessionModificationResponse: 365 | return true 366 | case message.MsgTypeSessionDeletionResponse: 367 | return true 368 | case message.MsgTypeSessionReportResponse: 369 | return true 370 | default: 371 | } 372 | return false 373 | } 374 | 375 | func setReqSeq(msgtmp message.Message, seq uint32) { 376 | switch msg := msgtmp.(type) { 377 | case *message.HeartbeatRequest: 378 | msg.SetSequenceNumber(seq) 379 | case *message.PFDManagementRequest: 380 | msg.SetSequenceNumber(seq) 381 | case *message.AssociationSetupRequest: 382 | msg.SetSequenceNumber(seq) 383 | case *message.AssociationUpdateRequest: 384 | msg.SetSequenceNumber(seq) 385 | case *message.AssociationReleaseRequest: 386 | msg.SetSequenceNumber(seq) 387 | case *message.NodeReportRequest: 388 | msg.SetSequenceNumber(seq) 389 | case *message.SessionSetDeletionRequest: 390 | msg.SetSequenceNumber(seq) 391 | case *message.SessionEstablishmentRequest: 392 | msg.SetSequenceNumber(seq) 393 | case *message.SessionModificationRequest: 394 | msg.SetSequenceNumber(seq) 395 | case *message.SessionDeletionRequest: 396 | msg.SetSequenceNumber(seq) 397 | case *message.SessionReportRequest: 398 | msg.SetSequenceNumber(seq) 399 | default: 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /internal/pfcp/pfcp_test.go: -------------------------------------------------------------------------------- 1 | package pfcp 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | "testing" 7 | "time" 8 | 9 | "github.com/sirupsen/logrus" 10 | "github.com/stretchr/testify/assert" 11 | 12 | "github.com/free5gc/go-upf/internal/forwarder" 13 | "github.com/free5gc/go-upf/internal/report" 14 | logger_util "github.com/free5gc/util/logger" 15 | ) 16 | 17 | type PfcpServerMock struct { 18 | PfcpServer 19 | } 20 | 21 | func (p *PfcpServerMock) GetRNodes() map[string]*RemoteNode { 22 | return p.rnodes 23 | } 24 | 25 | func (p *PfcpServerMock) AddRNode(rnodeid string, node *RemoteNode) { 26 | p.rnodes[rnodeid] = node 27 | } 28 | 29 | func TestStart(t *testing.T) { 30 | } 31 | 32 | func TestStop(t *testing.T) { 33 | s := &PfcpServer{ 34 | log: logrus.WithField(logger_util.FieldControlPlaneNodeID, "127.0.0.1"), 35 | } 36 | 37 | addr, err := net.ResolveUDPAddr("udp4", "127.0.0.1:0") 38 | if err != nil { 39 | t.Errorf("failed to resolve UDP address: %v", err) 40 | return 41 | } 42 | s.conn, err = net.ListenUDP("udp4", addr) 43 | if err != nil { 44 | t.Errorf("expected err to be nil, but got %v", err) 45 | } 46 | 47 | if s.conn == nil { 48 | t.Errorf("expected s.conn not to be nil") 49 | return 50 | } 51 | 52 | s.Stop() 53 | 54 | if !isConnClosed(s.conn) { 55 | t.Errorf("expected connection to be closed") 56 | } 57 | } 58 | 59 | func TestNewNode(t *testing.T) { 60 | s := &PfcpServer{ 61 | log: logrus.WithField(logger_util.FieldControlPlaneNodeID, "127.0.0.1"), 62 | } 63 | 64 | id := "smf1" 65 | driver := forwarder.Empty{} 66 | addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8805") 67 | if err != nil { 68 | t.Errorf("failed to resolve UDP address: %v", err) 69 | return 70 | } 71 | 72 | newNode := s.NewNode(id, addr, driver) 73 | 74 | assert.NotNil(t, newNode) 75 | assert.Equal(t, id, newNode.ID) 76 | } 77 | 78 | func TestUpdateNodeID(t *testing.T) { 79 | s := &PfcpServerMock{ 80 | PfcpServer: PfcpServer{ 81 | log: logrus.WithField(logger_util.FieldControlPlaneNodeID, "127.0.0.1"), 82 | rnodes: make(map[string]*RemoteNode), 83 | }, 84 | } 85 | 86 | origNodeId := "127.0.0.1" 87 | node := s.NewNode(origNodeId, nil, nil) 88 | s.AddRNode(origNodeId, node) 89 | 90 | newNodeId := "192.168.56.101" 91 | s.UpdateNodeID(node, newNodeId) 92 | 93 | assert.Nil(t, s.GetRNodes()[origNodeId]) 94 | assert.NotNil(t, s.GetRNodes()[newNodeId]) 95 | } 96 | 97 | func TestNotifySessReport(t *testing.T) { 98 | s := &PfcpServer{ 99 | srCh: make(chan report.SessReport), 100 | } 101 | 102 | reports := []report.Report{} 103 | 104 | sr := report.SessReport{ 105 | SEID: 1, 106 | Reports: reports, 107 | } 108 | 109 | var wg sync.WaitGroup 110 | wg.Add(1) 111 | 112 | go func() { 113 | defer wg.Done() 114 | receivedSr := <-s.srCh 115 | assert.EqualValues(t, sr, receivedSr) 116 | }() 117 | 118 | s.NotifySessReport(sr) 119 | 120 | wg.Wait() 121 | } 122 | 123 | func TestNotifyTransTimeout(t *testing.T) { 124 | s := &PfcpServer{ 125 | trToCh: make(chan TransactionTimeout), 126 | } 127 | txId := "127.0.0.1-1" 128 | 129 | var wg sync.WaitGroup 130 | wg.Add(1) 131 | 132 | go func() { 133 | defer wg.Done() 134 | receivedSeid := <-s.trToCh 135 | assert.EqualValues(t, TransactionTimeout{TrType: TX, TrID: txId}, receivedSeid) 136 | }() 137 | 138 | s.NotifyTransTimeout(TX, txId) 139 | 140 | wg.Wait() 141 | } 142 | 143 | func isConnClosed(conn *net.UDPConn) bool { 144 | oneByte := make([]byte, 1) 145 | err := conn.SetReadDeadline(time.Now()) 146 | if err != nil { 147 | return true 148 | } 149 | _, err = conn.Read(oneByte) 150 | if err != nil { 151 | netErr, ok := err.(net.Error) 152 | if ok && netErr.Timeout() { 153 | // The read timed out, which means the connection is still open 154 | return false 155 | } 156 | // Any other error means the connection is closed 157 | return true 158 | } 159 | 160 | // If we were able to read a byte, the connection is definitely open 161 | return false 162 | } 163 | -------------------------------------------------------------------------------- /internal/pfcp/report.go: -------------------------------------------------------------------------------- 1 | package pfcp 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/pkg/errors" 8 | "github.com/wmnsk/go-pfcp/ie" 9 | "github.com/wmnsk/go-pfcp/message" 10 | 11 | "github.com/free5gc/go-upf/internal/report" 12 | "github.com/free5gc/go-upf/pkg/factory" 13 | ) 14 | 15 | func (s *PfcpServer) ServeReport(sr *report.SessReport) { 16 | s.log.Debugf("ServeReport: SEID(%#x)", sr.SEID) 17 | sess, err := s.lnode.Sess(sr.SEID) 18 | if err != nil { 19 | s.log.Errorln(err) 20 | return 21 | } 22 | 23 | addr := fmt.Sprintf("%s:%d", sess.rnode.ID, factory.UpfPfcpDefaultPort) 24 | laddr, err := net.ResolveUDPAddr("udp4", addr) 25 | if err != nil { 26 | return 27 | } 28 | 29 | var usars []report.USAReport 30 | for _, rpt := range sr.Reports { 31 | switch r := rpt.(type) { 32 | case report.DLDReport: 33 | s.log.Debugf("ServeReport: SEID(%#x), type(%s)", sr.SEID, r.Type()) 34 | if r.Action&report.APPLY_ACT_BUFF != 0 && len(r.BufPkt) > 0 { 35 | sess.Push(r.PDRID, r.BufPkt) 36 | } 37 | if r.Action&report.APPLY_ACT_NOCP == 0 { 38 | return 39 | } 40 | err := s.serveDLDReport(laddr, sr.SEID, r.PDRID) 41 | if err != nil { 42 | s.log.Errorln(err) 43 | } 44 | case report.USAReport: 45 | s.log.Debugf("ServeReport: SEID(%#x), type(%s)", sr.SEID, r.Type()) 46 | usars = append(usars, r) 47 | default: 48 | s.log.Warnf("Unsupported Report: SEID(%#x), type(%d)", sr.SEID, rpt.Type()) 49 | } 50 | } 51 | 52 | if len(usars) > 0 { 53 | err := s.serveUSAReport(laddr, sr.SEID, usars) 54 | if err != nil { 55 | s.log.Errorln(err) 56 | } 57 | } 58 | } 59 | 60 | func (s *PfcpServer) serveDLDReport(addr net.Addr, lSeid uint64, pdrid uint16) error { 61 | s.log.Infoln("serveDLDReport") 62 | 63 | sess, err := s.lnode.Sess(lSeid) 64 | if err != nil { 65 | return errors.Wrap(err, "serveDLDReport") 66 | } 67 | 68 | req := message.NewSessionReportRequest( 69 | 0, 70 | 0, 71 | sess.RemoteID, 72 | 0, 73 | 0, 74 | ie.NewReportType(0, 0, 0, 1), 75 | ie.NewDownlinkDataReport( 76 | ie.NewPDRID(pdrid), 77 | /* 78 | ie.NewDownlinkDataServiceInformation( 79 | true, 80 | true, 81 | ppi, 82 | qfi, 83 | ), 84 | */ 85 | ), 86 | ) 87 | 88 | err = s.sendReqTo(req, addr) 89 | return errors.Wrap(err, "serveDLDReport") 90 | } 91 | 92 | func (s *PfcpServer) serveUSAReport(addr net.Addr, lSeid uint64, usars []report.USAReport) error { 93 | s.log.Infoln("serveUSAReport") 94 | 95 | sess, err := s.lnode.Sess(lSeid) 96 | if err != nil { 97 | return errors.Wrap(err, "serveUSAReport") 98 | } 99 | 100 | req := message.NewSessionReportRequest( 101 | 0, 102 | 0, 103 | sess.RemoteID, 104 | 0, 105 | 0, 106 | ie.NewReportType(0, 0, 1, 0), 107 | ) 108 | for _, r := range usars { 109 | urrInfo, ok := sess.URRIDs[r.URRID] 110 | if !ok { 111 | sess.log.Warnf("serveUSAReport: URRInfo[%#x] not found", r.URRID) 112 | continue 113 | } 114 | r.URSEQN = sess.URRSeq(r.URRID) 115 | req.UsageReport = append(req.UsageReport, 116 | ie.NewUsageReportWithinSessionReportRequest( 117 | r.IEsWithinSessReportReq( 118 | urrInfo.MeasureMethod, urrInfo.MeasureInformation)..., 119 | )) 120 | } 121 | 122 | err = s.sendReqTo(req, addr) 123 | return errors.Wrap(err, "serveUSAReport") 124 | } 125 | -------------------------------------------------------------------------------- /internal/pfcp/session.go: -------------------------------------------------------------------------------- 1 | package pfcp 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/wmnsk/go-pfcp/ie" 7 | "github.com/wmnsk/go-pfcp/message" 8 | 9 | "github.com/free5gc/go-upf/internal/report" 10 | ) 11 | 12 | func (s *PfcpServer) handleSessionEstablishmentRequest( 13 | req *message.SessionEstablishmentRequest, 14 | addr net.Addr, 15 | ) { 16 | // TODO: error response 17 | s.log.Infoln("handleSessionEstablishmentRequest") 18 | 19 | if req.NodeID == nil { 20 | s.log.Errorln("not found NodeID") 21 | return 22 | } 23 | rnodeid, err := req.NodeID.NodeID() 24 | if err != nil { 25 | s.log.Errorln(err) 26 | return 27 | } 28 | s.log.Debugf("remote nodeid: %v\n", rnodeid) 29 | 30 | rnode, ok := s.rnodes[rnodeid] 31 | if !ok { 32 | s.log.Errorf("not found NodeID %v\n", rnodeid) 33 | return 34 | } 35 | 36 | if req.CPFSEID == nil { 37 | s.log.Errorln("not found CP F-SEID") 38 | return 39 | } 40 | fseid, err := req.CPFSEID.FSEID() 41 | if err != nil { 42 | s.log.Errorln(err) 43 | return 44 | } 45 | s.log.Debugf("fseid.SEID: %#x\n", fseid.SEID) 46 | 47 | // allocate a session 48 | sess := rnode.NewSess(fseid.SEID) 49 | 50 | // TODO: rollback transaction 51 | for _, i := range req.CreateFAR { 52 | err = sess.CreateFAR(i) 53 | if err != nil { 54 | sess.log.Errorf("Est CreateFAR error: %+v", err) 55 | } 56 | } 57 | 58 | for _, i := range req.CreateQER { 59 | err = sess.CreateQER(i) 60 | if err != nil { 61 | sess.log.Errorf("Est CreateQER error: %+v", err) 62 | } 63 | } 64 | 65 | for _, i := range req.CreateURR { 66 | err = sess.CreateURR(i) 67 | if err != nil { 68 | sess.log.Errorf("Est CreateURR error: %+v", err) 69 | } 70 | } 71 | 72 | if req.CreateBAR != nil { 73 | err = sess.CreateBAR(req.CreateBAR) 74 | if err != nil { 75 | sess.log.Errorf("Est CreateBAR error: %+v", err) 76 | } 77 | } 78 | 79 | CreatedPDRList := make([]*ie.IE, 0) 80 | 81 | for _, i := range req.CreatePDR { 82 | err = sess.CreatePDR(i) 83 | if err != nil { 84 | sess.log.Errorf("Est CreatePDR error: %+v", err) 85 | } 86 | 87 | ueIPAddress := getUEAddressFromPDR(i) 88 | pdrId := getPDRIDFromPDR(i) 89 | 90 | if ueIPAddress != nil { 91 | ueIPv4 := ueIPAddress.IPv4Address.String() 92 | CreatedPDRList = append(CreatedPDRList, ie.NewCreatedPDR( 93 | ie.NewPDRID(pdrId), 94 | ie.NewUEIPAddress(2, ueIPv4, "", 0, 0), 95 | )) 96 | } 97 | } 98 | 99 | var v4 net.IP 100 | addrv4, err := net.ResolveIPAddr("ip4", s.nodeID) 101 | if err == nil { 102 | v4 = addrv4.IP.To4() 103 | } 104 | // TODO: support v6 105 | var v6 net.IP 106 | 107 | ies := make([]*ie.IE, 0) 108 | ies = append(ies, CreatedPDRList...) 109 | ies = append(ies, 110 | newIeNodeID(s.nodeID), 111 | ie.NewCause(ie.CauseRequestAccepted), 112 | ie.NewFSEID(sess.LocalID, v4, v6)) 113 | 114 | rsp := message.NewSessionEstablishmentResponse( 115 | 0, // mp 116 | 0, // fo 117 | sess.RemoteID, // seid 118 | req.Header.SequenceNumber, 119 | 0, // pri 120 | ies..., 121 | ) 122 | 123 | err = s.sendRspTo(rsp, addr) 124 | if err != nil { 125 | s.log.Errorln(err) 126 | return 127 | } 128 | } 129 | 130 | func (s *PfcpServer) handleSessionModificationRequest( 131 | req *message.SessionModificationRequest, 132 | addr net.Addr, 133 | ) { 134 | // TODO: error response 135 | s.log.Infoln("handleSessionModificationRequest") 136 | 137 | sess, err := s.lnode.Sess(req.SEID()) 138 | if err != nil { 139 | s.log.Errorf("handleSessionModificationRequest: %v", err) 140 | rsp := message.NewSessionModificationResponse( 141 | 0, // mp 142 | 0, // fo 143 | 0, // seid 144 | req.Header.SequenceNumber, 145 | 0, // pri 146 | ie.NewCause(ie.CauseSessionContextNotFound), 147 | ) 148 | 149 | err = s.sendRspTo(rsp, addr) 150 | if err != nil { 151 | s.log.Errorln(err) 152 | return 153 | } 154 | return 155 | } 156 | 157 | if req.NodeID != nil { 158 | // TS 29.244 7.5.4: 159 | // This IE shall be present if a new SMF in an SMF Set, 160 | // with one PFCP association per SMF and UPF (see clause 5.22.3), 161 | // takes over the control of the PFCP session. 162 | // When present, it shall contain the unique identifier of the new SMF. 163 | rnodeid, err1 := req.NodeID.NodeID() 164 | if err1 != nil { 165 | s.log.Errorln(err) 166 | return 167 | } 168 | s.log.Debugf("new remote nodeid: %v\n", rnodeid) 169 | s.UpdateNodeID(sess.rnode, rnodeid) 170 | } 171 | 172 | for _, i := range req.CreateFAR { 173 | err = sess.CreateFAR(i) 174 | if err != nil { 175 | sess.log.Errorf("Mod CreateFAR error: %+v", err) 176 | } 177 | } 178 | 179 | for _, i := range req.CreateQER { 180 | err = sess.CreateQER(i) 181 | if err != nil { 182 | sess.log.Errorf("Mod CreateQER error: %+v", err) 183 | } 184 | } 185 | 186 | for _, i := range req.CreateURR { 187 | err = sess.CreateURR(i) 188 | if err != nil { 189 | sess.log.Errorf("Mod CreateURR error: %+v", err) 190 | } 191 | } 192 | 193 | if req.CreateBAR != nil { 194 | err = sess.CreateBAR(req.CreateBAR) 195 | if err != nil { 196 | sess.log.Errorf("Mod CreateBAR error: %+v", err) 197 | } 198 | } 199 | 200 | for _, i := range req.CreatePDR { 201 | err = sess.CreatePDR(i) 202 | if err != nil { 203 | sess.log.Errorf("Mod CreatePDR error: %+v", err) 204 | } 205 | } 206 | 207 | for _, i := range req.RemoveFAR { 208 | err = sess.RemoveFAR(i) 209 | if err != nil { 210 | sess.log.Errorf("Mod RemoveFAR error: %+v", err) 211 | } 212 | } 213 | 214 | for _, i := range req.RemoveQER { 215 | err = sess.RemoveQER(i) 216 | if err != nil { 217 | sess.log.Errorf("Mod RemoveQER error: %+v", err) 218 | } 219 | } 220 | 221 | var usars []report.USAReport 222 | for _, i := range req.RemoveURR { 223 | rs, err1 := sess.RemoveURR(i) 224 | if err1 != nil { 225 | sess.log.Errorf("Mod RemoveURR error: %+v", err1) 226 | continue 227 | } 228 | if len(rs) > 0 { 229 | usars = append(usars, rs...) 230 | } 231 | } 232 | 233 | if req.RemoveBAR != nil { 234 | err = sess.RemoveBAR(req.RemoveBAR) 235 | if err != nil { 236 | sess.log.Errorf("Mod RemoveBAR error: %+v", err) 237 | } 238 | } 239 | 240 | for _, i := range req.RemovePDR { 241 | rs, err1 := sess.RemovePDR(i) 242 | if err1 != nil { 243 | sess.log.Errorf("Mod RemovePDR error: %+v", err1) 244 | } 245 | if len(rs) > 0 { 246 | usars = append(usars, rs...) 247 | } 248 | } 249 | 250 | for _, i := range req.UpdateFAR { 251 | err = sess.UpdateFAR(i) 252 | if err != nil { 253 | sess.log.Errorf("Mod UpdateFAR error: %+v", err) 254 | } 255 | } 256 | 257 | for _, i := range req.UpdateQER { 258 | err = sess.UpdateQER(i) 259 | if err != nil { 260 | sess.log.Errorf("Mod UpdateQER error: %+v", err) 261 | } 262 | } 263 | 264 | for _, i := range req.UpdateURR { 265 | rs, err1 := sess.UpdateURR(i) 266 | if err1 != nil { 267 | sess.log.Errorf("Mod UpdateURR error: %+v", err1) 268 | continue 269 | } 270 | if len(rs) > 0 { 271 | usars = append(usars, rs...) 272 | } 273 | } 274 | 275 | if req.UpdateBAR != nil { 276 | err = sess.UpdateBAR(req.UpdateBAR) 277 | if err != nil { 278 | sess.log.Errorf("Mod UpdateBAR error: %+v", err) 279 | } 280 | } 281 | 282 | for _, i := range req.UpdatePDR { 283 | rs, err1 := sess.UpdatePDR(i) 284 | if err1 != nil { 285 | sess.log.Errorf("Mod UpdatePDR error: %+v", err1) 286 | } 287 | if len(rs) > 0 { 288 | usars = append(usars, rs...) 289 | } 290 | } 291 | 292 | for _, i := range req.QueryURR { 293 | rs, err1 := sess.QueryURR(i) 294 | if err1 != nil { 295 | sess.log.Errorf("Mod QueryURR error: %+v", err1) 296 | continue 297 | } 298 | if len(rs) > 0 { 299 | usars = append(usars, rs...) 300 | } 301 | } 302 | 303 | rsp := message.NewSessionModificationResponse( 304 | 0, // mp 305 | 0, // fo 306 | sess.RemoteID, // seid 307 | req.Header.SequenceNumber, 308 | 0, // pri 309 | ie.NewCause(ie.CauseRequestAccepted), 310 | ) 311 | for _, r := range usars { 312 | urrInfo, ok := sess.URRIDs[r.URRID] 313 | if !ok { 314 | sess.log.Warnf("Sess Mod: URRInfo[%#x] not found", r.URRID) 315 | continue 316 | } 317 | r.URSEQN = sess.URRSeq(r.URRID) 318 | rsp.UsageReport = append(rsp.UsageReport, 319 | ie.NewUsageReportWithinSessionModificationResponse( 320 | r.IEsWithinSessModRsp( 321 | urrInfo.MeasureMethod, urrInfo.MeasureInformation)..., 322 | )) 323 | 324 | if urrInfo.removed { 325 | delete(sess.URRIDs, r.URRID) 326 | } 327 | } 328 | 329 | err = s.sendRspTo(rsp, addr) 330 | if err != nil { 331 | s.log.Errorln(err) 332 | return 333 | } 334 | } 335 | 336 | func (s *PfcpServer) handleSessionDeletionRequest( 337 | req *message.SessionDeletionRequest, 338 | addr net.Addr, 339 | ) { 340 | // TODO: error response 341 | s.log.Infoln("handleSessionDeletionRequest") 342 | 343 | lSeid := req.SEID() 344 | sess, err := s.lnode.Sess(lSeid) 345 | if err != nil { 346 | s.log.Errorf("handleSessionDeletionRequest: %v", err) 347 | rsp := message.NewSessionDeletionResponse( 348 | 0, // mp 349 | 0, // fo 350 | 0, // seid 351 | req.Header.SequenceNumber, 352 | 0, // pri 353 | ie.NewCause(ie.CauseSessionContextNotFound), 354 | ie.NewReportType(0, 0, 1, 0), 355 | ) 356 | 357 | err = s.sendRspTo(rsp, addr) 358 | if err != nil { 359 | s.log.Errorln(err) 360 | return 361 | } 362 | return 363 | } 364 | 365 | usars := sess.rnode.DeleteSess(lSeid) 366 | 367 | rsp := message.NewSessionDeletionResponse( 368 | 0, // mp 369 | 0, // fo 370 | sess.RemoteID, // seid 371 | req.Header.SequenceNumber, 372 | 0, // pri 373 | ie.NewCause(ie.CauseRequestAccepted), 374 | ) 375 | for _, r := range usars { 376 | urrInfo, ok := sess.URRIDs[r.URRID] 377 | if !ok { 378 | sess.log.Warnf("Sess Del: URRInfo[%#x] not found", r.URRID) 379 | continue 380 | } 381 | r.URSEQN = sess.URRSeq(r.URRID) 382 | // indicates usage report being reported for a URR due to the termination of the PFCP session 383 | r.USARTrigger.Flags |= report.USAR_TRIG_TERMR 384 | rsp.UsageReport = append(rsp.UsageReport, 385 | ie.NewUsageReportWithinSessionDeletionResponse( 386 | r.IEsWithinSessDelRsp( 387 | urrInfo.MeasureMethod, urrInfo.MeasureInformation)..., 388 | )) 389 | 390 | if urrInfo.removed { 391 | delete(sess.URRIDs, r.URRID) 392 | } 393 | } 394 | 395 | err = s.sendRspTo(rsp, addr) 396 | if err != nil { 397 | s.log.Errorln(err) 398 | return 399 | } 400 | } 401 | 402 | func (s *PfcpServer) handleSessionReportResponse( 403 | rsp *message.SessionReportResponse, 404 | addr net.Addr, 405 | req message.Message, 406 | ) { 407 | s.log.Infoln("handleSessionReportResponse") 408 | 409 | s.log.Debugf("seid: %#x\n", rsp.SEID()) 410 | if rsp.Header.SEID == 0 { 411 | s.log.Warnf("rsp SEID is 0; no this session on remote; delete it on local") 412 | sess, err := s.lnode.RemoteSess(req.SEID(), addr) 413 | if err != nil { 414 | s.log.Errorln(err) 415 | return 416 | } 417 | sess.rnode.DeleteSess(sess.LocalID) 418 | return 419 | } 420 | 421 | sess, err := s.lnode.Sess(rsp.SEID()) 422 | if err != nil { 423 | s.log.Errorln(err) 424 | return 425 | } 426 | 427 | s.log.Debugf("sess: %#+v\n", sess) 428 | } 429 | 430 | func (s *PfcpServer) handleSessionReportRequestTimeout( 431 | req *message.SessionReportRequest, 432 | addr net.Addr, 433 | ) { 434 | s.log.Warnf("handleSessionReportRequestTimeout: SEID[%#x]", req.SEID()) 435 | // TODO? 436 | } 437 | 438 | // getUEAddressFromPDR returns the UEIPaddress() from the PDR IE. 439 | func getUEAddressFromPDR(pdr *ie.IE) *ie.UEIPAddressFields { 440 | ies, err := pdr.CreatePDR() 441 | if err != nil { 442 | return nil 443 | } 444 | 445 | for _, i := range ies { 446 | // only care about PDI 447 | if i.Type == ie.PDI { 448 | ies, err := i.PDI() 449 | if err != nil { 450 | return nil 451 | } 452 | for _, x := range ies { 453 | if x.Type == ie.UEIPAddress { 454 | fields, err := x.UEIPAddress() 455 | if err != nil { 456 | return nil 457 | } 458 | return fields 459 | } 460 | } 461 | } 462 | } 463 | return nil 464 | } 465 | 466 | func getPDRIDFromPDR(pdr *ie.IE) uint16 { 467 | ies, err := pdr.CreatePDR() 468 | if err != nil { 469 | return 0 470 | } 471 | 472 | for _, i := range ies { 473 | if i.Type == ie.PDRID { 474 | id, err := i.PDRID() 475 | if err != nil { 476 | return 0 477 | } 478 | return id 479 | } 480 | } 481 | return 0 482 | } 483 | -------------------------------------------------------------------------------- /internal/pfcp/transaction.go: -------------------------------------------------------------------------------- 1 | package pfcp 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "time" 7 | 8 | "github.com/pkg/errors" 9 | "github.com/sirupsen/logrus" 10 | "github.com/wmnsk/go-pfcp/message" 11 | 12 | logger_util "github.com/free5gc/util/logger" 13 | ) 14 | 15 | type TxTransaction struct { 16 | server *PfcpServer 17 | raddr net.Addr 18 | seq uint32 19 | id string 20 | retransTimeout time.Duration 21 | maxRetrans uint8 22 | req message.Message 23 | msgBuf []byte 24 | timer *time.Timer 25 | retransCount uint8 26 | log *logrus.Entry 27 | } 28 | 29 | type RxTransaction struct { 30 | server *PfcpServer 31 | raddr net.Addr 32 | seq uint32 33 | id string 34 | timeout time.Duration 35 | msgBuf []byte 36 | timer *time.Timer 37 | log *logrus.Entry 38 | } 39 | 40 | func NewTxTransaction( 41 | server *PfcpServer, 42 | raddr net.Addr, 43 | seq uint32, 44 | ) *TxTransaction { 45 | tx := &TxTransaction{ 46 | server: server, 47 | raddr: raddr, 48 | seq: seq, 49 | id: fmt.Sprintf("%s-%d", raddr, seq), 50 | retransTimeout: server.cfg.Pfcp.RetransTimeout, 51 | maxRetrans: server.cfg.Pfcp.MaxRetrans, 52 | } 53 | tx.log = server.log.WithField(logger_util.FieldPFCPTxTransaction, tx.id) 54 | return tx 55 | } 56 | 57 | func (tx *TxTransaction) send(req message.Message) error { 58 | tx.log.Debugf("send req") 59 | 60 | setReqSeq(req, tx.seq) 61 | b := make([]byte, req.MarshalLen()) 62 | err := req.MarshalTo(b) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | // Start tx retransmission timer 68 | tx.req = req 69 | tx.msgBuf = b 70 | tx.timer = tx.startTimer() 71 | 72 | _, err = tx.server.conn.WriteTo(b, tx.raddr) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | return nil 78 | } 79 | 80 | func (tx *TxTransaction) recv(rsp message.Message) message.Message { 81 | tx.log.Debugf("recv rsp, delete txtr") 82 | 83 | // Stop tx retransmission timer 84 | tx.timer.Stop() 85 | tx.timer = nil 86 | 87 | delete(tx.server.txTrans, tx.id) 88 | return tx.req 89 | } 90 | 91 | func (tx *TxTransaction) handleTimeout() { 92 | if tx.retransCount < tx.maxRetrans { 93 | // Start tx retransmission timer 94 | tx.retransCount++ 95 | tx.log.Debugf("timeout, retransCount(%d)", tx.retransCount) 96 | _, err := tx.server.conn.WriteTo(tx.msgBuf, tx.raddr) 97 | if err != nil { 98 | tx.log.Errorf("retransmit[%d] error: %v", tx.retransCount, err) 99 | } 100 | tx.timer = tx.startTimer() 101 | } else { 102 | tx.log.Debugf("max retransmission reached - delete txtr") 103 | delete(tx.server.txTrans, tx.id) 104 | err := tx.server.txtoDispacher(tx.req, tx.raddr) 105 | if err != nil { 106 | tx.log.Errorf("txtoDispacher: %v", err) 107 | } 108 | } 109 | } 110 | 111 | func (tx *TxTransaction) startTimer() *time.Timer { 112 | tx.log.Debugf("start timer(%s)", tx.retransTimeout) 113 | t := time.AfterFunc( 114 | tx.retransTimeout, 115 | func() { 116 | tx.server.NotifyTransTimeout(TX, tx.id) 117 | }, 118 | ) 119 | return t 120 | } 121 | 122 | func NewRxTransaction( 123 | server *PfcpServer, 124 | raddr net.Addr, 125 | seq uint32, 126 | ) *RxTransaction { 127 | rx := &RxTransaction{ 128 | server: server, 129 | raddr: raddr, 130 | seq: seq, 131 | id: fmt.Sprintf("%s-%d", raddr, seq), 132 | timeout: server.cfg.Pfcp.RetransTimeout * time.Duration(server.cfg.Pfcp.MaxRetrans+1), 133 | } 134 | rx.log = server.log.WithField(logger_util.FieldPFCPRxTransaction, rx.id) 135 | // Start rx timer to delete rx 136 | rx.timer = rx.startTimer() 137 | return rx 138 | } 139 | 140 | func (rx *RxTransaction) send(rsp message.Message) error { 141 | rx.log.Debugf("send rsp") 142 | 143 | b := make([]byte, rsp.MarshalLen()) 144 | err := rsp.MarshalTo(b) 145 | if err != nil { 146 | return err 147 | } 148 | 149 | rx.msgBuf = b 150 | _, err = rx.server.conn.WriteTo(b, rx.raddr) 151 | if err != nil { 152 | return err 153 | } 154 | 155 | return nil 156 | } 157 | 158 | // True - need to handle this req 159 | // False - req already handled 160 | func (rx *RxTransaction) recv(req message.Message, rxTrFound bool) (bool, error) { 161 | rx.log.Debugf("recv req - rxTrFound(%v)", rxTrFound) 162 | if !rxTrFound { 163 | return true, nil 164 | } 165 | 166 | if len(rx.msgBuf) == 0 { 167 | rx.log.Warnf("recv req: no rsp to retransmit") 168 | return false, nil 169 | } 170 | 171 | rx.log.Debugf("recv req: retransmit rsp") 172 | _, err := rx.server.conn.WriteTo(rx.msgBuf, rx.raddr) 173 | if err != nil { 174 | return false, errors.Wrapf(err, "rxtr[%s] recv", rx.id) 175 | } 176 | return false, nil 177 | } 178 | 179 | func (rx *RxTransaction) handleTimeout() { 180 | rx.log.Debugf("timeout, delete rxtr") 181 | delete(rx.server.rxTrans, rx.id) 182 | } 183 | 184 | func (rx *RxTransaction) startTimer() *time.Timer { 185 | rx.log.Debugf("start timer(%s)", rx.timeout) 186 | t := time.AfterFunc( 187 | rx.timeout, 188 | func() { 189 | rx.server.NotifyTransTimeout(RX, rx.id) 190 | }, 191 | ) 192 | return t 193 | } 194 | -------------------------------------------------------------------------------- /internal/report/handler.go: -------------------------------------------------------------------------------- 1 | package report 2 | 3 | type Handler interface { 4 | NotifySessReport(SessReport) 5 | PopBufPkt(uint64, uint16) ([]byte, bool) 6 | } 7 | -------------------------------------------------------------------------------- /internal/report/report.go: -------------------------------------------------------------------------------- 1 | package report 2 | 3 | import ( 4 | "encoding/binary" 5 | "time" 6 | 7 | "github.com/pkg/errors" 8 | "github.com/wmnsk/go-pfcp/ie" 9 | ) 10 | 11 | type ReportType int 12 | 13 | // 29244-ga0 8.2.21 Report Type 14 | const ( 15 | DLDR ReportType = iota + 1 16 | USAR 17 | ERIR 18 | UPIR 19 | TMIR 20 | SESR 21 | UISR 22 | ) 23 | 24 | func (t ReportType) String() string { 25 | str := []string{"", "DLDR", "USAR", "ERIR", "UPIR", "TMIR", "SESR", "UISR"} 26 | return str[t] 27 | } 28 | 29 | type Report interface { 30 | Type() ReportType 31 | } 32 | 33 | type DLDReport struct { 34 | PDRID uint16 35 | Action uint16 36 | BufPkt []byte 37 | } 38 | 39 | func (r DLDReport) Type() ReportType { 40 | return DLDR 41 | } 42 | 43 | type MeasureMethod struct { 44 | DURAT bool 45 | VOLUM bool 46 | EVENT bool 47 | } 48 | 49 | type MeasureInformation struct { 50 | MBQE bool 51 | INAM bool 52 | RADI bool 53 | ISTM bool 54 | MNOP bool 55 | SSPOC bool 56 | ASPOC bool 57 | CIAM bool 58 | } 59 | 60 | type USAReport struct { 61 | URRID uint32 62 | URSEQN uint32 63 | USARTrigger UsageReportTrigger 64 | VolumMeasure VolumeMeasure 65 | DuratMeasure DurationMeasure 66 | QueryUrrRef uint32 67 | StartTime time.Time 68 | EndTime time.Time 69 | } 70 | 71 | func (r USAReport) Type() ReportType { 72 | return USAR 73 | } 74 | 75 | func (r USAReport) IEsWithinSessReportReq( 76 | method MeasureMethod, info MeasureInformation, 77 | ) []*ie.IE { 78 | ies := []*ie.IE{ 79 | ie.NewURRID(r.URRID), 80 | ie.NewURSEQN(r.URSEQN), 81 | r.USARTrigger.IE(), 82 | } 83 | if !r.USARTrigger.START() && !r.USARTrigger.STOPT() && !r.USARTrigger.MACAR() { 84 | // These IEs shall be present, except if the Usage Report 85 | // Trigger indicates 'Start of Traffic', 'Stop of Traffic' or 'MAC 86 | // Addresses Reporting'. 87 | ies = append(ies, ie.NewStartTime(r.StartTime), ie.NewEndTime(r.EndTime)) 88 | } 89 | if method.VOLUM { 90 | r.VolumMeasure.SetFlags(info.MNOP) 91 | ies = append(ies, r.VolumMeasure.IE()) 92 | } 93 | if method.DURAT { 94 | ies = append(ies, r.DuratMeasure.IE()) 95 | } 96 | return ies 97 | } 98 | 99 | func (r USAReport) IEsWithinSessModRsp( 100 | method MeasureMethod, info MeasureInformation, 101 | ) []*ie.IE { 102 | ies := []*ie.IE{ 103 | ie.NewURRID(r.URRID), 104 | ie.NewURSEQN(r.URSEQN), 105 | r.USARTrigger.IE(), 106 | } 107 | if !r.USARTrigger.START() && !r.USARTrigger.STOPT() && !r.USARTrigger.MACAR() { 108 | // These IEs shall be present, except if the Usage Report 109 | // Trigger indicates 'Start of Traffic', 'Stop of Traffic' or 'MAC 110 | // Addresses Reporting'. 111 | ies = append(ies, ie.NewStartTime(r.StartTime), ie.NewEndTime(r.EndTime)) 112 | } 113 | if method.VOLUM { 114 | r.VolumMeasure.SetFlags(info.MNOP) 115 | ies = append(ies, r.VolumMeasure.IE()) 116 | } 117 | if method.DURAT { 118 | ies = append(ies, r.DuratMeasure.IE()) 119 | } 120 | return ies 121 | } 122 | 123 | func (r USAReport) IEsWithinSessDelRsp( 124 | method MeasureMethod, info MeasureInformation, 125 | ) []*ie.IE { 126 | ies := []*ie.IE{ 127 | ie.NewURRID(r.URRID), 128 | ie.NewURSEQN(r.URSEQN), 129 | r.USARTrigger.IE(), 130 | } 131 | if !r.USARTrigger.START() && !r.USARTrigger.STOPT() && !r.USARTrigger.MACAR() { 132 | // These IEs shall be present, except if the Usage Report 133 | // Trigger indicates 'Start of Traffic', 'Stop of Traffic' or 'MAC 134 | // Addresses Reporting'. 135 | ies = append(ies, ie.NewStartTime(r.StartTime), ie.NewEndTime(r.EndTime)) 136 | } 137 | if method.VOLUM { 138 | r.VolumMeasure.SetFlags(info.MNOP) 139 | ies = append(ies, r.VolumMeasure.IE()) 140 | } 141 | if method.DURAT { 142 | ies = append(ies, r.DuratMeasure.IE()) 143 | } 144 | return ies 145 | } 146 | 147 | // Reporting Triggers IE bits definition 148 | const ( 149 | RPT_TRIG_PERIO = 1 << iota 150 | RPT_TRIG_VOLTH 151 | RPT_TRIG_TIMTH 152 | RPT_TRIG_QUHTI 153 | RPT_TRIG_START 154 | RPT_TRIG_STOPT 155 | RPT_TRIG_DROTH 156 | RPT_TRIG_LIUSA 157 | RPT_TRIG_VOLQU 158 | RPT_TRIG_TIMQU 159 | RPT_TRIG_ENVCL 160 | RPT_TRIG_MACAR 161 | RPT_TRIG_EVETH 162 | RPT_TRIG_EVEQU 163 | RPT_TRIG_IPMJL 164 | RPT_TRIG_QUVTI 165 | RPT_TRIG_REEMR 166 | RPT_TRIG_UPINT 167 | ) 168 | 169 | type ReportingTrigger struct { 170 | Flags uint32 171 | } 172 | 173 | func (r *ReportingTrigger) Unmarshal(b []byte) error { 174 | if len(b) < 2 { 175 | return errors.Errorf("ReportingTrigger Unmarshal: less than 2 bytes") 176 | } 177 | // slice len might be 2 or 3; enlarge slice to 4 bytes at least 178 | v := make([]byte, len(b)+2) 179 | copy(v, b) 180 | r.Flags = binary.LittleEndian.Uint32(v) 181 | return nil 182 | } 183 | 184 | func (r *ReportingTrigger) IE() *ie.IE { 185 | b := make([]byte, 4) 186 | binary.LittleEndian.PutUint32(b, r.Flags) 187 | return ie.NewReportingTriggers(b[:3]...) 188 | } 189 | 190 | func (r *ReportingTrigger) PERIO() bool { 191 | return r.Flags&RPT_TRIG_PERIO != 0 192 | } 193 | 194 | func (r *ReportingTrigger) VOLTH() bool { 195 | return r.Flags&RPT_TRIG_VOLTH != 0 196 | } 197 | 198 | func (r *ReportingTrigger) TIMTH() bool { 199 | return r.Flags&RPT_TRIG_TIMTH != 0 200 | } 201 | 202 | func (r *ReportingTrigger) QUHTI() bool { 203 | return r.Flags&RPT_TRIG_QUHTI != 0 204 | } 205 | 206 | func (r *ReportingTrigger) START() bool { 207 | return r.Flags&RPT_TRIG_START != 0 208 | } 209 | 210 | func (r *ReportingTrigger) STOPT() bool { 211 | return r.Flags&RPT_TRIG_STOPT != 0 212 | } 213 | 214 | func (r *ReportingTrigger) DROTH() bool { 215 | return r.Flags&RPT_TRIG_DROTH != 0 216 | } 217 | 218 | func (r *ReportingTrigger) LIUSA() bool { 219 | return r.Flags&RPT_TRIG_LIUSA != 0 220 | } 221 | 222 | func (r *ReportingTrigger) VOLQU() bool { 223 | return r.Flags&RPT_TRIG_VOLQU != 0 224 | } 225 | 226 | func (r *ReportingTrigger) TIMQU() bool { 227 | return r.Flags&RPT_TRIG_TIMQU != 0 228 | } 229 | 230 | func (r *ReportingTrigger) ENVCL() bool { 231 | return r.Flags&RPT_TRIG_ENVCL != 0 232 | } 233 | 234 | func (r *ReportingTrigger) MACAR() bool { 235 | return r.Flags&RPT_TRIG_MACAR != 0 236 | } 237 | 238 | func (r *ReportingTrigger) EVETH() bool { 239 | return r.Flags&RPT_TRIG_EVETH != 0 240 | } 241 | 242 | func (r *ReportingTrigger) EVEQU() bool { 243 | return r.Flags&RPT_TRIG_EVEQU != 0 244 | } 245 | 246 | func (r *ReportingTrigger) IPMJL() bool { 247 | return r.Flags&RPT_TRIG_IPMJL != 0 248 | } 249 | 250 | func (r *ReportingTrigger) QUVTI() bool { 251 | return r.Flags&RPT_TRIG_QUVTI != 0 252 | } 253 | 254 | func (r *ReportingTrigger) REEMR() bool { 255 | return r.Flags&RPT_TRIG_REEMR != 0 256 | } 257 | 258 | func (r *ReportingTrigger) UPINT() bool { 259 | return r.Flags&RPT_TRIG_UPINT != 0 260 | } 261 | 262 | // Usage Report Trigger IE bits definition 263 | const ( 264 | USAR_TRIG_PERIO = 1 << iota 265 | USAR_TRIG_VOLTH 266 | USAR_TRIG_TIMTH 267 | USAR_TRIG_QUHTI 268 | USAR_TRIG_START 269 | USAR_TRIG_STOPT 270 | USAR_TRIG_DROTH 271 | USAR_TRIG_IMMER 272 | USAR_TRIG_VOLQU 273 | USAR_TRIG_TIMQU 274 | USAR_TRIG_LIUSA 275 | USAR_TRIG_TERMR 276 | USAR_TRIG_MONIT 277 | USAR_TRIG_ENVCL 278 | USAR_TRIG_MACAR 279 | USAR_TRIG_EVETH 280 | USAR_TRIG_EVEQU 281 | USAR_TRIG_TEBUR 282 | USAR_TRIG_IPMJL 283 | USAR_TRIG_QUVTI 284 | USAR_TRIG_EMRRE 285 | USAR_TRIG_UPINT 286 | ) 287 | 288 | type UsageReportTrigger struct { 289 | Flags uint32 290 | } 291 | 292 | func (t *UsageReportTrigger) SetReportingTrigger(r uint32) { 293 | switch r { 294 | case RPT_TRIG_PERIO: 295 | t.Flags |= USAR_TRIG_PERIO 296 | case RPT_TRIG_VOLTH: 297 | t.Flags |= USAR_TRIG_VOLTH 298 | case RPT_TRIG_TIMTH: 299 | t.Flags |= USAR_TRIG_TIMTH 300 | case RPT_TRIG_QUHTI: 301 | t.Flags |= USAR_TRIG_QUHTI 302 | case RPT_TRIG_START: 303 | t.Flags |= USAR_TRIG_START 304 | case RPT_TRIG_STOPT: 305 | t.Flags |= USAR_TRIG_STOPT 306 | case RPT_TRIG_DROTH: 307 | t.Flags |= USAR_TRIG_DROTH 308 | case RPT_TRIG_LIUSA: 309 | t.Flags |= USAR_TRIG_LIUSA 310 | case RPT_TRIG_VOLQU: 311 | t.Flags |= USAR_TRIG_VOLQU 312 | case RPT_TRIG_TIMQU: 313 | t.Flags |= USAR_TRIG_TIMQU 314 | case RPT_TRIG_ENVCL: 315 | t.Flags |= USAR_TRIG_ENVCL 316 | case RPT_TRIG_MACAR: 317 | t.Flags |= USAR_TRIG_MACAR 318 | case RPT_TRIG_EVETH: 319 | t.Flags |= USAR_TRIG_EVETH 320 | case RPT_TRIG_EVEQU: 321 | t.Flags |= USAR_TRIG_EVEQU 322 | case RPT_TRIG_IPMJL: 323 | t.Flags |= USAR_TRIG_IPMJL 324 | case RPT_TRIG_QUVTI: 325 | t.Flags |= USAR_TRIG_QUVTI 326 | case RPT_TRIG_UPINT: 327 | t.Flags |= USAR_TRIG_UPINT 328 | } 329 | } 330 | 331 | func (t *UsageReportTrigger) IE() *ie.IE { 332 | b := make([]byte, 4) 333 | binary.LittleEndian.PutUint32(b, t.Flags) 334 | return ie.NewUsageReportTrigger(b[:3]...) 335 | } 336 | 337 | func (t *UsageReportTrigger) PERIO() bool { 338 | return t.Flags&USAR_TRIG_PERIO != 0 339 | } 340 | 341 | func (t *UsageReportTrigger) VOLTH() bool { 342 | return t.Flags&USAR_TRIG_VOLTH != 0 343 | } 344 | 345 | func (t *UsageReportTrigger) TIMTH() bool { 346 | return t.Flags&USAR_TRIG_TIMTH != 0 347 | } 348 | 349 | func (t *UsageReportTrigger) QUHTI() bool { 350 | return t.Flags&USAR_TRIG_QUHTI != 0 351 | } 352 | 353 | func (t *UsageReportTrigger) START() bool { 354 | return t.Flags&USAR_TRIG_START != 0 355 | } 356 | 357 | func (t *UsageReportTrigger) STOPT() bool { 358 | return t.Flags&USAR_TRIG_STOPT != 0 359 | } 360 | 361 | func (t *UsageReportTrigger) DROTH() bool { 362 | return t.Flags&USAR_TRIG_DROTH != 0 363 | } 364 | 365 | func (t *UsageReportTrigger) IMMER() bool { 366 | return t.Flags&USAR_TRIG_IMMER != 0 367 | } 368 | 369 | func (t *UsageReportTrigger) VOLQU() bool { 370 | return t.Flags&USAR_TRIG_VOLQU != 0 371 | } 372 | 373 | func (t *UsageReportTrigger) TIMQU() bool { 374 | return t.Flags&USAR_TRIG_TIMQU != 0 375 | } 376 | 377 | func (t *UsageReportTrigger) LIUSA() bool { 378 | return t.Flags&USAR_TRIG_LIUSA != 0 379 | } 380 | 381 | func (t *UsageReportTrigger) TERMR() bool { 382 | return t.Flags&USAR_TRIG_TERMR != 0 383 | } 384 | 385 | func (t *UsageReportTrigger) MONIT() bool { 386 | return t.Flags&USAR_TRIG_MONIT != 0 387 | } 388 | 389 | func (t *UsageReportTrigger) ENVCL() bool { 390 | return t.Flags&USAR_TRIG_ENVCL != 0 391 | } 392 | 393 | func (t *UsageReportTrigger) MACAR() bool { 394 | return t.Flags&USAR_TRIG_MACAR != 0 395 | } 396 | 397 | func (t *UsageReportTrigger) EVETH() bool { 398 | return t.Flags&USAR_TRIG_EVETH != 0 399 | } 400 | 401 | func (t *UsageReportTrigger) EVEQU() bool { 402 | return t.Flags&USAR_TRIG_EVEQU != 0 403 | } 404 | 405 | func (t *UsageReportTrigger) TEBUR() bool { 406 | return t.Flags&USAR_TRIG_TEBUR != 0 407 | } 408 | 409 | func (t *UsageReportTrigger) IPMJL() bool { 410 | return t.Flags&USAR_TRIG_IPMJL != 0 411 | } 412 | 413 | func (t *UsageReportTrigger) QUVTI() bool { 414 | return t.Flags&USAR_TRIG_QUVTI != 0 415 | } 416 | 417 | func (t *UsageReportTrigger) EMRRE() bool { 418 | return t.Flags&USAR_TRIG_EMRRE != 0 419 | } 420 | 421 | func (t *UsageReportTrigger) UPINT() bool { 422 | return t.Flags&USAR_TRIG_UPINT != 0 423 | } 424 | 425 | // Volume Measurement IE Flag bits definition 426 | const ( 427 | TOVOL uint8 = 1 << iota 428 | ULVOL 429 | DLVOL 430 | TONOP 431 | ULNOP 432 | DLNOP 433 | ) 434 | 435 | type VolumeMeasure struct { 436 | Flags uint8 437 | TotalVolume uint64 438 | UplinkVolume uint64 439 | DownlinkVolume uint64 440 | TotalPktNum uint64 441 | UplinkPktNum uint64 442 | DownlinkPktNum uint64 443 | } 444 | 445 | func (m *VolumeMeasure) SetFlags(mnop bool) { 446 | m.Flags |= (TOVOL | ULVOL | DLVOL) 447 | if mnop { 448 | m.Flags |= (TONOP | ULNOP | DLNOP) 449 | } 450 | } 451 | 452 | func (m *VolumeMeasure) IE() *ie.IE { 453 | return ie.NewVolumeMeasurement( 454 | m.Flags, 455 | m.TotalVolume, 456 | m.UplinkVolume, 457 | m.DownlinkVolume, 458 | m.TotalPktNum, 459 | m.UplinkPktNum, 460 | m.DownlinkPktNum, 461 | ) 462 | } 463 | 464 | type DurationMeasure struct { 465 | DurationValue uint64 466 | } 467 | 468 | func (m *DurationMeasure) IE() *ie.IE { 469 | return ie.NewDurationMeasurement(time.Duration(m.DurationValue)) 470 | } 471 | 472 | // Apply Action IE bits definition 473 | const ( 474 | APPLY_ACT_DROP = 1 << iota 475 | APPLY_ACT_FORW 476 | APPLY_ACT_BUFF 477 | APPLY_ACT_NOCP 478 | APPLY_ACT_DUPL 479 | APPLY_ACT_IPMA 480 | APPLY_ACT_IPMD 481 | APPLY_ACT_DFRT 482 | APPLY_ACT_EDRT 483 | APPLY_ACT_BDPN 484 | APPLY_ACT_DDPN 485 | APPLY_ACT_FSSM 486 | APPLY_ACT_MBSU 487 | ) 488 | 489 | type ApplyAction struct { 490 | Flags uint16 491 | } 492 | 493 | func (a *ApplyAction) Unmarshal(b []byte) error { 494 | if len(b) < 1 { 495 | return errors.Errorf("ApplyAction Unmarshal: less than 1 bytes") 496 | } 497 | 498 | // slice len might be 1 or 2; enlarge slice to 2 bytes at least 499 | v := make([]byte, max(2, len(b))) 500 | copy(v, b) 501 | a.Flags = binary.LittleEndian.Uint16(v) 502 | return nil 503 | } 504 | 505 | func (a *ApplyAction) DROP() bool { 506 | return a.Flags&APPLY_ACT_DROP != 0 507 | } 508 | 509 | func (a *ApplyAction) FORW() bool { 510 | return a.Flags&APPLY_ACT_FORW != 0 511 | } 512 | 513 | func (a *ApplyAction) BUFF() bool { 514 | return a.Flags&APPLY_ACT_BUFF != 0 515 | } 516 | 517 | func (a *ApplyAction) NOCP() bool { 518 | return a.Flags&APPLY_ACT_NOCP != 0 519 | } 520 | 521 | func (a *ApplyAction) DUPL() bool { 522 | return a.Flags&APPLY_ACT_DUPL != 0 523 | } 524 | 525 | func (a *ApplyAction) IPMA() bool { 526 | return a.Flags&APPLY_ACT_IPMA != 0 527 | } 528 | 529 | func (a *ApplyAction) IPMD() bool { 530 | return a.Flags&APPLY_ACT_IPMD != 0 531 | } 532 | 533 | func (a *ApplyAction) DFRT() bool { 534 | return a.Flags&APPLY_ACT_DFRT != 0 535 | } 536 | 537 | func (a *ApplyAction) EDRT() bool { 538 | return a.Flags&APPLY_ACT_EDRT != 0 539 | } 540 | 541 | func (a *ApplyAction) BDPN() bool { 542 | return a.Flags&APPLY_ACT_BDPN != 0 543 | } 544 | 545 | func (a *ApplyAction) DDPN() bool { 546 | return a.Flags&APPLY_ACT_DDPN != 0 547 | } 548 | 549 | func (a *ApplyAction) FSSM() bool { 550 | return a.Flags&APPLY_ACT_FSSM != 0 551 | } 552 | 553 | func (a *ApplyAction) MBSU() bool { 554 | return a.Flags&APPLY_ACT_MBSU != 0 555 | } 556 | 557 | type SessReport struct { 558 | SEID uint64 559 | Reports []Report 560 | } 561 | 562 | type BufInfo struct { 563 | SEID uint64 564 | PDRID uint16 565 | } 566 | -------------------------------------------------------------------------------- /internal/report/report_test.go: -------------------------------------------------------------------------------- 1 | package report_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/free5gc/go-upf/internal/report" 9 | ) 10 | 11 | func TestApplyAction0(t *testing.T) { 12 | var act report.ApplyAction 13 | e := act.Unmarshal([]byte{}) 14 | assert.Error(t, e) 15 | } 16 | 17 | func TestApplyAction1(t *testing.T) { 18 | var act report.ApplyAction 19 | e := act.Unmarshal([]byte{0x02}) 20 | assert.NoError(t, e) 21 | assert.Equal(t, uint16(0x0002), act.Flags) 22 | assert.False(t, act.DROP()) 23 | assert.True(t, act.FORW()) 24 | assert.False(t, act.BUFF()) 25 | assert.False(t, act.NOCP()) 26 | assert.False(t, act.DUPL()) 27 | assert.False(t, act.IPMA()) 28 | assert.False(t, act.IPMD()) 29 | assert.False(t, act.DFRT()) 30 | assert.False(t, act.EDRT()) 31 | assert.False(t, act.BDPN()) 32 | assert.False(t, act.DDPN()) 33 | assert.False(t, act.FSSM()) 34 | assert.False(t, act.MBSU()) 35 | } 36 | 37 | func TestApplyAction2(t *testing.T) { 38 | var act report.ApplyAction 39 | e := act.Unmarshal([]byte{0x0C, 0x00}) 40 | assert.NoError(t, e) 41 | assert.Equal(t, uint16(0x000C), act.Flags) 42 | assert.False(t, act.DROP()) 43 | assert.False(t, act.FORW()) 44 | assert.True(t, act.BUFF()) 45 | assert.True(t, act.NOCP()) 46 | assert.False(t, act.DUPL()) 47 | assert.False(t, act.IPMA()) 48 | assert.False(t, act.IPMD()) 49 | assert.False(t, act.DFRT()) 50 | assert.False(t, act.EDRT()) 51 | assert.False(t, act.BDPN()) 52 | assert.False(t, act.DDPN()) 53 | assert.False(t, act.FSSM()) 54 | assert.False(t, act.MBSU()) 55 | } 56 | -------------------------------------------------------------------------------- /pkg/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | "runtime/debug" 8 | "sync" 9 | "syscall" 10 | 11 | "github.com/sirupsen/logrus" 12 | 13 | "github.com/free5gc/go-upf/internal/forwarder" 14 | "github.com/free5gc/go-upf/internal/logger" 15 | "github.com/free5gc/go-upf/internal/pfcp" 16 | "github.com/free5gc/go-upf/pkg/factory" 17 | ) 18 | 19 | type UpfApp struct { 20 | ctx context.Context 21 | wg sync.WaitGroup 22 | cfg *factory.Config 23 | driver forwarder.Driver 24 | pfcpServer *pfcp.PfcpServer 25 | } 26 | 27 | func NewApp(cfg *factory.Config) (*UpfApp, error) { 28 | upf := &UpfApp{ 29 | cfg: cfg, 30 | } 31 | upf.SetLogLevel(cfg.Logger.Level) 32 | upf.SetLogReportCaller(cfg.Logger.ReportCaller) 33 | return upf, nil 34 | } 35 | 36 | func (u *UpfApp) Config() *factory.Config { 37 | return u.cfg 38 | } 39 | 40 | func (a *UpfApp) SetLogLevel(level string) { 41 | lvl, err := logrus.ParseLevel(level) 42 | if err != nil { 43 | logger.MainLog.Warnf("Log level [%s] is invalid", level) 44 | return 45 | } 46 | 47 | logger.MainLog.Infof("Log level is set to [%s]", level) 48 | if lvl == logger.Log.GetLevel() { 49 | return 50 | } 51 | 52 | logger.Log.SetLevel(lvl) 53 | } 54 | 55 | func (a *UpfApp) SetLogReportCaller(reportCaller bool) { 56 | logger.MainLog.Infof("Report Caller is set to [%v]", reportCaller) 57 | if reportCaller == logger.Log.ReportCaller { 58 | return 59 | } 60 | 61 | logger.Log.SetReportCaller(reportCaller) 62 | } 63 | 64 | func (u *UpfApp) Run() error { 65 | var cancel context.CancelFunc 66 | u.ctx, cancel = context.WithCancel(context.Background()) 67 | defer cancel() 68 | 69 | u.wg.Add(1) 70 | /* Go Routine is spawned here for listening for cancellation event on 71 | * context */ 72 | go u.listenShutdownEvent() 73 | 74 | var err error 75 | u.driver, err = forwarder.NewDriver(&u.wg, u.cfg) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | u.pfcpServer = pfcp.NewPfcpServer(u.cfg, u.driver) 81 | u.driver.HandleReport(u.pfcpServer) 82 | u.pfcpServer.Start(&u.wg) 83 | 84 | logger.MainLog.Infoln("UPF started") 85 | 86 | // Wait for interrupt signal to gracefully shutdown 87 | sigCh := make(chan os.Signal, 1) 88 | signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) 89 | <-sigCh 90 | 91 | // Receive the interrupt signal 92 | logger.MainLog.Infof("Shutdown UPF ...") 93 | // Notify each goroutine and wait them stopped 94 | cancel() 95 | u.WaitRoutineStopped() 96 | logger.MainLog.Infof("UPF exited") 97 | return nil 98 | } 99 | 100 | func (u *UpfApp) listenShutdownEvent() { 101 | defer func() { 102 | if p := recover(); p != nil { 103 | // Print stack for panic to log. Fatalf() will let program exit. 104 | logger.MainLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) 105 | } 106 | 107 | u.wg.Done() 108 | }() 109 | 110 | <-u.ctx.Done() 111 | if u.pfcpServer != nil { 112 | u.pfcpServer.Stop() 113 | } 114 | if u.driver != nil { 115 | u.driver.Close() 116 | } 117 | } 118 | 119 | func (u *UpfApp) WaitRoutineStopped() { 120 | u.wg.Wait() 121 | u.Terminate() 122 | } 123 | 124 | func (u *UpfApp) Start() { 125 | if err := u.Run(); err != nil { 126 | logger.MainLog.Errorf("UPF Run err: %v", err) 127 | } 128 | } 129 | 130 | func (u *UpfApp) Terminate() { 131 | logger.MainLog.Infof("Terminating UPF...") 132 | logger.MainLog.Infof("UPF terminated") 133 | } 134 | -------------------------------------------------------------------------------- /pkg/app/app_test.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "sync" 5 | "syscall" 6 | "testing" 7 | "time" 8 | 9 | "github.com/free5gc/go-upf/pkg/factory" 10 | ) 11 | 12 | func TestWaitRoutineStopped(t *testing.T) { 13 | if testing.Short() { 14 | t.Skip("skipping testing in short mode") 15 | } 16 | 17 | cfg := &factory.Config{ 18 | Version: "1.0.3", 19 | Pfcp: &factory.Pfcp{ 20 | Addr: "127.0.0.1", 21 | NodeID: "127.0.0.1", 22 | }, 23 | Gtpu: &factory.Gtpu{ 24 | Forwarder: "gtp5g", 25 | IfList: []factory.IfInfo{ 26 | { 27 | Addr: "127.0.0.1", 28 | Type: "", 29 | Name: "", 30 | IfName: "", 31 | }, 32 | }, 33 | }, 34 | DnnList: []factory.DnnList{ 35 | { 36 | Dnn: "internet", 37 | Cidr: "10.60.0.1/24", 38 | }, 39 | }, 40 | Logger: &factory.Logger{ 41 | Enable: true, 42 | Level: "info", 43 | }, 44 | } 45 | N := 10 46 | for i := 0; i < N; i++ { 47 | var wg sync.WaitGroup 48 | upf, err := NewApp(cfg) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | wg.Add(1) 53 | go func() { 54 | upf.Start() 55 | wg.Done() 56 | }() 57 | // Must wait for signal initialized 58 | time.Sleep(500 * time.Millisecond) 59 | err = syscall.Kill(syscall.Getpid(), syscall.SIGTERM) 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | wg.Wait() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /pkg/factory/config.go: -------------------------------------------------------------------------------- 1 | package factory 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/davecgh/go-spew/spew" 7 | 8 | "github.com/free5gc/go-upf/internal/logger" 9 | ) 10 | 11 | const ( 12 | UpfDefaultConfigPath = "./config/upfcfg.yaml" 13 | UpfDefaultIPv4 = "127.0.0.8" 14 | UpfPfcpDefaultPort = 8805 15 | UpfGtpDefaultPort = 2152 16 | ) 17 | 18 | type Config struct { 19 | Version string `yaml:"version" valid:"required,in(1.0.3)"` 20 | Description string `yaml:"description" valid:"optional"` 21 | Pfcp *Pfcp `yaml:"pfcp" valid:"required"` 22 | Gtpu *Gtpu `yaml:"gtpu" valid:"required"` 23 | DnnList []DnnList `yaml:"dnnList" valid:"required"` 24 | Logger *Logger `yaml:"logger" valid:"required"` 25 | } 26 | 27 | type Pfcp struct { 28 | Addr string `yaml:"addr" valid:"required,host"` 29 | NodeID string `yaml:"nodeID" valid:"required,host"` 30 | RetransTimeout time.Duration `yaml:"retransTimeout" valid:"required"` 31 | MaxRetrans uint8 `yaml:"maxRetrans" valid:"optional"` 32 | } 33 | 34 | type Gtpu struct { 35 | Forwarder string `yaml:"forwarder" valid:"required,in(gtp5g)"` 36 | IfList []IfInfo `yaml:"ifList" valid:"optional"` 37 | } 38 | 39 | type IfInfo struct { 40 | Addr string `yaml:"addr" valid:"required,host"` 41 | Type string `yaml:"type" valid:"required,in(N3|N9)"` 42 | Name string `yaml:"name" valid:"optional"` 43 | IfName string `yaml:"ifname" valid:"optional"` 44 | MTU uint32 `yaml:"mtu" valid:"optional"` 45 | } 46 | 47 | type DnnList struct { 48 | Dnn string `yaml:"dnn" valid:"required"` 49 | Cidr string `yaml:"cidr" valid:"required,cidr"` 50 | NatIfName string `yaml:"natifname" valid:"optional"` 51 | } 52 | 53 | type Logger struct { 54 | Enable bool `yaml:"enable" valid:"optional"` 55 | Level string `yaml:"level" valid:"required,in(trace|debug|info|warn|error|fatal|panic)"` 56 | ReportCaller bool `yaml:"reportCaller" valid:"optional"` 57 | } 58 | 59 | func (c *Config) GetVersion() string { 60 | return c.Version 61 | } 62 | 63 | func (c *Config) Print() { 64 | spew.Config.Indent = "\t" 65 | str := spew.Sdump(c) 66 | logger.CfgLog.Infof("==================================================") 67 | logger.CfgLog.Infof("%s", str) 68 | logger.CfgLog.Infof("==================================================") 69 | } 70 | -------------------------------------------------------------------------------- /pkg/factory/factory.go: -------------------------------------------------------------------------------- 1 | package factory 2 | 3 | import ( 4 | "net" 5 | "os" 6 | 7 | "github.com/asaskevich/govalidator" 8 | "github.com/pkg/errors" 9 | "gopkg.in/yaml.v2" 10 | 11 | "github.com/free5gc/go-upf/internal/logger" 12 | ) 13 | 14 | // TODO: Support configuration update from REST api 15 | func InitConfigFactory(f string, cfg *Config) error { 16 | if f == "" { 17 | // Use default config path 18 | f = UpfDefaultConfigPath 19 | } 20 | 21 | if content, err := os.ReadFile(f); err != nil { 22 | return errors.Errorf("[Factory] %+v", err) 23 | } else { 24 | logger.CfgLog.Infof("Read config from [%s]", f) 25 | if yamlErr := yaml.Unmarshal(content, cfg); yamlErr != nil { 26 | return errors.Errorf("[Factory] %+v", yamlErr) 27 | } 28 | } 29 | 30 | return nil 31 | } 32 | 33 | func ReadConfig(cfgPath string) (*Config, error) { 34 | cfg := &Config{} 35 | err := InitConfigFactory(cfgPath, cfg) 36 | if err != nil { 37 | return nil, errors.Errorf("ReadConfig [%s] Error: %+v", cfgPath, err) 38 | } 39 | 40 | govalidator.TagMap["cidr"] = govalidator.Validator(func(str string) bool { 41 | return govalidator.IsCIDR(str) 42 | }) 43 | _, err = govalidator.ValidateStruct(cfg) 44 | if err != nil { 45 | logger.CfgLog.Errorf("[-- PLEASE REFER TO SAMPLE CONFIG FILE COMMENTS --]") 46 | return nil, err 47 | } 48 | 49 | _, err = net.ResolveIPAddr("ip4", cfg.Pfcp.NodeID) 50 | if err != nil { 51 | return nil, errors.Errorf("cfg.Pfcp.NodeID[%s] can't be resolved", cfg.Pfcp.NodeID) 52 | } 53 | 54 | cfg.Print() 55 | return cfg, nil 56 | } 57 | -------------------------------------------------------------------------------- /testtools/upftest/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | "github.com/wmnsk/go-pfcp/ie" 10 | "github.com/wmnsk/go-pfcp/message" 11 | ) 12 | 13 | func main() { 14 | var ( 15 | server = flag.String("-s", "127.0.0.8:8805", "server's addr/port") 16 | nodeid = flag.String("-n", "127.0.0.8", "client's node id") 17 | boottime = time.Now() 18 | seq uint32 19 | err error 20 | buf = make([]byte, 1500) 21 | waiting bool 22 | ) 23 | flag.Parse() 24 | 25 | raddr, err := net.ResolveUDPAddr("udp4", *server) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | 30 | conn, err := net.DialUDP("udp4", nil, raddr) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | 35 | seq += 1 36 | asreq, err := message.NewAssociationSetupRequest( 37 | seq, // Sequence Number 38 | ie.NewNodeID(*nodeid, "", ""), 39 | ie.NewRecoveryTimeStamp(boottime), 40 | ).Marshal() 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | 45 | if _, err = conn.Write(asreq); err != nil { 46 | log.Fatal(err) 47 | } 48 | log.Printf("sent PFCP Association Setup Request to: %s", raddr) 49 | 50 | if err = conn.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil { 51 | log.Fatal(err) 52 | } 53 | 54 | waiting = true 55 | for waiting { 56 | n, addr, err1 := conn.ReadFrom(buf) 57 | if err1 != nil { 58 | log.Fatal(err1) 59 | } 60 | 61 | msg, err1 := message.Parse(buf[:n]) 62 | if err1 != nil { 63 | log.Printf("ignored undecodable message: %x, error: %s", buf[:n], err1) 64 | continue 65 | } 66 | 67 | asres, ok := msg.(*message.AssociationSetupResponse) 68 | if !ok { 69 | log.Printf("got unexpected message: %s, from: %s", msg.MessageTypeName(), addr) 70 | continue 71 | } 72 | 73 | waiting = false 74 | if asres.Cause == nil { 75 | log.Printf("got non accepted response") 76 | return 77 | } 78 | if cause, err1 := asres.Cause.Cause(); cause != ie.CauseRequestAccepted || err1 != nil { 79 | log.Printf("got non accepted response") 80 | return 81 | } 82 | } 83 | 84 | seq += 1 85 | sereq, err := message.NewSessionEstablishmentRequest( 86 | 1, // MP(Message Priority) flag 87 | 0, // FO(Follow On) flag 88 | 0, // SEID(Session Endpoint Identifier) 89 | seq, // Sequence Number 90 | 0, // Message Priority 91 | ie.NewNodeID(*nodeid, "", ""), 92 | ie.NewFSEID(1, net.ParseIP(*nodeid), nil), 93 | ie.NewCreatePDR( 94 | ie.NewPDRID(1), 95 | ie.NewPrecedence(255), 96 | ie.NewPDI( 97 | ie.NewSourceInterface(ie.SrcInterfaceAccess), 98 | ie.NewFTEID(1 /* flags */, 1 /* teid */, net.ParseIP("172.16.1.1"), nil, 0 /* chid */), 99 | ie.NewNetworkInstance(""), 100 | ie.NewUEIPAddress(2, "60.60.0.6", "", 0, 0), 101 | ), 102 | ie.NewOuterHeaderRemoval(0 /* desc */, 0 /* ext */), 103 | ie.NewFARID(1), 104 | ie.NewQERID(1), 105 | ), 106 | ie.NewCreatePDR( 107 | ie.NewPDRID(2), 108 | ie.NewPrecedence(255), 109 | ie.NewPDI( 110 | ie.NewSourceInterface(ie.SrcInterfaceCore), 111 | ie.NewNetworkInstance("internet"), 112 | ie.NewUEIPAddress(2, "60.60.0.6", "", 0, 0), 113 | ), 114 | ie.NewFARID(2), 115 | ie.NewQERID(1), 116 | ), 117 | ie.NewCreateFAR( 118 | ie.NewFARID(1), 119 | ie.NewApplyAction(2), // 2: FORW(Forward) 120 | ie.NewForwardingParameters( 121 | ie.NewDestinationInterface(ie.DstInterfaceSGiLANN6LAN), 122 | ie.NewNetworkInstance("internet"), 123 | ), 124 | ), 125 | ie.NewCreateFAR( 126 | ie.NewFARID(2), 127 | ie.NewApplyAction(2), // 2: FORW(Forward) 128 | ie.NewForwardingParameters( 129 | ie.NewDestinationInterface(ie.DstInterfaceAccess), 130 | ie.NewNetworkInstance("internet"), 131 | ie.NewOuterHeaderCreation( 132 | 256, // desc 133 | 2, // teid 134 | "172.16.1.3", // v4 135 | "", // v6 136 | 0, // port 137 | 0, // ctag 138 | 0, // stag 139 | ), 140 | ), 141 | ), 142 | ie.NewCreateQER( 143 | ie.NewQERID(1), 144 | ie.NewGateStatus(ie.GateStatusOpen, ie.GateStatusOpen), 145 | ie.NewMBR(2000000, 1000000), 146 | ie.NewQFI(1), 147 | ), 148 | ie.NewPDNType(ie.PDNTypeIPv4), 149 | ).Marshal() 150 | if err != nil { 151 | log.Fatal(err) 152 | } 153 | 154 | if _, err := conn.Write(sereq); err != nil { 155 | log.Fatal(err) 156 | } 157 | log.Printf("sent Session Establishment Request to: %s", raddr) 158 | 159 | if err := conn.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil { 160 | log.Fatal(err) 161 | } 162 | 163 | waiting = true 164 | for waiting { 165 | n, addr, err := conn.ReadFrom(buf) 166 | if err != nil { 167 | log.Fatal(err) 168 | } 169 | 170 | msg, err := message.Parse(buf[:n]) 171 | if err != nil { 172 | log.Printf("ignored undecodable message: %x, error: %s", buf[:n], err) 173 | continue 174 | } 175 | 176 | seres, ok := msg.(*message.SessionEstablishmentResponse) 177 | if !ok { 178 | log.Printf("got unexpected message: %s, from: %s", msg.MessageTypeName(), addr) 179 | continue 180 | } 181 | 182 | waiting = false 183 | if seres.Cause == nil { 184 | log.Printf("got non accepted response") 185 | return 186 | } 187 | if cause, err := seres.Cause.Cause(); cause != ie.CauseRequestAccepted || err != nil { 188 | log.Printf("got non accepted response") 189 | return 190 | } 191 | } 192 | } 193 | --------------------------------------------------------------------------------