xref: /freebsd/lib/libc/net/linkaddr.c (revision a1215090416b8afb346fb2ff5b38f25ba0134a3a)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1990, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/types.h>
33 #include <sys/socket.h>
34 
35 #include <net/if.h>
36 #include <net/if_dl.h>
37 
38 #include <assert.h>
39 #include <errno.h>
40 #include <stdint.h>
41 #include <string.h>
42 
43 int
link_addr(const char * addr,struct sockaddr_dl * sdl)44 link_addr(const char *addr, struct sockaddr_dl *sdl)
45 {
46 	char *cp = sdl->sdl_data;
47 	char *cplim = sdl->sdl_len + (char *)sdl;
48 	const char *nptr;
49 	size_t newsize;
50 	int error = 0;
51 	char delim = 0;
52 
53 	/* Initialise the sdl to zero, except for sdl_len. */
54 	bzero((char *)&sdl->sdl_family, sdl->sdl_len - 1);
55 	sdl->sdl_family = AF_LINK;
56 
57 	/*
58 	 * Everything up to the first ':' is the interface name.  Usually the
59 	 * ':' should always be present even if there's no interface name, but
60 	 * since this interface was poorly specified in the past, accept a
61 	 * missing colon as meaning no interface name.
62 	 */
63 	if ((nptr = strchr(addr, ':')) != NULL) {
64 		size_t namelen = nptr - addr;
65 
66 		/* Ensure the sdl is large enough to store the name. */
67 		if (namelen > cplim - cp) {
68 			errno = ENOSPC;
69 			return (-1);
70 		}
71 
72 		memcpy(cp, addr, namelen);
73 		cp += namelen;
74 		sdl->sdl_nlen = namelen;
75 		/* Skip the interface name and the colon. */
76 		addr += namelen + 1;
77 	}
78 
79 	/*
80 	 * The remainder of the string should be hex digits representing the
81 	 * address, with optional delimiters.  Each two hex digits form one
82 	 * octet, but octet output can be forced using a delimiter, so we accept
83 	 * a long string of hex digits, or a mix of delimited and undelimited
84 	 * digits like "1122.3344.5566", or delimited one- or two-digit octets
85 	 * like "1.22.3".
86 	 *
87 	 * If anything fails at this point, exit the loop so we set sdl_alen and
88 	 * sdl_len based on whatever we did manage to parse.  This preserves
89 	 * compatibility with the 4.3BSD version of link_addr, which had no way
90 	 * to indicate an error and would just return.
91 	 */
92 #define DIGIT(c)						\
93 	 (((c) >= '0' && (c) <= '9') ? ((c) - '0')		\
94 	: ((c) >= 'a' && (c) <= 'f') ? ((c) - 'a' + 10)		\
95 	: ((c) >= 'A' && (c) <= 'F') ? ((c) - 'A' + 10)		\
96 	: (-1))
97 #define ISDELIM(c) (((c) == '.' || (c) == ':' || (c) == '-') && \
98     (delim == 0 || delim == (c)))
99 
100 	for (;;) {
101 		int digit, digit2;
102 
103 		/*
104 		 * Treat any leading delimiters as empty bytes.  This supports
105 		 * the (somewhat obsolete) form of Ethernet addresses with empty
106 		 * octets, e.g. "1::3:4:5:6".
107 		 */
108 		while (ISDELIM(*addr) && cp < cplim) {
109 			delim = *addr++;
110 			*cp++ = 0;
111 		}
112 
113 		/* Did we reach the end of the string? */
114 		if (*addr == '\0')
115 			break;
116 
117 		/*
118 		 * If not, the next character must be a digit, so make sure we
119 		 * have room for at least one more octet.
120 		 */
121 
122 		if (cp >= cplim) {
123 			error = ENOSPC;
124 			break;
125 		}
126 
127 		if ((digit = DIGIT(*addr)) == -1) {
128 			error = EINVAL;
129 			break;
130 		}
131 
132 		++addr;
133 
134 		/* If the next character is another digit, consume it. */
135 		if ((digit2 = DIGIT(*addr)) != -1) {
136 			digit = (digit << 4) | digit2;
137 			++addr;
138 		}
139 
140 		if (ISDELIM(*addr)) {
141 			/*
142 			 * If the digit is followed by a delimiter, write it
143 			 * and consume the delimiter.
144 			 */
145 			delim = *addr++;
146 			*cp++ = digit;
147 		} else if (DIGIT(*addr) != -1) {
148 			/*
149 			 * If two digits are followed by a third digit, treat
150 			 * the two digits we have as a single octet and
151 			 * continue.
152 			 */
153 			*cp++ = digit;
154 		} else if (*addr == '\0') {
155 			/* If the digit is followed by EOS, we're done. */
156 			*cp++ = digit;
157 			break;
158 		} else {
159 			/* Otherwise, the input was invalid. */
160 			error = EINVAL;
161 			break;
162 		}
163 	}
164 #undef DIGIT
165 #undef ISDELIM
166 
167 	/* How many bytes did we write to the address? */
168 	sdl->sdl_alen = cp - LLADDR(sdl);
169 
170 	/*
171 	 * The user might have given us an sdl which is larger than sizeof(sdl);
172 	 * in that case, record the actual size of the new sdl.
173 	 */
174 	newsize = cp - (char *)sdl;
175 	if (newsize > sizeof(*sdl))
176 		sdl->sdl_len = (u_char)newsize;
177 
178 	if (error == 0)
179 		return (0);
180 
181 	errno = error;
182 	return (-1);
183 }
184 
185 
186 char *
link_ntoa(const struct sockaddr_dl * sdl)187 link_ntoa(const struct sockaddr_dl *sdl)
188 {
189 	static char obuf[64];
190 	size_t buflen;
191 	_Static_assert(sizeof(obuf) >= IFNAMSIZ + 20, "obuf is too small");
192 
193 	/*
194 	 * Ignoring the return value of link_ntoa_r() is safe here because it
195 	 * always writes the terminating NUL.  This preserves the traditional
196 	 * behaviour of link_ntoa().
197 	 */
198 	buflen = sizeof(obuf);
199 	(void)link_ntoa_r(sdl, obuf, &buflen);
200 	return obuf;
201 }
202 
203 int
link_ntoa_r(const struct sockaddr_dl * sdl,char * obuf,size_t * buflen)204 link_ntoa_r(const struct sockaddr_dl *sdl, char *obuf, size_t *buflen)
205 {
206 	static const char hexlist[] = "0123456789abcdef";
207 	char *out;
208 	const u_char *in, *inlim;
209 	int namelen, i, rem;
210 	size_t needed;
211 
212 	assert(sdl);
213 	assert(buflen);
214 	/* obuf may be null */
215 
216 	needed = 1; /* 1 for the NUL */
217 	out = obuf;
218 	if (obuf)
219 		rem = *buflen;
220 	else
221 		rem = 0;
222 
223 /*
224  * Check if at least n bytes are available in the output buffer, plus 1 for the
225  * trailing NUL.  If not, set rem = 0 so we stop writing.
226  * Either way, increment needed by the amount we would have written.
227  */
228 #define CHECK(n) do {				\
229 		if ((SIZE_MAX - (n)) >= needed)	\
230 			needed += (n);		\
231 		if (rem >= ((n) + 1))		\
232 			rem -= (n);		\
233 		else				\
234 			rem = 0;		\
235 	} while (0)
236 
237 /*
238  * Write the char c to the output buffer, unless the buffer is full.
239  * Note that if obuf is NULL, rem is always zero.
240  */
241 #define OUT(c) do {			\
242 		if (rem > 0)		\
243 			*out++ = (c);	\
244 	} while (0)
245 
246 	namelen = (sdl->sdl_nlen <= IFNAMSIZ) ? sdl->sdl_nlen : IFNAMSIZ;
247 	if (namelen > 0) {
248 		CHECK(namelen);
249 		if (rem > 0) {
250 			bcopy(sdl->sdl_data, out, namelen);
251 			out += namelen;
252 		}
253 
254 		if (sdl->sdl_alen > 0) {
255 			CHECK(1);
256 			OUT(':');
257 		}
258 	}
259 
260 	in = (const u_char *)LLADDR(sdl);
261 	inlim = in + sdl->sdl_alen;
262 
263 	while (in < inlim) {
264 		if (in != (const u_char *)LLADDR(sdl)) {
265 			CHECK(1);
266 			OUT('.');
267 		}
268 		i = *in++;
269 		if (i > 0xf) {
270 			CHECK(2);
271 			OUT(hexlist[i >> 4]);
272 			OUT(hexlist[i & 0xf]);
273 		} else {
274 			CHECK(1);
275 			OUT(hexlist[i]);
276 		}
277 	}
278 
279 #undef CHECK
280 #undef OUT
281 
282 	/*
283 	 * We always leave enough room for the NUL if possible, but the user
284 	 * might have passed a NULL or zero-length buffer.
285 	 */
286 	if (out && *buflen)
287 		*out = '\0';
288 
289 	*buflen = needed;
290 	return ((rem > 0) ? 0 : -1);
291 }
292