summaryrefslogtreecommitdiffstats
path: root/getroute.c
blob: 5f9475e9ec92854835652e5ab93d9ab1f91c09c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/*
 * Copyright 2012 Daniel Drown
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * getroute.c - get an ip route
 */
#include <string.h>
#include <errno.h>

#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <arpa/inet.h>

#include <netlink/handlers.h>
#include <netlink/msg.h>

#include "getroute.h"
#include "netlink_callbacks.h"
#include "netlink_msg.h"

/* function: get_default_route_cb
 * finds the default route with the request family and out interface and saves the gateway
 * msg  - netlink message
 * data - (struct default_route_data) requested filters and response storage
 */
static int get_default_route_cb(struct nl_msg *msg, void *data) {
  struct rtmsg *rt_p;
  struct rtattr *rta_p;
  int rta_len;
  struct default_route_data *default_route = data;
  union anyip *this_gateway = NULL;
  ssize_t this_gateway_size;
  int this_interface_id = -1;

  if(default_route->reply_found_route) { // we already found our route
    return NL_OK;
  }

  rt_p = (struct rtmsg *)nlmsg_data(nlmsg_hdr(msg));
  if(rt_p->rtm_dst_len != 0) { // not a default route
    return NL_OK;
  }
  if((rt_p->rtm_family != default_route->request_family) || (rt_p->rtm_table != RT_TABLE_MAIN)) { // not a route we care about
    return NL_OK;
  }

  rta_p = (struct rtattr *)RTM_RTA(rt_p);
  rta_len = RTM_PAYLOAD(nlmsg_hdr(msg));
  for(; RTA_OK(rta_p, rta_len); rta_p = RTA_NEXT(rta_p, rta_len)) {
    switch(rta_p->rta_type) {
      case RTA_GATEWAY:
        this_gateway = RTA_DATA(rta_p);
        this_gateway_size = RTA_PAYLOAD(rta_p);
        break;
      case RTA_OIF:
        this_interface_id = *(int *)RTA_DATA(rta_p);
        break;
      default:
        break;
    }
  }

  if(this_interface_id == default_route->request_interface_id) {
    default_route->reply_found_route = 1;
    if(this_gateway != NULL) {
      memcpy(&default_route->reply_gateway, this_gateway, this_gateway_size);
      default_route->reply_has_gateway = 1;
    } else {
      default_route->reply_has_gateway = 0;
    }
  }
  return NL_OK;
}

/* function: error_handler
 * error callback for get_default_route
 * nla  - where the message came from
 * err  - netlink message
 * arg  - (int *) storage for the error number
 */
static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) {
  int *retval = arg;
  if(err->error < 0) { // error_handler called even on no error (NLMSG_ERROR reply type used)
    *retval = err->error;
  }
  return NL_OK;
}

/* function: get_default_route
 * finds the first default route with the given family and interface, returns the gateway (if it exists) in the struct
 * default_route - requested family and interface, and response storage
 */
int get_default_route(struct default_route_data *default_route) {
  struct rtmsg msg;
  struct nl_cb *callbacks = NULL;
  struct nl_msg *nlmsg = NULL;
  int retval = 0;

  default_route->reply_has_gateway = 0;
  default_route->reply_found_route = 0;

  memset(&msg,'\0',sizeof(msg));
  msg.rtm_family = default_route->request_family;
  msg.rtm_table = RT_TABLE_MAIN;
  msg.rtm_protocol = RTPROT_KERNEL;
  msg.rtm_scope = RT_SCOPE_UNIVERSE;

  callbacks = nl_cb_alloc(NL_CB_DEFAULT);
  if(!callbacks) {
    retval = -ENOMEM;
    goto cleanup;
  }
  // get_default_route_cb sets the response fields in default_route
  nl_cb_set(callbacks, NL_CB_VALID, NL_CB_CUSTOM, get_default_route_cb, default_route);
  nl_cb_err(callbacks, NL_CB_CUSTOM, error_handler, &retval);

  nlmsg = nlmsg_alloc_rtmsg(RTM_GETROUTE, NLM_F_REQUEST | NLM_F_ROOT, &msg);
  if(!nlmsg) {
    retval = -ENOMEM;
    goto cleanup;
  }
  send_netlink_msg(nlmsg, callbacks);

cleanup:
  if(callbacks)
    nl_cb_put(callbacks);
  if(nlmsg)
    nlmsg_free(nlmsg);

  return retval;
}