@@ -18,26 +18,96 @@ limitations under the License.
1818
1919use crate :: domain:: NetworkInterface ;
2020
21- /// Parse network interfaces from ip command output
21+ /// Parse network interfaces from `ip addr show` command output
2222pub fn parse_ip_output ( ip_output : & str ) -> Result < Vec < NetworkInterface > , String > {
2323 let mut interfaces = Vec :: new ( ) ;
24+ let mut current_interface: Option < NetworkInterface > = None ;
2425
25- // Simplified parsing - real implementation would be more comprehensive
2626 for line in ip_output. lines ( ) {
27- if line. contains ( "eth" ) || line. contains ( "ens" ) {
28- interfaces. push ( NetworkInterface {
29- name : "eth0" . to_string ( ) ,
30- mac : "00:00:00:00:00:00" . to_string ( ) ,
31- ip : "192.168.1.100" . to_string ( ) ,
32- prefix : "24" . to_string ( ) ,
33- speed : Some ( "1000 Mbps" . to_string ( ) ) ,
34- type_ : "Ethernet" . to_string ( ) ,
35- vendor : "Unknown" . to_string ( ) ,
36- model : "Unknown" . to_string ( ) ,
37- pci_id : "Unknown" . to_string ( ) ,
38- numa_node : None ,
39- ..Default :: default ( )
40- } ) ;
27+ let trimmed = line. trim ( ) ;
28+
29+ // Interface line starts with a number followed by colon
30+ // Example: "2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ..."
31+ if !line. starts_with ( ' ' ) && line. contains ( ": " ) {
32+ // Save previous interface if it exists (and has a name)
33+ if let Some ( interface) = current_interface. take ( ) {
34+ // Skip loopback
35+ if interface. name != "lo" {
36+ interfaces. push ( interface) ;
37+ }
38+ }
39+
40+ // Extract interface name: "2: eth0:" -> "eth0"
41+ let parts: Vec < & str > = line. splitn ( 3 , ':' ) . collect ( ) ;
42+ if parts. len ( ) >= 2 {
43+ let name = parts[ 1 ] . trim ( ) . to_string ( ) ;
44+ // Skip loopback interface
45+ if name == "lo" {
46+ continue ;
47+ }
48+
49+ // Determine interface type based on name
50+ let interface_type = if name. starts_with ( "tailscale" ) {
51+ "tailscale" . to_string ( )
52+ } else if name. starts_with ( "wl" ) {
53+ "wireless" . to_string ( )
54+ } else if name. starts_with ( "docker" ) || name. starts_with ( "br-" ) {
55+ "bridge" . to_string ( )
56+ } else if name. starts_with ( "veth" ) {
57+ "veth" . to_string ( )
58+ } else {
59+ "ethernet" . to_string ( )
60+ } ;
61+
62+ current_interface = Some ( NetworkInterface {
63+ name,
64+ mac : String :: new ( ) ,
65+ ip : String :: new ( ) ,
66+ prefix : String :: new ( ) ,
67+ speed : None ,
68+ type_ : interface_type,
69+ vendor : "Unknown" . to_string ( ) ,
70+ model : "Unknown" . to_string ( ) ,
71+ pci_id : "Unknown" . to_string ( ) ,
72+ numa_node : None ,
73+ ..Default :: default ( )
74+ } ) ;
75+ }
76+ } else if let Some ( ref mut interface) = current_interface {
77+ // Parse interface details (indented lines)
78+
79+ // MAC address line: " link/ether 00:11:22:33:44:55 brd ff:ff:ff:ff:ff:ff"
80+ if trimmed. starts_with ( "link/ether " ) {
81+ if let Some ( mac) = trimmed. split_whitespace ( ) . nth ( 1 ) {
82+ interface. mac = mac. to_string ( ) ;
83+ }
84+ }
85+ // Point-to-point interfaces (like Tailscale) have "link/none"
86+ else if trimmed. starts_with ( "link/none" ) {
87+ interface. mac = "00:00:00:00:00:00" . to_string ( ) ;
88+ }
89+ // IPv4 address line: " inet 192.168.1.100/24 ..."
90+ else if trimmed. starts_with ( "inet " ) {
91+ if let Some ( addr_with_prefix) = trimmed. split_whitespace ( ) . nth ( 1 ) {
92+ let parts: Vec < & str > = addr_with_prefix. split ( '/' ) . collect ( ) ;
93+ if !parts. is_empty ( ) {
94+ // Only set IP if not already set (first IPv4 address wins)
95+ if interface. ip . is_empty ( ) {
96+ interface. ip = parts[ 0 ] . to_string ( ) ;
97+ if parts. len ( ) > 1 {
98+ interface. prefix = parts[ 1 ] . to_string ( ) ;
99+ }
100+ }
101+ }
102+ }
103+ }
104+ }
105+ }
106+
107+ // Don't forget the last interface
108+ if let Some ( interface) = current_interface {
109+ if interface. name != "lo" {
110+ interfaces. push ( interface) ;
41111 }
42112 }
43113
0 commit comments