@@ -24,6 +24,37 @@ pub async fn search_gateway(options: SearchOptions) -> Result<Gateway, SearchErr
24
24
25
25
let ( addr, root_url) = handle_broadcast_resp ( & from, & response_body) ?;
26
26
27
+ match ( & from, & addr) {
28
+ ( SocketAddr :: V4 ( src_ip) , SocketAddr :: V4 ( url_ip) ) => {
29
+ if src_ip. ip ( ) != url_ip. ip ( ) {
30
+ return Err ( SearchError :: SpoofedIp {
31
+ src_ip : ( * src_ip. ip ( ) ) . into ( ) ,
32
+ url_ip : ( * url_ip. ip ( ) ) . into ( ) ,
33
+ } ) ;
34
+ }
35
+ }
36
+ ( SocketAddr :: V6 ( src_ip) , SocketAddr :: V6 ( url_ip) ) => {
37
+ if src_ip. ip ( ) != url_ip. ip ( ) {
38
+ return Err ( SearchError :: SpoofedIp {
39
+ src_ip : ( * src_ip. ip ( ) ) . into ( ) ,
40
+ url_ip : ( * url_ip. ip ( ) ) . into ( ) ,
41
+ } ) ;
42
+ }
43
+ }
44
+ ( SocketAddr :: V6 ( src_ip) , SocketAddr :: V4 ( url_ip) ) => {
45
+ return Err ( SearchError :: SpoofedIp {
46
+ src_ip : ( * src_ip. ip ( ) ) . into ( ) ,
47
+ url_ip : ( * url_ip. ip ( ) ) . into ( ) ,
48
+ } )
49
+ }
50
+ ( SocketAddr :: V4 ( src_ip) , SocketAddr :: V6 ( url_ip) ) => {
51
+ return Err ( SearchError :: SpoofedIp {
52
+ src_ip : ( * src_ip. ip ( ) ) . into ( ) ,
53
+ url_ip : ( * url_ip. ip ( ) ) . into ( ) ,
54
+ } )
55
+ }
56
+ }
57
+
27
58
let ( control_schema_url, control_url) =
28
59
run_with_timeout ( options. http_timeout , get_control_urls ( & addr, & root_url) ) . await ??;
29
60
let control_schema =
@@ -126,3 +157,47 @@ async fn get_control_schemas(
126
157
let c = std:: io:: Cursor :: new ( & resp) ;
127
158
parsing:: parse_schemas ( c)
128
159
}
160
+
161
+ #[ cfg( test) ]
162
+ mod tests {
163
+ use super :: * ;
164
+ use std:: {
165
+ net:: { Ipv4Addr , SocketAddrV4 } ,
166
+ time:: Duration ,
167
+ } ;
168
+ use test_log:: test;
169
+
170
+ #[ test( tokio:: test) ]
171
+ async fn ip_spoofing_in_broadcast_response ( ) {
172
+ let port = {
173
+ // Not 100% reliable way to find a free port number, but should be good enough
174
+ let sock = UdpSocket :: bind ( ( Ipv4Addr :: UNSPECIFIED , 0 ) ) . await . unwrap ( ) ;
175
+ sock. local_addr ( ) . unwrap ( ) . port ( )
176
+ } ;
177
+
178
+ let options = SearchOptions {
179
+ bind_addr : SocketAddr :: V4 ( SocketAddrV4 :: new ( Ipv4Addr :: UNSPECIFIED , port) ) ,
180
+ timeout : Some ( Duration :: from_secs ( 5 ) ) ,
181
+ http_timeout : Some ( Duration :: from_secs ( 1 ) ) ,
182
+ ..Default :: default ( )
183
+ } ;
184
+
185
+ tokio:: spawn ( async move {
186
+ tokio:: time:: sleep ( Duration :: from_secs ( 1 ) ) . await ;
187
+
188
+ let sock = UdpSocket :: bind ( ( Ipv4Addr :: LOCALHOST , 0 ) ) . await . unwrap ( ) ;
189
+
190
+ sock. send_to ( b"location: http://1.2.3.4:5/test" , ( Ipv4Addr :: LOCALHOST , port) )
191
+ . await
192
+ . unwrap ( ) ;
193
+ } ) ;
194
+
195
+ let result = search_gateway ( options) . await ;
196
+ if let Err ( SearchError :: SpoofedIp { src_ip, url_ip } ) = result {
197
+ assert_eq ! ( src_ip, Ipv4Addr :: LOCALHOST ) ;
198
+ assert_eq ! ( url_ip, Ipv4Addr :: new( 1 , 2 , 3 , 4 ) ) ;
199
+ } else {
200
+ panic ! ( "Unexpected result: {result:?}" ) ;
201
+ }
202
+ }
203
+ }
0 commit comments