| ... | @@ -1,9 +1,11 @@ | ... | @@ -1,9 +1,11 @@ |
| 1 | const linux = @import("linux.zig"); | 1 | const linux = @import("linux.zig"); |
| 2 | const errno = @import("errno.zig"); | 2 | const errno = @import("errno.zig"); |
| | 3 | const assert = @import("index.zig").assert; |
| 3 | | 4 | |
| 4 | pub error SigInterrupt; | 5 | pub error SigInterrupt; |
| 5 | pub error Unexpected; | 6 | pub error Unexpected; |
| 6 | pub error Io; | 7 | pub error Io; |
| | 8 | pub error TimedOut; |
| 7 | | 9 | |
| 8 | struct Connection { | 10 | struct Connection { |
| 9 | socket_fd: i32, | 11 | socket_fd: i32, |
| ... | @@ -20,17 +22,37 @@ struct Connection { | ... | @@ -20,17 +22,37 @@ struct Connection { |
| 20 | } | 22 | } |
| 21 | | 23 | |
| 22 | struct Address { | 24 | struct Address { |
| 23 | addr: linux.sockaddr, | 25 | family: u16, |
| | 26 | scope_id: u32, |
| | 27 | addr: [16]u8, |
| | 28 | sort_key: i32, |
| 24 | } | 29 | } |
| 25 | | 30 | |
| 26 | pub fn lookup(hostname: []const u8, out_addrs: []Address) -> %[]Address { | 31 | pub fn lookup(hostname: []const u8, out_addrs: []Address) -> %[]Address { |
| | 32 | if (hostname.len == 0) { |
| | 33 | |
| | 34 | /* |
| | 35 | 		if (family != AF_INET6) |
| | 36 | 			buf[cnt++] = (struct address){ .family = AF_INET, .addr = { 127,0,0,1 } }; |
| | 37 | 		if (family != AF_INET) |
| | 38 | 			buf[cnt++] = (struct address){ .family = AF_INET6, .addr = { [15] = 1 } }; |
| | 39 | */ |
| | 40 | unreachable{} // TODO |
| | 41 | } |
| | 42 | |
| | 43 | switch (parse_ip_literal(hostname)) { |
| | 44 | Ok => |addr| { |
| | 45 | out_addrs[0] = addr; |
| | 46 | return out_addrs[0...1]; |
| | 47 | }, |
| | 48 | else => {}, |
| | 49 | }; |
| | 50 | |
| 27 | unreachable{} // TODO | 51 | unreachable{} // TODO |
| 28 | } | 52 | } |
| 29 | | 53 | |
| 30 | pub fn connect_addr(addr: &Address, port: u16) -> %Connection { | 54 | pub fn connect_addr(addr: &Address, port: u16) -> %Connection { |
| 31 | addr.addr.port = port; | 55 | const socket_ret = linux.socket(addr.family, linux.SOCK_STREAM, linux.PROTO_tcp); |
| 32 | | | |
| 33 | const socket_ret = linux.socket(linux.PF_INET, linux.SOCK_STREAM, linux.PROTO_tcp); | | |
| 34 | const socket_err = linux.get_errno(socket_ret); | 56 | const socket_err = linux.get_errno(socket_ret); |
| 35 | if (socket_err > 0) { | 57 | if (socket_err > 0) { |
| 36 | // TODO figure out possible errors from socket() | 58 | // TODO figure out possible errors from socket() |
| ... | @@ -38,10 +60,33 @@ pub fn connect_addr(addr: &Address, port: u16) -> %Connection { | ... | @@ -38,10 +60,33 @@ pub fn connect_addr(addr: &Address, port: u16) -> %Connection { |
| 38 | } | 60 | } |
| 39 | const socket_fd = i32(socket_ret); | 61 | const socket_fd = i32(socket_ret); |
| 40 | | 62 | |
| 41 | const connect_err = linux.get_errno(linux.connect(socket_fd, &addr.addr, @sizeof(linux.sockaddr))); | 63 | const connect_ret = if (addr.family == linux.AF_INET) { |
| | 64 | var os_addr: linux.sockaddr_in = undefined; |
| | 65 | os_addr.family = addr.family; |
| | 66 | os_addr.port = host_to_be(u16)(port); |
| | 67 | @memcpy((&u8)(&os_addr.addr), &addr.addr[0], 4); |
| | 68 | @memset(&os_addr.zero, 0, @sizeof(@typeof(os_addr.zero))); |
| | 69 | linux.connect(socket_fd, (&linux.sockaddr)(&os_addr), @sizeof(linux.sockaddr_in)) |
| | 70 | } else if (addr.family == linux.AF_INET6) { |
| | 71 | var os_addr: linux.sockaddr_in6 = undefined; |
| | 72 | os_addr.family = addr.family; |
| | 73 | os_addr.port = host_to_be(u16)(port); |
| | 74 | os_addr.flowinfo = 0; |
| | 75 | os_addr.scope_id = addr.scope_id; |
| | 76 | @memcpy(&os_addr.addr[0], &addr.addr[0], 16); |
| | 77 | linux.connect(socket_fd, (&linux.sockaddr)(&os_addr), @sizeof(linux.sockaddr_in6)) |
| | 78 | } else { |
| | 79 | unreachable{} |
| | 80 | }; |
| | 81 | const connect_err = linux.get_errno(connect_ret); |
| 42 | if (connect_err > 0) { | 82 | if (connect_err > 0) { |
| 43 | // TODO figure out possible errors from connect() | 83 | switch (connect_err) { |
| 44 | return error.Unexpected; | 84 | errno.ETIMEDOUT => return error.TimedOut, |
| | 85 | else => { |
| | 86 | // TODO figure out possible errors from connect() |
| | 87 | return error.Unexpected; |
| | 88 | }, |
| | 89 | } |
| 45 | } | 90 | } |
| 46 | | 91 | |
| 47 | return Connection { | 92 | return Connection { |
| ... | @@ -56,3 +101,232 @@ pub fn connect(hostname: []const u8, port: u16) -> %Connection { | ... | @@ -56,3 +101,232 @@ pub fn connect(hostname: []const u8, port: u16) -> %Connection { |
| 56 | | 101 | |
| 57 | return connect_addr(main_addr, port); | 102 | return connect_addr(main_addr, port); |
| 58 | } | 103 | } |
| | 104 | |
| | 105 | pub error InvalidIpLiteral; |
| | 106 | |
| | 107 | pub fn parse_ip_literal(buf: []const u8) -> %Address { |
| | 108 | switch (parse_ip4(buf)) { |
| | 109 | Ok => |ip4| { |
| | 110 | var result: Address = undefined; |
| | 111 | @memcpy(&result.addr[0], (&u8)(&ip4), @sizeof(u32)); |
| | 112 | result.family = linux.AF_INET; |
| | 113 | result.scope_id = 0; |
| | 114 | return result; |
| | 115 | }, |
| | 116 | else => {}, |
| | 117 | } |
| | 118 | switch (parse_ip6(buf)) { |
| | 119 | Ok => |addr| { |
| | 120 | return addr; |
| | 121 | }, |
| | 122 | else => {}, |
| | 123 | } |
| | 124 | |
| | 125 | return error.InvalidIpLiteral; |
| | 126 | } |
| | 127 | |
| | 128 | fn hex_digit(c: u8) -> u8 { |
| | 129 | // TODO use switch with range |
| | 130 | if ('0' <= c && c <= '9') { |
| | 131 | c - '0' |
| | 132 | } else if ('A' <= c && c <= 'Z') { |
| | 133 | c - 'A' + 10 |
| | 134 | } else if ('a' <= c && c <= 'z') { |
| | 135 | c - 'a' + 10 |
| | 136 | } else { |
| | 137 | @max_value(u8) |
| | 138 | } |
| | 139 | } |
| | 140 | |
| | 141 | error InvalidChar; |
| | 142 | error Overflow; |
| | 143 | error JunkAtEnd; |
| | 144 | error Incomplete; |
| | 145 | |
| | 146 | #static_eval_enable(false) |
| | 147 | fn parse_ip6(buf: []const u8) -> %Address { |
| | 148 | var result: Address = undefined; |
| | 149 | result.family = linux.AF_INET6; |
| | 150 | result.scope_id = 0; |
| | 151 | const ip_slice = result.addr[0...]; |
| | 152 | |
| | 153 | var x: u16 = 0; |
| | 154 | var saw_any_digits = false; |
| | 155 | var index: u8 = 0; |
| | 156 | var scope_id = false; |
| | 157 | for (buf) |c| { |
| | 158 | if (scope_id) { |
| | 159 | if (c >= '0' && c <= '9') { |
| | 160 | const digit = c - '0'; |
| | 161 | if (@mul_with_overflow(u32, result.scope_id, 10, &result.scope_id)) { |
| | 162 | return error.Overflow; |
| | 163 | } |
| | 164 | if (@add_with_overflow(u32, result.scope_id, digit, &result.scope_id)) { |
| | 165 | return error.Overflow; |
| | 166 | } |
| | 167 | } else { |
| | 168 | return error.InvalidChar; |
| | 169 | } |
| | 170 | } else if (c == ':') { |
| | 171 | if (!saw_any_digits) { |
| | 172 | return error.InvalidChar; |
| | 173 | } |
| | 174 | if (index == 14) { |
| | 175 | return error.JunkAtEnd; |
| | 176 | } |
| | 177 | ip_slice[index] = @truncate(u8, x >> 8); |
| | 178 | index += 1; |
| | 179 | ip_slice[index] = @truncate(u8, x); |
| | 180 | index += 1; |
| | 181 | |
| | 182 | x = 0; |
| | 183 | saw_any_digits = false; |
| | 184 | } else if (c == '%') { |
| | 185 | if (!saw_any_digits) { |
| | 186 | return error.InvalidChar; |
| | 187 | } |
| | 188 | if (index == 14) { |
| | 189 | ip_slice[index] = @truncate(u8, x >> 8); |
| | 190 | index += 1; |
| | 191 | ip_slice[index] = @truncate(u8, x); |
| | 192 | index += 1; |
| | 193 | } |
| | 194 | scope_id = true; |
| | 195 | saw_any_digits = false; |
| | 196 | } else { |
| | 197 | const digit = hex_digit(c); |
| | 198 | if (digit == @max_value(u8)) { |
| | 199 | return error.InvalidChar; |
| | 200 | } |
| | 201 | if (@mul_with_overflow(u16, x, 16, &x)) { |
| | 202 | return error.Overflow; |
| | 203 | } |
| | 204 | if (@add_with_overflow(u16, x, digit, &x)) { |
| | 205 | return error.Overflow; |
| | 206 | } |
| | 207 | saw_any_digits = true; |
| | 208 | } |
| | 209 | } |
| | 210 | |
| | 211 | if (!saw_any_digits) { |
| | 212 | return error.Incomplete; |
| | 213 | } |
| | 214 | |
| | 215 | /* |
| | 216 | 	if (p) { |
| | 217 | 		if (isdigit(*++p)) scopeid = strtoull(p, &z, 10); |
| | 218 | 		else z = p-1; |
| | 219 | 		if (*z) { |
| | 220 | 			if (!IN6_IS_ADDR_LINKLOCAL(&a6) && |
| | 221 | 			 !IN6_IS_ADDR_MC_LINKLOCAL(&a6)) |
| | 222 | 				return EAI_NONAME; |
| | 223 | 			scopeid = if_nametoindex(p); |
| | 224 | 			if (!scopeid) return EAI_NONAME; |
| | 225 | 		} |
| | 226 | 		if (scopeid > UINT_MAX) return EAI_NONAME; |
| | 227 | 	} |
| | 228 | */ |
| | 229 | |
| | 230 | if (scope_id) { |
| | 231 | return result; |
| | 232 | } |
| | 233 | |
| | 234 | if (index == 14) { |
| | 235 | ip_slice[14] = @truncate(u8, x >> 8); |
| | 236 | ip_slice[15] = @truncate(u8, x); |
| | 237 | return result; |
| | 238 | } |
| | 239 | |
| | 240 | return error.Incomplete; |
| | 241 | } |
| | 242 | |
| | 243 | fn parse_ip4(buf: []const u8) -> %u32 { |
| | 244 | var result: u32 = undefined; |
| | 245 | const out_ptr = ([]u8)((&result)[0...1]); |
| | 246 | |
| | 247 | var x: u8 = 0; |
| | 248 | var index: u8 = 0; |
| | 249 | var saw_any_digits = false; |
| | 250 | for (buf) |c| { |
| | 251 | if (c == '.') { |
| | 252 | if (!saw_any_digits) { |
| | 253 | return error.InvalidChar; |
| | 254 | } |
| | 255 | if (index == 3) { |
| | 256 | return error.JunkAtEnd; |
| | 257 | } |
| | 258 | out_ptr[index] = x; |
| | 259 | index += 1; |
| | 260 | x = 0; |
| | 261 | saw_any_digits = false; |
| | 262 | } else if (c >= '0' && c <= '9') { |
| | 263 | saw_any_digits = true; |
| | 264 | const digit = c - '0'; |
| | 265 | if (@mul_with_overflow(u8, x, 10, &x)) { |
| | 266 | return error.Overflow; |
| | 267 | } |
| | 268 | if (@add_with_overflow(u8, x, digit, &x)) { |
| | 269 | return error.Overflow; |
| | 270 | } |
| | 271 | } else { |
| | 272 | return error.InvalidChar; |
| | 273 | } |
| | 274 | } |
| | 275 | if (index == 3 && saw_any_digits) { |
| | 276 | out_ptr[index] = x; |
| | 277 | return result; |
| | 278 | } |
| | 279 | |
| | 280 | return error.Incomplete; |
| | 281 | } |
| | 282 | |
| | 283 | |
| | 284 | #attribute("test") |
| | 285 | fn test_parse_ip4() { |
| | 286 | assert(%%parse_ip4("127.0.0.1") == be_to_host(u32)(0x7f000001)); |
| | 287 | switch (parse_ip4("256.0.0.1")) { Overflow => {}, else => unreachable {}, } |
| | 288 | switch (parse_ip4("x.0.0.1")) { InvalidChar => {}, else => unreachable {}, } |
| | 289 | switch (parse_ip4("127.0.0.1.1")) { JunkAtEnd => {}, else => unreachable {}, } |
| | 290 | switch (parse_ip4("127.0.0.")) { Incomplete => {}, else => unreachable {}, } |
| | 291 | switch (parse_ip4("100..0.1")) { InvalidChar => {}, else => unreachable {}, } |
| | 292 | } |
| | 293 | |
| | 294 | #attribute("test") |
| | 295 | fn test_parse_ip6() { |
| | 296 | { |
| | 297 | const addr = %%parse_ip6("FF01:0:0:0:0:0:0:FB"); |
| | 298 | assert(addr.addr[0] == 0xff); |
| | 299 | assert(addr.addr[1] == 0x01); |
| | 300 | assert(addr.addr[2] == 0x00); |
| | 301 | } |
| | 302 | } |
| | 303 | |
| | 304 | #attribute("test") |
| | 305 | fn test_lookup_simple_ip() { |
| | 306 | { |
| | 307 | var addrs_buf: [5]Address = undefined; |
| | 308 | const addrs = %%lookup("192.168.1.1", addrs_buf); |
| | 309 | assert(addrs.len == 1); |
| | 310 | const addr = addrs[0]; |
| | 311 | assert(addr.family == linux.AF_INET); |
| | 312 | assert(addr.addr[0] == 192); |
| | 313 | assert(addr.addr[1] == 168); |
| | 314 | assert(addr.addr[2] == 1); |
| | 315 | assert(addr.addr[3] == 1); |
| | 316 | } |
| | 317 | } |
| | 318 | |
| | 319 | const be_to_host = host_to_be; |
| | 320 | fn host_to_be(T: type)(x: T) -> T { |
| | 321 | if (@compile_var("is_big_endian")) x else endian_swap(T)(x) |
| | 322 | } |
| | 323 | |
| | 324 | fn endian_swap(T: type)(x: T) -> T { |
| | 325 | const x_slice = ([]u8)((&const x)[0...1]); |
| | 326 | var result: T = undefined; |
| | 327 | const result_slice = ([]u8)((&result)[0...1]); |
| | 328 | for (result_slice) |*b, i| { |
| | 329 | *b = x_slice[@sizeof(T) - i - 1]; |
| | 330 | } |
| | 331 | return result; |
| | 332 | } |