1 | module net |
2 | |
3 | import time |
4 | |
5 | const ( |
6 | udp_default_read_timeout = time.second / 10 |
7 | udp_default_write_timeout = time.second / 10 |
8 | ) |
9 | |
10 | struct UdpSocket { |
11 | Socket |
12 | l Addr |
13 | // TODO(emily): replace with option again |
14 | // when i figure out how to coerce it properly |
15 | mut: |
16 | has_r bool |
17 | r Addr |
18 | } |
19 | |
20 | pub struct UdpConn { |
21 | pub mut: |
22 | sock UdpSocket |
23 | mut: |
24 | write_deadline time.Time |
25 | read_deadline time.Time |
26 | read_timeout time.Duration |
27 | write_timeout time.Duration |
28 | } |
29 | |
30 | pub fn dial_udp(raddr string) !&UdpConn { |
31 | addrs := resolve_addrs_fuzzy(raddr, .udp)! |
32 | |
33 | for addr in addrs { |
34 | // create a local socket for this |
35 | // bind to any port (or file) (we dont care in this |
36 | // case because we only care about the remote) |
37 | if sock := new_udp_socket_for_remote(addr) { |
38 | return &UdpConn{ |
39 | sock: sock |
40 | read_timeout: net.udp_default_read_timeout |
41 | write_timeout: net.udp_default_write_timeout |
42 | } |
43 | } |
44 | } |
45 | |
46 | return error('none') |
47 | } |
48 | |
49 | // pub fn dial_udp(laddr string, raddr string) ?&UdpConn { |
50 | // local := resolve_addr(laddr, .inet, .udp)? |
51 | |
52 | // sbase := new_udp_socket()? |
53 | |
54 | // sock := UdpSocket{ |
55 | // handle: sbase.handle |
56 | // l: local |
57 | // r: resolve_wrapper(raddr) |
58 | // } |
59 | // } |
60 | |
61 | pub fn (mut c UdpConn) write_ptr(b &u8, len int) !int { |
62 | remote := c.sock.remote() or { return err_no_udp_remote } |
63 | return c.write_to_ptr(remote, b, len) |
64 | } |
65 | |
66 | pub fn (mut c UdpConn) write(buf []u8) !int { |
67 | return c.write_ptr(buf.data, buf.len) |
68 | } |
69 | |
70 | pub fn (mut c UdpConn) write_string(s string) !int { |
71 | return c.write_ptr(s.str, s.len) |
72 | } |
73 | |
74 | pub fn (mut c UdpConn) write_to_ptr(addr Addr, b &u8, len int) !int { |
75 | res := C.sendto(c.sock.handle, b, len, 0, voidptr(&addr), addr.len()) |
76 | if res >= 0 { |
77 | return res |
78 | } |
79 | code := error_code() |
80 | if code == int(error_ewouldblock) { |
81 | c.wait_for_write()! |
82 | socket_error(C.sendto(c.sock.handle, b, len, 0, voidptr(&addr), addr.len()))! |
83 | } else { |
84 | wrap_error(code)! |
85 | } |
86 | return error('none') |
87 | } |
88 | |
89 | // write_to blocks and writes the buf to the remote addr specified |
90 | pub fn (mut c UdpConn) write_to(addr Addr, buf []u8) !int { |
91 | return c.write_to_ptr(addr, buf.data, buf.len) |
92 | } |
93 | |
94 | // write_to_string blocks and writes the buf to the remote addr specified |
95 | pub fn (mut c UdpConn) write_to_string(addr Addr, s string) !int { |
96 | return c.write_to_ptr(addr, s.str, s.len) |
97 | } |
98 | |
99 | // read reads from the socket into buf up to buf.len returning the number of bytes read |
100 | pub fn (mut c UdpConn) read(mut buf []u8) !(int, Addr) { |
101 | mut addr := Addr{ |
102 | addr: AddrData{ |
103 | Ip6: Ip6{} |
104 | } |
105 | } |
106 | len := sizeof(Addr) |
107 | mut res := wrap_read_result(C.recvfrom(c.sock.handle, voidptr(buf.data), buf.len, |
108 | 0, voidptr(&addr), &len))! |
109 | if res > 0 { |
110 | return res, addr |
111 | } |
112 | code := error_code() |
113 | if code == int(error_ewouldblock) { |
114 | c.wait_for_read()! |
115 | // same setup as in tcp |
116 | res = wrap_read_result(C.recvfrom(c.sock.handle, voidptr(buf.data), buf.len, 0, |
117 | voidptr(&addr), &len))! |
118 | res2 := socket_error(res)! |
119 | return res2, addr |
120 | } else { |
121 | wrap_error(code)! |
122 | } |
123 | return error('none') |
124 | } |
125 | |
126 | pub fn (c &UdpConn) read_deadline() !time.Time { |
127 | if c.read_deadline.unix == 0 { |
128 | return c.read_deadline |
129 | } |
130 | return error('none') |
131 | } |
132 | |
133 | pub fn (mut c UdpConn) set_read_deadline(deadline time.Time) { |
134 | c.read_deadline = deadline |
135 | } |
136 | |
137 | pub fn (c &UdpConn) write_deadline() !time.Time { |
138 | if c.write_deadline.unix == 0 { |
139 | return c.write_deadline |
140 | } |
141 | return error('none') |
142 | } |
143 | |
144 | pub fn (mut c UdpConn) set_write_deadline(deadline time.Time) { |
145 | c.write_deadline = deadline |
146 | } |
147 | |
148 | pub fn (c &UdpConn) read_timeout() time.Duration { |
149 | return c.read_timeout |
150 | } |
151 | |
152 | pub fn (mut c UdpConn) set_read_timeout(t time.Duration) { |
153 | c.read_timeout = t |
154 | } |
155 | |
156 | pub fn (c &UdpConn) write_timeout() time.Duration { |
157 | return c.write_timeout |
158 | } |
159 | |
160 | pub fn (mut c UdpConn) set_write_timeout(t time.Duration) { |
161 | c.write_timeout = t |
162 | } |
163 | |
164 | [inline] |
165 | pub fn (mut c UdpConn) wait_for_read() ! { |
166 | return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout) |
167 | } |
168 | |
169 | [inline] |
170 | pub fn (mut c UdpConn) wait_for_write() ! { |
171 | return wait_for_write(c.sock.handle, c.write_deadline, c.write_timeout) |
172 | } |
173 | |
174 | pub fn (c &UdpConn) str() string { |
175 | // TODO |
176 | return 'UdpConn' |
177 | } |
178 | |
179 | pub fn (mut c UdpConn) close() ! { |
180 | return c.sock.close() |
181 | } |
182 | |
183 | pub fn listen_udp(laddr string) !&UdpConn { |
184 | addrs := resolve_addrs_fuzzy(laddr, .udp)! |
185 | // TODO(emily): |
186 | // here we are binding to the first address |
187 | // and that is probably not ideal |
188 | addr := addrs[0] |
189 | return &UdpConn{ |
190 | sock: new_udp_socket(addr)! |
191 | read_timeout: net.udp_default_read_timeout |
192 | write_timeout: net.udp_default_write_timeout |
193 | } |
194 | } |
195 | |
196 | fn new_udp_socket(local_addr Addr) !&UdpSocket { |
197 | family := local_addr.family() |
198 | |
199 | sockfd := socket_error(C.socket(family, SocketType.udp, 0))! |
200 | mut s := &UdpSocket{ |
201 | handle: sockfd |
202 | l: local_addr |
203 | r: Addr{ |
204 | addr: AddrData{ |
205 | Ip6: Ip6{} |
206 | } |
207 | } |
208 | } |
209 | |
210 | s.set_option_bool(.reuse_addr, true)! |
211 | |
212 | if family == .ip6 { |
213 | s.set_dualstack(true)! |
214 | } |
215 | |
216 | $if !net_blocking_sockets ? { |
217 | // NOTE: refer to comments in tcp.v |
218 | $if windows { |
219 | t := u32(1) // true |
220 | socket_error(C.ioctlsocket(sockfd, fionbio, &t))! |
221 | } $else { |
222 | socket_error(C.fcntl(sockfd, C.F_SETFD, C.O_NONBLOCK))! |
223 | } |
224 | } |
225 | |
226 | // cast to the correct type |
227 | socket_error(C.bind(s.handle, voidptr(&local_addr), local_addr.len()))! |
228 | return s |
229 | } |
230 | |
231 | fn new_udp_socket_for_remote(raddr Addr) !&UdpSocket { |
232 | // Invent a sutible local address for this remote addr |
233 | // Appease compiler |
234 | mut addr := Addr{ |
235 | addr: AddrData{ |
236 | Ip6: Ip6{} |
237 | } |
238 | } |
239 | match raddr.family() { |
240 | .ip { |
241 | // Use ip dualstack |
242 | addr = new_ip(0, addr_ip_any) |
243 | } |
244 | .ip6 { |
245 | // Use ip6 dualstack |
246 | addr = new_ip6(0, addr_ip6_any) |
247 | } |
248 | .unix { |
249 | addr = temp_unix()! |
250 | } |
251 | else { |
252 | panic('Invalid family') |
253 | } |
254 | } |
255 | mut sock := new_udp_socket(addr)! |
256 | sock.has_r = true |
257 | sock.r = raddr |
258 | |
259 | return sock |
260 | } |
261 | |
262 | pub fn (mut s UdpSocket) set_option_bool(opt SocketOption, value bool) ! { |
263 | // TODO reenable when this `in` operation works again |
264 | // if opt !in opts_can_set { |
265 | // return err_option_not_settable |
266 | // } |
267 | // if opt !in opts_bool { |
268 | // return err_option_wrong_type |
269 | // } |
270 | x := int(value) |
271 | socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &x, sizeof(int)))! |
272 | } |
273 | |
274 | pub fn (mut s UdpSocket) set_dualstack(on bool) ! { |
275 | x := int(!on) |
276 | socket_error(C.setsockopt(s.handle, C.IPPROTO_IPV6, int(SocketOption.ipv6_only), &x, |
277 | sizeof(int)))! |
278 | } |
279 | |
280 | fn (mut s UdpSocket) close() ! { |
281 | shutdown(s.handle) |
282 | return close(s.handle) |
283 | } |
284 | |
285 | fn (mut s UdpSocket) @select(test Select, timeout time.Duration) !bool { |
286 | return @select(s.handle, test, timeout) |
287 | } |
288 | |
289 | fn (s &UdpSocket) remote() !Addr { |
290 | if s.has_r { |
291 | return s.r |
292 | } |
293 | return error('none') |
294 | } |