| ... | @@ -4,6 +4,7 @@ const assert = std.debug.assert; | ... | @@ -4,6 +4,7 @@ const assert = std.debug.assert; |
| 4 | const net = @This(); | 4 | const net = @This(); |
| 5 | const mem = std.mem; | 5 | const mem = std.mem; |
| 6 | const os = std.os; | 6 | const os = std.os; |
| | 7 | const fs = std.fs; |
| 7 | | 8 | |
| 8 | pub const TmpWinAddr = struct { | 9 | pub const TmpWinAddr = struct { |
| 9 | family: u8, | 10 | family: u8, |
| ... | @@ -285,7 +286,7 @@ test "std.net.parseIp6" { | ... | @@ -285,7 +286,7 @@ test "std.net.parseIp6" { |
| 285 | std.testing.expect(mem.eql(u8, "[ff01::fb]:80", printed)); | 286 | std.testing.expect(mem.eql(u8, "[ff01::fb]:80", printed)); |
| 286 | } | 287 | } |
| 287 | | 288 | |
| 288 | pub fn connectUnixSocket(path: []const u8) !std.fs.File { | 289 | pub fn connectUnixSocket(path: []const u8) !fs.File { |
| 289 | const opt_non_block = if (std.event.Loop.instance != null) os.SOCK_NONBLOCK else 0; | 290 | const opt_non_block = if (std.event.Loop.instance != null) os.SOCK_NONBLOCK else 0; |
| 290 | const sockfd = try os.socket( | 291 | const sockfd = try os.socket( |
| 291 | os.AF_UNIX, | 292 | os.AF_UNIX, |
| ... | @@ -312,7 +313,7 @@ pub fn connectUnixSocket(path: []const u8) !std.fs.File { | ... | @@ -312,7 +313,7 @@ pub fn connectUnixSocket(path: []const u8) !std.fs.File { |
| 312 | try os.connect(sockfd, &sock_addr, size); | 313 | try os.connect(sockfd, &sock_addr, size); |
| 313 | } | 314 | } |
| 314 | | 315 | |
| 315 | return std.fs.File.openHandle(sockfd); | 316 | return fs.File.openHandle(sockfd); |
| 316 | } | 317 | } |
| 317 | | 318 | |
| 318 | pub const AddressList = struct { | 319 | pub const AddressList = struct { |
| ... | @@ -356,7 +357,7 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !* | ... | @@ -356,7 +357,7 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !* |
| 356 | | 357 | |
| 357 | const hints = os.addrinfo{ | 358 | const hints = os.addrinfo{ |
| 358 | .flags = c.AI_NUMERICSERV, | 359 | .flags = c.AI_NUMERICSERV, |
| 359 | .family = os.AF_UNSPEC, | 360 | .family = os.AF_INET, // TODO os.AF_UNSPEC, |
| 360 | .socktype = os.SOCK_STREAM, | 361 | .socktype = os.SOCK_STREAM, |
| 361 | .protocol = os.IPPROTO_TCP, | 362 | .protocol = os.IPPROTO_TCP, |
| 362 | .canonname = null, | 363 | .canonname = null, |
| ... | @@ -413,40 +414,41 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !* | ... | @@ -413,40 +414,41 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !* |
| 413 | return result; | 414 | return result; |
| 414 | } | 415 | } |
| 415 | if (builtin.os == .linux) { | 416 | if (builtin.os == .linux) { |
| 416 | const flags = os.AI_NUMERICSERV; | 417 | const flags = std.c.AI_NUMERICSERV; |
| 417 | const family = os.AF_INET; //TODO os.AF_UNSPEC; | 418 | const family = os.AF_INET; //TODO os.AF_UNSPEC; |
| 418 | // The limit of 48 results is a non-sharp bound on the number of addresses | 419 | // The limit of 48 results is a non-sharp bound on the number of addresses |
| 419 | // that can fit in one 512-byte DNS packet full of v4 results and a second | 420 | // that can fit in one 512-byte DNS packet full of v4 results and a second |
| 420 | // packet full of v6 results. Due to headers, the actual limit is lower. | 421 | // packet full of v6 results. Due to headers, the actual limit is lower. |
| 421 | var buf: [48]LookupAddr = undefined; | 422 | var addrs = std.ArrayList(LookupAddr).init(allocator); |
| 422 | var canon_buf: [256]u8 = undefined; | 423 | defer addrs.deinit(); |
| 423 | var canon_len: usize = 0; | | |
| 424 | const cnt = try linuxLookupName(buf[0..], &canon_buf, &canon_len, name, family, flags); | | |
| 425 | | 424 | |
| 426 | result.addrs = try arena.alloc(Address, cnt); | 425 | var canon = std.Buffer.initNull(allocator); |
| | 426 | defer canon.deinit(); |
| 427 | | 427 | |
| 428 | if (canon_len != 0) { | 428 | try linuxLookupName(&addrs, &canon, name, family, flags); |
| 429 | result.canon_name = try mem.dupe(arena, u8, canon_buf[0..canon_len]); | 429 | |
| | 430 | result.addrs = try arena.alloc(Address, addrs.len); |
| | 431 | if (!canon.isNull()) { |
| | 432 | result.canon_name = canon.toOwnedSlice(); |
| 430 | } | 433 | } |
| 431 | | 434 | |
| 432 | var i: usize = 0; | 435 | for (addrs.toSliceConst()) |addr, i| { |
| 433 | while (i < cnt) : (i += 1) { | 436 | const os_addr = if (addr.family == os.AF_INET6) |
| 434 | const os_addr = if (buf[i].family == os.AF_INET6) | | |
| 435 | os.sockaddr{ | 437 | os.sockaddr{ |
| 436 | .in6 = os.sockaddr_in6{ | 438 | .in6 = os.sockaddr_in6{ |
| 437 | .family = buf[i].family, | 439 | .family = addr.family, |
| 438 | .port = mem.nativeToBig(u16, port), | 440 | .port = mem.nativeToBig(u16, port), |
| 439 | .flowinfo = 0, | 441 | .flowinfo = 0, |
| 440 | .addr = buf[i].addr, | 442 | .addr = addr.addr, |
| 441 | .scope_id = buf[i].scope_id, | 443 | .scope_id = addr.scope_id, |
| 442 | }, | 444 | }, |
| 443 | } | 445 | } |
| 444 | else | 446 | else |
| 445 | os.sockaddr{ | 447 | os.sockaddr{ |
| 446 | .in = os.sockaddr_in{ | 448 | .in = os.sockaddr_in{ |
| 447 | .family = buf[i].family, | 449 | .family = addr.family, |
| 448 | .port = mem.nativeToBig(u16, port), | 450 | .port = mem.nativeToBig(u16, port), |
| 449 | .addr = @ptrCast(*align(1) u32, &buf[i].addr).*, | 451 | .addr = @ptrCast(*align(1) const u32, &addr.addr).*, |
| 450 | .zero = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }, | 452 | .zero = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }, |
| 451 | }, | 453 | }, |
| 452 | }; | 454 | }; |
| ... | @@ -466,51 +468,52 @@ const LookupAddr = struct { | ... | @@ -466,51 +468,52 @@ const LookupAddr = struct { |
| 466 | }; | 468 | }; |
| 467 | | 469 | |
| 468 | fn linuxLookupName( | 470 | fn linuxLookupName( |
| 469 | buf: []LookupAddr, | 471 | addrs: *std.ArrayList(LookupAddr), |
| 470 | canon_buf: []u8, | 472 | canon: *std.Buffer, |
| 471 | canon_len: *usize, | | |
| 472 | opt_name: ?[]const u8, | 473 | opt_name: ?[]const u8, |
| 473 | family: i32, | 474 | family: i32, |
| 474 | flags: u32, | 475 | flags: u32, |
| 475 | ) !usize { | 476 | ) !void { |
| 476 | var cnt: usize = 0; | | |
| 477 | if (opt_name) |name| { | 477 | if (opt_name) |name| { |
| 478 | // reject empty name and check len so it fits into temp bufs | 478 | // reject empty name and check len so it fits into temp bufs |
| 479 | if (name.len >= 254) return error.UnknownName; | 479 | try canon.replaceContents(name); |
| 480 | mem.copy(u8, canon_buf, name); | 480 | try linuxLookupNameFromNumeric(addrs, name, family); |
| 481 | canon_len.* = name.len; | 481 | if (addrs.len == 0 and (flags & std.c.AI_NUMERICHOST) == 0) { |
| 482 | | 482 | try linuxLookupNameFromHosts(addrs, canon, name, family); |
| 483 | cnt = (linuxLookupNameFromNumeric(buf, name, family) catch |err| switch (err) { | 483 | if (addrs.len == 0) { |
| 484 | error.ExpectedIPv6ButFoundIPv4 => unreachable, | 484 | try linuxLookupNameFromDnsSearch(addrs, canon, name, family); |
| 485 | error.ExpectedIPv4ButFoundIPv6 => unreachable, | | |
| 486 | }); | | |
| 487 | if (cnt == 0 and (flags & os.AI_NUMERICHOST) == 0) { | | |
| 488 | cnt = try linuxLookupNameFromHosts(buf, canon_buf, canon_len, name, family); | | |
| 489 | if (cnt == 0) { | | |
| 490 | cnt = try linuxLookupNameFromDnsSearch(buf, canon_buf, canon_len, name, family); | | |
| 491 | } | 485 | } |
| 492 | } | 486 | } |
| 493 | } else { | 487 | } else { |
| 494 | canon_len.* = 0; | 488 | try canon.resize(0); |
| 495 | cnt = linuxLookupNameFromNull(buf, family, flags); | 489 | try linuxLookupNameFromNull(addrs, family, flags); |
| 496 | } | 490 | } |
| 497 | if (cnt == 0) return error.UnknownName; | 491 | if (addrs.len == 0) return error.UnknownName; |
| 498 | | 492 | |
| 499 | // No further processing is needed if there are fewer than 2 | 493 | // No further processing is needed if there are fewer than 2 |
| 500 | // results or if there are only IPv4 results. | 494 | // results or if there are only IPv4 results. |
| 501 | if (cnt == 1 or family == os.AF_INET) return cnt; | 495 | if (addrs.len == 1 or family == os.AF_INET) return; |
| 502 | | 496 | |
| 503 | @panic("port the RFC 3484/6724 destination address selection from musl libc"); | 497 | @panic("port the RFC 3484/6724 destination address selection from musl libc"); |
| 504 | } | 498 | } |
| 505 | | 499 | |
| 506 | fn linuxLookupNameFromNumeric(buf: []LookupAddr, name: []const u8, family: i32) !usize { | 500 | fn linuxLookupNameFromNumericUnspec(addrs: *std.ArrayList(LookupAddr), name: []const u8) !void { |
| | 501 | return linuxLookupNameFromNumeric(addrs, name, os.AF_UNSPEC) catch |err| switch (err) { |
| | 502 | error.ExpectedIPv6ButFoundIPv4 => unreachable, |
| | 503 | error.ExpectedIPv4ButFoundIPv6 => unreachable, |
| | 504 | else => |e| return e, |
| | 505 | }; |
| | 506 | } |
| | 507 | |
| | 508 | fn linuxLookupNameFromNumeric(addrs: *std.ArrayList(LookupAddr), name: []const u8, family: i32) !void { |
| 507 | if (parseIp4(name)) |ip4| { | 509 | if (parseIp4(name)) |ip4| { |
| 508 | if (family == os.AF_INET6) return error.ExpectedIPv6ButFoundIPv4; | 510 | if (family == os.AF_INET6) return error.ExpectedIPv6ButFoundIPv4; |
| | 511 | const item = try addrs.addOne(); |
| 509 | // TODO [0..4] should return *[4]u8, making this pointer cast unnecessary | 512 | // TODO [0..4] should return *[4]u8, making this pointer cast unnecessary |
| 510 | mem.writeIntNative(u32, @ptrCast(*[4]u8, &buf[0].addr), ip4); | 513 | mem.writeIntNative(u32, @ptrCast(*[4]u8, &item.addr), ip4); |
| 511 | buf[0].family = os.AF_INET; | 514 | item.family = os.AF_INET; |
| 512 | buf[0].scope_id = 0; | 515 | item.scope_id = 0; |
| 513 | return 1; | 516 | return; |
| 514 | } else |err| switch (err) { | 517 | } else |err| switch (err) { |
| 515 | error.Overflow, | 518 | error.Overflow, |
| 516 | error.InvalidEnd, | 519 | error.InvalidEnd, |
| ... | @@ -521,10 +524,11 @@ fn linuxLookupNameFromNumeric(buf: []LookupAddr, name: []const u8, family: i32) | ... | @@ -521,10 +524,11 @@ fn linuxLookupNameFromNumeric(buf: []LookupAddr, name: []const u8, family: i32) |
| 521 | | 524 | |
| 522 | if (parseIp6(name)) |ip6| { | 525 | if (parseIp6(name)) |ip6| { |
| 523 | if (family == os.AF_INET) return error.ExpectedIPv4ButFoundIPv6; | 526 | if (family == os.AF_INET) return error.ExpectedIPv4ButFoundIPv6; |
| 524 | @memcpy(&buf[0].addr, &ip6.addr, 16); | 527 | const item = try addrs.addOne(); |
| 525 | buf[0].family = os.AF_INET6; | 528 | @memcpy(&item.addr, &ip6.addr, 16); |
| 526 | buf[0].scope_id = ip6.scope_id; | 529 | item.family = os.AF_INET6; |
| 527 | return 1; | 530 | item.scope_id = ip6.scope_id; |
| | 531 | return; |
| 528 | } else |err| switch (err) { | 532 | } else |err| switch (err) { |
| 529 | error.Overflow, | 533 | error.Overflow, |
| 530 | error.InvalidEnd, | 534 | error.InvalidEnd, |
| ... | @@ -532,64 +536,54 @@ fn linuxLookupNameFromNumeric(buf: []LookupAddr, name: []const u8, family: i32) | ... | @@ -532,64 +536,54 @@ fn linuxLookupNameFromNumeric(buf: []LookupAddr, name: []const u8, family: i32) |
| 532 | error.Incomplete, | 536 | error.Incomplete, |
| 533 | => {}, | 537 | => {}, |
| 534 | } | 538 | } |
| 535 | | | |
| 536 | return 0; | | |
| 537 | } | 539 | } |
| 538 | | 540 | |
| 539 | fn linuxLookupNameFromNull(buf: []LookupAddr, family: i32, flags: u32) usize { | 541 | fn linuxLookupNameFromNull(addrs: *std.ArrayList(LookupAddr), family: i32, flags: u32) !void { |
| 540 | var cnt: usize = 0; | 542 | if ((flags & std.c.AI_PASSIVE) != 0) { |
| 541 | if ((flags & os.AI_PASSIVE) != 0) { | | |
| 542 | if (family != os.AF_INET6) { | 543 | if (family != os.AF_INET6) { |
| 543 | buf[cnt] = LookupAddr{ | 544 | (try addrs.addOne()).* = LookupAddr{ |
| 544 | .family = os.AF_INET, | 545 | .family = os.AF_INET, |
| 545 | .addr = [1]u8{0} ** 16, | 546 | .addr = [1]u8{0} ** 16, |
| 546 | }; | 547 | }; |
| 547 | cnt += 1; | | |
| 548 | } | 548 | } |
| 549 | if (family != os.AF_INET) { | 549 | if (family != os.AF_INET) { |
| 550 | buf[cnt] = LookupAddr{ | 550 | (try addrs.addOne()).* = LookupAddr{ |
| 551 | .family = os.AF_INET6, | 551 | .family = os.AF_INET6, |
| 552 | .addr = [1]u8{0} ** 16, | 552 | .addr = [1]u8{0} ** 16, |
| 553 | }; | 553 | }; |
| 554 | cnt += 1; | | |
| 555 | } | 554 | } |
| 556 | } else { | 555 | } else { |
| 557 | if (family != os.AF_INET6) { | 556 | if (family != os.AF_INET6) { |
| 558 | buf[cnt] = LookupAddr{ | 557 | (try addrs.addOne()).* = LookupAddr{ |
| 559 | .family = os.AF_INET, | 558 | .family = os.AF_INET, |
| 560 | .addr = [4]u8{ 127, 0, 0, 1 } ++ ([1]u8{0} ** 12), | 559 | .addr = [4]u8{ 127, 0, 0, 1 } ++ ([1]u8{0} ** 12), |
| 561 | }; | 560 | }; |
| 562 | cnt += 1; | | |
| 563 | } | 561 | } |
| 564 | if (family != os.AF_INET) { | 562 | if (family != os.AF_INET) { |
| 565 | buf[cnt] = LookupAddr{ | 563 | (try addrs.addOne()).* = LookupAddr{ |
| 566 | .family = os.AF_INET6, | 564 | .family = os.AF_INET6, |
| 567 | .addr = ([1]u8{0} ** 15) ++ [1]u8{1}, | 565 | .addr = ([1]u8{0} ** 15) ++ [1]u8{1}, |
| 568 | }; | 566 | }; |
| 569 | cnt += 1; | | |
| 570 | } | 567 | } |
| 571 | } | 568 | } |
| 572 | return cnt; | | |
| 573 | } | 569 | } |
| 574 | | 570 | |
| 575 | fn linuxLookupNameFromHosts( | 571 | fn linuxLookupNameFromHosts( |
| 576 | buf: []LookupAddr, | 572 | addrs: *std.ArrayList(LookupAddr), |
| 577 | canon_buf: []u8, | 573 | canon: *std.Buffer, |
| 578 | canon_len: *usize, | | |
| 579 | name: []const u8, | 574 | name: []const u8, |
| 580 | family: i32, | 575 | family: i32, |
| 581 | ) !usize { | 576 | ) !void { |
| 582 | const file = std.fs.File.openReadC(c"/etc/hosts") catch |err| switch (err) { | 577 | const file = fs.File.openReadC(c"/etc/hosts") catch |err| switch (err) { |
| 583 | error.FileNotFound, | 578 | error.FileNotFound, |
| 584 | error.NotDir, | 579 | error.NotDir, |
| 585 | error.AccessDenied, | 580 | error.AccessDenied, |
| 586 | => return 0, | 581 | => return, |
| 587 | else => |e| return e, | 582 | else => |e| return e, |
| 588 | }; | 583 | }; |
| 589 | defer file.close(); | 584 | defer file.close(); |
| 590 | | 585 | |
| 591 | var cnt: usize = 0; | 586 | const stream = &std.io.BufferedInStream(fs.File.ReadError).init(&file.inStream().stream).stream; |
| 592 | const stream = &std.io.BufferedInStream(std.fs.File.ReadError).init(&file.inStream().stream).stream; | | |
| 593 | var line_buf: [512]u8 = undefined; | 587 | var line_buf: [512]u8 = undefined; |
| 594 | while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) { | 588 | while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) { |
| 595 | error.StreamTooLong => blk: { | 589 | error.StreamTooLong => blk: { |
| ... | @@ -612,26 +606,20 @@ fn linuxLookupNameFromHosts( | ... | @@ -612,26 +606,20 @@ fn linuxLookupNameFromHosts( |
| 612 | } | 606 | } |
| 613 | } else continue; | 607 | } else continue; |
| 614 | | 608 | |
| 615 | switch (linuxLookupNameFromNumeric(buf[cnt..], ip_text, family) catch |err| switch (err) { | 609 | const prev_len = addrs.len; |
| | 610 | linuxLookupNameFromNumeric(addrs, ip_text, family) catch |err| switch (err) { |
| 616 | error.ExpectedIPv6ButFoundIPv4 => continue, | 611 | error.ExpectedIPv6ButFoundIPv4 => continue, |
| 617 | error.ExpectedIPv4ButFoundIPv6 => continue, | 612 | error.ExpectedIPv4ButFoundIPv6 => continue, |
| 618 | }) { | 613 | error.OutOfMemory => |e| return e, |
| 619 | 0 => continue, | 614 | }; |
| 620 | 1 => { | 615 | if (addrs.len > prev_len) { |
| 621 | // first name is canonical name | 616 | // first name is canonical name |
| 622 | const name_text = first_name_text.?; | 617 | const name_text = first_name_text.?; |
| 623 | if (isValidHostName(name_text)) { | 618 | if (isValidHostName(name_text)) { |
| 624 | mem.copy(u8, canon_buf, name_text); | 619 | try canon.replaceContents(name_text); |
| 625 | canon_len.* = name_text.len; | 620 | } |
| 626 | } | | |
| 627 | | | |
| 628 | cnt += 1; | | |
| 629 | if (cnt == buf.len) break; | | |
| 630 | }, | | |
| 631 | else => unreachable, | | |
| 632 | } | 621 | } |
| 633 | } | 622 | } |
| 634 | return cnt; | | |
| 635 | } | 623 | } |
| 636 | | 624 | |
| 637 | pub fn isValidHostName(hostname: []const u8) bool { | 625 | pub fn isValidHostName(hostname: []const u8) bool { |
| ... | @@ -647,50 +635,414 @@ pub fn isValidHostName(hostname: []const u8) bool { | ... | @@ -647,50 +635,414 @@ pub fn isValidHostName(hostname: []const u8) bool { |
| 647 | } | 635 | } |
| 648 | | 636 | |
| 649 | fn linuxLookupNameFromDnsSearch( | 637 | fn linuxLookupNameFromDnsSearch( |
| 650 | buf: []LookupAddr, | 638 | addrs: *std.ArrayList(LookupAddr), |
| 651 | canon_buf: []u8, | 639 | canon: *std.Buffer, |
| 652 | canon_len: *usize, | | |
| 653 | name: []const u8, | 640 | name: []const u8, |
| 654 | family: i32, | 641 | family: i32, |
| 655 | ) !usize { | 642 | ) !void { |
| 656 | var search: [256]u8 = undefined; | 643 | var rc: ResolvConf = undefined; |
| 657 | const resolv_conf = try getResolvConf(&search); | 644 | try getResolvConf(addrs.allocator, &rc); |
| | 645 | defer rc.deinit(); |
| 658 | | 646 | |
| 659 | // Count dots, suppress search when >=ndots or name ends in | 647 | // Count dots, suppress search when >=ndots or name ends in |
| 660 | // a dot, which is an explicit request for global scope. | 648 | // a dot, which is an explicit request for global scope. |
| 661 | //var dots: usize = 0; | 649 | var dots: usize = 0; |
| 662 | //for (name) |byte| { | 650 | for (name) |byte| { |
| 663 | // if (byte == '.') dots += 1; | 651 | if (byte == '.') dots += 1; |
| 664 | //} | 652 | } |
| | 653 | |
| | 654 | const search = if (rc.search.isNull() or dots >= rc.ndots or mem.endsWith(u8, name, ".")) |
| | 655 | [_]u8{} |
| | 656 | else |
| | 657 | rc.search.toSliceConst(); |
| | 658 | |
| | 659 | var canon_name = name; |
| | 660 | |
| | 661 | // Strip final dot for canon, fail if multiple trailing dots. |
| | 662 | if (mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1; |
| | 663 | if (mem.endsWith(u8, canon_name, ".")) return error.UnknownName; |
| | 664 | |
| | 665 | // Name with search domain appended is setup in canon[]. This both |
| | 666 | // provides the desired default canonical name (if the requested |
| | 667 | // name is not a CNAME record) and serves as a buffer for passing |
| | 668 | // the full requested name to name_from_dns. |
| | 669 | try canon.resize(canon_name.len); |
| | 670 | mem.copy(u8, canon.toSlice(), canon_name); |
| | 671 | try canon.appendByte('.'); |
| | 672 | |
| | 673 | var tok_it = mem.tokenize(search, " \t"); |
| | 674 | while (tok_it.next()) |tok| { |
| | 675 | canon.shrink(canon_name.len + 1); |
| | 676 | try canon.append(tok); |
| | 677 | try linuxLookupNameFromDns(addrs, canon, canon.toSliceConst(), family, rc); |
| | 678 | if (addrs.len != 0) return; |
| | 679 | } |
| | 680 | |
| | 681 | canon.shrink(canon_name.len); |
| | 682 | return linuxLookupNameFromDns(addrs, canon, name, family, rc); |
| | 683 | } |
| | 684 | |
| | 685 | const dpc_ctx = struct { |
| | 686 | addrs: *std.ArrayList(LookupAddr), |
| | 687 | canon: *std.Buffer, |
| | 688 | }; |
| | 689 | |
| | 690 | fn linuxLookupNameFromDns( |
| | 691 | addrs: *std.ArrayList(LookupAddr), |
| | 692 | canon: *std.Buffer, |
| | 693 | name: []const u8, |
| | 694 | family: i32, |
| | 695 | rc: ResolvConf, |
| | 696 | ) !void { |
| | 697 | var ctx = dpc_ctx{ |
| | 698 | .addrs = addrs, |
| | 699 | .canon = canon, |
| | 700 | }; |
| | 701 | const AfRr = struct { |
| | 702 | af: i32, |
| | 703 | rr: u8, |
| | 704 | }; |
| | 705 | const afrrs = [_]AfRr{ |
| | 706 | AfRr{ .af = os.AF_INET6, .rr = os.RR_A }, |
| | 707 | AfRr{ .af = os.AF_INET, .rr = os.RR_AAAA }, |
| | 708 | }; |
| | 709 | var qbuf: [2][280]u8 = undefined; |
| | 710 | var abuf: [2][512]u8 = undefined; |
| | 711 | var qp: [2][]const u8 = undefined; |
| | 712 | const apbuf = [2][]u8{ &abuf[0], &abuf[1] }; |
| | 713 | var nq: usize = 0; |
| | 714 | |
| | 715 | for (afrrs) |afrr| { |
| | 716 | if (family != afrr.af) { |
| | 717 | const len = os.res_mkquery(0, name, 1, afrr.rr, [_]u8{}, null, &qbuf[nq]); |
| | 718 | qp[nq] = qbuf[nq][0..len]; |
| | 719 | nq += 1; |
| | 720 | } |
| | 721 | } |
| | 722 | |
| | 723 | var ap = [2][]u8{ apbuf[0][0..0], apbuf[1][0..0] }; |
| | 724 | try resMSendRc(qp[0..nq], ap[0..nq], apbuf[0..nq], rc); |
| | 725 | |
| | 726 | var i: usize = 0; |
| | 727 | while (i < nq) : (i += 1) { |
| | 728 | dnsParse(ap[i], ctx, dnsParseCallback) catch {}; |
| | 729 | } |
| | 730 | |
| | 731 | if (addrs.len != 0) return; |
| | 732 | if (ap[0].len < 4 or (ap[0][3] & 15) == 2) return error.TemporaryNameServerFailure; |
| | 733 | if ((ap[0][3] & 15) == 0) return error.UnknownName; |
| | 734 | if ((ap[0][3] & 15) == 3) return; |
| | 735 | return error.NameServerFailure; |
| | 736 | } |
| | 737 | |
| | 738 | const ResolvConf = struct { |
| | 739 | attempts: u32, |
| | 740 | ndots: u32, |
| | 741 | timeout: u32, |
| | 742 | search: std.Buffer, |
| | 743 | ns: std.ArrayList(LookupAddr), |
| | 744 | |
| | 745 | fn deinit(rc: *ResolvConf) void { |
| | 746 | rc.ns.deinit(); |
| | 747 | rc.search.deinit(); |
| | 748 | rc.* = undefined; |
| | 749 | } |
| | 750 | }; |
| | 751 | |
| | 752 | /// Ignores lines longer than 512 bytes. |
| | 753 | /// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/2761 |
| | 754 | fn getResolvConf(allocator: *mem.Allocator, rc: *ResolvConf) !void { |
| | 755 | rc.* = ResolvConf{ |
| | 756 | .ns = std.ArrayList(LookupAddr).init(allocator), |
| | 757 | .search = std.Buffer.initNull(allocator), |
| | 758 | .ndots = 1, |
| | 759 | .timeout = 5, |
| | 760 | .attempts = 2, |
| | 761 | }; |
| | 762 | errdefer rc.deinit(); |
| | 763 | |
| | 764 | const file = fs.File.openReadC(c"/etc/resolv.conf") catch |err| switch (err) { |
| | 765 | error.FileNotFound, |
| | 766 | error.NotDir, |
| | 767 | error.AccessDenied, |
| | 768 | => return linuxLookupNameFromNumericUnspec(&rc.ns, "127.0.0.1"), |
| | 769 | else => |e| return e, |
| | 770 | }; |
| | 771 | defer file.close(); |
| | 772 | |
| | 773 | var cnt: usize = 0; |
| | 774 | const stream = &std.io.BufferedInStream(fs.File.ReadError).init(&file.inStream().stream).stream; |
| | 775 | var line_buf: [512]u8 = undefined; |
| | 776 | while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) { |
| | 777 | error.StreamTooLong => blk: { |
| | 778 | // Skip to the delimiter in the stream, to fix parsing |
| | 779 | try stream.skipUntilDelimiterOrEof('\n'); |
| | 780 | // Give an empty line to the while loop, which will be skipped. |
| | 781 | break :blk line_buf[0..0]; |
| | 782 | }, |
| | 783 | else => |e| return e, |
| | 784 | }) |line| { |
| | 785 | const no_comment_line = mem.separate(line, "#").next().?; |
| | 786 | var line_it = mem.tokenize(no_comment_line, " \t"); |
| | 787 | |
| | 788 | const token = line_it.next() orelse continue; |
| | 789 | if (mem.eql(u8, token, "options")) { |
| | 790 | while (line_it.next()) |sub_tok| { |
| | 791 | var colon_it = mem.separate(sub_tok, ":"); |
| | 792 | const name = colon_it.next().?; |
| | 793 | const value_txt = colon_it.next() orelse continue; |
| | 794 | const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) { |
| | 795 | error.Overflow => 255, |
| | 796 | error.InvalidCharacter => continue, |
| | 797 | }; |
| | 798 | if (mem.eql(u8, name, "ndots")) { |
| | 799 | rc.ndots = std.math.min(value, 15); |
| | 800 | } else if (mem.eql(u8, name, "attempts")) { |
| | 801 | rc.attempts = std.math.min(value, 10); |
| | 802 | } else if (mem.eql(u8, name, "timeout")) { |
| | 803 | rc.timeout = std.math.min(value, 60); |
| | 804 | } |
| | 805 | } |
| | 806 | } else if (mem.eql(u8, token, "nameserver")) { |
| | 807 | const ip_txt = line_it.next() orelse continue; |
| | 808 | try linuxLookupNameFromNumericUnspec(&rc.ns, ip_txt); |
| | 809 | } else if (mem.eql(u8, token, "domain") or mem.eql(u8, token, "search")) { |
| | 810 | try rc.search.replaceContents(line_it.rest()); |
| | 811 | } |
| | 812 | } |
| | 813 | |
| | 814 | if (rc.ns.len == 0) { |
| | 815 | return linuxLookupNameFromNumericUnspec(&rc.ns, "127.0.0.1"); |
| | 816 | } |
| | 817 | } |
| | 818 | |
| | 819 | fn eqlSockAddr(a: *const os.sockaddr, b: *const os.sockaddr, len: usize) bool { |
| | 820 | const a_bytes = @ptrCast([*]const u8, a)[0..len]; |
| | 821 | const b_bytes = @ptrCast([*]const u8, b)[0..len]; |
| | 822 | return mem.eql(u8, a_bytes, b_bytes); |
| | 823 | } |
| | 824 | |
| | 825 | fn resMSendRc( |
| | 826 | queries: []const []const u8, |
| | 827 | answers: [][]u8, |
| | 828 | answer_bufs: []const []u8, |
| | 829 | rc: ResolvConf, |
| | 830 | ) !void { |
| | 831 | const timeout = 1000 * rc.timeout; |
| | 832 | const attempts = rc.attempts; |
| | 833 | |
| | 834 | var sl: os.socklen_t = @sizeOf(os.sockaddr_in); |
| | 835 | var family: os.sa_family_t = os.AF_INET; |
| | 836 | |
| | 837 | var ns_list = std.ArrayList(os.sockaddr).init(rc.ns.allocator); |
| | 838 | defer ns_list.deinit(); |
| 665 | | 839 | |
| 666 | //if (dots >= conf.ndots || name[l-1]=='.') *search = 0; | 840 | try ns_list.resize(rc.ns.len); |
| 667 | | 841 | const ns = ns_list.toSlice(); |
| 668 | //// Strip final dot for canon, fail if multiple trailing dots. | 842 | |
| 669 | //if (name[l-1]=='.') l--; | 843 | for (rc.ns.toSliceConst()) |iplit, i| { |
| 670 | //if (!l || name[l-1]=='.') return EAI_NONAME; | 844 | if (iplit.family == os.AF_INET) { |
| 671 | | 845 | ns[i] = os.sockaddr{ |
| 672 | //// This can never happen; the caller already checked length. | 846 | .in = os.sockaddr_in{ |
| 673 | //if (l >= 256) return EAI_NONAME; | 847 | .family = os.AF_INET, |
| 674 | | 848 | .port = mem.nativeToBig(u16, 53), |
| 675 | //// Name with search domain appended is setup in canon[]. This both | 849 | .addr = mem.readIntNative(u32, @ptrCast(*const [4]u8, &iplit.addr)), |
| 676 | //// provides the desired default canonical name (if the requested | 850 | .zero = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }, |
| 677 | //// name is not a CNAME record) and serves as a buffer for passing | 851 | }, |
| 678 | //// the full requested name to name_from_dns. | 852 | }; |
| 679 | //memcpy(canon, name, l); | 853 | } else { |
| 680 | //canon[l] = '.'; | 854 | ns[i] = os.sockaddr{ |
| 681 | | 855 | .in6 = os.sockaddr_in6{ |
| 682 | //for (p=search; *p; p=z) { | 856 | .family = os.AF_INET6, |
| 683 | // for (; isspace(*p); p++); | 857 | .port = mem.nativeToBig(u16, 53), |
| 684 | // for (z=p; *z && !isspace(*z); z++); | 858 | .flowinfo = 0, |
| 685 | // if (z==p) break; | 859 | .addr = iplit.addr, |
| 686 | // if (z-p < 256 - l - 1) { | 860 | .scope_id = iplit.scope_id, |
| 687 | // memcpy(canon+l+1, p, z-p); | 861 | }, |
| 688 | // canon[z-p+1+l] = 0; | 862 | }; |
| 689 | // int cnt = name_from_dns(buf, canon, canon, family, &conf); | 863 | sl = @sizeOf(os.sockaddr_in6); |
| 690 | // if (cnt) return cnt; | 864 | family = os.AF_INET6; |
| | 865 | } |
| | 866 | } |
| | 867 | |
| | 868 | // Get local address and open/bind a socket |
| | 869 | var sa: os.sockaddr = undefined; |
| | 870 | @memset(@ptrCast([*]u8, &sa), 0, @sizeOf(os.sockaddr)); |
| | 871 | sa.in.family = family; |
| | 872 | const flags = os.SOCK_DGRAM | os.SOCK_CLOEXEC | os.SOCK_NONBLOCK; |
| | 873 | const fd = os.socket(family, flags, 0) catch |err| switch (err) { |
| | 874 | error.AddressFamilyNotSupported => blk: { |
| | 875 | // Handle case where system lacks IPv6 support |
| | 876 | if (family == os.AF_INET6) { |
| | 877 | family = os.AF_INET; |
| | 878 | break :blk try os.socket(os.AF_INET, flags, 0); |
| | 879 | } |
| | 880 | return err; |
| | 881 | }, |
| | 882 | else => |e| return e, |
| | 883 | }; |
| | 884 | defer os.close(fd); |
| | 885 | try os.bind(fd, &sa, sl); |
| | 886 | |
| | 887 | // Past this point, there are no errors. Each individual query will |
| | 888 | // yield either no reply (indicated by zero length) or an answer |
| | 889 | // packet which is up to the caller to interpret. |
| | 890 | |
| | 891 | // Convert any IPv4 addresses in a mixed environment to v4-mapped |
| | 892 | // TODO |
| | 893 | //if (family == AF_INET6) { |
| | 894 | // setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &(int){0}, sizeof 0); |
| | 895 | // for (i=0; i<nns; i++) { |
| | 896 | // if (ns[i].sin.sin_family != AF_INET) continue; |
| | 897 | // memcpy(ns[i].sin6.sin6_addr.s6_addr+12, |
| | 898 | // &ns[i].sin.sin_addr, 4); |
| | 899 | // memcpy(ns[i].sin6.sin6_addr.s6_addr, |
| | 900 | // "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12); |
| | 901 | // ns[i].sin6.sin6_family = AF_INET6; |
| | 902 | // ns[i].sin6.sin6_flowinfo = 0; |
| | 903 | // ns[i].sin6.sin6_scope_id = 0; |
| 691 | // } | 904 | // } |
| 692 | //} | 905 | //} |
| 693 | | 906 | |
| 694 | //canon[l] = 0; | 907 | var pfd = [1]os.pollfd{os.pollfd{ |
| 695 | //return name_from_dns(buf, canon, name, family, &conf); | 908 | .fd = fd, |
| | 909 | .events = os.POLLIN, |
| | 910 | .revents = undefined, |
| | 911 | }}; |
| | 912 | const retry_interval = timeout / attempts; |
| | 913 | var next: u32 = 0; |
| | 914 | var t2: usize = std.time.milliTimestamp(); |
| | 915 | var t0 = t2; |
| | 916 | var t1 = t2 - retry_interval; |
| | 917 | |
| | 918 | var servfail_retry: usize = undefined; |
| | 919 | |
| | 920 | outer: while (t2 - t0 < timeout) : (t2 = std.time.milliTimestamp()) { |
| | 921 | if (t2 - t1 >= retry_interval) { |
| | 922 | // Query all configured nameservers in parallel |
| | 923 | var i: usize = 0; |
| | 924 | while (i < queries.len) : (i += 1) { |
| | 925 | if (answers[i].len == 0) { |
| | 926 | var j: usize = 0; |
| | 927 | while (j < ns.len) : (j += 1) { |
| | 928 | _ = os.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j], sl) catch undefined; |
| | 929 | } |
| | 930 | } |
| | 931 | } |
| | 932 | t1 = t2; |
| | 933 | servfail_retry = 2 * queries.len; |
| | 934 | } |
| | 935 | |
| | 936 | // Wait for a response, or until time to retry |
| | 937 | const clamped_timeout = std.math.min(u31(std.math.maxInt(u31)), t1 + retry_interval - t2); |
| | 938 | const nevents = os.poll(&pfd, clamped_timeout) catch 0; |
| | 939 | if (nevents == 0) continue; |
| | 940 | |
| | 941 | while (true) { |
| | 942 | var sl_copy = sl; |
| | 943 | const rlen = os.recvfrom(fd, answer_bufs[next], 0, &sa, &sl_copy) catch break; |
| | 944 | |
| | 945 | // Ignore non-identifiable packets |
| | 946 | if (rlen < 4) continue; |
| | 947 | |
| | 948 | // Ignore replies from addresses we didn't send to |
| | 949 | var j: usize = 0; |
| | 950 | while (j < ns.len and !eqlSockAddr(&ns[j], &sa, sl)) : (j += 1) {} |
| | 951 | if (j == ns.len) continue; |
| | 952 | |
| | 953 | // Find which query this answer goes with, if any |
| | 954 | var i: usize = next; |
| | 955 | while (i < queries.len and (answer_bufs[next][0] != queries[i][0] or |
| | 956 | answer_bufs[next][1] != queries[i][1])) : (i += 1) |
| | 957 | {} |
| | 958 | |
| | 959 | if (i == queries.len) continue; |
| | 960 | if (answers[i].len != 0) continue; |
| | 961 | |
| | 962 | // Only accept positive or negative responses; |
| | 963 | // retry immediately on server failure, and ignore |
| | 964 | // all other codes such as refusal. |
| | 965 | switch (answer_bufs[next][3] & 15) { |
| | 966 | 0, 3 => {}, |
| | 967 | 2 => if (servfail_retry != 0) { |
| | 968 | servfail_retry -= 1; |
| | 969 | _ = os.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j], sl) catch undefined; |
| | 970 | }, |
| | 971 | else => continue, |
| | 972 | } |
| | 973 | |
| | 974 | // Store answer in the right slot, or update next |
| | 975 | // available temp slot if it's already in place. |
| | 976 | answers[i].len = rlen; |
| | 977 | if (i == next) { |
| | 978 | while (next < queries.len and answers[next].len != 0) : (next += 1) {} |
| | 979 | } else { |
| | 980 | mem.copy(u8, answer_bufs[i], answer_bufs[next][0..rlen]); |
| | 981 | } |
| | 982 | |
| | 983 | if (next == queries.len) break :outer; |
| | 984 | } |
| | 985 | } |
| | 986 | } |
| | 987 | |
| | 988 | fn dnsParse( |
| | 989 | r: []const u8, |
| | 990 | ctx: var, |
| | 991 | comptime callback: var, |
| | 992 | ) !void { |
| | 993 | if (r.len < 12) return error.InvalidDnsPacket; |
| | 994 | if ((r[3] & 15) != 0) return; |
| | 995 | var p = r.ptr + 12; |
| | 996 | var qdcount = r[4] * usize(256) + r[5]; |
| | 997 | var ancount = r[6] * usize(256) + r[7]; |
| | 998 | if (qdcount + ancount > 64) return error.InvalidDnsPacket; |
| | 999 | while (qdcount != 0) { |
| | 1000 | qdcount -= 1; |
| | 1001 | while (@ptrToInt(p) - @ptrToInt(r.ptr) < r.len and p[0] -% 1 < 127) p += 1; |
| | 1002 | if (p[0] > 193 or (p[0] == 193 and p[1] > 254) or @ptrToInt(p) > @ptrToInt(r.ptr) + r.len - 6) |
| | 1003 | return error.InvalidDnsPacket; |
| | 1004 | p += usize(5) + @boolToInt(p[0] != 0); |
| | 1005 | } |
| | 1006 | while (ancount != 0) { |
| | 1007 | ancount -= 1; |
| | 1008 | while (@ptrToInt(p) - @ptrToInt(r.ptr) < r.len and p[0] -% 1 < 127) p += 1; |
| | 1009 | if (p[0] > 193 or (p[0] == 193 and p[1] > 254) or @ptrToInt(p) > @ptrToInt(r.ptr) + r.len - 6) |
| | 1010 | return error.InvalidDnsPacket; |
| | 1011 | p += usize(1) + @boolToInt(p[0] != 0); |
| | 1012 | const len = p[8] * usize(256) + p[9]; |
| | 1013 | if (@ptrToInt(p) + len > @ptrToInt(r.ptr) + r.len) return error.InvalidDnsPacket; |
| | 1014 | try callback(ctx, p[1], p[10 .. 10 + len], r); |
| | 1015 | p += 10 + len; |
| | 1016 | } |
| | 1017 | } |
| | 1018 | |
| | 1019 | fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) !void { |
| | 1020 | var tmp: [256]u8 = undefined; |
| | 1021 | switch (rr) { |
| | 1022 | os.RR_A => { |
| | 1023 | if (data.len != 4) return error.InvalidDnsARecord; |
| | 1024 | const new_addr = try ctx.addrs.addOne(); |
| | 1025 | new_addr.* = LookupAddr{ |
| | 1026 | .family = os.AF_INET, |
| | 1027 | .addr = undefined, |
| | 1028 | }; |
| | 1029 | mem.copy(u8, &new_addr.addr, data); |
| | 1030 | }, |
| | 1031 | os.RR_AAAA => { |
| | 1032 | if (data.len != 16) return error.InvalidDnsAAAARecord; |
| | 1033 | const new_addr = try ctx.addrs.addOne(); |
| | 1034 | new_addr.* = LookupAddr{ |
| | 1035 | .family = os.AF_INET6, |
| | 1036 | .addr = undefined, |
| | 1037 | }; |
| | 1038 | mem.copy(u8, &new_addr.addr, data); |
| | 1039 | }, |
| | 1040 | os.RR_CNAME => { |
| | 1041 | @panic("TODO dn_expand"); |
| | 1042 | //if (__dn_expand(packet, (const unsigned char *)packet + 512, |
| | 1043 | // data, tmp, sizeof tmp) > 0 && is_valid_hostname(tmp)) |
| | 1044 | // strcpy(ctx->canon, tmp); |
| | 1045 | }, |
| | 1046 | else => return, |
| | 1047 | } |
| 696 | } | 1048 | } |