├── .devcontainer.json ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── application ├── http.go └── server.go ├── docker-compose.yml ├── examples ├── application │ └── main.go ├── internet │ └── main.go ├── network │ └── main.go ├── todo │ └── main.go └── transport │ └── main.go ├── go.mod ├── internet ├── ip.go └── ip_header.go ├── network └── tun.go └── transport ├── connection_manager.go ├── tcp.go └── tcp_header.go /.devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "go", 3 | "dockerComposeFile": ["./docker-compose.yml"], 4 | "service": "ubuntu", 5 | "remoteUser": "root", 6 | "workspaceFolder": "/var/lib/tcp-ip-go", 7 | "customizations": { 8 | "vscode": { 9 | "extensions": ["golang.go"], 10 | "settings": { 11 | "editor.formatOnSave": true, 12 | "[go]": { 13 | "editor.defaultFormatter": "golang.go" 14 | } 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/main 2 | wireshark/ 3 | capture.pcap 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | 3 | RUN apt update &&\ 4 | apt upgrade -y &&\ 5 | apt install iproute2 -y &&\ 6 | apt install curl -y &&\ 7 | apt install git -y &&\ 8 | apt install make -y &&\ 9 | apt install tcpdump -y 10 | 11 | RUN apt install software-properties-common -y &&\ 12 | add-apt-repository ppa:longsleep/golang-backports &&\ 13 | apt install golang-go -y 14 | 15 | RUN go install github.com/uudashr/gopkgs/v2/cmd/gopkgs@latest &&\ 16 | go install github.com/ramya-rao-a/go-outline@latest &&\ 17 | go install github.com/nsf/gocode@latest &&\ 18 | go install github.com/acroca/go-symbols@latest &&\ 19 | go install github.com/fatih/gomodifytags@latest &&\ 20 | go install github.com/josharian/impl@latest &&\ 21 | go install github.com/haya14busa/goplay/cmd/goplay@latest &&\ 22 | go install github.com/go-delve/delve/cmd/dlv@latest &&\ 23 | go install golang.org/x/lint/golint@latest &&\ 24 | go install golang.org/x/tools/gopls@latest -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ryo Kawamura 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Docker 2 | build: 3 | docker-compose build --progress=plain --no-cache 4 | up: 5 | docker-compose up -d 6 | down: 7 | docker-compose down 8 | remove: 9 | docker-compose down --remove-orphans 10 | 11 | # TCP/IP 12 | tuntap: 13 | ip tuntap add mode tun dev tun0 &&\ 14 | ip link set tun0 up &&\ 15 | ip addr add 10.0.0.1/24 dev tun0 16 | curl: 17 | curl --interface tun0 http://10.0.0.2/ 18 | 19 | # Wireshark 20 | capture: 21 | tcpdump -i tun0 -w wireshark/capture.pcap -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Tutorial 2 | 3 | 1. Build docker image 4 | 5 | ```sh 6 | make build 7 | ``` 8 | 9 | 2. Run docker container 10 | 11 | ```sh 12 | make up 13 | ``` 14 | 15 | 3. Run main.go(in docker container) 16 | 17 | ```sh 18 | go run example/todo/main.go 19 | ``` 20 | 21 | 4. curl execution(in docker container) 22 | 23 | ```sh 24 | curl --interface tun0 -X POST -H "Content-Type: application/json" -d '{ 25 | "title": "ToDo2" 26 | }' 'http://10.0.0.2/todos' 27 | 28 | curl --interface tun0 http://10.0.0.2/todos 29 | ``` 30 | 31 | ## Dump TCP packets using Wireshark 32 | 33 | 1. Packet Monitoring(in docker container) 34 | 35 | ```sh 36 | make capture 37 | ``` 38 | 39 | 2. Open wireshark/capture.pcap in wireshark 40 | -------------------------------------------------------------------------------- /application/http.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "strings" 7 | "unicode/utf8" 8 | ) 9 | 10 | type HttpRequest struct { 11 | Method string 12 | URI string 13 | Version string 14 | Headers map[string]string 15 | Body string 16 | } 17 | 18 | type HttpResponse struct { 19 | Version string 20 | Status string 21 | Headers map[string]string 22 | Body string 23 | } 24 | 25 | type HttpStatus int 26 | 27 | const ( 28 | HttpStatusOK HttpStatus = iota 29 | HttpStatusCreated 30 | HttpStatusNotFound 31 | HttpStatusInternalServerError 32 | ) 33 | 34 | func (s HttpStatus) String() string { 35 | switch s { 36 | case HttpStatusOK: 37 | return "200 OK" 38 | case HttpStatusCreated: 39 | return "201 Created" 40 | case HttpStatusNotFound: 41 | return "404 Not Found" 42 | case HttpStatusInternalServerError: 43 | return "500 Internal Server Error" 44 | default: 45 | return "500 Internal Server Error" 46 | } 47 | } 48 | 49 | const ( 50 | HTTP_VERSION = "HTTP/1.0" 51 | ) 52 | 53 | // Parse an HTTP request from a string. 54 | func ParseHttpRequest(raw string) (*HttpRequest, error) { 55 | scanner := bufio.NewScanner(strings.NewReader(raw)) 56 | var requestLine string 57 | 58 | if scanner.Scan() { 59 | requestLine = scanner.Text() 60 | } else { 61 | return nil, fmt.Errorf("failed to read request line") 62 | } 63 | 64 | parts := strings.Split(requestLine, " ") 65 | if len(parts) != 3 { 66 | return nil, fmt.Errorf("invalid request line: %s", requestLine) 67 | } 68 | 69 | request := &HttpRequest{ 70 | Method: parts[0], 71 | URI: parts[1], 72 | Version: parts[2], 73 | Headers: make(map[string]string), 74 | } 75 | 76 | for scanner.Scan() { 77 | line := scanner.Text() 78 | if line == "" { 79 | break 80 | } 81 | 82 | headerParts := strings.SplitN(line, ": ", 2) 83 | if len(headerParts) != 2 { 84 | return nil, fmt.Errorf("invalid header: %s", line) 85 | } 86 | 87 | request.Headers[headerParts[0]] = headerParts[1] 88 | } 89 | 90 | if request.Method == "POST" || request.Method == "PUT" { 91 | for scanner.Scan() { 92 | request.Body += scanner.Text() 93 | } 94 | } 95 | 96 | return request, nil 97 | } 98 | 99 | // Create a new HTTP response. 100 | func NewHttpResponse(status HttpStatus, body string) *HttpResponse { 101 | contentLen := fmt.Sprintf("%d", utf8.RuneCountInString(body)) 102 | res := HttpResponse{ 103 | Version: HTTP_VERSION, 104 | Status: status.String(), 105 | Headers: map[string]string{ 106 | "Content-Type": "text/plain", 107 | "Content-Length": contentLen, 108 | }, 109 | Body: body, 110 | } 111 | 112 | return &res 113 | } 114 | 115 | // Convert an HTTP response to a string. 116 | func (res *HttpResponse) String() string { 117 | var response string 118 | 119 | response += fmt.Sprintf("%s %s\r\n", res.Version, res.Status) 120 | 121 | for key, value := range res.Headers { 122 | response += fmt.Sprintf("%s: %s\r\n", key, value) 123 | } 124 | response += "\r\n" 125 | 126 | response += res.Body 127 | 128 | return response 129 | } 130 | -------------------------------------------------------------------------------- /application/server.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kawa1214/tcp-ip-go/internet" 7 | "github.com/kawa1214/tcp-ip-go/network" 8 | "github.com/kawa1214/tcp-ip-go/transport" 9 | ) 10 | 11 | type Server struct { 12 | network *network.NetDevice 13 | ipPacketQueue *internet.IpPacketQueue 14 | tcpPacketQueue *transport.TcpPacketQueue 15 | } 16 | 17 | func NewServer() *Server { 18 | return &Server{} 19 | } 20 | 21 | func (s *Server) ListenAndServe() error { 22 | network, err := network.NewTun() 23 | network.Bind() 24 | s.network = network 25 | if err != nil { 26 | return err 27 | } 28 | s.serve() 29 | return nil 30 | } 31 | 32 | func (s *Server) serve() { 33 | ipPacketQueue := internet.NewIpPacketQueue() 34 | ipPacketQueue.ManageQueues(s.network) 35 | s.ipPacketQueue = ipPacketQueue 36 | 37 | tcpPacketQueue := transport.NewTcpPacketQueue() 38 | tcpPacketQueue.ManageQueues(ipPacketQueue) 39 | s.tcpPacketQueue = tcpPacketQueue 40 | } 41 | 42 | func (s *Server) Close() { 43 | s.network.Close() 44 | s.ipPacketQueue.Close() 45 | s.tcpPacketQueue.Close() 46 | } 47 | 48 | func (s *Server) Accept() (transport.Connection, error) { 49 | conn, err := s.tcpPacketQueue.ReadAcceptConnection() 50 | if err != nil { 51 | return transport.Connection{}, fmt.Errorf("accept error: %s", err) 52 | } 53 | 54 | return conn, nil 55 | } 56 | 57 | func (s *Server) Write(conn transport.Connection, resp *HttpResponse) { 58 | s.tcpPacketQueue.Write(conn, transport.HeaderFlags{ 59 | PSH: true, 60 | ACK: true, 61 | }, 62 | []byte(resp.String()), 63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | ubuntu: 4 | build: 5 | dockerfile: Dockerfile 6 | tty: true 7 | privileged: true 8 | volumes: 9 | - .:/var/lib/tcp-ip-go 10 | -------------------------------------------------------------------------------- /examples/application/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/kawa1214/tcp-ip-go/application" 7 | ) 8 | 9 | func main() { 10 | s := application.NewServer() 11 | defer s.Close() 12 | s.ListenAndServe() 13 | 14 | for { 15 | conn, err := s.Accept() 16 | if err != nil { 17 | log.Printf("accept error: %s", err) 18 | continue 19 | } 20 | 21 | reqRaw := string(conn.Pkt.Packet.Buf[conn.Pkt.IpHeader.IHL*4+conn.Pkt.TcpHeader.DataOff*4:]) 22 | req, err := application.ParseHttpRequest(reqRaw) 23 | if err != nil { 24 | log.Printf("parse error: %s", err) 25 | continue 26 | } 27 | 28 | log.Printf("request: %v", req) 29 | if req.Method == "GET" && req.URI == "/" { 30 | resp := application.NewHttpResponse(application.HttpStatusOK, "Hello, World!\r\n") 31 | s.Write(conn, resp) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/internet/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kawa1214/tcp-ip-go/internet" 7 | "github.com/kawa1214/tcp-ip-go/network" 8 | ) 9 | 10 | func main() { 11 | network, _ := network.NewTun() 12 | network.Bind() 13 | ip := internet.NewIpPacketQueue() 14 | ip.ManageQueues(network) 15 | 16 | for { 17 | pkt, _ := ip.Read() 18 | fmt.Printf("IP Header: %+v\n", pkt.IpHeader) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/network/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | "github.com/kawa1214/tcp-ip-go/network" 8 | ) 9 | 10 | func main() { 11 | network, _ := network.NewTun() 12 | network.Bind() 13 | 14 | for { 15 | pkt, _ := network.Read() 16 | fmt.Print(hex.Dump(pkt.Buf[:pkt.N])) 17 | network.Write(pkt) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/todo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "sync" 7 | "time" 8 | 9 | "github.com/kawa1214/tcp-ip-go/application" 10 | ) 11 | 12 | type Todo struct { 13 | Id int `json:"id"` 14 | Title string `json:"title"` 15 | CreatedAt time.Time `json:"createdAt"` 16 | } 17 | 18 | type CreateTodoRequest struct { 19 | Title string `json:"title"` 20 | } 21 | 22 | type todoList struct { 23 | todos []Todo 24 | lock sync.Mutex 25 | } 26 | 27 | func newTodoList() *todoList { 28 | return &todoList{ 29 | todos: make([]Todo, 0), 30 | } 31 | } 32 | 33 | func (l *todoList) add(title string) Todo { 34 | l.lock.Lock() 35 | defer l.lock.Unlock() 36 | todo := Todo{ 37 | Id: len(l.todos) + 1, 38 | Title: title, 39 | CreatedAt: time.Now(), 40 | } 41 | l.todos = append(l.todos, todo) 42 | return todo 43 | } 44 | 45 | func (l *todoList) getAll() []Todo { 46 | l.lock.Lock() 47 | defer l.lock.Unlock() 48 | return l.todos 49 | } 50 | 51 | func main() { 52 | todoList := newTodoList() 53 | todoList.add("todo1") 54 | 55 | s := application.NewServer() 56 | defer s.Close() 57 | s.ListenAndServe() 58 | 59 | for { 60 | conn, err := s.Accept() 61 | if err != nil { 62 | log.Printf("accept error: %s", err) 63 | continue 64 | } 65 | 66 | reqRaw := string(conn.Pkt.Packet.Buf[conn.Pkt.IpHeader.IHL*4+conn.Pkt.TcpHeader.DataOff*4:]) 67 | req, err := application.ParseHttpRequest(reqRaw) 68 | if err != nil { 69 | resp := application.NewHttpResponse(application.HttpStatusInternalServerError, err.Error()) 70 | s.Write(conn, resp) 71 | continue 72 | } 73 | 74 | log.Printf("request: %v", req) 75 | if req.Method == "GET" && req.URI == "/todos" { 76 | todos := todoList.getAll() 77 | body, err := json.Marshal(todos) 78 | if err != nil { 79 | resp := application.NewHttpResponse(application.HttpStatusInternalServerError, err.Error()) 80 | s.Write(conn, resp) 81 | continue 82 | } 83 | resp := application.NewHttpResponse(application.HttpStatusOK, string(body)+string('\n')) 84 | s.Write(conn, resp) 85 | } 86 | 87 | if req.Method == "POST" && req.URI == "/todos" { 88 | var createReq CreateTodoRequest 89 | err := json.Unmarshal([]byte(req.Body), &createReq) 90 | if err != nil { 91 | resp := application.NewHttpResponse(application.HttpStatusInternalServerError, err.Error()) 92 | s.Write(conn, resp) 93 | continue 94 | } 95 | todo := todoList.add(createReq.Title) 96 | body, err := json.Marshal(todo) 97 | if err != nil { 98 | resp := application.NewHttpResponse(application.HttpStatusInternalServerError, err.Error()) 99 | s.Write(conn, resp) 100 | continue 101 | } 102 | 103 | resp := application.NewHttpResponse(application.HttpStatusCreated, string(body)+string('\n')) 104 | s.Write(conn, resp) 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /examples/transport/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kawa1214/tcp-ip-go/internet" 7 | "github.com/kawa1214/tcp-ip-go/network" 8 | "github.com/kawa1214/tcp-ip-go/transport" 9 | ) 10 | 11 | func main() { 12 | network, _ := network.NewTun() 13 | network.Bind() 14 | ip := internet.NewIpPacketQueue() 15 | ip.ManageQueues(network) 16 | tcp := transport.NewTcpPacketQueue() 17 | tcp.ManageQueues(ip) 18 | 19 | for { 20 | pkt, _ := tcp.ReadAcceptConnection() 21 | fmt.Printf("TCP Header: %+v\n", pkt.Pkt.TcpHeader) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kawa1214/tcp-ip-go 2 | 3 | go 1.20 4 | -------------------------------------------------------------------------------- /internet/ip.go: -------------------------------------------------------------------------------- 1 | package internet 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/kawa1214/tcp-ip-go/network" 9 | ) 10 | 11 | const ( 12 | QUEUE_SIZE = 10 13 | ) 14 | 15 | type IpPacket struct { 16 | IpHeader *Header 17 | Packet network.Packet 18 | } 19 | 20 | type IpPacketQueue struct { 21 | incomingQueue chan IpPacket 22 | outgoingQueue chan network.Packet 23 | ctx context.Context 24 | cancel context.CancelFunc 25 | } 26 | 27 | func NewIpPacketQueue() *IpPacketQueue { 28 | return &IpPacketQueue{ 29 | incomingQueue: make(chan IpPacket, QUEUE_SIZE), 30 | outgoingQueue: make(chan network.Packet, QUEUE_SIZE), 31 | } 32 | } 33 | 34 | func (ip *IpPacketQueue) ManageQueues(network *network.NetDevice) { 35 | ip.ctx, ip.cancel = context.WithCancel(context.Background()) 36 | 37 | go func() { 38 | for { 39 | select { 40 | case <-ip.ctx.Done(): 41 | return 42 | default: 43 | pkt, err := network.Read() 44 | if err != nil { 45 | log.Printf("read error: %s", err.Error()) 46 | } 47 | ipHeader, err := unmarshal(pkt.Buf[:pkt.N]) 48 | if err != nil { 49 | log.Printf("unmarshal error: %s", err) 50 | continue 51 | } 52 | ipPacket := IpPacket{ 53 | IpHeader: ipHeader, 54 | Packet: pkt, 55 | } 56 | ip.incomingQueue <- ipPacket 57 | } 58 | } 59 | }() 60 | 61 | go func() { 62 | for { 63 | select { 64 | case <-ip.ctx.Done(): 65 | return 66 | case pkt := <-ip.outgoingQueue: 67 | err := network.Write(pkt) 68 | if err != nil { 69 | log.Printf("write error: %s", err.Error()) 70 | } 71 | } 72 | } 73 | }() 74 | } 75 | 76 | func (q *IpPacketQueue) Close() { 77 | q.cancel() 78 | } 79 | 80 | func (q *IpPacketQueue) Read() (IpPacket, error) { 81 | pkt, ok := <-q.incomingQueue 82 | if !ok { 83 | return IpPacket{}, fmt.Errorf("incoming queue is closed") 84 | } 85 | return pkt, nil 86 | } 87 | 88 | func (q *IpPacketQueue) Write(pkt network.Packet) error { 89 | select { 90 | case q.outgoingQueue <- pkt: 91 | return nil 92 | case <-q.ctx.Done(): 93 | return fmt.Errorf("network closed") 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /internet/ip_header.go: -------------------------------------------------------------------------------- 1 | package internet 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | ) 7 | 8 | type Header struct { 9 | Version uint8 10 | IHL uint8 11 | TOS uint8 12 | TotalLength uint16 13 | ID uint16 14 | Flags uint8 15 | FragmentOffset uint16 16 | TTL uint8 17 | Protocol uint8 18 | Checksum uint16 19 | SrcIP [4]byte 20 | DstIP [4]byte 21 | } 22 | 23 | const ( 24 | IP_VERSION = 4 25 | IHL = 5 26 | TOS = 0 27 | TTL = 64 28 | LENGTH = IHL * 4 29 | TCP_PROTOCOL = 6 30 | IP_HEADER_MIN_LEN = 20 31 | ) 32 | 33 | // New creates a new IP header from packet. 34 | func unmarshal(pkt []byte) (*Header, error) { 35 | if len(pkt) < IP_HEADER_MIN_LEN { 36 | return nil, fmt.Errorf("invalid IP header length") 37 | } 38 | 39 | header := &Header{ 40 | Version: pkt[0] >> 4, 41 | IHL: pkt[0] & 0x0F, 42 | TOS: pkt[1], 43 | TotalLength: binary.BigEndian.Uint16(pkt[2:4]), 44 | ID: binary.BigEndian.Uint16(pkt[4:6]), 45 | Flags: pkt[6] >> 5, 46 | FragmentOffset: binary.BigEndian.Uint16(pkt[6:8]) & 0x1FFF, 47 | TTL: pkt[8], 48 | Protocol: pkt[9], 49 | Checksum: binary.BigEndian.Uint16(pkt[10:12]), 50 | } 51 | 52 | copy(header.SrcIP[:], pkt[12:16]) 53 | copy(header.DstIP[:], pkt[16:20]) 54 | 55 | return header, nil 56 | } 57 | 58 | // Create a new IP header. 59 | func NewIp(srcIP, dstIP [4]byte, len int) *Header { 60 | return &Header{ 61 | Version: IP_VERSION, 62 | IHL: IHL, 63 | TOS: TOS, 64 | TotalLength: uint16(LENGTH + len), 65 | ID: 0, 66 | Flags: 0x40, 67 | TTL: 64, 68 | Protocol: TCP_PROTOCOL, 69 | Checksum: 0, 70 | SrcIP: srcIP, 71 | DstIP: dstIP, 72 | } 73 | } 74 | 75 | // Return a byte slice of the packet. 76 | func (h *Header) Marshal() []byte { 77 | versionAndIHL := (h.Version << 4) | h.IHL 78 | flagsAndFragmentOffset := (uint16(h.FragmentOffset) << 13) | (h.FragmentOffset & 0x1FFF) 79 | 80 | pkt := make([]byte, 20) 81 | pkt[0] = versionAndIHL 82 | pkt[1] = 0 83 | binary.BigEndian.PutUint16(pkt[2:4], h.TotalLength) 84 | binary.BigEndian.PutUint16(pkt[4:6], h.ID) 85 | binary.BigEndian.PutUint16(pkt[6:8], flagsAndFragmentOffset) 86 | pkt[8] = h.TTL 87 | pkt[9] = h.Protocol 88 | binary.BigEndian.PutUint16(pkt[10:12], h.Checksum) 89 | copy(pkt[12:16], h.SrcIP[:]) 90 | copy(pkt[16:20], h.DstIP[:]) 91 | 92 | h.setChecksum(pkt) 93 | binary.BigEndian.PutUint16(pkt[10:12], h.Checksum) 94 | 95 | return pkt 96 | } 97 | 98 | // Calculates the checksum of the packet and sets Header. 99 | func (h *Header) setChecksum(pkt []byte) { 100 | length := len(pkt) 101 | var checksum uint32 102 | 103 | for i := 0; i < length; i += 2 { 104 | checksum += uint32(binary.BigEndian.Uint16(pkt[i : i+2])) 105 | } 106 | 107 | for checksum > 0xffff { 108 | checksum = (checksum & 0xffff) + (checksum >> 16) 109 | } 110 | 111 | h.Checksum = ^uint16(checksum) 112 | } 113 | -------------------------------------------------------------------------------- /network/tun.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "syscall" 9 | "unsafe" 10 | ) 11 | 12 | type ifreq struct { 13 | ifrName [16]byte 14 | ifrFlags int16 15 | } 16 | 17 | const ( 18 | TUNSETIFF = 0x400454ca 19 | IFF_TUN = 0x0001 20 | IFF_NO_PI = 0x1000 21 | PACKET_SIZE = 2048 22 | QUEUE_SIZE = 10 23 | ) 24 | 25 | type Packet struct { 26 | Buf []byte 27 | N uintptr 28 | } 29 | 30 | type NetDevice struct { 31 | file *os.File 32 | incomingQueue chan Packet 33 | outgoingQueue chan Packet 34 | ctx context.Context 35 | cancel context.CancelFunc 36 | } 37 | 38 | func NewTun() (*NetDevice, error) { 39 | file, err := os.OpenFile("/dev/net/tun", os.O_RDWR, 0) 40 | if err != nil { 41 | return nil, fmt.Errorf("open error: %s", err.Error()) 42 | } 43 | 44 | ifr := ifreq{} 45 | copy(ifr.ifrName[:], []byte("tun0")) 46 | ifr.ifrFlags = IFF_TUN | IFF_NO_PI 47 | 48 | _, _, sysErr := syscall.Syscall(syscall.SYS_IOCTL, file.Fd(), uintptr(TUNSETIFF), uintptr(unsafe.Pointer(&ifr))) 49 | if sysErr != 0 { 50 | return nil, fmt.Errorf("ioctl error: %s", sysErr.Error()) 51 | } 52 | 53 | return &NetDevice{ 54 | file: file, 55 | incomingQueue: make(chan Packet, QUEUE_SIZE), 56 | outgoingQueue: make(chan Packet, QUEUE_SIZE), 57 | }, nil 58 | } 59 | 60 | func (t *NetDevice) Close() error { 61 | err := t.file.Close() 62 | if err != nil { 63 | return fmt.Errorf("close error: %s", err.Error()) 64 | } 65 | t.cancel() 66 | 67 | return nil 68 | } 69 | 70 | func (t *NetDevice) read(buf []byte) (uintptr, error) { 71 | n, _, sysErr := syscall.Syscall(syscall.SYS_READ, t.file.Fd(), uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf))) 72 | if sysErr != 0 { 73 | return 0, fmt.Errorf("read error: %s", sysErr.Error()) 74 | } 75 | return n, nil 76 | } 77 | 78 | func (t *NetDevice) write(buf []byte) (uintptr, error) { 79 | n, _, sysErr := syscall.Syscall(syscall.SYS_WRITE, t.file.Fd(), uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf))) 80 | if sysErr != 0 { 81 | return 0, fmt.Errorf("write error: %s", sysErr.Error()) 82 | } 83 | return n, nil 84 | } 85 | 86 | func (tun *NetDevice) Bind() { 87 | tun.ctx, tun.cancel = context.WithCancel(context.Background()) 88 | 89 | go func() { 90 | for { 91 | select { 92 | case <-tun.ctx.Done(): 93 | return 94 | default: 95 | buf := make([]byte, PACKET_SIZE) 96 | n, err := tun.read(buf) 97 | if err != nil { 98 | log.Printf("read error: %s", err.Error()) 99 | } 100 | packet := Packet{ 101 | Buf: buf[:n], 102 | N: n, 103 | } 104 | tun.incomingQueue <- packet 105 | } 106 | } 107 | }() 108 | 109 | go func() { 110 | for { 111 | select { 112 | case <-tun.ctx.Done(): 113 | return 114 | case pkt := <-tun.outgoingQueue: 115 | _, err := tun.write(pkt.Buf[:pkt.N]) 116 | if err != nil { 117 | log.Printf("write error: %s", err.Error()) 118 | } 119 | } 120 | } 121 | }() 122 | } 123 | 124 | func (t *NetDevice) Read() (Packet, error) { 125 | pkt, ok := <-t.incomingQueue 126 | if !ok { 127 | return Packet{}, fmt.Errorf("incoming queue is closed") 128 | } 129 | return pkt, nil 130 | } 131 | 132 | func (t *NetDevice) Write(pkt Packet) error { 133 | select { 134 | case t.outgoingQueue <- pkt: 135 | return nil 136 | case <-t.ctx.Done(): 137 | return fmt.Errorf("device closed") 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /transport/connection_manager.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type State int 11 | 12 | const ( 13 | StateListen State = iota 14 | StateSynReceived 15 | StateEstablished 16 | StateCloseWait 17 | StateLastAck 18 | StateClosed 19 | ) 20 | 21 | type Connection struct { 22 | SrcPort uint16 23 | DstPort uint16 24 | State State 25 | Pkt TcpPacket 26 | N uintptr 27 | 28 | initialSeqNum uint32 29 | incrementSeqNum uint32 30 | 31 | isAccept bool 32 | } 33 | 34 | type ConnectionManager struct { 35 | Connections []Connection 36 | AcceptConnectionQueue chan Connection 37 | lock sync.Mutex 38 | } 39 | 40 | func NewConnectionManager() *ConnectionManager { 41 | return &ConnectionManager{ 42 | Connections: make([]Connection, 0), 43 | AcceptConnectionQueue: make(chan Connection, QUEUE_SIZE), 44 | } 45 | } 46 | 47 | func (m *ConnectionManager) recv(queue *TcpPacketQueue, pkt TcpPacket) { 48 | conn, ok := m.find(pkt) 49 | if ok { 50 | conn.Pkt = pkt 51 | } else { 52 | conn = m.addConnection(pkt) 53 | } 54 | 55 | if pkt.TcpHeader.Flags.SYN && !ok { 56 | log.Printf("Received SYN Packet") 57 | 58 | queue.Write(conn, HeaderFlags{ 59 | SYN: true, 60 | ACK: true, 61 | }, nil) 62 | 63 | m.update(pkt, StateSynReceived, false) 64 | } 65 | 66 | if ok && pkt.TcpHeader.Flags.ACK && conn.State == StateSynReceived { 67 | log.Printf("Received ACK Packet") 68 | m.update(pkt, StateEstablished, false) 69 | } 70 | 71 | if ok && pkt.TcpHeader.Flags.PSH && conn.State == StateEstablished { 72 | log.Printf("Received PSH Packet") 73 | 74 | queue.Write(conn, HeaderFlags{ 75 | ACK: true, 76 | }, nil) 77 | m.update(pkt, StateEstablished, true) 78 | 79 | m.AcceptConnectionQueue <- conn 80 | } 81 | 82 | if ok && pkt.TcpHeader.Flags.FIN && conn.State == StateEstablished { 83 | log.Printf("Received FIN Packet") 84 | 85 | queue.Write(conn, HeaderFlags{ 86 | ACK: true, 87 | }, nil) 88 | m.update(pkt, StateCloseWait, false) 89 | 90 | queue.Write(conn, HeaderFlags{ 91 | FIN: true, 92 | ACK: true, 93 | }, nil) 94 | m.update(pkt, StateLastAck, false) 95 | } 96 | 97 | if ok && pkt.TcpHeader.Flags.ACK && conn.State == StateLastAck { 98 | log.Printf("Received ACK Packet") 99 | m.update(pkt, StateClosed, false) 100 | m.remove(pkt) 101 | } 102 | } 103 | 104 | func (m *ConnectionManager) addConnection(pkt TcpPacket) Connection { 105 | m.lock.Lock() 106 | defer m.lock.Unlock() 107 | seed := time.Now().UnixNano() 108 | r := rand.New(rand.NewSource(seed)) 109 | 110 | conn := Connection{ 111 | SrcPort: pkt.TcpHeader.SrcPort, 112 | DstPort: pkt.TcpHeader.DstPort, 113 | State: StateSynReceived, 114 | N: pkt.Packet.N, 115 | Pkt: pkt, 116 | initialSeqNum: uint32(r.Int31()), 117 | incrementSeqNum: 0, 118 | } 119 | m.Connections = append(m.Connections, conn) 120 | 121 | return conn 122 | } 123 | 124 | func (m *ConnectionManager) remove(pkt TcpPacket) { 125 | m.lock.Lock() 126 | defer m.lock.Unlock() 127 | 128 | for i, conn := range m.Connections { 129 | if conn.SrcPort == pkt.TcpHeader.SrcPort && conn.DstPort == pkt.TcpHeader.DstPort { 130 | m.Connections = append(m.Connections[:i], m.Connections[i+1:]...) 131 | return 132 | } 133 | } 134 | } 135 | 136 | func (m *ConnectionManager) find(pkt TcpPacket) (Connection, bool) { 137 | m.lock.Lock() 138 | defer m.lock.Unlock() 139 | 140 | for _, conn := range m.Connections { 141 | if conn.SrcPort == pkt.TcpHeader.SrcPort && conn.DstPort == pkt.TcpHeader.DstPort { 142 | return conn, true 143 | } 144 | } 145 | 146 | return Connection{}, false 147 | } 148 | 149 | func (m *ConnectionManager) update(pkt TcpPacket, state State, isAccept bool) { 150 | m.lock.Lock() 151 | defer m.lock.Unlock() 152 | 153 | for i, conn := range m.Connections { 154 | if conn.SrcPort == pkt.TcpHeader.SrcPort && conn.DstPort == pkt.TcpHeader.DstPort { 155 | m.Connections[i].State = state 156 | m.Connections[i].isAccept = isAccept 157 | return 158 | } 159 | } 160 | } 161 | 162 | func (m *ConnectionManager) updateIncrementSeqNum(pkt TcpPacket, val uint32) { 163 | m.lock.Lock() 164 | defer m.lock.Unlock() 165 | 166 | for i, conn := range m.Connections { 167 | if conn.SrcPort == pkt.TcpHeader.SrcPort && conn.DstPort == pkt.TcpHeader.DstPort { 168 | m.Connections[i].incrementSeqNum += val 169 | return 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /transport/tcp.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/kawa1214/tcp-ip-go/internet" 9 | "github.com/kawa1214/tcp-ip-go/network" 10 | ) 11 | 12 | const ( 13 | QUEUE_SIZE = 10 14 | ) 15 | 16 | type TcpPacket struct { 17 | IpHeader *internet.Header 18 | TcpHeader *Header 19 | Packet network.Packet 20 | } 21 | 22 | type TcpPacketQueue struct { 23 | manager *ConnectionManager 24 | outgoingQueue chan network.Packet 25 | ctx context.Context 26 | cancel context.CancelFunc 27 | } 28 | 29 | func NewTcpPacketQueue() *TcpPacketQueue { 30 | ConnectionManager := NewConnectionManager() 31 | return &TcpPacketQueue{ 32 | manager: ConnectionManager, 33 | outgoingQueue: make(chan network.Packet, QUEUE_SIZE), 34 | } 35 | } 36 | 37 | func (tcp *TcpPacketQueue) ManageQueues(ip *internet.IpPacketQueue) { 38 | tcp.ctx, tcp.cancel = context.WithCancel(context.Background()) 39 | go func() { 40 | for { 41 | select { 42 | case <-tcp.ctx.Done(): 43 | return 44 | default: 45 | ipPkt, err := ip.Read() 46 | if err != nil { 47 | log.Printf("read error: %s", err.Error()) 48 | } 49 | tcpHeader, err := unmarshal(ipPkt.Packet.Buf[ipPkt.IpHeader.IHL*4 : ipPkt.Packet.N]) 50 | if err != nil { 51 | log.Printf("unmarshal error: %s", err) 52 | continue 53 | } 54 | tcpPacket := TcpPacket{ 55 | IpHeader: ipPkt.IpHeader, 56 | TcpHeader: tcpHeader, 57 | Packet: ipPkt.Packet, 58 | } 59 | 60 | tcp.manager.recv(tcp, tcpPacket) 61 | } 62 | } 63 | }() 64 | 65 | go func() { 66 | for { 67 | select { 68 | case <-tcp.ctx.Done(): 69 | return 70 | case pkt := <-tcp.outgoingQueue: 71 | err := ip.Write(pkt) 72 | if err != nil { 73 | log.Printf("write error: %s", err.Error()) 74 | } 75 | } 76 | } 77 | }() 78 | } 79 | 80 | func (tcp *TcpPacketQueue) Close() { 81 | tcp.cancel() 82 | } 83 | 84 | func (tcp *TcpPacketQueue) Write(conn Connection, flgs HeaderFlags, data []byte) { 85 | pkt := conn.Pkt 86 | tcpDataLen := int(pkt.Packet.N) - (int(pkt.IpHeader.IHL) * 4) - (int(pkt.TcpHeader.DataOff) * 4) 87 | 88 | incrementAckNum := 0 89 | if tcpDataLen == 0 { 90 | incrementAckNum = 1 91 | } else { 92 | incrementAckNum = tcpDataLen 93 | } 94 | ackNum := pkt.TcpHeader.SeqNum + uint32(incrementAckNum) 95 | 96 | seqNum := conn.initialSeqNum + conn.incrementSeqNum 97 | 98 | writeIpHdr := internet.NewIp(pkt.IpHeader.DstIP, pkt.IpHeader.SrcIP, LENGTH+len(data)) 99 | writeTcpHdr := New( 100 | pkt.TcpHeader.DstPort, 101 | pkt.TcpHeader.SrcPort, 102 | seqNum, 103 | ackNum, 104 | flgs, 105 | ) 106 | 107 | ipHdr := writeIpHdr.Marshal() 108 | tcpHdr := writeTcpHdr.Marshal(conn.Pkt.IpHeader, data) 109 | 110 | writePkt := append(ipHdr, tcpHdr...) 111 | writePkt = append(writePkt, data...) 112 | 113 | incrementSeqNum := 0 114 | if flgs.SYN || flgs.FIN { 115 | incrementSeqNum += 1 116 | } 117 | incrementSeqNum += len(data) 118 | tcp.manager.updateIncrementSeqNum(pkt, uint32(incrementSeqNum)) 119 | 120 | tcp.outgoingQueue <- network.Packet{ 121 | Buf: writePkt, 122 | N: uintptr(len(writePkt)), 123 | } 124 | } 125 | 126 | func (tcp *TcpPacketQueue) ReadAcceptConnection() (Connection, error) { 127 | pkt, ok := <-tcp.manager.AcceptConnectionQueue 128 | if !ok { 129 | return Connection{}, fmt.Errorf("connection queue is closed") 130 | } 131 | 132 | return pkt, nil 133 | } 134 | -------------------------------------------------------------------------------- /transport/tcp_header.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | 7 | "github.com/kawa1214/tcp-ip-go/internet" 8 | ) 9 | 10 | const ( 11 | LENGTH = 20 12 | WINDOW_SIZE = 65535 13 | PROTOCOL = 6 14 | ) 15 | 16 | type Header struct { 17 | SrcPort uint16 18 | DstPort uint16 19 | SeqNum uint32 20 | AckNum uint32 21 | DataOff uint8 22 | Reserved uint8 23 | Flags HeaderFlags 24 | WindowSize uint16 25 | Checksum uint16 26 | UrgentPtr uint16 27 | } 28 | 29 | // New creates a new TCP header from packet. 30 | func unmarshal(pkt []byte) (*Header, error) { 31 | if len(pkt) < 20 { 32 | return nil, fmt.Errorf("invalid TCP header length") 33 | } 34 | 35 | flags := unmarshalFlag(pkt[13]) 36 | 37 | header := &Header{ 38 | SrcPort: binary.BigEndian.Uint16(pkt[0:2]), 39 | DstPort: binary.BigEndian.Uint16(pkt[2:4]), 40 | SeqNum: binary.BigEndian.Uint32(pkt[4:8]), 41 | AckNum: binary.BigEndian.Uint32(pkt[8:12]), 42 | DataOff: pkt[12] >> 4, 43 | Reserved: pkt[12] & 0x0E, 44 | Flags: flags, 45 | WindowSize: binary.BigEndian.Uint16(pkt[14:16]), 46 | Checksum: binary.BigEndian.Uint16(pkt[16:18]), 47 | UrgentPtr: binary.BigEndian.Uint16(pkt[18:20]), 48 | } 49 | 50 | return header, nil 51 | } 52 | 53 | // Create a new TCP header. 54 | func New(srcPort, dstPort uint16, seqNum, ackNum uint32, flags HeaderFlags) *Header { 55 | dataOff := uint16(LENGTH / 4) 56 | dataOff <<= 4 57 | return &Header{ 58 | SrcPort: srcPort, 59 | DstPort: dstPort, 60 | SeqNum: seqNum, 61 | AckNum: ackNum, 62 | DataOff: uint8(dataOff), 63 | Reserved: 0x12, 64 | Flags: flags, 65 | WindowSize: uint16(WINDOW_SIZE), 66 | Checksum: 0, 67 | UrgentPtr: 0, 68 | } 69 | } 70 | 71 | // Return a byte slice of the packet. 72 | func (h *Header) Marshal(ipHdr *internet.Header, data []byte) []byte { 73 | f := h.Flags.marshal() 74 | 75 | pkt := make([]byte, 20) 76 | binary.BigEndian.PutUint16(pkt[0:2], h.SrcPort) 77 | binary.BigEndian.PutUint16(pkt[2:4], h.DstPort) 78 | binary.BigEndian.PutUint32(pkt[4:8], h.SeqNum) 79 | binary.BigEndian.PutUint32(pkt[8:12], h.AckNum) 80 | pkt[12] = h.DataOff 81 | pkt[13] = f 82 | binary.BigEndian.PutUint16(pkt[14:16], h.WindowSize) 83 | binary.BigEndian.PutUint16(pkt[16:18], h.Checksum) 84 | binary.BigEndian.PutUint16(pkt[18:20], h.UrgentPtr) 85 | 86 | h.setChecksum(ipHdr, append(pkt, data...)) 87 | binary.BigEndian.PutUint16(pkt[16:18], h.Checksum) 88 | 89 | return pkt 90 | } 91 | 92 | // Calculates the checksum of the packet and sets Header. 93 | func (h *Header) setChecksum(ipHeader *internet.Header, pkt []byte) { 94 | pseudoHeader := make([]byte, 12) 95 | copy(pseudoHeader[0:4], ipHeader.SrcIP[:]) 96 | copy(pseudoHeader[4:8], ipHeader.DstIP[:]) 97 | pseudoHeader[8] = 0 98 | pseudoHeader[9] = PROTOCOL 99 | binary.BigEndian.PutUint16(pseudoHeader[10:12], uint16(len(pkt))) 100 | 101 | buf := append(pseudoHeader, pkt...) 102 | if len(buf)%2 != 0 { 103 | buf = append(buf, 0) 104 | } 105 | 106 | var checksum uint32 107 | for i := 0; i < len(buf); i += 2 { 108 | checksum += uint32(binary.BigEndian.Uint16(buf[i : i+2])) 109 | } 110 | 111 | for checksum > 0xffff { 112 | checksum = (checksum & 0xffff) + (checksum >> 16) 113 | } 114 | 115 | h.Checksum = ^uint16(checksum) 116 | } 117 | 118 | type HeaderFlags struct { 119 | CWR bool 120 | ECE bool 121 | URG bool 122 | ACK bool 123 | PSH bool 124 | RST bool 125 | SYN bool 126 | FIN bool 127 | } 128 | 129 | // Return a string representation of the packet byte. 130 | func unmarshalFlag(f uint8) HeaderFlags { 131 | return HeaderFlags{ 132 | CWR: f&0x80 == 0x80, 133 | ECE: f&0x40 == 0x40, 134 | URG: f&0x20 == 0x20, 135 | ACK: f&0x10 == 0x10, 136 | PSH: f&0x08 == 0x08, 137 | RST: f&0x04 == 0x04, 138 | SYN: f&0x02 == 0x02, 139 | FIN: f&0x01 == 0x01, 140 | } 141 | } 142 | 143 | // Return a byte slice of the packet. 144 | func (f *HeaderFlags) marshal() uint8 { 145 | var packedFlags uint8 146 | if f.CWR { 147 | packedFlags |= 0x80 148 | } 149 | if f.ECE { 150 | packedFlags |= 0x40 151 | } 152 | if f.URG { 153 | packedFlags |= 0x20 154 | } 155 | if f.ACK { 156 | packedFlags |= 0x10 157 | } 158 | if f.PSH { 159 | packedFlags |= 0x08 160 | } 161 | if f.RST { 162 | packedFlags |= 0x04 163 | } 164 | if f.SYN { 165 | packedFlags |= 0x02 166 | } 167 | if f.FIN { 168 | packedFlags |= 0x01 169 | } 170 | return packedFlags 171 | } 172 | --------------------------------------------------------------------------------