sim.js
1 2 3 var visited_host = []; 4 5 var private_subnets = [ 6 {'hid':'__none__', 'rid':'private_class_A', 'route':'10.0.0.0/8', 'gate':'0.0.0.0', 'route_edit':'false', 'gate_edit':'false', 'h':{'type':'internet'}}, 7 {'hid':'__none__', 'rid':'private_class_B', 'route':'172.16.0.0/12', 'gate':'0.0.0.0', 'route_edit':'false', 'gate_edit':'false', 'h':{'type':'internet'}}, 8 {'hid':'__none__', 'rid':'private_class_C', 'route':'192.168.0.0/16', 'gate':'0.0.0.0', 'route_edit':'false', 'gate_edit':'false', 'h':{'type':'internet'}} 9 ]; 10 11 // simulate network 12 13 14 function ip_to_int(s) 15 { 16 var tab = s.split('.'); 17 tab.forEach((el, idx) => {this[idx] = parseInt(el);}); 18 if (tab.length != 4) return (null); 19 if (isNaN(tab[0]) || tab[0] < 0 || tab[0] > 223 || isNaN(tab[1]) || tab[1] < 0 || tab[1] > 255 || 20 isNaN(tab[2]) || tab[2] < 0 || tab[2] > 255 || isNaN(tab[3]) || tab[3] < 0 || tab[3] > 255) return (null); 21 if (tab[0] == 127) { g_sim_logs += "loopback address detected on outside interface\n"; return (null); } // maybe not the best option to deal with 127/8 loopback addresses... 22 return ( ( (tab[0] << 24) | (tab[1] << 16) | (tab[2] << 8) | (tab[3]) ) >>> 0); 23 } 24 25 function mask_to_int(s) 26 { 27 if (s.length == 0) return (null); 28 if (s[0] == '/') 29 { 30 var cidr = parseInt(s.substring(1)); 31 if (isNaN(cidr) || cidr < 0 || cidr > 32) return (null); 32 if (cidr == 32) return ((-1)>>>0); 33 return ( ((((1 << cidr)>>>0)-1) << (32-cidr))>>>0 ); 34 } 35 var tab = s.split('.'); 36 tab.forEach((el, idx) => {this[idx] = parseInt(el);}); 37 if (tab.length != 4) return (null); 38 if (isNaN(tab[0]) || tab[0] < 0 || tab[0] > 255 || isNaN(tab[1]) || tab[1] < 0 || tab[1] > 255 || 39 isNaN(tab[2]) || tab[2] < 0 || tab[2] > 255 || isNaN(tab[3]) || tab[3] < 0 || tab[3] > 255) return (null); 40 if (tab[0] != 255 && (tab[1] != 0 || tab[2] != 0 || tab[3] != 0)) return (null); 41 if (tab[0] == 255 && tab[1] != 255 && (tab[2] != 0 || tab[3] != 0)) return (null); 42 if (tab[0] == 255 && tab[1] == 255 && tab[2] != 255 && tab[3] != 0) return (null); 43 // magic trick to check if we have continuity of 1 then 0 44 var mask = ( ( ( tab[0] << 24) | (( tab[1] ) << 16) | (( tab[2] ) << 8) | ( tab[3] ) ) >>> 0); 45 if (mask == 0) return (0); 46 if ( ( ((~mask)+1) & (~mask) ) == 0) 47 return (mask); 48 return (null); 49 } 50 51 52 53 // mask on interface 54 function get_if_mask_str(itf) 55 { 56 if (itf['mask_edit'] == 'true') 57 return (document.getElementById('mask_'+itf['if']).value); 58 return (itf['mask']); 59 } 60 function get_if_mask(itf) 61 { 62 return (mask_to_int(get_if_mask_str(itf))); 63 } 64 65 // ip on interface 66 function get_if_ip_str(itf) 67 { 68 if (itf['ip_edit'] == 'true') 69 return (document.getElementById('ip_'+itf['if']).value); 70 return (itf['ip']); 71 } 72 function get_if_ip(itf) 73 { 74 var the_ip = ip_to_int(get_if_ip_str(itf)); 75 // check if ip is not the network or broadcast address 76 var the_mask = get_if_mask(itf); 77 if ( (the_ip & (~the_mask)) == 0 || 78 (the_ip & (~the_mask)) == (~the_mask) ) 79 return (null); 80 return (the_ip); 81 } 82 83 84 // route in routes 85 function get_route_route_str(r) 86 { 87 if (r['route_edit'] == 'true') 88 return (document.getElementById('route_'+r['rid']).value); 89 return (r['route']); 90 } 91 92 // gate in routes 93 function get_route_gate_str(r) 94 { 95 if (r['gate_edit'] == 'true') 96 return (document.getElementById('gate_'+r['rid']).value); 97 return (r['gate']); 98 } 99 function get_route_gate(r) 100 { 101 return (ip_to_int(get_route_gate_str(r))); 102 } 103 104 105 106 function ip_match_if(ip, itf) 107 { 108 var iip, imask; 109 if ((iip = get_if_ip(itf)) === null) { g_sim_logs += 'on interface '+itf['if']+': invalid IP address\n'; return (0); } 110 if ((imask = get_if_mask(itf)) === null) { g_sim_logs += 'on interface '+itf['if']+': invalid netmask\n'; return (0); } 111 // my_console_log("## "+iip+" & "+imask+" == "+ip+" & "+imask); 112 if (iip == ip) { g_sim_logs += "duplicate IP ("+get_if_ip_str(itf)+")\n"; return (0); } // ip_match_if is called only on output, not on arrival 113 if ((iip & imask) == (ip & imask)) 114 { 115 // if ip match the interface network, check that the ip is not the network addr or broadcast ? 116 return (1); 117 } 118 return (0); 119 } 120 121 122 function ip_match_route(ip, r) 123 { 124 var str, rip, rmask; 125 str = get_route_route_str(r); 126 if (str == 'default') str = '0.0.0.0/0'; 127 // my_console_log("ip_match_route route :"+JSON.stringify(r)); 128 if (r['h']['type'] == "internet" && str == '0.0.0.0/0') 129 { g_sim_logs += 'invalid default route on internet '+r['hid']+'\n'; return (0); } 130 var tab = str.split('/'); 131 // my_console_log("ip_match_route check : "+str+" againt ip "+ip); 132 if (tab.length != 2) 133 { g_sim_logs += 'invalid route on host '+r['hid']+'\n'; return (0); } 134 if ((rip = ip_to_int(tab[0])) === null) 135 { g_sim_logs += 'invalid route on host '+r['hid']+'\n'; return (0); } 136 if ((rmask = mask_to_int('/'+tab[1])) === null) 137 { g_sim_logs += 'invalid route on host '+r['hid']+'\n'; return (0); } 138 if ((rip & rmask) == (ip & rmask)) return (1); 139 return (0); 140 } 141 142 143 144 145 function rec_route(ip_dest, local_target, input_itf, h) // return array of dest itf 146 { 147 var i, nbif, nb_routes, ret, j; 148 var itf_ip; 149 150 if (input_itf != null) 151 my_console_log(" ** to "+ip_dest+" / host "+h['id']+" input itf "+input_itf['if']+" / to match local target "+local_target); 152 else 153 my_console_log(" ** to "+ip_dest+" / host "+h['id']); 154 155 // loop detection here 156 if (visited_host.includes(h)) { g_sim_logs += "on "+h['id']+' : loop detected\n'; return ([]); } 157 visited_host.push(h); 158 159 // if switch : rec_route to all links 160 if (h['type'] == 'switch') 161 { 162 g_sim_logs += 'on switch '+h['id']+': pass to all connections\n'; 163 ret = []; 164 links.forEach(l => {if (l['e1']['hid'] == h['id']) ret = ret.concat(rec_route(ip_dest, local_target, l['e2'], l['h2'])); 165 else if (l['e2']['hid'] == h['id']) ret = ret.concat(rec_route(ip_dest, local_target, l['e1'], l['h1']))}); 166 return (ret); 167 } 168 169 // on a host, is my current gate ip == the ip of the input itf ? 170 if (input_itf != null) 171 { 172 if ((itf_ip = get_if_ip(input_itf)) === null) { g_sim_logs += 'on '+h['id']+': invalid IP on input interface '+input_itf['if']+'\n'; return ([]); } 173 if (itf_ip != local_target) { g_sim_logs += 'on '+h['id']+' : packet not for me\n'; return ([]); } 174 } 175 176 // accepted on host 177 g_sim_logs += 'on '+h['id']+' : packet accepted\n'; 178 179 // internet does no route private addresses, so "internet interface" reject private subnets 180 if (h['type'] == 'internet') 181 { 182 if (ip_match_route(ip_dest, private_subnets[0]) || ip_match_route(ip_dest, private_subnets[1]) || ip_match_route(ip_dest, private_subnets[2])) 183 { g_sim_logs += 'private subnets not routed over internet\n'; return ([]); } 184 } 185 186 // arrived ? check not only input itf, in case another itf is the target; do not check if input_itf is null (departure host) 187 if (input_itf != null) 188 { 189 ret = []; 190 ifs.forEach(itf => {if (itf['hid'] == h['id'] && (itf_ip = get_if_ip(itf)) !== null && ip_dest === itf_ip) ret.push(itf);}); 191 if (ret.length > 0) { g_sim_logs += 'on '+h['id']+': destination IP reached\n'; my_console_log(" destination reached !"); return (ret); } // keep multiple : means that 2 interfaces have the same ip 192 } 193 194 // ip_dest match an interface ? 195 my_console_log('on '+h['id']+': check '+ip_dest+" against all interfaces"); 196 nbif = 0; ret = []; 197 for (i = 0; i < ifs.length; i++) 198 { 199 if (ifs[i]['hid'] == h['id']) 200 { 201 my_console_log(" chk with itf "+ifs[i]['if']); 202 if (ip_match_if(ip_dest, ifs[i])) 203 { 204 my_console_log(" match itf "+ifs[i]['if']); 205 nbif ++; 206 g_sim_logs += 'on '+h['id']+': send to '+ifs[i]['if']+'\n'; 207 links.forEach(l => {if (l['if1'] == ifs[i]['if']) ret = ret.concat(rec_route(ip_dest, ip_dest, l['e2'], l['h2'])); 208 else if (l['if2'] == ifs[i]['if']) ret = ret.concat(rec_route(ip_dest, ip_dest, l['e1'], l['h1']))}); 209 } 210 } 211 } 212 // force fail if multiple ifs on same subnet 213 if (nbif > 1) { g_sim_logs += 'on '+h['id']+': error on destination ip - multiple interface match\n'; return ([]); } 214 if (nbif == 1) return (ret); 215 216 // else nbif == 0, no interface match, explore routes 217 my_console_log(" no itf for ip destination, go through gate"); 218 g_sim_logs += 'on '+h['id']+': destination does not match any interface. pass through routing table\n'; 219 220 ret = []; 221 nb_routes = 0; 222 for (j = 0; j < routes.length; j++) 223 { 224 if (routes[j]['hid'] == h['id']) 225 { 226 if (ip_match_route(ip_dest, routes[j])) 227 { 228 g_sim_logs += 'on '+h['id']+' : route match '+get_route_route_str(routes[j])+'\n'; 229 nb_routes ++; 230 var ip_gate = get_route_gate(routes[j]); 231 if (ip_gate === null) { g_sim_logs += "on "+h['id']+": invalid gate IP, route "+get_route_route_str(routes[j])+"\n"; return ([]);} 232 nbif = 0; 233 for (i = 0; i < ifs.length; i++) 234 { 235 if (ifs[i]['hid'] == h['id']) 236 { 237 if (ip_match_if(ip_gate, ifs[i])) 238 { 239 my_console_log(" gate ip match itf "+ifs[i]['if']); 240 g_sim_logs += 'on '+h['id']+': send to gateway '+get_route_gate_str(routes[j])+' through interface '+ifs[i]['if']+'\n'; 241 nbif ++; 242 links.forEach(l => {if (l['if1'] == ifs[i]['if']) ret = ret.concat(rec_route(ip_dest, ip_gate, l['e2'], l['h2'])); 243 else if (l['if2'] == ifs[i]['if']) ret = ret.concat(rec_route(ip_dest, ip_gate, l['e1'], l['h1']))}); 244 } 245 } 246 } 247 if (nbif > 1) { g_sim_logs += 'on '+h['id']+' : error on gate ip - multiple interface match\n'; return ([]); } 248 } 249 } 250 if (nb_routes > 0) // only first route is explored. 251 { 252 if (nbif == 0) g_sim_logs += 'on '+h['id']+' : route match but no interface for gateway '+get_route_gate_str(routes[j])+'\n'; 253 return (ret); 254 } 255 } 256 // no match, fail 257 g_sim_logs += 'on '+h['id']+': destination does not match any route\n'; 258 return ([]); 259 } 260 261 262 263 function sim_reach(g) 264 { 265 my_console_log("check reach : "+g['id1']+" -> "+g['id2']); 266 var ret = []; 267 var i; 268 var itf_ip; 269 for (i = 0; i < ifs.length; i++) 270 { 271 if (g['id2'] == ifs[i]['hid']) 272 { 273 visited_host = []; 274 if ((itf_ip = get_if_ip(ifs[i])) === null) 275 g_sim_logs += "on interface "+ifs[i]['if']+": invalid IP\n"; 276 else 277 { 278 g_sim_logs +="forward way : "+g['id1']+" -> "+g['id2']+" ("+get_if_ip_str(ifs[i])+")\n"; 279 ret = ret.concat(rec_route(itf_ip, 0, null, g['h1'])); 280 } 281 if (ret.length > 0) 282 break; // if one interface matches, that's enough - if ret > 1 it's because another host matches 283 } 284 } 285 if (ret.length <= 0) return ({text:'KO - No forward way, try again ...', status:0}); 286 if (ret.length > 1) return ({text:'KO - Multiple destination hosts match ... ', status:0}); 287 if (ret[0]['hid'] != g['id2']) return ({text:'KO - Correct IP reached but on wrong host, try again ...', status:0}); 288 289 // now reverse way 290 291 my_console_log("check reach : "+g['id2']+" -> "+g['id1']); 292 ret = []; 293 for (i = 0; i < ifs.length; i++) 294 { 295 if (g['id1'] == ifs[i]['hid']) 296 { 297 visited_host = []; 298 if ((itf_ip = get_if_ip(ifs[i])) === null) 299 g_sim_logs += "on interface "+ifs[i]['if']+": invalid IP\n"; 300 else 301 { 302 g_sim_logs +="reverse way : "+g['id2']+" -> "+g['id1']+" ("+get_if_ip_str(ifs[i])+")\n"; 303 ret = ret.concat(rec_route(itf_ip, 0, null, g['h2'])); 304 } 305 if (ret.length > 0) 306 break; // if one interface matches, that's enough - if ret > 1 it's because another host matches 307 } 308 } 309 if (ret.length <= 0) return ({text:'KO - No reverse way, try again ...', status:0}); 310 if (ret.length > 1) return ({text:'KO - Multiple origin hosts match ... ', status:0}); 311 if (ret[0]['hid'] != g['id1']) return ({text:'KO - Correct IP reached but on wrong host, try again ...', status:0}); 312 313 return ({text:'OK - Congratulations !!', status:1}); 314 } 315 316 317 function sim_reach_if(g) 318 { 319 my_console_log("check reach interface : "+g['if_id1']+" -> "+g['if_id2']); 320 var ret = []; 321 var i; 322 var itf_ip; 323 for (i = 0; i < ifs.length; i++) 324 { 325 if (g['if_id2'] == ifs[i]['if']) 326 { 327 visited_host = []; 328 if ((itf_ip = get_if_ip(ifs[i])) === null) 329 g_sim_logs += "on inerface "+ifs[i]['if']+": invalid IP\n"; 330 else 331 { 332 g_sim_logs +="forward way : "+g['if_id1']+" -> "+g['if_id2']+" ("+get_if_ip_str(ifs[i])+")\n"; 333 ret = ret.concat(rec_route(itf_ip, 0, null, g['h1'])); 334 } 335 if (ret.length > 0) 336 break; // if one interface matches, that's enough - if ret > 1 it's because another host matches 337 } 338 } 339 if (ret.length <= 0) return ({text:'KO - No forward way, try again ...', status:0}); 340 if (ret.length > 1) return ({text:'KO - Multiple destination hosts match ... ', status:0}); 341 if (ret[0]['if'] != g['if_id2']) return ({text:'KO - Correct IP reached but wrong interface, try again ...', status:0}); 342 343 // now reverse way 344 345 my_console_log("check reach interface : "+g['if_id2']+" -> "+g['if_id1']); 346 ret = []; 347 for (i = 0; i < ifs.length; i++) 348 { 349 if (g['if_id1'] == ifs[i]['if']) 350 { 351 visited_host = []; 352 if ((itf_ip = get_if_ip(ifs[i])) === null) 353 g_sim_logs += "on interface "+ifs[i]['if']+": invalid IP\n"; 354 else 355 { 356 g_sim_logs +="reverse way : "+g['if_id2']+" -> "+g['if_id1']+" ("+get_if_ip_str(ifs[i])+")\n"; 357 ret = ret.concat(rec_route(itf_ip, 0, null, g['h2'])); 358 } 359 if (ret.length > 0) 360 break; // if one interface matches, that's enough - if ret > 1 it's because another host matches 361 } 362 } 363 if (ret.length <= 0) return ({text:'KO - No reverse way, try again ...', status:0}); 364 if (ret.length > 1) return ({text:'KO - Multiple origin hosts match ... ', status:0}); 365 if (ret[0]['if'] != g['if_id1']) return ({text:'KO - Correct IP reached but wrong interface, try again ...', status:0}); 366 367 return ({text:'OK - Congratulations !!', status:1}); 368 } 369 370 371 372 function sim_goal(g) 373 { 374 if (g['type'] == 'reach') 375 return (sim_reach(g)); 376 if (g['type'] == 'reach_if') 377 return (sim_reach_if(g)); 378 }