diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..a92596f5 --- /dev/null +++ b/.clang-format @@ -0,0 +1,245 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: false +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAdjacentStringLiterals: true +BreakAfterAttributes: Leave +BreakAfterJavaFieldAnnotations: false +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +KeepEmptyLinesAtEOF: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakScopeResolution: 500 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +PPIndentWidth: -1 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SkipMacroDefinitionBody: false +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterPlacementOperator: true + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +... diff --git a/.cursor/rules/markdown-writing.mdc b/.cursor/rules/markdown-writing.mdc new file mode 100644 index 00000000..52e62875 --- /dev/null +++ b/.cursor/rules/markdown-writing.mdc @@ -0,0 +1,75 @@ +--- +description: Rules for writing Markdown documents, including code blocks, sequence diagrams, and language-specific conventions. +# Cursor +globs: "**/*.md" +alwaysApply: false +# Claude Code +paths: + - "**/*.md" +--- + +# Markdown Document Writing Guidelines + +## Common Writing Rules + +- Do not use dividers (`---`, `***`, `___`) +- Always specify a language identifier on code blocks. Use `txt` if no programming language applies. + + Bad: + + ````txt + ``` + some golang code + ``` + ```` + + Good: + + ````txt + ```go + some golang code + ``` + ```` + +- When writing `code` spans or English words inline in Japanese text, add a single half-width space before and after them +- Do not use bold emphasis (`**...**` or `__...__`) in Markdown prose; rely on headings, lists, and `code` spans for structure and emphasis + +## Sequence Diagram Writing Rules + +- Use language-agnostic `mermaid` for sequence diagrams +- Write all text in English, except API-specific Japanese proper nouns +- Use the official full name for participant labels (e.g. `Biprogy API`, not `BipAPI`) + +### `alt` / `else` blocks + +- Use `alt/else` for mutually exclusive branches, not `opt` +- Always put the error case first, success case in `else` + +### Self-arrows + +- Use a self-arrow (`Server->>Server`) for internal processing such as token expiry validation + +### HTTP communication + +- Prefix outgoing HTTP requests with `Request:
` followed by the method and path +- Response arrows must use dashed lines (`-->>`) — prefix with `Response:
` followed by the response body + + ```mermaid + Client->>+Server: Request:
POST /oauth/v2/token/{api_key} + Server-->>-Client: Response:
{ access_token, expires_in, refresh_token } + ``` + +### DB operations + +- Prefix DB reads with `Read:
` and DB writes with `Write:
` +- Response arrows must use dashed lines (`-->>`) — write the result directly with no prefix + + ```mermaid + Server->>+DB: Read:
user_id=123 + DB-->>-Server: user={id: 123, name: "John Doe", encrypted_email: "john.doe@example.com"} or nil + ``` + + ```mermaid + Server->>+DB: Write:
user_id=123
name="John Doe"
encrypted_email="xxx" + DB-->>-Server: OK + ``` diff --git a/.cursor/rules/respond-japanese.mdc b/.cursor/rules/respond-japanese.mdc new file mode 100644 index 00000000..efb1ad3c --- /dev/null +++ b/.cursor/rules/respond-japanese.mdc @@ -0,0 +1,16 @@ +--- +description: +alwaysApply: true +--- + +# Respond in Japanese in chat + +- Respond in Japanese in chat +- If the user asks for a code block, respond in English +- If the user asks for a command, respond in English +- If the user asks for a path, respond in English +- If the user asks for an identifier, respond in English +- If the user asks for a description, respond in English +- If the user asks for a summary, respond in English +- If the user asks for an explanation, respond in English +- If the user asks for a tutorial, respond in English diff --git a/.cursor/rules/review-doc.mdc b/.cursor/rules/review-doc.mdc new file mode 100644 index 00000000..ab6262fe --- /dev/null +++ b/.cursor/rules/review-doc.mdc @@ -0,0 +1,73 @@ +--- +description: Rules for reviewing Markdown documents for readability, consistency, and accuracy. +# Cursor +globs: "**/*.md" +alwaysApply: false +# Claude Code +paths: + - "**/*.md" +--- + +# Document Review Guidelines + +When reviewing a document, check each of the following areas and report issues grouped by severity: **critical** (blocks publication), **moderate** (degrades quality), **minor** (polish). + +## Readability + +### Sentence length and complexity + +- Flag sentences that pack multiple distinct points into a single breath; suggest splitting them. +- In Japanese prose, a sentence exceeding roughly 100 characters without a natural pause is a candidate for review. +- Flag deeply nested parenthetical clauses that make the main point hard to follow. + +### First-time terminology + +- Flag any technical or domain-specific term that appears for the first time without an inline explanation or a pointer to where it is defined. +- Do not flag terms that are clearly within the stated target audience's prior knowledge. +- Check that an abbreviation is expanded on its first use (e.g., "テストランナー(以下、ランナー)"). + +### Redundancy + +- Flag a sentence that restates information already given in the immediately preceding sentence without adding new context. +- Flag a section introduction that does nothing but repeat the heading. +- Flag a summary or conclusion that lists exactly what was already covered without adding synthesis or a takeaway. + +## Consistency + +### Terminology + +- Flag a concept that is referred to by more than one name within the document (e.g., "実行単位" vs "テストの実行単位"). +- Flag a term whose meaning or scope appears to shift between sections. + +### Writing style + +- Flag mixing of formal (です・ます) and plain (だ・である) sentence-ending forms unless the mix is clearly intentional (e.g., inside quoted code comments vs. prose). +- Flag heading styles that are inconsistent (e.g., some headings end with a noun, others with a verb form). + +### Document structure + +- Flag a table of contents entry that does not exactly match the corresponding section heading. +- Flag heading levels that skip a rank (e.g., `##` directly followed by `####`). +- Flag internal cross-references (links or section names in prose) that point to a heading that does not exist or has been renamed. + +### Code and prose alignment + +- Flag a prose description that contradicts the adjacent code block (e.g., wrong argument order, wrong return type). +- Flag identifiers in prose (variable names, function names, type names) that do not match those in the code blocks. + +## Accuracy + +### Technical correctness + +- Flag factual claims that appear incorrect or are inconsistent with what the code or referenced sources show (e.g., wrong API signatures, mischaracterized behavior). +- Flag code blocks that contain syntax errors or would produce output different from what the surrounding text claims. + +### Version and environment information + +- Flag version numbers, OS names, or tool versions that are inconsistent within the document. +- Flag environment details in the prose that do not match the environment section (if one exists). + +### References and links + +- Flag broken anchor links or links to renamed headings. +- Flag a footnote or reference label that is defined but never cited, or cited but never defined. diff --git a/.gitignore b/.gitignore index c6127b38..2868f72a 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,9 @@ modules.order Module.symvers Mkfile.old dkms.conf + +# Vagrant +.vagrant/ + +# Log files (if you are creating logs in debug mode, uncomment this) +# *.log diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..328d77a8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + + // LSP + "clangd.path": "/usr/bin/clangd", + // for compile_commands.json + // "clangd.arguments": [ + // "--compile-commands-dir=${workspaceFolder}" + // ], + + // Formatter + "clang-format.executable": "/usr/bin/clang-format", + + // Editor settings for C/C++ + "[c]": { + "editor.defaultFormatter": "xaver.clang-format", + "editor.tabSize": 4, + "editor.indentSize": "tabSize", + } +} diff --git a/Makefile b/Makefile index f5f40c10..4116d4aa 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,25 @@ -APPS = +SRC_DIR := src +TEST_DIR := test -DRIVERS = +APPS = -OBJS = util.o \ +DRIVERS = $(SRC_DIR)/driver/dummy.o -TESTS = test/step0.exe \ +OBJS = $(SRC_DIR)/util.o \ + $(SRC_DIR)/net.o \ -CFLAGS := $(CFLAGS) -g -W -Wall -Wno-unused-parameter -iquote . +TESTS = $(TEST_DIR)/step0.exe \ + $(TEST_DIR)/step1.exe \ + $(TEST_DIR)/step2.exe \ + +CC := gcc +CFLAGS := $(CFLAGS) -g -W -Wall -Wno-unused-parameter -iquote $(SRC_DIR) ifeq ($(shell uname),Linux) # Linux specific settings - BASE = platform/linux + BASE = $(SRC_DIR)/platform/linux CFLAGS := $(CFLAGS) -pthread -iquote $(BASE) + OBJS := $(OBJS) $(BASE)/intr.o endif ifeq ($(shell uname),Darwin) @@ -21,14 +29,14 @@ endif .SUFFIXES: .SUFFIXES: .c .o -.PHONY: all clean +.PHONY: all clean format all: $(APPS) $(TESTS) $(APPS): %.exe : %.o $(OBJS) $(DRIVERS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) -$(TESTS): %.exe : %.o $(OBJS) $(DRIVERS) test/test.h +$(TESTS): %.exe : %.o $(OBJS) $(DRIVERS) $(TEST_DIR)/test.h $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) .c.o: @@ -36,3 +44,6 @@ $(TESTS): %.exe : %.o $(OBJS) $(DRIVERS) test/test.h clean: rm -rf $(APPS) $(APPS:.exe=.o) $(OBJS) $(DRIVERS) $(TESTS) $(TESTS:.exe=.o) + +format: + find . -name "*.c" -o -name "*.h" | xargs clang-format -i --sort-includes diff --git a/README.md b/README.md new file mode 100644 index 00000000..6cc776be --- /dev/null +++ b/README.md @@ -0,0 +1,151 @@ +# microps + +[![MIT license badge][mit-badge]][mit-url] + +[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg +[mit-url]: https://github.com/nukopy/microps/blob/main/LICENSE + +microps is an implementation of a small TCP/IP protocol stack written in C for learning. + +This project is based on the development guide, [KLab Expert Camp 6](https://drive.google.com/drive/folders/1k2vymbC3vUk5CTJbay4LLEdZ9HemIpZe) (only in Japanese). + +Original implementation is [pandax381/microps](https://github.com/pandax381/microps). + +## Features + +TODO + +ref: https://github.com/pandax381/microps#features + +## Environment + +Host + +- OS: macOS 14.7 +- UTM 4.6.5 +- Ruby 3.3.0 +- Vagrant 2.4.9 +- vagrant_utm 0.1.5 (Version Constraint: > 0) +- [vagrant-bindfs](https://github.com/gael-ian/vagrant-bindfs) 1.3.1 + +VM for development + +- OS: Ubuntu 24.04 + - Box definition in HashiCorp: [utm/ubuntu-24.04](https://portal.cloud.hashicorp.com/vagrant/discover/utm/ubuntu-24.04) + - Official doc of UTM: [UTM Vagrant Box Gallery](https://naveenrajm7.github.io/vagrant_utm/boxes/utm_box_gallery.html) + +## Setup + +### Install UTM + +See [UTM homepage](https://mac.getutm.app/). + +For macOS users, this article is helpful (only in Japanese): [【macOS】UTM で Ubuntu Desktop 24.04 LTS の仮想マシン環境を構築する](https://zenn.dev/pyteyon/scraps/0c8cec3c56812b) + +### Install Vagrant + +```sh +brew install vagrant + +# install vagrant plugins for UTM +vagrant plugin install vagrant_utm --plugin-version 0.1.5 +vagrant plugin install vagrant-bindfs --plugin-version 1.3.1 +``` + +### Setup VM + +Commands for managing VM: + +```sh +cd microps + +# start VM +vagrant up + +# start VM with provision (rerun bootstrap script) +vagrant up --provision + +# reload VM +vagrant reload + +# realod VM with provision +vagrant reload --provision + +# status VM +vagrant status + +# ssh to VM +vagrant ssh + +# get SSH config (e.g. for VSCode Remote-SSH) +vagrant ssh-config + +# stop VM +vagrant halt +``` + +Before starting development, you should set Git config in VM like below: + +```sh +git config --global user.name "nukopy" +git config --global user.email "nukopy@gmail.com" +``` + +## Development + +Working directory in VM is `/home/me/workspace/microps`, so following commands should be run in this directory. + +### Build + +```sh +cd /home/me/workspace/microps + +# build +make + +# clean +make clean +``` + +### Test + +```sh +# build +make + +# run tests: step0.exe, step1.exe, ... +./test/step0.exe +``` + +Build with hexdump flag: + +```sh +make clean +CFLAGS=-DHEXDUMP make + +# run tests: step0.exe, step1.exe, ... +./test/step0.exe +``` + +### Format + +```sh +make format +``` + +## References + +- [github.com/pandax381/microps](https://github.com/pandax381/microps) + - Implementation of TCP/IP protocol stack in C. This is a reference implementation of this project. +- [KLab Expert Camp 6](https://drive.google.com/drive/folders/1k2vymbC3vUk5CTJbay4LLEdZ9HemIpZe) + - A series of lectures on TCP/IP protocol stack, [microps](https://github.com/pandax381/microps). This project is based on the contents of this lecture. +- [Available Vagrant boxes for UTM](https://portal.cloud.hashicorp.com/vagrant/discover?query=utm) + - Vagrant plugin for UTM. + +## License + +Copyright (c) 2012-2021 YAMAMOTO Masaya + +microps is licensed under the MIT License. For more details, check out the [LICENSE](./LICENSE) file. + +# microps diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 00000000..3b7b6da7 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,61 @@ +$vm_name = "toy-tcpip" +$vm_cpus = 4 +$vm_memory = 4096 +$vm_notes = "VM for development of toy-tcpip" +$vm_directory_share_mode = "virtFS" +$vm_forwarded_port = 80 +$vm_host_port = 8080 + +# for synced folder +$base_dir = "/home/me" +$base_work_dir = "#{$base_dir}/workspace" +$work_dir = "#{$base_work_dir}/microps" +$sync_dir = "#{$base_work_dir}/_mnt_microps" + +# ref: https://naveenrajm7.github.io/vagrant_utm/configuration.html +Vagrant.configure("2") do |config| + # ref: https://portal.cloud.hashicorp.com/vagrant/discover/utm/ubuntu-24.04 + config.vm.box = "utm/ubuntu-24.04" + config.vm.box_version = "0.0.1" + + # Hostname inside the VM + config.vm.hostname = $vm_name + + # Ports to forward + # localhost:80 access is forwarded to :8080 + config.vm.network "forwarded_port", guest: $vm_forwarded_port, host: $vm_host_port + + # Synced folder + config.vm.synced_folder ".", $sync_dir, create: true + + # 501:20 → 1000:1000 に見せる + config.bindfs.bind_folder $sync_dir, $work_dir, + map: "501/1000:@20/@1000", perms: "u=rwX:g=rwX:o=rX", create_as_user: true + + # Provider specific configs + config.vm.provider "utm" do |u| + # Name in UTM UI + u.name = $vm_name + + # Machine type + # not supported on vagrant_utm plugin + # u.machine = "vf" + + # CPU in cores + u.cpus = $vm_cpus + + # Memory in MB + u.memory = $vm_memory + + # Notes for UTM VM (Appears in UTM UI) + u.notes = $vm_notes + + # QEMU Directoy Share mode for the VM. + # Takes none, webDAV or virtFS + u.directory_share_mode = $vm_directory_share_mode + end + + # Provisioner config, supports all built provisioners + # ref: https://developer.hashicorp.com/vagrant/docs/provisioning/shell + config.vm.provision "shell", privileged: false, path: "scripts/bootstrap.sh" +end diff --git a/compile_flags.txt b/compile_flags.txt index ec1c3067..d2f81450 100644 --- a/compile_flags.txt +++ b/compile_flags.txt @@ -1,2 +1,4 @@ --I. --Iplatform/linux +-iquote +src +-iquote +src/platform/linux diff --git a/docs/microps-architecture.excalidraw b/docs/microps-architecture.excalidraw new file mode 100644 index 00000000..401338e7 --- /dev/null +++ b/docs/microps-architecture.excalidraw @@ -0,0 +1,14 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff", + "lockedMultiSelections": {} + }, + "files": {} +} \ No newline at end of file diff --git a/docs/notes/integration-test-e2e-boundary.md b/docs/notes/integration-test-e2e-boundary.md new file mode 100644 index 00000000..e18d9418 --- /dev/null +++ b/docs/notes/integration-test-e2e-boundary.md @@ -0,0 +1,155 @@ +# integration test と E2E test の境界 + +境界は、「どこまでつないで、何を保証したいか」で考えると分かりやすいです。 + +かなり雑に言うと、次のとおりです。 + +- 結合テスト( `integration test` ): 複数コンポーネントをつないで、その連携が正しいかを確かめる +- E2E テスト( `end-to-end test` 、以下 E2E): ユーザーや外部利用者の入口から出口まで、ひとつの利用フロー全体が成立するかを確かめる + +## まず integration + +integration は、モジュール単体ではなく、境界をまたいだ接続部分を見るテストです。 + +たとえば Web アプリなら、次のような組み合わせです。 + +- API サーバ + DB +- API サーバ + Redis +- 認証 middleware + handler + DB +- HTTP handler + service + repository + +ここで見たいのは、次のような点です。 + +- データの受け渡しが正しいか +- インターフェースのつなぎ込みが正しいか +- トランザクションや永続化が正しいか +- 依存先と合わせたときに壊れないか + +つまり integration は、システム全体でなくてもよいです。一部だけ本物をつないで確認するテストでも integration です。 + +## 次に E2E + +E2E は、利用者の行動単位で見ることが多いです。 + +たとえば Web サービスなら、次のような流れです。 + +- ログインする +- 商品を検索する +- カートに入れる +- 購入する +- 注文完了画面が出る +- DB やメール送信まで含めて成立する + +という、最初から最後までの業務フローを確認します。 + +ここでは内部のモジュール境界より、次の点が重要です。 + +- ユーザーにとって機能が成立しているか +- 本番に近い構成で動くか +- 主要フローが壊れていないか + +## 境界はどこか + +実際の境界はきれいに線引きできるわけではありません。ただ、次の観点でかなり整理できます。 + +### 1. 見ている視点 + +- integration: 開発者視点で「この部品同士の接続が正しいか」 +- E2E: 利用者視点で「この機能フローが最後まで成功するか」 + +### 2. 範囲 + +- integration: 部分的でもよい +- E2E: 基本的に入口から出口まで + +### 3. 失敗したときの意味 + +- integration: どの境界の接続が壊れたかを知りたい +- E2E: ユーザー体験として成立しないことを検知したい + +### 4. 実行コスト + +- integration: 比較的軽い +- E2E: 重い、遅い、不安定になりやすい + +## 具体例 + +### 例1: Go の API サーバ + +`POST /users` でユーザー作成する場合です。 + +#### integration test + +- HTTP handler を起動 +- 実 DB につなぐ +- リクエストを送る +- DB に保存されているか確認 + +これは handler、service、repository、DB の連携を見るので integration です。UI もメール送信も本番全体も見ていません。 + +#### E2E test + +- ブラウザを立ち上げる +- サインアップ画面に入力 +- 送信 +- メール認証 +- ログイン +- マイページ表示確認 + +これはユーザーの流れ全体を見ているので E2E です。 + +### 例2: 自作 TCP/IP スタック + +#### integration test + +- Ethernet 層と IP 層を接続 +- IP パケットを渡す +- 正しく分解・転送されるか確認 + +あるいは次のような例も integration です。 + +- TCP 層 + timer + retransmission queue を本物でつなぐ +- ACK ロス時に再送されるか確認 + +複数コンポーネント連携を見ていますが、システム全体の利用フローではありません。 + +#### E2E test + +- Node A と Node B を立てる +- 自作スタックで 3-way handshake +- データ送信 +- 再送 +- 切断 +- 最後に期待した payload が相手に届く + +これはかなり E2E 的です。「TCP 通信という利用フロー全体」が成立しているかを見ています。 + +## よくある迷い + +### 「本物の DB を使ったら E2E?」 + +いいえ、必ずしも違います。本物の依存先を使うテストは integration であることは多いですが、E2E とは限りません。 + +### 「API を叩いたら E2E?」 + +これも違います。API を直接叩いて 1 endpoint の保存処理だけ見ているなら、integration のことが多いです。 + +### 「全部本物なら E2E?」 + +これも半分だけ正しいです。全部本物でも、見ている範囲が一部分なら integration です。入口から出口までのユーザーフローを見て初めて E2E と呼びやすいです。 + +## 実務での判断基準 + +迷ったら、次の質問で判定するとよいです。 + +「このテストは、部品同士の接続確認なのか、利用フロー全体の成立確認なのか?」 + +- 接続確認なら integration +- 利用フロー全体なら E2E + +## 一言でまとめると + +- integration は「部品と部品のつながりを見るテスト」 +- E2E は「ユーザーの目的達成まで全部通るかを見るテスト」 + +E2E は integration を含む、より広い範囲のテストだと思うと分かりやすいです。 diff --git a/platform/linux/platform.h b/platform/linux/platform.h deleted file mode 100644 index 0b8bce2c..00000000 --- a/platform/linux/platform.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef PLATFORM_H -#define PLATFORM_H - -#include -#include -#include - -/* - * Memory - */ - -static inline void * -memory_alloc(size_t size) -{ - return calloc(1, size); -} - -static inline void -memory_free(void *ptr) -{ - free(ptr); -} - -/* - * Mutex - */ - -typedef pthread_mutex_t mutex_t; - -#define MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER - -static inline int -mutex_init(mutex_t *mutex) -{ - return pthread_mutex_init(mutex, NULL); -} - -static inline int -mutex_lock(mutex_t *mutex) -{ - return pthread_mutex_lock(mutex); -} - -static inline int -mutex_unlock(mutex_t *mutex) -{ - return pthread_mutex_unlock(mutex); -} - -#endif diff --git a/scripts/bootstrap.rust.sh b/scripts/bootstrap.rust.sh new file mode 100644 index 00000000..30539b19 --- /dev/null +++ b/scripts/bootstrap.rust.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -exu pipefail + +export DEBIAN_FRONTEND=noninteractive + +echo "Bootstrapping VM for toy-tcpip..." +echo "Run script as non-root user: whoami=$(whoami)" + +# ------------------------------------------------------------ +# install general packages +# ------------------------------------------------------------ + +sudo apt-get update -y && sudo apt-get install -y git gcc + +# ------------------------------------------------------------ +# install Rust +# ------------------------------------------------------------ + +# ref: https://github.com/rust-lang-deprecated/rustup.sh/issues/83 +curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +# set cargo path +source $HOME/.cargo/env +echo "cargo version: $(cargo --version)" + +# add to bashrc to add Rust toolchains to PATH +echo "source $HOME/.cargo/env" >> $HOME/.bashrc + +set +exu pipefail + +echo "Bootstrap script completed successfully!" diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh new file mode 100644 index 00000000..5e327a44 --- /dev/null +++ b/scripts/bootstrap.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -exu + +export DEBIAN_FRONTEND=noninteractive + +echo "Bootstrapping VM for toy-tcpip..." +echo "Run script as non-root user: whoami=$(whoami)" + +# install general packages +echo "Installing general packages..." +sudo apt-get update -y && sudo apt-get install -y \ + git \ + make \ + manpages-dev \ + gcc \ + clangd \ + clang-format + +set +exu + +echo "Bootstrap script completed successfully!" diff --git a/src/driver/dummy.c b/src/driver/dummy.c new file mode 100644 index 00000000..bd9944f0 --- /dev/null +++ b/src/driver/dummy.c @@ -0,0 +1,93 @@ +#include +#include +#include + +#include "platform.h" + +#include "net.h" +#include "util.h" + +#define DUMMY_MTU UINT16_MAX /* maximum size of IP datagram (0 ~ 65535) */ + +// ダミーデバイスが使う IRQ 番号 +// microps の IRQ 番号は 35 始まり +/* platform/linux/platform.h +#define INTR_IRQ_BASE (SIGRTMIN+1) +*/ +#define DUMMY_IRQ INTR_IRQ_BASE + +static int dummy_transmit(struct net_device *dev, uint16_t type, + const uint8_t *data, size_t len, const void *dst) { + debugf("dummy transmit to device: dev=%s, type=0x%04x, len=%zu", dev->name, + type, len); + debugdump(data, len); + + // データを破棄 + // ダミーデバイスなのでデータに対しては何もしない + + // テスト用に割り込みを発生させる + intr_raise_irq(DUMMY_IRQ); + + return 0; +} + +// デバイスドライバに実装されている関数へのポインタを格納する構造体 +// ダミーデバイス用に transmit のみ登録 +static struct net_device_ops dummy_ops = { + .transmit = dummy_transmit, +}; + +static int dummy_isr(unsigned int irq, void *id) { + // 現段階ではダミーデバイス用の割り込みハンドラが呼び出されたことがわかれば良いのでデバッグ出力のみ + // FIXME: 本来は net_device 以外にも対応するため、キャストは行わない。 + // あくまで現段階ではデバッグ用 + struct net_device *dev = (struct net_device *)id; // 無理やりキャスト + debugf("ISR called: irq=%u, dev=%s", irq, dev->name); + + return 0; +} + +/* ダミーデバイスの初期化 + +## ダミーデバイスの仕様 + +- 入力:なし(データを受信することはない) +- 出力:データを破棄 +*/ +struct net_device *dummy_init(void) { + struct net_device *dev; + + // ダミーデバイスを生成 + dev = net_device_alloc(); + if (!dev) { + errorf("net_device_alloc() failure on initializing dummy device"); + return NULL; + } + + // ダミーデバイスの設定 + // note: dev->name は net_device_register + // 内で更新されるのでここでは設定しない + dev->type = NET_DEVICE_TYPE_DUMMY; + dev->mtu = DUMMY_MTU; + // ヘッダもアドレスも存在しない + dev->hlen = 0; + dev->alen = 0; + // デバイスドライバに実装されている関数へのポインタを格納する構造体 + dev->ops = &dummy_ops; + + // デバイスを登録 + if (net_device_register(dev) == -1) { + debugf("net_device_register() failure on initializaing dummy device: " + "dev=%s, type=0x%04x", + dev->name, dev->type); + return NULL; + } + debugf("dummy device initialized: dev=%s", dev->name); + + // デバイスの割り込みハンドラとして dummy_isr を登録する + infof("register interrupt handler for dummy device"); + intr_register_irq_entry(DUMMY_IRQ, dummy_isr, INTR_IRQ_SHARED, dev->name, + dev); + + return dev; +} diff --git a/src/driver/dummy.h b/src/driver/dummy.h new file mode 100644 index 00000000..6a4fe9ec --- /dev/null +++ b/src/driver/dummy.h @@ -0,0 +1,8 @@ +#ifndef DUMMY_H +#define DUMMY_H + +#include "net.h" + +extern struct net_device *dummy_init(void); + +#endif diff --git a/src/net.c b/src/net.c new file mode 100644 index 00000000..3e4651fe --- /dev/null +++ b/src/net.c @@ -0,0 +1,260 @@ +#include +#include +#include + +#include "platform.h" + +#include "net.h" +#include "util.h" + +/* NOTE: if you want to add/delete the entries after net_run(), you need to + * protect these lists with a mutex. */ +// デバイスリスト(リストの先頭を指すポインタ) +static struct net_device *devices; + +struct net_device *net_device_alloc(void) { + // pointer to new net_device + struct net_device *dev; + + // デバイス構造体のサイズのメモリを確保 + // - memory_alloc() で確保したメモリ領域は 0 で初期化されている + // - メモリが確保できなかったらエラーとして NULL を返す + // - メモリの確保と解放には memory_alloc() と memory_free() を使う + dev = memory_alloc(sizeof(*dev)); + if (!dev) { + errorf("memory_alloc() failure"); + return NULL; + } + + return dev; +} + +/* NOTE: must not be call after net_run() */ +// デバイスの登録 +int net_device_register(struct net_device *dev) { + // (関数の初回実行時だけ動く)デバイスのインデックス番号の初期化 + // note: 関数内で static + // な変数を一度だけ初期化し、関数呼出し間で保持できる。 note: + // 関数に副作用を入れることになる。 note: + // マルチスレッド環境でのデータ競合に注意。 + static unsigned int index = 0; + + // デバイスのインデックス番号の設定 + // note: 後置インクリメントなので現在の値を代入してからインクリメントされる + dev->index = index++; // set, then increment + + // デバイス名を生成する(net0, net1, net2, ...) + snprintf(dev->name, sizeof(dev->name), "net%d", dev->index); + + // デバイスリストの先頭にデバイスを追加 + // ref: Day 1 - デバイスの生成と登録 + // https://docs.google.com/presentation/d/1ID6ggxASfc_1bWiJfDy1IFKIwzxvfYy8rUBrWYTRFj8/edit?slide=id.gd328c3072b_0_1179#slide=id.gd328c3072b_0_1179 + // 新規登録するデバイスの次のデバイスを、現在のデバイスリストの先頭のデバイスに設定する + dev->next = devices; + + // 現在のデバイスリストの先頭のデバイスを新規登録するデバイスに設定する + devices = dev; + infof("registered device: dev=%s, type=0x%04x", dev->name, dev->type); + + return 0; +} + +static int net_device_open(struct net_device *dev) { + // デバイスの状態を確認(既に UP 状態の場合はエラーを返す) + if (NET_DEVICE_IS_UP(dev)) { + errorf("device is already opened: dev=%s", dev->name); + return -1; + } + + // デバイスドライバのオープン関数を呼び出す + // - オープン関数が設定されていない場合は呼び出しをスキップ + // - エラーが返されたらこの関数もエラーを返す + if (dev->ops->open) { + if (dev->ops->open(dev) == -1) { + errorf("failed to open device: dev=%s", dev->name); + return -1; + } + } + + // デバイスのオープンに成功したら UP フラグを立てる + dev->flags |= NET_DEVICE_FLAG_UP; + infof("opened device status: dev=%s, state=%s", dev->name, + NET_DEVICE_STATE(dev)); + + return 0; +} + +static int net_device_close(struct net_device *dev) { + // デバイスの状態を確認 + // UP 状態でないデバイスを close しようとしたときはエラーを返す + if (!NET_DEVICE_IS_UP(dev)) { + errorf("device is not opened: dev=%s", dev->name); + } + + // デバイスドライバのクローズ関数を呼び出す + // - クローズ関数が設定されていない場合は呼び出しをスキップ + // - エラーが返されたらこの関数もエラーを返す + if (dev->ops->close) { + if (dev->ops->close(dev) == -1) { + errorf("failed to close device: dev=%s", dev->name); + return -1; + } + } + + // UP フラグを落とす + dev->flags &= ~NET_DEVICE_FLAG_UP; + infof("closed device status: dev=%s, state=%s", dev->name, + NET_DEVICE_STATE(dev)); + + return 0; +} + +// デバイスへの出力 +int net_device_output(struct net_device *dev, uint16_t type, + const uint8_t *data, size_t len, const void *dst) { + // デバイスの状態を確認 + // UP 状態でないデバイスに出力する場合はエラーを返す + if (!NET_DEVICE_IS_UP(dev)) { + errorf("device is not opened on transmit: dev=%s", dev->name); + return -1; + } + + // データのサイズを確認 + // デバイスの MTU を超えるサイズのデータは送信できないのでエラーを返す + if (len > dev->mtu) { + errorf("data is too long on transmit: dev=%s, mtu=%u, len=%zu", + dev->name, dev->mtu, len); + return -1; + } + /* note: フォーマット指定子 %04x について + + - `%04x`: 値を 16 進数で出力する。ゼロ 4 桁で padding。 + - 幅を 4 桁に指定し、ゼロで埋める (`%0` + `4` + `x`) + - 16 進数で足りない桁は先頭を 0 で埋めて 4 桁で出力する + */ + debugf("output to device: dev=%s, type=0x%04x, len=%zu", dev->name, type, + len); + debugdump(data, len); + + // デバイスドライバの出力関数を呼び出す + // エラーが返されたらこの関数もエラーを返す + if (dev->ops->transmit(dev, type, data, len, dst) == -1) { + errorf("device transmit failure on output: dev=%s, len=%zu", dev->name, + len); + return -1; + } + + return 0; +} + +/* デバイスからの入力: デバイスが受信したパケットをプロトコルスタックに渡す関数 + +・プロトコルスタックへのデータの入口であり、デバイスドライバから呼び出されることを想定している +・複数のデバイスドライバからの入力を一手に受け取る + + デバイスドライからこの関数が呼び出されるイメージ: + + +----------------------+ + | net_input_handler() | + +-----------^----------+ + | + +------------+------------+ + | | + +--------------+ +--------------+ + | Device Driver| | Device Driver| + +------^-------+ +------^-------+ + | | + Device Device +*/ +int net_input_handler(uint16_t type, const uint8_t *data, size_t len, + struct net_device *dev) { + // TODO: 今の段階では呼び出されたことがわかればよいのでデバッグ出力のみ + debugf("received data from device: dev=%s, type=0x%04x, len=%zu", dev->name, + type, len); + debugdump(data, len); + + return 0; +} + +/* プロトコルスタックの起動 + +## 呼び出しのコード例: net_run / net_shutdown + +```c +int main(void) { + if (net_init() == -1) { + return -1; + } + + // デバイスの登録処理 + // ... + + // プロトコルスタックの起動 + if (net_run() == -1) { + return -1; + } + + // アプリケーションの処理 + // ... + + net_shutdown(); + + return 0; +} +``` +*/ +int net_run(void) { + struct net_device *dev; + + // 割り込み機構の起動 + debugf("start interrupt processing..."); + if (intr_run() == -1) { + errorf("intr_run() failure"); + return -1; + } + + // 登録済みの全デバイスをオープン + debugf("open all devices..."); + for (dev = devices; dev; dev = dev->next) { + if (net_device_open(dev) == -1) { + errorf("failed to open device on net_run: dev=%s"); + // 失敗しても for 文を抜けはしない + } + } + debugf("running..."); + + return 0; +} + +// プロトコルスタックの停止 +void net_shutdown(void) { + struct net_device *dev; + + // 登録済みの全デバイスをクローズ + debugf("close all devices..."); + for (dev = devices; dev; dev = dev->next) { + if (net_device_close(dev) == -1) { + errorf("failed to close device on shutting down: dev=%s"); + // 失敗しても for 文を抜けはしない + } + } + + // 割り込み機構の終了 + debugf("shutdown interrupt processing..."); + intr_shutdown(); + + debugf("shutting down"); +} + +int net_init(void) { + // 割り込み機構の初期化 + debugf("initialize interrupt processing..."); + if (intr_init() == -1) { + errorf("intr_init() failure"); + return -1; + } + + infof("initialized protocol stack"); + + return 0; +} diff --git a/src/net.h b/src/net.h new file mode 100644 index 00000000..3a0009ce --- /dev/null +++ b/src/net.h @@ -0,0 +1,92 @@ +#ifndef NET_H +#define NET_H + +#include +#include + +#ifndef IFNAMSIZ +#define IFNAMSIZ 16 +#endif + +#define NET_DEVICE_TYPE_DUMMY 0x0000 +#define NET_DEVICE_TYPE_LOOPBACK 0x0001 +#define NET_DEVICE_TYPE_ETHERNET 0x0002 + +// NET_DEVICE_FLAG_UP: 0000_0000_0000_0001 = 1 +#define NET_DEVICE_FLAG_UP 0x0001 +// NET_DEVICE_FLAG_LOOPBACK: 0000_0000_0001_0000 = 16 +#define NET_DEVICE_FLAG_LOOPBACK 0x0010 +// NET_DEVICE_FLAG_BROADCAST: 0000_0000_0010_0000 = 32 +#define NET_DEVICE_FLAG_BROADCAST 0x0020 +// NET_DEVICE_FLAG_P2P: 0000_0000_0100_0000 = 64 +#define NET_DEVICE_FLAG_P2P 0x0040 +// NET_DEVICE_FLAG_NEED_ARP: 0000_0001_0000_0000 = 256 (= 16^2 * 1) +#define NET_DEVICE_FLAG_NEED_ARP 0x0100 + +#define NET_DEVICE_ADDR_LEN 16 + +// UP flag が立っていればビット演算の論理積 AND の値が非 0 になり、 +// truthy な値となる +#define NET_DEVICE_IS_UP(x) ((x)->flags & NET_DEVICE_FLAG_UP) +#define NET_DEVICE_STATE(x) (NET_DEVICE_IS_UP(x) ? "up" : "down") + +// ネットワークデバイスの定義 +// ref: +// https://docs.google.com/presentation/d/1ID6ggxASfc_1bWiJfDy1IFKIwzxvfYy8rUBrWYTRFj8/edit?slide=id.gd328c3072b_0_1161#slide=id.gd328c3072b_0_1161 +struct net_device { + // 次のデバイスへのポインタ + struct net_device *next; + unsigned int index; + char name[IFNAMSIZ]; + + // デバイスの種別(net.h に NET_DEVICE_TYPE_XXX として定義) + uint16_t type; + + // ----- デバイスの種別によって変化する値 --------------- + uint16_t mtu; // デバイスの MTU (Maximum Transmission Unit) の値 + uint16_t flags; + uint16_t hlen; /* header length */ + uint16_t alen; /* address length */ + // ------------------------------------------------ + + // デバイスのハードウェアアドレス + // - デバイスによってアドレスサイズが異なるので大きめのバッファを用意 + // - アドレスを持たないデバイスでは値は設定されない + uint8_t addr[NET_DEVICE_ADDR_LEN]; + union { + uint8_t peer[NET_DEVICE_ADDR_LEN]; + uint8_t broadcast[NET_DEVICE_ADDR_LEN]; + }; + + // デバイスドライバに実装されている関数を + // 格納している構造体 struct net_device_ops へのポインタ + struct net_device_ops *ops; + + // デバイスドライバが使用するプライベートなデータへのポインタ + void *priv; +}; + +// デバイスドライバに実装されている関数へのポインタを格納 +struct net_device_ops { + // optional + int (*open)(struct net_device *dev); + // optional + int (*close)(struct net_device *dev); + // required: 送信関数 transmit は必須 + int (*transmit)(struct net_device *dev, uint16_t type, const uint8_t *data, + size_t len, const void *dst); +}; + +extern struct net_device *net_device_alloc(void); +extern int net_device_register(struct net_device *dev); +extern int net_device_output(struct net_device *dev, uint16_t type, + const uint8_t *data, size_t len, const void *dst); + +extern int net_input_handler(uint16_t type, const uint8_t *data, size_t len, + struct net_device *dev); + +extern int net_run(void); +extern void net_shutdown(void); +extern int net_init(void); + +#endif diff --git a/src/platform/linux/intr.c b/src/platform/linux/intr.c new file mode 100644 index 00000000..fc62446b --- /dev/null +++ b/src/platform/linux/intr.c @@ -0,0 +1,334 @@ +#include +#include +#include +#include + +#include "platform.h" + +#include "util.h" + +// 割り込み要求(IRQ, Interrupt Request)の構造体 +// ネットワークデバイス(net_device 構造体)と同様に linked list 構造で管理する +struct irq_entry { + // 次の IRQ 構造体 + struct irq_entry *next; + // 割り込み番号(IRQ 番号) + unsigned int irq; + // 割り込みハンドラ(割り込みが発生した際に呼び出す関数へのポインタ) + int (*handler)(unsigned int irq, void *dev); + // フラグ(INTR_IRQ_SHARED が指定された場合は IRQ 番号を共有可能) + int flags; + // デバッグ出力で識別するための名前 + char name[16]; + /* 割り込みの発生元となるデバイス + + ---------- + + struct net_device 以外にも対応できるように void * で保持 + */ + void *dev; +}; + +/* NOTE: if you want to add/delete the entries after intr_run(), you need to + * protect these lists with a mutex. */ +// IRQ リスト(リストの先頭を指すポインタ) +static struct irq_entry *irqs; + +// シグナルマスク用のシグナル集合 +static sigset_t sigmask; + +// 割り込みスレッドのスレッド ID +static pthread_t tid; +// スレッド間の同期のためのバリア +static pthread_barrier_t barrier; +// バリアで同期するスレッドの数 +// (今回はメインスレッド、割り込み処理スレッドの 2 つ) +static const int NUM_THREADS_FOR_BARRIER = 2; + +/* 割り込み要求エントリ(IRQ エントリ)を IRQ リストに登録する + * + * ---------- + * + * ## microps における「割り込み」の実装 + * + * microps では、IRQ 番号にシグナル番号を使用する。 + * IRQ 番号は、ハードウェア割り込みにおいては、 + * 「どのハードウェアによって割り込み要求(IRQ)が発生したか」を識別するための数値。 + * microsはユーザ空間で動くので、カーネル空間で動くハードウェア割り込みの仕組みは使えない。 + * そこで処理の割り込みを行うためにシグナルを使ったソフトウェア割り込みを実装する。 + * ハードウェア割り込みを模倣した割り込みの仕組みをユーザ空間で実装する。 + * + * ## シグナルによりソフトウェア割り込みにマルチスレッドを使用する理由 + * + * シグナル受信時に非同期に実行されるシグナルハンドラでは実行できる処理が大きく制限される。 + * シグナル安全(signal-safe)、非同期安全な処理の範囲で処理を実装しなければならない。 + * + * ref: https://www.jpcert.or.jp/sc-rules/c-sig30-c.html + * + * そのため、割り込み処理のために専用のスレッドを起動してシグナルの発生を待ち受けて処理する。 + * 割り込みの仕組みをマルチスレッドで実装する理由はこれ。 + * + */ +int intr_register_irq_entry(unsigned int irq, + int (*handler)(unsigned int irq, void *dev), + int flags, const char *name, void *dev) { + struct irq_entry *entry; + + debugf("registering new IRQ: irq=%u, flags=%d, name=%s", irq, flags, name); + + // -------------------------------------------------- + // IRQ 番号が既に登録されている場合、 + // IRQ 番号の共有が許可されているかどうかチェック + // どちらかが共有を許可していない場合はエラーを返す + // -------------------------------------------------- + + // IRQ リストを走査 + for (entry = irqs; entry; entry = entry->next) { + // IRQ リストに新規 IRQ エントリの IRQ 番号が既に登録されている場合、 + // その IRQ 番号に対して追加のハンドラを登録できるかどうか確認する。 + if (entry->irq == irq) { + /* + XOR(^) はビット単位の排他的論理和(2 つの値が異なるとき 1 になる) + 真理値表 (A ^ B): + A B | A^B + 0 0 | 0 + 0 1 | 1 + 1 0 | 1 + 1 1 | 0 + */ + /* + ・既存の IRQ のフラグが `INTR_IRQ_SHARED` と完全に一致しているか + - flags と INTR_IRQ_SHARED が一致している場合は XOR + の結果は偽になる + - e.g. flags ^ INTR_IRQ_SHARED = 0001 ^ 0001 = 0000 -> 偽 + - flags と INTR_IRQ_SHARED が一致していない場合は XOR の結果は真 + - e.g. flags ^ INTR_IRQ_SHARED = 0000 ^ 0001 = 0001 -> 真 + ・新しく登録しようとしているフラグが`INTR_IRQ_SHARED`と完全に一致しているか + - + + をチェックしていて、どちらか一方でも値が違えば衝突とみなす。 + + 注意点として、XOR は「`INTR_IRQ_SHARED` + ビットが立っているかどうか」ではなく + 「値がまるごと同一かどうか」を判定している。 + フラグにほかのビットが含まれる場合は真になってしまうので、共有可否をビット単位で見る意図なら + `flags & INTR_IRQ_SHARED` のようにマスクした方が適切かもしれない。 + */ + + const int is_existing_irq_allows_share = + (entry->flags ^ INTR_IRQ_SHARED) == 0; + const int is_new_irq_allows_share = (flags ^ INTR_IRQ_SHARED) == 0; + const int is_irq_allows_share = + is_existing_irq_allows_share || is_new_irq_allows_share; + if (!is_irq_allows_share) { + errorf("conflicts with already registered IRQs"); + return -1; + } + } + } + + // -------------------------------------------------- + // IRQ リストへ新しいエントリを追加 + // -------------------------------------------------- + + // 新しい IRQ エントリのメモリを確保 + entry = memory_alloc(sizeof(*entry)); + if (!entry) { + errorf("memory_alloc() on registering IRQ entry failure"); + return -1; + } + + // IRQ エントリ構造体に値を設定 + entry->irq = irq; + entry->handler = handler; + entry->flags = flags; + strncpy(entry->name, name, sizeof(entry->name) - 1); + entry->dev = dev; + + // 新しい IRQ エントリを IRQ リストの先頭へ挿入 + entry->next = irqs; + irqs = entry; + + // シグナル集合へ新しいシグナルを追加 + // ここって既存のシグナルの被らないの? + // -> 被らない。既存のシグナルとの OR 演算なので。 + sigaddset(&sigmask, irq); + debugf("registered new IRQ entry: irq=%u, name=%s", entry->irq, + entry->name); + + return 0; +} + +int intr_raise_irq(unsigned int irq) { + // 割り込み処理用のスレッドへシグナルを送信 + // microps では、IRQ 番号にシグナル番号を使用する + // 後続の処理では、 + // 発生したシグナルのシグナル番号と IRQ エントリの IRQ 番号が一致したときに + // IRQ エントリに登録されているハンドラが実行される + return pthread_kill(tid, (int)irq); +} + +// 割り込み処理を実行するスレッドのエントリーポイント +static void *intr_thread(void *arg) { + int terminate = 0; + int sig; + int err; + + struct irq_entry *entry; + + infof("start thread for interrupt"); + + // メインスレッドと同期を取るための処理 + pthread_barrier_wait(&barrier); + debugf("barrier unlocked on intr_thread!"); + + // ハードウェア割り込みに見立てたシグナルを処理するループ + while (!terminate) { + // ハードウェア割り込みに見立てたシグナルが発生するまで待機 + err = sigwait(&sigmask, &sig); + if (err) { + errorf("sigwait() failure: %s", strerror(err)); + break; + } + + // 発生したシグナルの種類に応じて処理 + switch (sig) { + // SIGHUP シグナルの処理: + // 割り込み処理用のスレッドへ終了を通知するためのシグナル + case SIGHUP: + terminate = 1; + break; + // デバイス割り込み用のシグナルを処理 + default: + // IRQ リストを巡回 + for (entry = irqs; entry; entry = entry->next) { + // IRQ 番号が一致するエントリの割り込みハンドラを呼び出す + // microps では、IRQ 番号にシグナル番号を使用する + if (entry->irq == (unsigned int)sig) { + debugf("call ISR: irq=%d, name=%s", entry->irq, + entry->name); + entry->handler(entry->irq, entry->dev); + } + } + break; + } + } + warnf("terminated thread for interrupt"); + + return NULL; +} + +// 割り込み機構を起動する +// intr_init が呼ばれている状態でこの関数を呼ぶこと +int intr_run(void) { + int err; + + // シグナルマスクの設定 + // intr_init 内で sigmask の設定ができていることが前提 + err = pthread_sigmask(SIG_BLOCK, &sigmask, NULL); + if (err) { + errorf("pthread_sigmask"); + return -1; + } + + // 割り込み処理スレッドの起動 + err = pthread_create(&tid, NULL, intr_thread, NULL); + if (err) { + errorf("pthread_create() failed on intr_run: %s", strerror(err)); + return -1; + } + + // スレッドが動き出すまで待つ + // 他のスレッドが同じように pthread_barrier_wait() を呼び出し、 + // バリアのカウントが指定の数になるまでスレッドを停止する + infof("waiting for starting thread for interrupt with barrier..."); + pthread_barrier_wait(&barrier); + infof("thread for interrupt started!"); + + return 0; +} + +void intr_shutdown(void) { + int err; + + // 割り込み処理スレッドが起動済みかどうか確認 + /* Q. + なぜ以下のコードで割り込み処理スレッドが起動済みかどうか確認できるのか? + * 前提:関数の実行順序は intr_init -> intr_run -> intr_shutdown + * + * `pthread_equal(tid, pthread_self())`は + *「今の呼び出し元スレッド(メインスレッド)の + * ID」と、グローバルに保持している `tid` を比較している。 + * + * ここの比較は `intr_init` の段階で `tid` に「メインスレッド(=呼び出し元) + * の `pthread_t`」を代入しておく前提になっている。 + * `intr_run` で割り込み処理スレッドを起動するときに + * `pthread_create(&tid, …)` として `tid` を新しいスレッドの ID + で上書きする。 + * + * つまり、`intr_shutdown` をメインスレッドから呼ぶときに、 + * `tid` がまだメインスレッドと同じ ID のままなら + * 「新しいスレッドに置き換わっていない → スレッドを作れていない」= + *`pthread_equal` が真になる、という判定になる。 + * + * 逆に、スレッドが正常に起動済みなら `tid` は別の ID + に差し替わっているので、 + * `pthread_equal` は偽になり、 `pthread_kill` や `pthread_join` + * に進む、という流れとなっている。 + + * 注意点として、このロジックは + * + * - `intr_init` をメインスレッドで呼ぶこと + * - 初期値として `tid = pthread_self()` をセットしていること + * - `intr_shutdown` をメインスレッドで呼ぶこと + * + * の 3 つが前提なので、別スレッドから呼び出したり、 + * 初期化を忘れたりすると意図通りにならない点には気をつける必要がある。 + */ + if (pthread_equal(tid, pthread_self()) != 0) { + warnf("thread not created"); + return; + } + + // 割り込み処理スレッドにシグナル(SIGHUP)を送信 + err = pthread_kill(tid, SIGHUP); + if (err) { + warnf("failed to kill process with SIGHUP: tid=%d, err=%s", tid, + strerror(err)); + return; + } + + // 割り込み処理スレッドが完全に終了するのを待つ + err = pthread_join(tid, NULL); + if (err) { + warnf("failed to pthread_join: tid=%d, err=%s", tid, strerror(err)); + return; + } + + infof("shutdown interrupt successfully!"); + + return; +} + +int intr_init(void) { + infof("init interrupt"); + + // スレッド ID の初期値にメインスレッドの ID を設定する + tid = pthread_self(); + + // pthread_barrier の初期化(カウントを 2 に設定) + infof("init pthread barrier for %d threads", NUM_THREADS_FOR_BARRIER); + pthread_barrier_init(&barrier, NULL, NUM_THREADS_FOR_BARRIER); + debugf("barrier unlocked on intr_init!"); + + // グローバルな割り込み処理用のシグナル集合(シグナルマスク)を初期化(空にする) + infof("init sigmask"); + sigemptyset(&sigmask); + + // シグナル集合に SIGHUP を追加(割り込みスレッド終了通知用) + sigaddset(&sigmask, SIGHUP); + // 注意:この段階ではまだプロセス(メインスレッド)にシグナルマスクは設定されていない + // pthread_sigmask が実行されて初めてシグナルマスクがプロセスに適用される + + return 0; +} diff --git a/src/platform/linux/platform.h b/src/platform/linux/platform.h new file mode 100644 index 00000000..d2b7138c --- /dev/null +++ b/src/platform/linux/platform.h @@ -0,0 +1,69 @@ +#ifndef PLATFORM_H +#define PLATFORM_H + +#include +#include +#include +#include + +/* + * Memory + */ + +static inline void *memory_alloc(size_t size) { return calloc(1, size); } + +static inline void memory_free(void *ptr) { free(ptr); } + +/* + * Mutex + */ + +typedef pthread_mutex_t mutex_t; + +#define MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER + +static inline int mutex_init(mutex_t *mutex) { + return pthread_mutex_init(mutex, NULL); +} + +static inline int mutex_lock(mutex_t *mutex) { + return pthread_mutex_lock(mutex); +} + +static inline int mutex_unlock(mutex_t *mutex) { + return pthread_mutex_unlock(mutex); +} + +/* + * Interrupt + */ + +// IRQ 番号の開始番号 +// +/* + +microps では IRQ 番号としてシグナル番号を使用する。 + +Linux では、SIGRTMIN ~ SIGRTMAX (34 ~ 64)までのシグナルを +アプリケーションが任意の目的で利用できる。 + +ただし、SIGRTMIN (34) に関しては、glibc が内部的に利用しているため、 ++1 した番号から利用するようにしている。 + +そのため、microps の IRQ 番号は 35 始まり。 + +*/ +#define INTR_IRQ_BASE (SIGRTMIN + 1) + +#define INTR_IRQ_SHARED 0x0001 + +extern int intr_register_irq_entry(unsigned int irq, + int (*handler)(unsigned int irq, void *id), + int flags, const char *name, void *dev); +extern int intr_raise_irq(unsigned int irq); + +extern int intr_run(void); +extern void intr_shutdown(void); +extern int intr_init(void); + +#endif diff --git a/util.c b/src/util.c similarity index 68% rename from util.c rename to src/util.c index bd7697be..994d08b4 100644 --- a/util.c +++ b/src/util.c @@ -1,13 +1,13 @@ -#include +#include +#include +#include #include #include +#include #include #include -#include -#include -#include -#include #include +#include #include "platform.h" @@ -17,9 +17,8 @@ * Logging */ -int -lprintf(FILE *fp, int level, const char *file, int line, const char *func, const char *fmt, ...) -{ +int lprintf(FILE *fp, int level, const char *file, int line, const char *func, + const char *fmt, ...) { struct timeval tv; struct tm tm; char timestamp[32]; @@ -29,7 +28,8 @@ lprintf(FILE *fp, int level, const char *file, int line, const char *func, const flockfile(fp); gettimeofday(&tv, NULL); strftime(timestamp, sizeof(timestamp), "%T", localtime_r(&tv.tv_sec, &tm)); - n += fprintf(fp, "%s.%03d [%c] %s: ", timestamp, (int)(tv.tv_usec / 1000), level, func); + n += fprintf(fp, "%s.%03d [%c] %s: ", timestamp, (int)(tv.tv_usec / 1000), + level, func); va_start(ap, fmt); n += vfprintf(fp, fmt, ap); va_end(ap); @@ -38,28 +38,29 @@ lprintf(FILE *fp, int level, const char *file, int line, const char *func, const return n; } -void -hexdump(FILE *fp, const void *data, size_t size) -{ +void hexdump(FILE *fp, const void *data, size_t size) { unsigned char *src; int offset, index; flockfile(fp); src = (unsigned char *)data; - fprintf(fp, "+------+-------------------------------------------------+------------------+\n"); - for(offset = 0; offset < (int)size; offset += 16) { + fprintf(fp, + "+------+-------------------------------------------------+------" + "------------+\n"); + for (offset = 0; offset < (int)size; offset += 16) { fprintf(fp, "| %04x | ", offset); - for(index = 0; index < 16; index++) { - if(offset + index < (int)size) { + for (index = 0; index < 16; index++) { + if (offset + index < (int)size) { fprintf(fp, "%02x ", 0xff & src[offset + index]); } else { fprintf(fp, " "); } } fprintf(fp, "| "); - for(index = 0; index < 16; index++) { - if(offset + index < (int)size) { - if(isascii(src[offset + index]) && isprint(src[offset + index])) { + for (index = 0; index < 16; index++) { + if (offset + index < (int)size) { + if (isascii(src[offset + index]) && + isprint(src[offset + index])) { fprintf(fp, "%c", src[offset + index]); } else { fprintf(fp, "."); @@ -70,7 +71,9 @@ hexdump(FILE *fp, const void *data, size_t size) } fprintf(fp, " |\n"); } - fprintf(fp, "+------+-------------------------------------------------+------------------+\n"); + fprintf(fp, + "+------+-------------------------------------------------+------" + "------------+\n"); funlockfile(fp); } @@ -83,17 +86,13 @@ struct queue_entry { void *data; }; -void -queue_init(struct queue_head *queue) -{ +void queue_init(struct queue_head *queue) { queue->head = NULL; queue->tail = NULL; queue->num = 0; } -void * -queue_push(struct queue_head *queue, void *data) -{ +void *queue_push(struct queue_head *queue, void *data) { struct queue_entry *entry; if (!queue) { @@ -116,9 +115,7 @@ queue_push(struct queue_head *queue, void *data) return data; } -void * -queue_pop(struct queue_head *queue) -{ +void *queue_pop(struct queue_head *queue) { struct queue_entry *entry; void *data; @@ -136,18 +133,15 @@ queue_pop(struct queue_head *queue) return data; } -void * -queue_peek(struct queue_head *queue) -{ +void *queue_peek(struct queue_head *queue) { if (!queue || !queue->head) { return NULL; } return queue->head->data; } -void -queue_foreach(struct queue_head *queue, void (*func)(void *arg, void *data), void *arg) -{ +void queue_foreach(struct queue_head *queue, + void (*func)(void *arg, void *data), void *arg) { struct queue_entry *entry; if (!queue || !func) { @@ -171,55 +165,43 @@ queue_foreach(struct queue_head *queue, void (*func)(void *arg, void *data), voi static int endian; -static int -byteorder(void) { +static int byteorder(void) { uint32_t x = 0x00000001; return *(uint8_t *)&x ? __LITTLE_ENDIAN : __BIG_ENDIAN; } -static uint16_t -byteswap16(uint16_t v) -{ - return (v & 0x00ff) << 8 | (v & 0xff00 ) >> 8; +static uint16_t byteswap16(uint16_t v) { + return (v & 0x00ff) << 8 | (v & 0xff00) >> 8; } -static uint32_t -byteswap32(uint32_t v) -{ - return (v & 0x000000ff) << 24 | (v & 0x0000ff00) << 8 | (v & 0x00ff0000) >> 8 | (v & 0xff000000) >> 24; +static uint32_t byteswap32(uint32_t v) { + return (v & 0x000000ff) << 24 | (v & 0x0000ff00) << 8 | + (v & 0x00ff0000) >> 8 | (v & 0xff000000) >> 24; } -uint16_t -hton16(uint16_t h) -{ +uint16_t hton16(uint16_t h) { if (!endian) { endian = byteorder(); } return endian == __LITTLE_ENDIAN ? byteswap16(h) : h; } -uint16_t -ntoh16(uint16_t n) -{ +uint16_t ntoh16(uint16_t n) { if (!endian) { endian = byteorder(); } return endian == __LITTLE_ENDIAN ? byteswap16(n) : n; } -uint32_t -hton32(uint32_t h) -{ +uint32_t hton32(uint32_t h) { if (!endian) { endian = byteorder(); } return endian == __LITTLE_ENDIAN ? byteswap32(h) : h; } -uint32_t -ntoh32(uint32_t n) -{ +uint32_t ntoh32(uint32_t n) { if (!endian) { endian = byteorder(); } @@ -230,9 +212,7 @@ ntoh32(uint32_t n) * Checksum */ -uint16_t -cksum16(uint16_t *addr, uint16_t count, uint32_t init) -{ +uint16_t cksum16(uint16_t *addr, uint16_t count, uint32_t init) { uint32_t sum; sum = init; diff --git a/src/util.h b/src/util.h new file mode 100644 index 00000000..7220b644 --- /dev/null +++ b/src/util.h @@ -0,0 +1,108 @@ +#ifndef UTIL_H +#define UTIL_H + +#include +#include +#include + +/* + * Compare + */ + +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif + +/* + * Array + */ + +#define countof(x) ((sizeof(x) / sizeof(*x))) +#define tailof(x) (x + countof(x)) +#define indexof(x, y) (((uintptr_t)y - (uintptr_t)x) / sizeof(*y)) + +/* + * Time + */ + +#define timeval_add_usec(x, y) \ + do { \ + (x)->tv_sec += y / 1000000; \ + (x)->tv_usec += y % 1000000; \ + if ((x)->tv_usec >= 1000000) { \ + (x)->tv_sec += 1; \ + (x)->tv_usec -= 1000000; \ + } \ + } while (0); + +#define timespec_add_nsec(x, y) \ + do { \ + (x)->tv_sec += y / 1000000000; \ + (x)->tv_nsec += y % 1000000000; \ + if ((x)->tv_nsec >= 1000000000) { \ + (x)->tv_sec += 1; \ + (x)->tv_nsec -= 1000000000; \ + } \ + } while (0); + +/* + * Logging + */ + +#define errorf(...) \ + lprintf(stderr, 'E', __FILE__, __LINE__, __func__, __VA_ARGS__) +#define warnf(...) \ + lprintf(stderr, 'W', __FILE__, __LINE__, __func__, __VA_ARGS__) +#define infof(...) \ + lprintf(stderr, 'I', __FILE__, __LINE__, __func__, __VA_ARGS__) +#define debugf(...) \ + lprintf(stderr, 'D', __FILE__, __LINE__, __func__, __VA_ARGS__) + +#ifdef HEXDUMP +#define debugdump(...) hexdump(stderr, __VA_ARGS__) +#else +#define debugdump(...) +#endif + +extern int lprintf(FILE *fp, int level, const char *file, int line, + const char *func, const char *fmt, ...); +extern void hexdump(FILE *fp, const void *data, size_t size); + +/* + * Queue + */ + +struct queue_entry; + +struct queue_head { + struct queue_entry *head; + struct queue_entry *tail; + unsigned int num; +}; + +extern void queue_init(struct queue_head *queue); +extern void *queue_push(struct queue_head *queue, void *data); +extern void *queue_pop(struct queue_head *queue); +extern void *queue_peek(struct queue_head *queue); +extern void queue_foreach(struct queue_head *queue, + void (*func)(void *arg, void *data), void *arg); + +/* + * Byteorder + */ + +extern uint16_t hton16(uint16_t h); +extern uint16_t ntoh16(uint16_t n); +extern uint32_t hton32(uint32_t h); +extern uint32_t ntoh32(uint32_t n); + +/* + * Checksum + */ + +extern uint16_t cksum16(uint16_t *addr, uint16_t count, uint32_t init); + +#endif diff --git a/test/step0.c b/test/step0.c index 80f28c3f..b70b315d 100644 --- a/test/step0.c +++ b/test/step0.c @@ -1,9 +1,7 @@ +#include "test.h" // IWYU pragma: keep #include "util.h" -#include "test.h" -int -main(void) -{ +int main(void) { debugf("Hello, World!"); debugdump(test_data, sizeof(test_data)); diff --git a/test/step1.c b/test/step1.c new file mode 100644 index 00000000..2cb888d5 --- /dev/null +++ b/test/step1.c @@ -0,0 +1,91 @@ +#include +#include +#include + +#include "net.h" +#include "util.h" + +#include "driver/dummy.h" + +#include "test.h" + +static volatile sig_atomic_t terminate; + +/* シグナルハンドラ + +原則、シグナルハンドラの中では下記以外のことはしない: + +- 非同期安全な関数の呼び出し + - ref: https://www.jpcert.or.jp/sc-rules/c-sig30-c.html +- volatile sig_atomic_t 型の変数への書き込み +*/ +static void on_signal(int signal) { + /* + 一般に、I/O 関数をシグナルハンドラ内で呼び出すのは安全ではない。 + シグナルハンドラ内で I/O 関数を使用する場合は、 + 使用するシステムの非同期安全な関数を事前に確認すること。 + */ + // warnf("on_signal: signal received %d", signal); + + /* `(void)signal;` は必要? + `(void)signal;` は「この引数は使っていない」ことを意図的に示す + ためのお決まりの書き方。多くのコンパイラは未使用引数に警告を出すので、 + それを抑止する目的で入れている。 + + - 既にハンドラ内で `signal` を使って何かするなら不要。 + - 使わないならそのまま残しておけば警告を避けられる。 + - 別の書き方として、 + - `__attribute__((unused))` + - `void on_signal(int \/\*signal\*\/)` + - のようにコメントで示す例もある。 + - 単に `(void)signal;` がもっとも広く使われる方法です。 + */ + + // (void)signal; + terminate = 1; +} + +int main(int argc, char *argv[]) { + struct net_device *dev; + + // シグナルハンドラの設定 + // Ctrl + C が押された際に行儀よく終了するようにする + signal(SIGINT, on_signal); + + // プロトコルスタックの初期化 + if (net_init() == -1) { + errorf("net_init() failure"); + return -1; + } + + // ダミーデバイスの初期化(ダミーデバイスの生成、設定、登録) + dev = dummy_init(); + + // プロトコルスタックの起動 + if (net_run() == -1) { + errorf("net_run() failure"); + return -1; + } + + // 1 秒おきにデバイスにパケットを書き込む + // まだパケットを自力で生成できないのでテストデータを用いる + uint16_t type = 0x0800; + size_t len = sizeof(test_data); + // Ctrl + C が押されるとシグナルハンドラ on_signal() の中で + // terminate に 1 が設定され、while ループを抜ける + while (!terminate) { + // Internet Layer -> Network Interface Layer への書き込み + // Internet Layer から来たパケットをデバイスに書き込む + if (net_device_output(dev, type, test_data, len, NULL) == -1) { + errorf("net_device_output() failure"); + break; + } + + sleep(1); + } + + // プロトコルスタックの停止 + net_shutdown(); + + return 0; +} diff --git a/test/step2.c b/test/step2.c new file mode 100644 index 00000000..2cb888d5 --- /dev/null +++ b/test/step2.c @@ -0,0 +1,91 @@ +#include +#include +#include + +#include "net.h" +#include "util.h" + +#include "driver/dummy.h" + +#include "test.h" + +static volatile sig_atomic_t terminate; + +/* シグナルハンドラ + +原則、シグナルハンドラの中では下記以外のことはしない: + +- 非同期安全な関数の呼び出し + - ref: https://www.jpcert.or.jp/sc-rules/c-sig30-c.html +- volatile sig_atomic_t 型の変数への書き込み +*/ +static void on_signal(int signal) { + /* + 一般に、I/O 関数をシグナルハンドラ内で呼び出すのは安全ではない。 + シグナルハンドラ内で I/O 関数を使用する場合は、 + 使用するシステムの非同期安全な関数を事前に確認すること。 + */ + // warnf("on_signal: signal received %d", signal); + + /* `(void)signal;` は必要? + `(void)signal;` は「この引数は使っていない」ことを意図的に示す + ためのお決まりの書き方。多くのコンパイラは未使用引数に警告を出すので、 + それを抑止する目的で入れている。 + + - 既にハンドラ内で `signal` を使って何かするなら不要。 + - 使わないならそのまま残しておけば警告を避けられる。 + - 別の書き方として、 + - `__attribute__((unused))` + - `void on_signal(int \/\*signal\*\/)` + - のようにコメントで示す例もある。 + - 単に `(void)signal;` がもっとも広く使われる方法です。 + */ + + // (void)signal; + terminate = 1; +} + +int main(int argc, char *argv[]) { + struct net_device *dev; + + // シグナルハンドラの設定 + // Ctrl + C が押された際に行儀よく終了するようにする + signal(SIGINT, on_signal); + + // プロトコルスタックの初期化 + if (net_init() == -1) { + errorf("net_init() failure"); + return -1; + } + + // ダミーデバイスの初期化(ダミーデバイスの生成、設定、登録) + dev = dummy_init(); + + // プロトコルスタックの起動 + if (net_run() == -1) { + errorf("net_run() failure"); + return -1; + } + + // 1 秒おきにデバイスにパケットを書き込む + // まだパケットを自力で生成できないのでテストデータを用いる + uint16_t type = 0x0800; + size_t len = sizeof(test_data); + // Ctrl + C が押されるとシグナルハンドラ on_signal() の中で + // terminate に 1 が設定され、while ループを抜ける + while (!terminate) { + // Internet Layer -> Network Interface Layer への書き込み + // Internet Layer から来たパケットをデバイスに書き込む + if (net_device_output(dev, type, test_data, len, NULL) == -1) { + errorf("net_device_output() failure"); + break; + } + + sleep(1); + } + + // プロトコルスタックの停止 + net_shutdown(); + + return 0; +} diff --git a/test/test.h b/test/test.h index 0cc02a54..c8fcbafd 100644 --- a/test/test.h +++ b/test/test.h @@ -3,32 +3,26 @@ #include -/* Scope of Internet host loopback address. see https://tools.ietf.org/html/rfc5735 */ +/* Scope of Internet host loopback address. see + * https://tools.ietf.org/html/rfc5735 */ #define LOOPBACK_IP_ADDR "127.0.0.1" #define LOOPBACK_NETMASK "255.0.0.0" #define ETHER_TAP_NAME "tap0" -/* Scope of EUI-48 Documentation Values. see https://tools.ietf.org/html/rfc7042 */ +/* Scope of EUI-48 Documentation Values. see https://tools.ietf.org/html/rfc7042 + */ #define ETHER_TAP_HW_ADDR "00:00:5e:00:53:01" -/* Scope of Documentation Address Blocks (TEST-NET-1). see https://tools.ietf.org/html/rfc5731 */ +/* Scope of Documentation Address Blocks (TEST-NET-1). see + * https://tools.ietf.org/html/rfc5731 */ #define ETHER_TAP_IP_ADDR "192.0.2.2" #define ETHER_TAP_NETMASK "255.255.255.0" #define DEFAULT_GATEWAY "192.0.2.1" const uint8_t test_data[] = { - 0x45, 0x00, 0x00, 0x30, - 0x00, 0x80, 0x00, 0x00, - 0xff, 0x01, 0xbd, 0x4a, - 0x7f, 0x00, 0x00, 0x01, - 0x7f, 0x00, 0x00, 0x01, - 0x08, 0x00, 0x35, 0x64, - 0x00, 0x80, 0x00, 0x01, - 0x31, 0x32, 0x33, 0x34, - 0x35, 0x36, 0x37, 0x38, - 0x39, 0x30, 0x21, 0x40, - 0x23, 0x24, 0x25, 0x5e, - 0x26, 0x2a, 0x28, 0x29 -}; + 0x45, 0x00, 0x00, 0x30, 0x00, 0x80, 0x00, 0x00, 0xff, 0x01, 0xbd, 0x4a, + 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x08, 0x00, 0x35, 0x64, + 0x00, 0x80, 0x00, 0x01, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x30, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5e, 0x26, 0x2a, 0x28, 0x29}; #endif diff --git a/util.h b/util.h deleted file mode 100644 index ea4e491d..00000000 --- a/util.h +++ /dev/null @@ -1,114 +0,0 @@ -#ifndef UTIL_H -#define UTIL_H - -#include -#include -#include - -/* - * Compare - */ - -#ifndef MAX -#define MAX(x, y) ((x) > (y) ? (x) : (y)) -#endif -#ifndef MIN -#define MIN(x, y) ((x) < (y) ? (x) : (y)) -#endif - -/* - * Array - */ - -#define countof(x) ((sizeof(x) / sizeof(*x))) -#define tailof(x) (x + countof(x)) -#define indexof(x, y) (((uintptr_t)y - (uintptr_t)x) / sizeof(*y)) - -/* - * Time - */ - -#define timeval_add_usec(x, y) \ - do { \ - (x)->tv_sec += y / 1000000; \ - (x)->tv_usec += y % 1000000; \ - if ((x)->tv_usec >= 1000000) { \ - (x)->tv_sec += 1; \ - (x)->tv_usec -= 1000000; \ - } \ - } while(0); - -#define timespec_add_nsec(x, y) \ - do { \ - (x)->tv_sec += y / 1000000000; \ - (x)->tv_nsec += y % 1000000000; \ - if ((x)->tv_nsec >= 1000000000) { \ - (x)->tv_sec += 1; \ - (x)->tv_nsec -= 1000000000; \ - } \ - } while(0); - -/* - * Logging - */ - -#define errorf(...) lprintf(stderr, 'E', __FILE__, __LINE__, __func__, __VA_ARGS__) -#define warnf(...) lprintf(stderr, 'W', __FILE__, __LINE__, __func__, __VA_ARGS__) -#define infof(...) lprintf(stderr, 'I', __FILE__, __LINE__, __func__, __VA_ARGS__) -#define debugf(...) lprintf(stderr, 'D', __FILE__, __LINE__, __func__, __VA_ARGS__) - -#ifdef HEXDUMP -#define debugdump(...) hexdump(stderr, __VA_ARGS__) -#else -#define debugdump(...) -#endif - -extern int -lprintf(FILE *fp, int level, const char *file, int line, const char *func, const char *fmt, ...); -extern void -hexdump(FILE *fp, const void *data, size_t size); - -/* - * Queue - */ - -struct queue_entry; - -struct queue_head { - struct queue_entry *head; - struct queue_entry *tail; - unsigned int num; -}; - -extern void -queue_init(struct queue_head *queue); -extern void * -queue_push(struct queue_head *queue, void *data); -extern void * -queue_pop(struct queue_head *queue); -extern void * -queue_peek(struct queue_head *queue); -extern void -queue_foreach(struct queue_head *queue, void (*func)(void *arg, void *data), void *arg); - -/* - * Byteorder - */ - -extern uint16_t -hton16(uint16_t h); -extern uint16_t -ntoh16(uint16_t n); -extern uint32_t -hton32(uint32_t h); -extern uint32_t -ntoh32(uint32_t n); - -/* - * Checksum - */ - -extern uint16_t -cksum16(uint16_t *addr, uint16_t count, uint32_t init); - -#endif