06b90a83f66f28674c0d0a3285bb1f4a2228affb
[psmb.git] / src / psmb_socket.c
1 #include <stdlib.h>
2 #include <errno.h>
3 #include <unistd.h>
4 #include <sys/types.h>
5 #include <arpa/inet.h>
6 #include <netinet/in.h>
7 #include <sys/socket.h>
8
9 #include <psmb.h>
10 #include "psmb_priv.h"
11
12 static void dummy_log(void *log_priv, int priority, const char *format, ...) {}
13
14 psmb_ctx_t *psmb_new(void)
15 {
16         return psmb_new_mm(malloc, free, realloc);
17 }
18
19 psmb_ctx_t *psmb_new_mm(void *(*malloc)(size_t size),
20                         void (*free)(void *ptr),
21                         void *(*realloc)(void *ptr, size_t size))
22 {
23         psmb_ctx_t *ctx = (*malloc)(sizeof(psmb_ctx_t));
24         if (!ctx) {
25                 int sverr = errno;
26                 LOG(ctx, LOG_ERR, "failed to allocate psmb_ctx: %m");
27                 errno = sverr;
28                 return NULL;
29         }
30         *ctx = (psmb_ctx_t){
31                 .fd = -1,
32                 .malloc = malloc, .free = free, .realloc = realloc,
33                 .logf = dummy_log,
34                 .prefix = (struct in6_addr){{{  0xff, 0xff, 0x01, 0x05,
35                                                 0xb0, 0x55, 0xff, 0xe7,
36                                                 0x00, 0x00, 0x00, 0x00,
37                                                 0x00, 0x00, 0x00, 0x00 }}},
38                 .prefixlen = 64,
39                 .pmtu = PSMB_DEFAULT_PMTU,
40                 .port = PSMB_DEFAULT_PORT};
41         return ctx;
42 }
43
44 psmb_result_t psmb_set_logf(psmb_ctx_t *ctx,
45         void (*logf)(void *log_priv, int priority, const char *format, ...),
46         void *log_priv)
47 {
48         if (ctx->fd == -1) {
49                 ctx->logf = logf;
50                 ctx->log_priv = log_priv;
51                 return (psmb_result_t){PSMB_OK};
52         } else {
53                 LOG(ctx, LOG_ERR, "psmb_set_...() used after psmb_open()");
54                 errno = EBUSY;
55                 return (psmb_result_t){PSMB_ERROR};
56         }
57 }
58
59 psmb_result_t psmb_set_pmtu(psmb_ctx_t *ctx, unsigned int pmtu)
60 {
61         if (ctx->fd == -1) {
62                 ctx->pmtu = pmtu;
63                 return (psmb_result_t){PSMB_OK};
64         } else {
65                 LOG(ctx, LOG_ERR, "psmb_set_...() used after psmb_open()");
66                 errno = EBUSY;
67                 return (psmb_result_t){PSMB_ERROR};
68         }
69 }
70
71 psmb_result_t psmb_set_port(psmb_ctx_t *ctx, unsigned short port)
72 {
73         if (ctx->fd == -1) {
74                 ctx->port = port;
75                 return (psmb_result_t){PSMB_OK};
76         } else {
77                 LOG(ctx, LOG_ERR, "psmb_set_...() used after psmb_open()");
78                 errno = EBUSY;
79                 return (psmb_result_t){PSMB_ERROR};
80         }
81 }
82
83 psmb_result_t psmb_set_mgrp(psmb_ctx_t *ctx, struct in6_addr prefix,
84         unsigned char prefixlen)
85 {
86         if (ctx->fd == -1) {
87                 ctx->prefix = prefix;
88                 ctx->prefixlen = prefixlen;
89                 return (psmb_result_t){PSMB_OK};
90         } else {
91                 LOG(ctx, LOG_ERR, "psmb_set_...() used after psmb_open()");
92                 errno = EBUSY;
93                 return (psmb_result_t){PSMB_ERROR};
94         }
95 }
96
97 psmb_result_t psmb_open(psmb_ctx_t *ctx)
98 {
99         unsigned long on = 1;
100         struct sockaddr_in6 addr = (struct sockaddr_in6){
101                 .sin6_family = AF_INET6,
102                 .sin6_addr = in6addr_any,
103                 .sin6_port = htons(ctx->port)
104         };
105
106         if (ctx->fd != -1) {
107                 LOG(ctx, LOG_ERR, "redundant call to psmb_open()");
108                 errno = EBUSY;
109                 return (psmb_result_t){PSMB_ERROR};
110         }
111         ctx->fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
112         if (ctx->fd == -1) {
113                 int sverr = errno;
114                 LOG(ctx, LOG_ERR, "socket: %m");
115                 errno = sverr;
116                 return (psmb_result_t){PSMB_ERROR};
117         }
118         if (setsockopt(ctx->fd, SOL_SOCKET, SO_REUSEADDR,
119                         &on, sizeof(on)) < 0) {
120                 int sverr = errno;
121                 LOG(ctx, LOG_ERR, "setsockopt(..., SO_REUSEADDR, ...): %m");
122                 close(ctx->fd);
123                 ctx->fd = -1;
124                 errno = sverr;
125                 return (psmb_result_t){PSMB_ERROR};
126         }
127         if (setsockopt(ctx->fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
128                         &on, sizeof(on)) < 0) {
129                 int sverr = errno;
130                 LOG(ctx, LOG_ERR, "setsockopt(..., IPV6_PKTINFO, ...): %m");
131                 close(ctx->fd);
132                 ctx->fd = -1;
133                 errno = sverr;
134                 return (psmb_result_t){PSMB_ERROR};
135         }
136         if (bind(ctx->fd, (struct sockaddr *)&addr,
137                         sizeof(struct sockaddr_in6)) == -1) {
138                 int sverr = errno;
139                 LOG(ctx, LOG_ERR, "bind(): %m");
140                 close(ctx->fd);
141                 ctx->fd = -1;
142                 errno = sverr;
143                 return (psmb_result_t){PSMB_ERROR};
144         }
145         return (psmb_result_t){PSMB_OK};
146 }
147
148 static psmb_result_t psmb_sub_unsub(psmb_ctx_t *ctx, char *channel, int option)
149 {
150         struct ipv6_mreq mreq = { 0 };
151
152         if (ctx->fd == -1) {
153                 LOG(ctx, LOG_ERR, "subscribe: psmb is not open");
154                 errno = EINVAL;
155                 return (psmb_result_t){PSMB_ERROR};
156         }
157         mreq.ipv6mr_multiaddr = ctx->prefix; /* use hash of the channel */
158         mreq.ipv6mr_interface = 0; /* how to use this??? */
159         if (setsockopt(ctx->fd, IPPROTO_IPV6, option,
160                 (void *)&mreq, sizeof(mreq)) == -1) {
161                 int sverr = errno;
162                 LOG(ctx, LOG_ERR, "add_membership(): %m");
163                 errno = sverr;
164                 return (psmb_result_t){PSMB_ERROR};
165         }
166         return (psmb_result_t){PSMB_OK};
167 }
168
169 psmb_result_t psmb_subscribe(psmb_ctx_t *ctx, char *channel) {
170         return psmb_sub_unsub(ctx, channel, IPV6_ADD_MEMBERSHIP);
171 }
172
173 psmb_result_t psmb_unsubscribe(psmb_ctx_t *ctx, char *channel) {
174         return psmb_sub_unsub(ctx, channel, IPV6_DROP_MEMBERSHIP);
175 }
176
177 bool psmb_success(psmb_result_t result)
178 {
179         return !(result.code & PSMB_ERROR);
180 }
181
182 bool psmb_message_waiting(psmb_result_t result)
183 {
184         return !!(result.code & PSMB_MESSAGE);
185 }
186
187 bool psmb_need_write_wait(psmb_result_t result)
188 {
189         return !!(result.code & PSMB_NEED_WRITE);
190 }
191
192 void psmb_destroy(psmb_ctx_t *ctx)
193 {
194         if (ctx->fd == -1) {
195                 LOG(ctx, LOG_ERR, "psmb_ctx is not open");
196         } else {
197                 if (close(ctx->fd) == -1)
198                         LOG(ctx, LOG_ERR, "close(): %m");
199         }
200         /* clean up the rest */
201         (*ctx->free)(ctx);
202 }