Browse Source

增加 ListenTable 来检测端口占用 (#291)

* 增加 ListenTable 来检测端口占用


* 使用Arc封装GlobalSocketHandle

* 删除 listen 处的端口检测逻辑,延至实现端口复用时完成

* 设立两张表,分别记录TCP和UDP的端口占用

* 实现 meatadata 相关逻辑

* 实现socket关闭时,端口在表中移除

* 使用端口管理器重构端口记录表

* 修正与RawSocket相关的端口管理逻辑

* 补充测试文件

* 修正 unbind_port 在逻辑错误

* 修正格式问题

---------

Co-authored-by: longjin <[email protected]>
Xshine 1 year ago
parent
commit
821bb9a2dc

+ 1 - 1
kernel/src/filesystem/vfs/core.rs

@@ -228,7 +228,7 @@ pub fn do_mkdir(path: &str, _mode: FileMode) -> Result<u64, SystemError> {
     return Ok(0);
 }
 
-/// @breif 删除文件夹
+/// @brief 删除文件夹
 pub fn do_remove_dir(path: &str) -> Result<u64, SystemError> {
     // 文件名过长
     if path.len() > PAGE_4K_SIZE as usize {

+ 1 - 1
kernel/src/io/device.rs

@@ -81,7 +81,7 @@ pub trait BlockDevice: Any + Send + Sync + Debug {
     /// @brief: 同步磁盘信息,把所有的dirty数据写回硬盘 - 待实现
     fn sync(&self) -> Result<(), SystemError>;
 
-    /// @breif: 每个块设备都必须固定自己块大小,而且该块大小必须是2的幂次
+    /// @brief: 每个块设备都必须固定自己块大小,而且该块大小必须是2的幂次
     /// @return: 返回一个固定量,硬编码(编程的时候固定的常量).
     fn blk_size_log2(&self) -> u8;
 

+ 2 - 2
kernel/src/mm/allocator/buddy.rs

@@ -70,9 +70,9 @@ pub struct BuddyAllocator<A> {
 
 impl<A: MemoryManagementArch> BuddyAllocator<A> {
     const BUDDY_ENTRIES: usize =
-    // 定义一个变量记录buddy表的大小
+        // 定义一个变量记录buddy表的大小
         (A::PAGE_SIZE - mem::size_of::<PageList<A>>()) / mem::size_of::<PhysAddr>();
-    
+
     pub unsafe fn new(mut bump_allocator: BumpAllocator<A>) -> Option<Self> {
         let initial_free_pages = bump_allocator.usage().free();
         kdebug!("Free pages before init buddy: {:?}", initial_free_pages);

+ 184 - 46
kernel/src/net/socket.rs

@@ -1,5 +1,6 @@
 #![allow(dead_code)]
 use alloc::{boxed::Box, sync::Arc, vec::Vec};
+use hashbrown::HashMap;
 use smoltcp::{
     iface::{SocketHandle, SocketSet},
     socket::{raw, tcp, udp},
@@ -25,6 +26,100 @@ lazy_static! {
     /// TODO: 优化这里,自己实现SocketSet!!!现在这样的话,不管全局有多少个网卡,每个时间点都只会有1个进程能够访问socket
     pub static ref SOCKET_SET: SpinLock<SocketSet<'static >> = SpinLock::new(SocketSet::new(vec![]));
     pub static ref SOCKET_WAITQUEUE: WaitQueue = WaitQueue::INIT;
+    /// 端口管理器
+    pub static ref PORT_MANAGER: PortManager = PortManager::new();
+}
+
+/// @brief TCP 和 UDP 的端口管理器。
+/// 如果 TCP/UDP 的 socket 绑定了某个端口,它会在对应的表中记录,以检测端口冲突。
+pub struct PortManager {
+    // TCP 端口记录表
+    tcp_port_table: SpinLock<HashMap<u16, Arc<GlobalSocketHandle>>>,
+    // UDP 端口记录表
+    udp_port_table: SpinLock<HashMap<u16, Arc<GlobalSocketHandle>>>,
+}
+
+impl PortManager {
+    pub fn new() -> Self {
+        return Self {
+            tcp_port_table: SpinLock::new(HashMap::new()),
+            udp_port_table: SpinLock::new(HashMap::new()),
+        };
+    }
+
+    /// @brief 自动分配一个相对应协议中未被使用的PORT,如果动态端口均已被占用,返回错误码 EADDRINUSE
+    pub fn get_ephemeral_port(&self, socket_type: SocketType) -> Result<u16, SystemError> {
+        // TODO selects non-conflict high port
+
+        static mut EPHEMERAL_PORT: u16 = 0;
+        unsafe {
+            if EPHEMERAL_PORT == 0 {
+                EPHEMERAL_PORT = (49152 + rand() % (65536 - 49152)) as u16;
+            }
+        }
+
+        let mut remaining = 65536 - 49152; // 剩余尝试分配端口次数
+        let mut port: u16;
+        while remaining > 0 {
+            unsafe {
+                if EPHEMERAL_PORT == 65535 {
+                    EPHEMERAL_PORT = 49152;
+                } else {
+                    EPHEMERAL_PORT = EPHEMERAL_PORT + 1;
+                }
+                port = EPHEMERAL_PORT;
+            }
+
+            // 使用 ListenTable 检查端口是否被占用
+            let listen_table_guard = match socket_type {
+                SocketType::UdpSocket => self.udp_port_table.lock(),
+                SocketType::TcpSocket => self.tcp_port_table.lock(),
+                SocketType::RawSocket => todo!(),
+            };
+            if let None = listen_table_guard.get(&port) {
+                drop(listen_table_guard);
+                return Ok(port);
+            }
+            remaining -= 1;
+        }
+        return Err(SystemError::EADDRINUSE);
+    }
+
+    /// @brief 检测给定端口是否已被占用,如果未被占用则在 TCP/UDP 对应的表中记录
+    ///
+    /// TODO: 增加支持端口复用的逻辑
+    pub fn get_port(
+        &self,
+        socket_type: SocketType,
+        port: u16,
+        handle: Arc<GlobalSocketHandle>,
+    ) -> Result<(), SystemError> {
+        if port > 0 {
+            let mut listen_table_guard = match socket_type {
+                SocketType::UdpSocket => self.udp_port_table.lock(),
+                SocketType::TcpSocket => self.tcp_port_table.lock(),
+                SocketType::RawSocket => panic!("RawSocket cann't bind a port"),
+            };
+            match listen_table_guard.get(&port) {
+                Some(_) => return Err(SystemError::EADDRINUSE),
+                None => listen_table_guard.insert(port, handle),
+            };
+            drop(listen_table_guard);
+        }
+        return Ok(());
+    }
+
+    /// @brief 在对应的端口记录表中将端口和 socket 解绑
+    pub fn unbind_port(&self, socket_type: SocketType, port: u16) -> Result<(), SystemError> {
+        let mut listen_table_guard = match socket_type {
+            SocketType::UdpSocket => self.udp_port_table.lock(),
+            SocketType::TcpSocket => self.tcp_port_table.lock(),
+            SocketType::RawSocket => return Ok(()),
+        };
+        listen_table_guard.remove(&port);
+        drop(listen_table_guard);
+        return Ok(());
+    }
 }
 
 /* For setsockopt(2) */
@@ -38,8 +133,8 @@ pub const SOL_SOCKET: u8 = 1;
 pub struct GlobalSocketHandle(SocketHandle);
 
 impl GlobalSocketHandle {
-    pub fn new(handle: SocketHandle) -> Self {
-        Self(handle)
+    pub fn new(handle: SocketHandle) -> Arc<Self> {
+        return Arc::new(Self(handle));
     }
 }
 
@@ -59,7 +154,7 @@ impl Drop for GlobalSocketHandle {
 }
 
 /// @brief socket的类型
-#[derive(Debug)]
+#[derive(Debug, Clone, Copy)]
 pub enum SocketType {
     /// 原始的socket
     RawSocket,
@@ -86,7 +181,7 @@ bitflags! {
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 /// @brief 在trait Socket的metadata函数中返回该结构体供外部使用
 pub struct SocketMetadata {
     /// socket的类型
@@ -101,18 +196,36 @@ pub struct SocketMetadata {
     pub options: SocketOptions,
 }
 
+impl SocketMetadata {
+    fn new(
+        socket_type: SocketType,
+        send_buf_size: usize,
+        recv_buf_size: usize,
+        metadata_buf_size: usize,
+        options: SocketOptions,
+    ) -> Self {
+        Self {
+            socket_type,
+            send_buf_size,
+            recv_buf_size,
+            metadata_buf_size,
+            options,
+        }
+    }
+}
+
 /// @brief 表示原始的socket。原始套接字绕过传输层协议(如 TCP 或 UDP)并提供对网络层协议(如 IP)的直接访问。
 ///
 /// ref: https://man7.org/linux/man-pages/man7/raw.7.html
 #[derive(Debug, Clone)]
 pub struct RawSocket {
-    handle: GlobalSocketHandle,
+    handle: Arc<GlobalSocketHandle>,
     /// 用户发送的数据包是否包含了IP头.
     /// 如果是true,用户发送的数据包,必须包含IP头。(即用户要自行设置IP头+数据)
     /// 如果是false,用户发送的数据包,不包含IP头。(即用户只要设置数据)
     header_included: bool,
-    /// socket的选项
-    options: SocketOptions,
+    /// socket的metadata
+    metadata: SocketMetadata,
 }
 
 impl RawSocket {
@@ -147,12 +260,21 @@ impl RawSocket {
         );
 
         // 把socket添加到socket集合中,并得到socket的句柄
-        let handle: GlobalSocketHandle = GlobalSocketHandle::new(SOCKET_SET.lock().add(socket));
+        let handle: Arc<GlobalSocketHandle> =
+            GlobalSocketHandle::new(SOCKET_SET.lock().add(socket));
+
+        let metadata = SocketMetadata::new(
+            SocketType::RawSocket,
+            Self::DEFAULT_RX_BUF_SIZE,
+            Self::DEFAULT_TX_BUF_SIZE,
+            Self::DEFAULT_METADATA_BUF_SIZE,
+            options,
+        );
 
         return Self {
             handle,
             header_included: false,
-            options,
+            metadata,
         };
     }
 }
@@ -177,7 +299,7 @@ impl Socket for RawSocket {
                     );
                 }
                 Err(smoltcp::socket::raw::RecvError::Exhausted) => {
-                    if !self.options.contains(SocketOptions::BLOCK) {
+                    if !self.metadata.options.contains(SocketOptions::BLOCK) {
                         // 如果是非阻塞的socket,就返回错误
                         return (Err(SystemError::EAGAIN_OR_EWOULDBLOCK), Endpoint::Ip(None));
                     }
@@ -271,7 +393,7 @@ impl Socket for RawSocket {
     }
 
     fn metadata(&self) -> Result<SocketMetadata, SystemError> {
-        todo!()
+        Ok(self.metadata.clone())
     }
 
     fn box_clone(&self) -> alloc::boxed::Box<dyn Socket> {
@@ -284,9 +406,9 @@ impl Socket for RawSocket {
 /// https://man7.org/linux/man-pages/man7/udp.7.html
 #[derive(Debug, Clone)]
 pub struct UdpSocket {
-    pub handle: GlobalSocketHandle,
+    pub handle: Arc<GlobalSocketHandle>,
     remote_endpoint: Option<Endpoint>, // 记录远程endpoint提供给connect(), 应该使用IP地址。
-    options: SocketOptions,
+    metadata: SocketMetadata,
 }
 
 impl UdpSocket {
@@ -315,17 +437,29 @@ impl UdpSocket {
         let socket = udp::Socket::new(tx_buffer, rx_buffer);
 
         // 把socket添加到socket集合中,并得到socket的句柄
-        let handle: GlobalSocketHandle = GlobalSocketHandle::new(SOCKET_SET.lock().add(socket));
+        let handle: Arc<GlobalSocketHandle> =
+            GlobalSocketHandle::new(SOCKET_SET.lock().add(socket));
+
+        let metadata = SocketMetadata::new(
+            SocketType::UdpSocket,
+            Self::DEFAULT_RX_BUF_SIZE,
+            Self::DEFAULT_TX_BUF_SIZE,
+            Self::DEFAULT_METADATA_BUF_SIZE,
+            options,
+        );
 
         return Self {
             handle,
             remote_endpoint: None,
-            options,
+            metadata,
         };
     }
 
     fn do_bind(&self, socket: &mut udp::Socket, endpoint: Endpoint) -> Result<(), SystemError> {
         if let Endpoint::Ip(Some(ip)) = endpoint {
+            // 检测端口是否已被占用
+            PORT_MANAGER.get_port(self.metadata.socket_type, ip.port, self.handle.clone())?;
+
             let bind_res = if ip.addr.is_unspecified() {
                 socket.bind(ip.port)
             } else {
@@ -388,7 +522,7 @@ impl Socket for UdpSocket {
         // kdebug!("is open()={}", socket.is_open());
         // kdebug!("socket endpoint={:?}", socket.endpoint());
         if socket.endpoint().port == 0 {
-            let temp_port = get_ephemeral_port();
+            let temp_port = PORT_MANAGER.get_ephemeral_port(self.metadata.socket_type)?;
 
             let local_ep = match remote_endpoint.addr {
                 // 远程remote endpoint使用什么协议,发送的时候使用的协议是一样的吧
@@ -461,7 +595,7 @@ impl Socket for UdpSocket {
         todo!()
     }
     fn metadata(&self) -> Result<SocketMetadata, SystemError> {
-        todo!()
+        Ok(self.metadata.clone())
     }
 
     fn box_clone(&self) -> alloc::boxed::Box<dyn Socket> {
@@ -499,10 +633,10 @@ impl Socket for UdpSocket {
 /// https://man7.org/linux/man-pages/man7/tcp.7.html
 #[derive(Debug, Clone)]
 pub struct TcpSocket {
-    handle: GlobalSocketHandle,
+    handle: Arc<GlobalSocketHandle>,
     local_endpoint: Option<wire::IpEndpoint>, // save local endpoint for bind()
     is_listening: bool,
-    options: SocketOptions,
+    metadata: SocketMetadata,
 }
 
 impl TcpSocket {
@@ -525,13 +659,22 @@ impl TcpSocket {
         let socket = tcp::Socket::new(tx_buffer, rx_buffer);
 
         // 把socket添加到socket集合中,并得到socket的句柄
-        let handle: GlobalSocketHandle = GlobalSocketHandle::new(SOCKET_SET.lock().add(socket));
+        let handle: Arc<GlobalSocketHandle> =
+            GlobalSocketHandle::new(SOCKET_SET.lock().add(socket));
+
+        let metadata = SocketMetadata::new(
+            SocketType::TcpSocket,
+            Self::DEFAULT_RX_BUF_SIZE,
+            Self::DEFAULT_TX_BUF_SIZE,
+            Self::DEFAULT_METADATA_BUF_SIZE,
+            options,
+        );
 
         return Self {
             handle,
             local_endpoint: None,
             is_listening: false,
-            options,
+            metadata,
         };
     }
     fn do_listen(
@@ -546,7 +689,7 @@ impl TcpSocket {
             // kdebug!("Tcp Socket Listen on {local_endpoint}");
             socket.listen(local_endpoint)
         };
-        // todo: 增加端口占用检查
+        // TODO: 增加端口占用检查
         return match listen_result {
             Ok(()) => {
                 // kdebug!(
@@ -668,7 +811,7 @@ impl Socket for TcpSocket {
         let socket = sockets.get_mut::<tcp::Socket>(self.handle.0);
 
         if let Endpoint::Ip(Some(ip)) = endpoint {
-            let temp_port = get_ephemeral_port();
+            let temp_port = PORT_MANAGER.get_ephemeral_port(self.metadata.socket_type)?;
             // kdebug!("temp_port: {}", temp_port);
             let iface: Arc<dyn NetDriver> = NET_DRIVERS.write().get(&0).unwrap().clone();
             let mut inner_iface = iface.inner_iface().lock();
@@ -737,9 +880,12 @@ impl Socket for TcpSocket {
     fn bind(&mut self, endpoint: Endpoint) -> Result<(), SystemError> {
         if let Endpoint::Ip(Some(mut ip)) = endpoint {
             if ip.port == 0 {
-                ip.port = get_ephemeral_port();
+                ip.port = PORT_MANAGER.get_ephemeral_port(self.metadata.socket_type)?;
             }
 
+            // 检测端口是否已被占用
+            PORT_MANAGER.get_port(self.metadata.socket_type, ip.port, self.handle.clone())?;
+
             self.local_endpoint = Some(ip);
             self.is_listening = false;
             return Ok(());
@@ -785,11 +931,19 @@ impl Socket for TcpSocket {
                     let new_handle = GlobalSocketHandle::new(sockets.add(tcp_socket));
                     let old_handle = ::core::mem::replace(&mut self.handle, new_handle);
 
+                    let metadata = SocketMetadata {
+                        socket_type: SocketType::TcpSocket,
+                        send_buf_size: Self::DEFAULT_RX_BUF_SIZE,
+                        recv_buf_size: Self::DEFAULT_TX_BUF_SIZE,
+                        metadata_buf_size: Self::DEFAULT_METADATA_BUF_SIZE,
+                        options: self.metadata.options,
+                    };
+
                     Box::new(TcpSocket {
                         handle: old_handle,
                         local_endpoint: self.local_endpoint,
                         is_listening: false,
-                        options: self.options,
+                        metadata,
                     })
                 };
                 // kdebug!("tcp accept: new socket: {:?}", new_socket);
@@ -825,7 +979,7 @@ impl Socket for TcpSocket {
     }
 
     fn metadata(&self) -> Result<SocketMetadata, SystemError> {
-        todo!()
+        Ok(self.metadata.clone())
     }
 
     fn box_clone(&self) -> alloc::boxed::Box<dyn Socket> {
@@ -833,26 +987,6 @@ impl Socket for TcpSocket {
     }
 }
 
-/// @breif 自动分配一个未被使用的PORT
-///
-/// TODO: 增加ListenTable, 用于检查端口是否被占用
-pub fn get_ephemeral_port() -> u16 {
-    // TODO selects non-conflict high port
-
-    static mut EPHEMERAL_PORT: u16 = 0;
-    unsafe {
-        if EPHEMERAL_PORT == 0 {
-            EPHEMERAL_PORT = (49152 + rand() % (65536 - 49152)) as u16;
-        }
-        if EPHEMERAL_PORT == 65535 {
-            EPHEMERAL_PORT = 49152;
-        } else {
-            EPHEMERAL_PORT = EPHEMERAL_PORT + 1;
-        }
-        EPHEMERAL_PORT
-    }
-}
-
 /// @brief 地址族的枚举
 ///
 /// 参考:https://opengrok.ringotek.cn/xref/linux-5.19.10/include/linux/socket.h#180
@@ -1012,6 +1146,10 @@ impl IndexNode for SocketInode {
         &self,
         _data: &mut crate::filesystem::vfs::FilePrivateData,
     ) -> Result<(), SystemError> {
+        let socket = self.0.lock();
+        if let Some(Endpoint::Ip(Some(ip))) = socket.endpoint() {
+            PORT_MANAGER.unbind_port(socket.metadata().unwrap().socket_type, ip.port)?;
+        }
         return Ok(());
     }
 

+ 0 - 1
kernel/src/process/process.rs

@@ -348,7 +348,6 @@ impl process_control_block {
     }
 }
 
-
 /// @brief 初始化pid=1的进程的stdio
 pub fn init_stdio() -> Result<(), SystemError> {
     if current_pcb().pid != 1 {

+ 27 - 0
user/apps/test_bind/Makefile

@@ -0,0 +1,27 @@
+CC=$(DragonOS_GCC)/x86_64-elf-gcc
+LD=ld
+OBJCOPY=objcopy
+# 修改这里,把它改为你的relibc的sysroot路径
+RELIBC_OPT=$(DADK_BUILD_CACHE_DIR_RELIBC_0_1_0)
+CFLAGS=-I $(RELIBC_OPT)/include
+
+tmp_output_dir=$(ROOT_PATH)/bin/tmp/user
+output_dir=$(DADK_BUILD_CACHE_DIR_HTTP_SERVER_0_1_0)
+
+LIBC_OBJS:=$(shell find $(RELIBC_OPT)/lib -name "*.o" | sort )
+LIBC_OBJS+=$(RELIBC_OPT)/lib/libc.a
+
+all: main.o
+	mkdir -p $(tmp_output_dir)
+	
+	$(LD) -b elf64-x86-64 -z muldefs -o $(tmp_output_dir)/test_bind  $(shell find . -name "*.o") $(LIBC_OBJS) -T link.lds
+
+	$(OBJCOPY) -I elf64-x86-64 -R ".eh_frame" -R ".comment" -O elf64-x86-64 $(tmp_output_dir)/test_bind $(output_dir)/test_bind.elf
+
+	mv $(output_dir)/test_bind.elf $(output_dir)/test_bind
+	
+main.o: main.c
+	$(CC) $(CFLAGS) -c main.c  -o main.o
+
+clean:
+	rm -f *.o

+ 239 - 0
user/apps/test_bind/link.lds

@@ -0,0 +1,239 @@
+/* Script for -z combreloc */
+/* Copyright (C) 2014-2020 Free Software Foundation, Inc.
+   Copying and distribution of this script, with or without modification,
+   are permitted in any medium without royalty provided the copyright
+   notice and this notice are preserved.  */
+OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
+              "elf64-x86-64")
+OUTPUT_ARCH(i386:x86-64)
+ENTRY(_start)
+
+SECTIONS
+{
+  /* Read-only sections, merged into text segment: */
+  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x20000000) + SIZEOF_HEADERS;
+  .interp         : { *(.interp) }
+  .note.gnu.build-id  : { *(.note.gnu.build-id) }
+  .hash           : { *(.hash) }
+  .gnu.hash       : { *(.gnu.hash) }
+  .dynsym         : { *(.dynsym) }
+  .dynstr         : { *(.dynstr) }
+  .gnu.version    : { *(.gnu.version) }
+  .gnu.version_d  : { *(.gnu.version_d) }
+  .gnu.version_r  : { *(.gnu.version_r) }
+  .rela.dyn       :
+    {
+      *(.rela.init)
+      *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
+      *(.rela.fini)
+      *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
+      *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
+      *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
+      *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
+      *(.rela.ctors)
+      *(.rela.dtors)
+      *(.rela.got)
+      *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
+      *(.rela.ldata .rela.ldata.* .rela.gnu.linkonce.l.*)
+      *(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*)
+      *(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*)
+      *(.rela.ifunc)
+    }
+  .rela.plt       :
+    {
+      *(.rela.plt)
+      PROVIDE_HIDDEN (__rela_iplt_start = .);
+      *(.rela.iplt)
+      PROVIDE_HIDDEN (__rela_iplt_end = .);
+    }
+  . = ALIGN(CONSTANT (MAXPAGESIZE));
+  .init           :
+  {
+    KEEP (*(SORT_NONE(.init)))
+  }
+  .plt            : { *(.plt) *(.iplt) }
+.plt.got        : { *(.plt.got) }
+.plt.sec        : { *(.plt.sec) }
+  .text           :
+  {
+    *(.text.unlikely .text.*_unlikely .text.unlikely.*)
+    *(.text.exit .text.exit.*)
+    *(.text.startup .text.startup.*)
+    *(.text.hot .text.hot.*)
+    *(.text .stub .text.* .gnu.linkonce.t.*)
+    /* .gnu.warning sections are handled specially by elf.em.  */
+    *(.gnu.warning)
+  }
+  .fini           :
+  {
+    KEEP (*(SORT_NONE(.fini)))
+  }
+  PROVIDE (__etext = .);
+  PROVIDE (_etext = .);
+  PROVIDE (etext = .);
+  . = ALIGN(CONSTANT (MAXPAGESIZE));
+  /* Adjust the address for the rodata segment.  We want to adjust up to
+     the same address within the page on the next page up.  */
+  . = SEGMENT_START("rodata-segment", ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)));
+  .rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
+  .rodata1        : { *(.rodata1) }
+  .eh_frame_hdr   : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) }
+  .eh_frame       : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) }
+  .gcc_except_table   : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) }
+  .gnu_extab   : ONLY_IF_RO { *(.gnu_extab*) }
+  /* These sections are generated by the Sun/Oracle C++ compiler.  */
+  .exception_ranges   : ONLY_IF_RO { *(.exception_ranges*) }
+  /* Adjust the address for the data segment.  We want to adjust up to
+     the same address within the page on the next page up.  */
+  . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
+  /* Exception handling  */
+  .eh_frame       : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) }
+  .gnu_extab      : ONLY_IF_RW { *(.gnu_extab) }
+  .gcc_except_table   : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
+  .exception_ranges   : ONLY_IF_RW { *(.exception_ranges*) }
+  /* Thread Local Storage sections  */
+  .tdata          :
+   {
+     PROVIDE_HIDDEN (__tdata_start = .);
+     *(.tdata .tdata.* .gnu.linkonce.td.*)
+   }
+  .tbss           : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
+  .preinit_array    :
+  {
+    PROVIDE_HIDDEN (__preinit_array_start = .);
+    KEEP (*(.preinit_array))
+    PROVIDE_HIDDEN (__preinit_array_end = .);
+  }
+  .init_array    :
+  {
+    PROVIDE_HIDDEN (__init_array_start = .);
+    KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
+    KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
+    PROVIDE_HIDDEN (__init_array_end = .);
+  }
+  .fini_array    :
+  {
+    PROVIDE_HIDDEN (__fini_array_start = .);
+    KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
+    KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
+    PROVIDE_HIDDEN (__fini_array_end = .);
+  }
+  .ctors          :
+  {
+    /* gcc uses crtbegin.o to find the start of
+       the constructors, so we make sure it is
+       first.  Because this is a wildcard, it
+       doesn't matter if the user does not
+       actually link against crtbegin.o; the
+       linker won't look for a file to match a
+       wildcard.  The wildcard also means that it
+       doesn't matter which directory crtbegin.o
+       is in.  */
+    KEEP (*crtbegin.o(.ctors))
+    KEEP (*crtbegin?.o(.ctors))
+    /* We don't want to include the .ctor section from
+       the crtend.o file until after the sorted ctors.
+       The .ctor section from the crtend file contains the
+       end of ctors marker and it must be last */
+    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
+    KEEP (*(SORT(.ctors.*)))
+    KEEP (*(.ctors))
+  }
+  .dtors          :
+  {
+    KEEP (*crtbegin.o(.dtors))
+    KEEP (*crtbegin?.o(.dtors))
+    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
+    KEEP (*(SORT(.dtors.*)))
+    KEEP (*(.dtors))
+  }
+  .jcr            : { KEEP (*(.jcr)) }
+  .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) }
+  .dynamic        : { *(.dynamic) }
+  .got            : { *(.got) *(.igot) }
+  . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .);
+  .got.plt        : { *(.got.plt) *(.igot.plt) }
+  .data           :
+  {
+    *(.data .data.* .gnu.linkonce.d.*)
+    SORT(CONSTRUCTORS)
+  }
+  .data1          : { *(.data1) }
+  _edata = .; PROVIDE (edata = .);
+  . = .;
+  __bss_start = .;
+  .bss            :
+  {
+   *(.dynbss)
+   *(.bss .bss.* .gnu.linkonce.b.*)
+   *(COMMON)
+   /* Align here to ensure that the .bss section occupies space up to
+      _end.  Align after .bss to ensure correct alignment even if the
+      .bss section disappears because there are no input sections.
+      FIXME: Why do we need it? When there is no .bss section, we do not
+      pad the .data section.  */
+   . = ALIGN(. != 0 ? 64 / 8 : 1);
+  }
+  .lbss   :
+  {
+    *(.dynlbss)
+    *(.lbss .lbss.* .gnu.linkonce.lb.*)
+    *(LARGE_COMMON)
+  }
+  . = ALIGN(64 / 8);
+  . = SEGMENT_START("ldata-segment", .);
+  .lrodata   ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
+  {
+    *(.lrodata .lrodata.* .gnu.linkonce.lr.*)
+  }
+  .ldata   ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
+  {
+    *(.ldata .ldata.* .gnu.linkonce.l.*)
+    . = ALIGN(. != 0 ? 64 / 8 : 1);
+  }
+  . = ALIGN(64 / 8);
+  _end = .; PROVIDE (end = .);
+  . = DATA_SEGMENT_END (.);
+  /* Stabs debugging sections.  */
+  .stab          0 : { *(.stab) }
+  .stabstr       0 : { *(.stabstr) }
+  .stab.excl     0 : { *(.stab.excl) }
+  .stab.exclstr  0 : { *(.stab.exclstr) }
+  .stab.index    0 : { *(.stab.index) }
+  .stab.indexstr 0 : { *(.stab.indexstr) }
+  .comment       0 : { *(.comment) }
+  .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }
+  /* DWARF debug sections.
+     Symbols in the DWARF debugging sections are relative to the beginning
+     of the section so we begin them at 0.  */
+  /* DWARF 1 */
+  .debug          0 : { *(.debug) }
+  .line           0 : { *(.line) }
+  /* GNU DWARF 1 extensions */
+  .debug_srcinfo  0 : { *(.debug_srcinfo) }
+  .debug_sfnames  0 : { *(.debug_sfnames) }
+  /* DWARF 1.1 and DWARF 2 */
+  .debug_aranges  0 : { *(.debug_aranges) }
+  .debug_pubnames 0 : { *(.debug_pubnames) }
+  /* DWARF 2 */
+  .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
+  .debug_abbrev   0 : { *(.debug_abbrev) }
+  .debug_line     0 : { *(.debug_line .debug_line.* .debug_line_end) }
+  .debug_frame    0 : { *(.debug_frame) }
+  .debug_str      0 : { *(.debug_str) }
+  .debug_loc      0 : { *(.debug_loc) }
+  .debug_macinfo  0 : { *(.debug_macinfo) }
+  /* SGI/MIPS DWARF 2 extensions */
+  .debug_weaknames 0 : { *(.debug_weaknames) }
+  .debug_funcnames 0 : { *(.debug_funcnames) }
+  .debug_typenames 0 : { *(.debug_typenames) }
+  .debug_varnames  0 : { *(.debug_varnames) }
+  /* DWARF 3 */
+  .debug_pubtypes 0 : { *(.debug_pubtypes) }
+  .debug_ranges   0 : { *(.debug_ranges) }
+  /* DWARF Extension.  */
+  .debug_macro    0 : { *(.debug_macro) }
+  .debug_addr     0 : { *(.debug_addr) }
+  .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
+  /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
+}

+ 234 - 0
user/apps/test_bind/main.c

@@ -0,0 +1,234 @@
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define PORT 12580
+#define MAX_REQUEST_SIZE 1500
+#define MAX_RESPONSE_SIZE 1500
+#define EXIT_CODE 1
+#define min(a, b) ((a) < (b) ? (a) : (b))
+
+struct sockaddr_in address;
+int addrlen = sizeof(address);
+char buffer[MAX_REQUEST_SIZE] = {0};
+int opt = 1;
+
+void test_tcp_bind()
+{
+    int tcp_sk_fd1, tcp_sk_fd2, tcp_sk_fd3;
+    
+    // create tcp sockets
+    if ((tcp_sk_fd1 = socket(AF_INET, SOCK_STREAM, 0)) == 0)
+    {
+        perror("tcp socket (1) failed");
+        exit(EXIT_CODE);
+    }
+    if ((tcp_sk_fd2 = socket(AF_INET, SOCK_STREAM, 0)) == 0)
+    {
+        perror("tcp socket (2) failed");
+        exit(EXIT_CODE);
+    }
+    if ((tcp_sk_fd3 = socket(AF_INET, SOCK_STREAM, 0)) == 0)
+    {
+        perror("tcp socket (3) failed");
+        exit(EXIT_CODE);
+    }
+    
+    // TEST tcp bind diff ports
+    if (bind(tcp_sk_fd1, (struct sockaddr *)&address, sizeof(address)) < 0)
+    {
+        perror("tcp bind (1) failed");
+        exit(EXIT_CODE);
+    }
+    address.sin_port = htons(PORT+1);
+    if (bind(tcp_sk_fd2, (struct sockaddr *)&address, sizeof(address)) < 0)
+    {
+        perror("tcp bind (2) failed");
+        exit(EXIT_CODE);
+    }
+    printf("===TEST 4 PASSED===\n");
+    
+    // TEST tcp bind same ports
+    address.sin_port = htons(PORT);
+    if (bind(tcp_sk_fd3, (struct sockaddr *)&address, sizeof(address)) < 0)
+    {
+        perror("tcp bind (3) failed");
+        // exit(EXIT_CODE);
+    }
+    printf("===TEST 5 PASSED===\n");
+    
+    if (close(tcp_sk_fd1) < 0)
+    {
+        perror("tcp close (1) failed");
+        exit(EXIT_CODE);
+    }
+    if (close(tcp_sk_fd2) < 0)
+    {
+        perror("tcp close (2) failed");
+        exit(EXIT_CODE);
+    }
+    if (close(tcp_sk_fd3) < 0)
+    {
+        perror("tcp close (3) failed");
+        exit(EXIT_CODE);
+    }
+    printf("===TEST 6 PASSED===\n");
+}
+
+void test_udp_bind()
+{
+    int udp_sk_fd1, udp_sk_fd2, udp_sk_fd3;
+    
+    // create tcp sockets
+    if ((udp_sk_fd1 = socket(AF_INET, SOCK_DGRAM, 0)) == 0)
+    {
+        perror("udp socket (1) failed");
+        exit(EXIT_CODE);
+    }
+    if ((udp_sk_fd2 = socket(AF_INET, SOCK_DGRAM, 0)) == 0)
+    {
+        perror("udp socket (2) failed");
+        exit(EXIT_CODE);
+    }
+    if ((udp_sk_fd3 = socket(AF_INET, SOCK_DGRAM, 0)) == 0)
+    {
+        perror("udp socket (3) failed");
+        exit(EXIT_CODE);
+    }
+    
+    // TEST udp bind diff ports
+    if (bind(udp_sk_fd1, (struct sockaddr *)&address, sizeof(address)) < 0)
+    {
+        perror("udp bind (1) failed");
+        exit(EXIT_CODE);
+    }
+    address.sin_port = htons(PORT+1);
+    if (bind(udp_sk_fd2, (struct sockaddr *)&address, sizeof(address)) < 0)
+    {
+        perror("udp bind (2) failed");
+        exit(EXIT_CODE);
+    }
+    printf("===TEST 7 PASSED===\n");
+    
+    // TEST udp bind same ports
+    address.sin_port = htons(PORT);
+    if (bind(udp_sk_fd3, (struct sockaddr *)&address, sizeof(address)) < 0)
+    {
+        perror("udp bind (3) failed");
+        // exit(EXIT_CODE);
+    }
+    printf("===TEST 8 PASSED===\n");
+    
+    if (close(udp_sk_fd1) < 0)
+    {
+        perror("udp close (1) failed");
+        exit(EXIT_CODE);
+    }
+    if (close(udp_sk_fd2) < 0)
+    {
+        perror("udp close (2) failed");
+        exit(EXIT_CODE);
+    }
+    if (close(udp_sk_fd3) < 0)
+    {
+        perror("udp close (3) failed");
+        exit(EXIT_CODE);
+    }
+    printf("===TEST 9 PASSED===\n");
+}
+
+void test_all_ports() {
+    int count = 0;
+    
+    while (1) {
+    	int tcp_fd;
+    	if ((tcp_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
+    	{
+            perror("socket failed");
+            exit(EXIT_CODE);
+    	}
+    	
+    	address.sin_port = htons(0);
+    	if (bind(tcp_fd, (struct sockaddr *)&address, sizeof(address)) < 0)
+    	{
+            perror("bind failed");
+            // exit(EXIT_CODE);
+            break;
+    	}
+    	
+    	count++;
+    }
+    printf("===TEST 10===\n");
+    printf("count: %d\n", count);
+}
+
+int main(int argc, char const *argv[])
+{
+    int server_fd;
+    int udp_sk_fd;
+
+    // 创建socket
+    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
+    {
+        perror("tcp socket failed");
+        exit(EXIT_CODE);
+    }
+    
+    if ((udp_sk_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0)
+    {
+        perror("udp socket failed");
+        exit(EXIT_CODE);
+    }
+
+    // 设置地址和端口
+    address.sin_family = AF_INET;
+    address.sin_addr.s_addr = INADDR_ANY;
+    address.sin_port = htons(PORT);
+
+    // TEST socket's bind
+    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0)
+    {
+        perror("tcp bind failed");
+        exit(EXIT_CODE);
+    }
+    address.sin_port = htons(PORT);
+    if (bind(udp_sk_fd, (struct sockaddr *)&address, sizeof(address)) < 0)
+    {
+        perror("udp bind failed");
+        exit(EXIT_CODE);
+    }
+    printf("===TEST 1 PASSED===\n");
+
+    // TEST socket's listen
+    if (listen(server_fd, 3) < 0)
+    {
+        perror("listen failed");
+        exit(EXIT_CODE);
+    }
+    printf("===TEST 2 PASSED===\n");
+
+    // TEST socket's close
+    if (close(server_fd) < 0)
+    {
+        perror("tcp close failed");
+        exit(EXIT_CODE);
+    }
+    if (close(udp_sk_fd) < 0)
+    {
+        perror("udp close failed");
+        exit(EXIT_CODE);
+    }
+    printf("===TEST 3 PASSED===\n");
+    
+    
+    test_tcp_bind();
+    test_udp_bind();
+    test_all_ports();
+
+    return 0;
+}

+ 28 - 0
user/dadk/config/test_bind-0.1.0.dadk

@@ -0,0 +1,28 @@
+{
+  "name": "test_bind",
+  "version": "0.1.0",
+  "description": "一个简单的test bind",
+  "task_type": {
+    "BuildFromSource": {
+      "Local": {
+        "path": "apps/test_bind"
+      }
+    }
+  },
+  "depends": [
+    {
+      "name": "relibc",
+      "version": "0.1.0"
+    }
+  ],
+  "build": {
+    "build_command": "make"
+  },
+  "install": {
+    "in_dragonos_path": "/bin"
+  },
+  "clean": {
+    "clean_command": "make clean"
+  },
+  "envs": []
+}