/* * OpenBTS provides an open source alternative to legacy telco protocols and * traditionally complex, proprietary hardware systems. * * Copyright 2011, 2014 Range Networks, Inc. * * This software is distributed under the terms of the GNU Affero General * Public License version 3. See the COPYING and NOTICE files in the main * directory for licensing information. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include // pat added for gethostbyname #include // pat added for IPv4 iphdr #include // pat added for tcphdr #include // pat added for udphdr #include // pat added. #include // pat added. #include // pat added. This defines NCC, which is bad, because used in GSMConfig.h #undef NCC // Just in case you want to include GSM files later. #include // pat added #include // pat added #include // pat added. #include // pat added. #include #include #include #include "miniggsn.h" #include // for gConfig #include namespace SGSN { #define EXPORT // Returns fractional seconds. double pat_timef() { struct timeval tv; gettimeofday(&tv,NULL); return tv.tv_usec / 1000000.0 + tv.tv_sec; } double pat_elapsedf() { static double start = 0; double now = pat_timef(); if (start == 0) start = now; return now - start; } // ip address is in network order; return as dotted string. EXPORT char *ip_ntoa(int32_t ip, char *buf) { static char sbuf[30]; if (buf == NULL) { buf = sbuf;} ip = ntohl(ip); sprintf(buf,"%d.%d.%d.%d",(ip>>24)&0xff,(ip>>16)&0xff,(ip>>8)&0xff,ip&0xff); return buf; } // Given an address like "192.168.1.0/24" return the base ip and the mask. EXPORT bool ip_addr_crack(const char *input,uint32_t *paddr, uint32_t *pmask) { char ip_buf[42]; if (strlen(input) > 40) return false; strcpy(ip_buf,input); // Look for the '/'; char *sl = strchr(ip_buf,'/'); if (sl) *sl = 0; *paddr = inet_addr(ip_buf); if (sl == 0) { // Indicates no mask specified: *pmask = 0; } else { int maskbits = atoi(sl+1); if (maskbits < 0 || maskbits >32) { *pmask = 0; // be safe. return false; // but return invalid. } *pmask = htonl(~ ( (1u<<(32-maskbits)) - 1 )); } return true; } EXPORT char *ip_sockaddr2a(void * /*struct sockaddr * */ sap,char *buf) { static char sbuf[100]; if (buf == NULL) { buf = sbuf;} struct sockaddr_in *to = (struct sockaddr_in*)sap; sprintf(buf,"proto=%d%s port=%d ip=%s", ntohs(to->sin_family), to->sin_family == AF_INET ? "(AF_INET)" : "", ntohs(to->sin_port), ip_ntoa(to->sin_addr.s_addr,NULL)); return buf; } // Add an address to the interface. ifname is like "eth0" or "tun1" // You have to be root to do this. // Note that if you want to bind to a non-local address must set the ip_nonlocal_bind // kernel option set in /proc. Update: that did not work, so use ip_addr_add() // on the ethernet card if you want to send directly to the machine. EXPORT int ip_add_addr(char *ifname, int32_t ipaddr, int maskbits) { char fulladdr[100]; sprintf(fulladdr,"%s/%d",ip_ntoa(ipaddr,NULL),maskbits); if (fork() == 0) { execl("/sbin/ip","ip","addr","add",fulladdr,"dev",ifname,NULL); exit(0); // Just in case. } return 0; } EXPORT const char *ip_proto_name(int ipproto) { static char buf[10]; switch(ipproto) { case IPPROTO_TCP: return "tcp"; case IPPROTO_UDP: return "udp"; case IPPROTO_ICMP: return "icmp"; default: sprintf(buf,"%d",ipproto); return buf; } } // IP standard checksum, see wikipedia "IPv4 Header" // len is in bytes, will normally be 20 == sizeof(struct iphdr). EXPORT unsigned int ip_checksum(void *ptr, unsigned len, void *dummyhdr) { //if (len != 20) printf("WARNING: unexpected header length in ip_checksum\n"); uint32_t i, sum = 0; uint16_t *pp = (uint16_t*)ptr; while (len > 1) { sum += *pp++; len -= 2; } if (len == 1) { uint16_t foo = 0; unsigned char *cp = (unsigned char*)&foo; *cp = *(unsigned char *)pp; sum += foo; } if (dummyhdr) { // For TCP and UDP the dummy header is 3 words = 6 shorts. pp = (uint16_t*)dummyhdr; for (i = 0; i < 6; i++) { sum += pp[i]; } } //printf("intermediate sum=0x%x\n",sum); // Convert from 2s complement to 1s complement: //sum = ((sum >> 16)&0xffff) + (sum & 0xffff); /* add hi 16 to low 16 */ sum = ((sum >> 16)) + (sum & 0xffff); /* add hi 16 to low 16 */ sum += (sum >> 16); /* add carry */ //printf("intermediate sum16=0x%x result=0x%x\n",sum,~sum); return 0xffff & ~sum; } #if 0 // from ifconfig.c - skfd is undefined static int set_ip_using(const char *name, int c, unsigned long ip) { struct ifreq ifr; struct sockaddr_in sin; /*safe_*/strncpy(ifr.ifr_name, name, IFNAMSIZ); memset(&sin, 0, sizeof(struct sockaddr)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = ip; memcpy(&ifr.ifr_addr, &sin, sizeof(struct sockaddr)); if (ioctl(skfd, c, &ifr) < 0) return -1; return 0; } #endif EXPORT void ip_hdr_dump(unsigned char *packet, const char *msg) { struct iptcp { // This is only accurate if no ip options specified. struct iphdr ip; struct tcphdr tcp; }; struct iptcp *pp = (struct iptcp*)packet; char nbuf[100]; printf("%s: ",msg); printf("%d bytes protocol=%d saddr=%s daddr=%s version=%d ihl=%d tos=%d id=%d\n", ntohs(pp->ip.tot_len),pp->ip.protocol,ip_ntoa(pp->ip.saddr,nbuf),ip_ntoa(pp->ip.daddr,NULL), pp->ip.version,pp->ip.ihl,pp->ip.tos, ntohs(pp->ip.id)); printf("\tcheck=%d computed=%d frag=%d ttl=%d\n", pp->ip.check,ip_checksum(packet,sizeof(struct iphdr),NULL), ntohs(pp->ip.frag_off),pp->ip.ttl); printf("\ttcp SYN=%d ACK=%d FIN=%d RES=%d sport=%u dport=%u\n", pp->tcp.syn,pp->tcp.ack,pp->tcp.fin,pp->tcp.rst, ntohs(pp->tcp.source),ntohs(pp->tcp.dest)); printf("\t\tseq=%u ackseq=%u window=%u check=%u\n", ntohl(pp->tcp.seq),ntohl(pp->tcp.ack_seq),htons(pp->tcp.window),htons(pp->tcp.check)); } // Run the command. // This is not ip specific, but used to call linux "ip" and "route" commands. // If commands starts with '|', capture stdout and return the file descriptor to read it. EXPORT int runcmd(const char *path, ...) { int pipefd[2]; int dopipe = 0; if (*path == '|') { path++; dopipe = 1; if (pipe(pipefd) == -1) { MGERROR("could not create pipe: %s",strerror(errno)); return -1; } } int pid = fork(); if (pid == -1) { MGERROR("could not fork: %s",strerror(errno)); return -1; } if (pid) { // This is the parent; wait for child to exit, then return childs status. int status; if (dopipe) { close(pipefd[1]); // Close unused write end of pipe. } //int result = 0; for (int quartersecs = 0; quartersecs < 5*4; quartersecs++) { if (waitpid(pid,&status,WNOHANG) == pid) { // WARNING: This assumes the amount of info returned by the command // is small enough to fit in the pipe. return dopipe ? pipefd[0] : status; // Return read end of pipe, if piped. } usleep(250*1000); } MGERROR("sub-process did not complete in 5 seconds: %s",path); return -1; } // This is the child process. if (dopipe) { close(pipefd[0]); // Close unused read end of pipe. dup2(pipefd[1],1); // Capture stdout to pipe. close(pipefd[1]); // Close now redundant fd. } // Gather args into argc,argv; int argc = 0; char *argv[100]; va_list ap; va_start(ap, path); do { argv[argc] = va_arg(ap,char*); } while (argv[argc++]); argv[argc] = NULL; va_end(ap); // Print them out. // But dont print if piped, because it goes into the pipe! if (! dopipe) { char buf[208], *bp = buf, *ep = &buf[200]; int i; for (i = 0; argv[i]; i++) { int len = strlen(argv[i]); if (bp + len > ep) { strcpy(bp,"..."); break; } strcpy(bp,argv[i]); bp += len; *bp++ = ' '; *bp = 0; } buf[200] = 0; MGINFO("%s",buf); } // exec them. execv(path,argv); _exit(2); // Just in case. return 0; // This is never used. } // The addrstr is the tunnel address and must include the mask, eg: "192.168.2.0/24" EXPORT int ip_tun_open(const char *tname, const char *addrstr) // int32_t ipaddr, int maskbits) { struct ifreq ifr; int fd; const char *clonedev = "/dev/net/tun"; if ((fd = open(clonedev,O_RDWR)) < 0) { // Hmmph. Try to create the tunnel device. runcmd("/sbin/modprobe","modprobe","tun",NULL); runcmd("/sbin/modprobe","modprobe","ipip",NULL); sleep(2); } if ((fd = open(clonedev,O_RDWR)) < 0) { MGERROR("error: Could not open: %s\n",clonedev); return -1; } // This attaches to our existing mstun interface, if any, because // of the magic TUNSETPERSIST flag. memset(&ifr,0,sizeof(ifr)); strcpy(ifr.ifr_name,tname); ifr.ifr_flags = IFF_TUN | IFF_NO_PI; // Disable packet info. if (ioctl(fd,TUNSETIFF,&ifr) < 0) { MGERROR("could not create tunnel %s: ioctl error: %s\n",tname,strerror(errno)); return -1; } if (ioctl(fd,TUNSETPERSIST,1) < 0) { MGERROR("could not setpersist tunnel %s: ioctl error: %s\n",tname,strerror(errno)); } // This (and only this) magic works: // We invoke with: // ./miniggsn -v -t 192.168.1.75/32 -f 192.168.1.75 router // or: ./miniggsn -v -t 192.168.2.0/24 -f 192.168.2.1 router runcmd("/sbin/ip","ip","link","set",tname,"up",NULL); // TODO: Instead of individual routes, we can also do: // ip route add to 192.168.3.0/24 dev mstun # Note you must use .0, not .1 //runcmd("/sbin/ip","ip","route","add","to",options.from,"dev",tname,NULL); runcmd("/sbin/ip","ip","route","add","to",addrstr,"dev",tname,NULL); // Establishing a "forwarding route" causes linux proxy-arp to automagically add an ARP // for mstun, even though it has the "NOARP" option set. What a mess. // Now we can set the interface addr using ip command. //ip_add_addr(tname,ipaddr,maskbits); // The addrstr must include the correct maskbits, like /24, or it just doesnt work. // If you call any of these, the output is no longer routed unless // you called TUNSETPERSIST first. //runcmd("/sbin/ifconfig","ifconfig",tname,"arp",NULL); //runcmd("/sbin/ip","ip","addr","add",addrstr,"dev",tname,NULL); //runcmd("/sbin/iptables","iptables","-A","FORWARD","-i",tname,"-j","ACCEPT", NULL); // todo #include // SIOCADDRT struct ifreq* /* add routing table entry */ // SIOCGIFADDR get interface address. // SIOCADDRT add interface address. // SIOCSIFADDR, SIOCSIFNETMASK /* if (set_ip_using(buf, SIOCSIFADDR, ip) == -1) { MGERROR("ioctl SIOCSIFADDR failed\n"); return 2; } if (set_ip_using(buf, SIOCSIFNETMASK , mask) == -1) { MGERROR("ioctl SIOCSIFNETMASK failed\n"); return 2; } */ // We wont set a broadcast address using SIOCSIFBRDADDR return fd; } static int setprocoption(const char *procfn) { int fd; const char *str = "1\n"; fd = open(procfn,1); if (fd < 0) { MGERROR("Can not open file %s error: %s\n",procfn,strerror(errno)); return 2;} int __attribute__((unused)) foobar=write(fd,str,2); // the foobar shuts up g++ close(fd); return 0; } void ip_init() { // Enable port forwarding and non-local bind if (setprocoption("/proc/sys/net/ipv4/ip_forward")) /*exit(2)*/ ; // This did not work: if (setprocoption("/proc/sys/net/ipv4/ip_nonlocal_bind")) /*exit(2)*/ ; } // Attempt to read the DNS servers directly from the dnsmask location. This is undocumented and dangerous, // but since we are running on a known Ubuntu based operating system, go ahead and try it. // The lines we want look like: server=xxx.xxx.xxx.xxx static int ip_getdnsmasq(uint32_t *dns) { int nfnd = 0; const char *fn = "/var/run/nm-dns-dnsmasq.conf"; FILE *pf = fopen(fn,"r"); if (!pf) { return 0; } char buf[300]; while (fgets(buf,299,pf)) { buf[299] = 0; char *cp = buf; while (*cp && isspace(*cp)) cp++; if (0 == strncasecmp(cp,"server=",strlen("server="))) { cp += strlen("server="); while (*cp && isspace(*cp)) cp++; uint32_t addr; if (0 == inet_aton(cp,(struct in_addr*) &addr)) { MGERROR("GGSN: Error: Invalid IP address in file %s at: %s\n",fn,buf); continue; } dns[nfnd++] = addr; if (nfnd == 2) break; } } fclose(pf); //printf("using ip_getdnsmasq nfnd=%d\n",nfnd); return nfnd; } static int ip_getDnsDefault(uint32_t *dns) { int nfnd = 0; FILE *pf = fopen("/etc/resolv.conf","r"); if (pf == NULL) { MGERROR("GGSN: DNS servers: error: could not open /etc/resolv.conf\n"); return 0; } char buf[300]; while (fgets(buf,299,pf)) { buf[299] = 0; char *cp = buf; while (*cp && isspace(*cp)) cp++; if (0 == strncasecmp(cp,"nameserver",strlen("nameserver"))) { cp += strlen("nameserver"); while (*cp && isspace(*cp)) cp++; uint32_t addr; if (0 == inet_aton(cp,(struct in_addr*) &addr)) { MGERROR("GGSN: Error: Invalid IP address in /etc/resolv.conf at: %s\n",buf); continue; } dns[nfnd++] = addr; if (nfnd == 2) break; } } fclose(pf); //printf("using ip_getDnsDefault nfnd=%d\n",nfnd); // If there is only one DNS server and it is 127.0.0.1, it means that there is a local dns server running, // probably dnsmasq. The upstream DNS servers were specified in some other file, the nitwits, which is // probably in the dns config directory, and also living in /run/nm-dns-dnsmasq.conf. // Sending 127.0.0.1 to the MS devices does not work, not sure why not. Maybe just a routing issue. // But even it did I dont think we want to open up the local server to them for security reasons. // resolvconf queries eth0 DNS and puts the info in /var/run/resolvconf/interface/eth0.dhclient, // then runs scripts to notify dnsmasq and others. // But Ubuntu is so foobar I had to enter the danged DNS servers manually in the setup program. // What we should really do here is query the real DNS server. // As an interim solution I am going to read the dnsmasq database directory. // Then if that fails, throw an alert and point DNS at a public DNS server. if (nfnd == 1 && dns[0] == inet_addr("127.0.0.1")) { nfnd = ip_getdnsmasq(dns); } if (nfnd == 0) { LOG(ALERT) << "No DNS servers found; using public DNS servers. Please set the GGSN.DNS config option."; dns[nfnd++] = inet_addr("8.8.8.8"); dns[nfnd++] = inet_addr("8.8.4.4"); } return nfnd; } // If a DNS option was specified, put the addresses in dns[2] and return number found. // The DNS may be a list of space separated servers. static int ip_getDnsOption(uint32_t *dns) { std::string sdns = gConfig.getStr("GGSN.DNS"); int len = sdns.length(); if (len <= 0) {return 0;} char *copy = strcpy((char*)alloca(len+1),sdns.c_str()); // make a copy of the option string. // Insist on dotted notation to catch stupid errors like setting it to '1' by mistake if (!strchr(copy,'.')) { MGWARN("Invalid GGSN.DNS option ignored: '%s'",copy); return 0; } char *argv[3]; int argc = cstrSplit(copy,argv,3); // split into argv if (argc > 2) { MGWARN("GGSN: invalid GGSN.DNS option, more than 2 servers specified: '%s'\n",sdns.c_str()); // But go ahead and use the first two. argc = 2; } for (int i = 0; i < argc; i++) { if (0 == inet_aton(argv[i],(struct in_addr*) &dns[i])) { // is it an invalid IP address? // failed. MGERROR("GGSN: unrecognized GGSN.DNS option: %s\n",sdns.c_str()); return i; } } return argc; } // Find the dns addresses. Return the number of dns addresses found. // dns must point to uint32_t dns[2]; EXPORT int ip_finddns(uint32_t *dns) { dns[0] = dns[1] = 0; int nfnd = ip_getDnsOption(dns); if (!nfnd) { nfnd = ip_getDnsDefault(dns); } // Print out the dns servers whenever they change. // If dns were not found, they will print as 0s, which is a good enough message. static uint32_t prevdns[2] = {0,0}; if (dns[0] == 0) { // This is a disaster. MGWARN("GGSN: No DNS servers found; GPRS service will not work"); } else if (dns[0] != prevdns[0] || dns[1] != prevdns[1]) { char bf[2][30]; MGINFO("GGSN: DNS servers: %s %s",ip_ntoa(dns[0],bf[0]),ip_ntoa(dns[1],bf[1])); prevdns[0] = dns[0]; prevdns[1] = dns[1]; } return nfnd; } // Return an array of ip addresses, terminated by a -1 address. EXPORT uint32_t *ip_findmyaddr() { const int maxaddrs = 5; static uint32_t addrs[maxaddrs+1]; int n = 0; int fd = runcmd("|/bin/hostname","hostname","-I", NULL); if (fd < 0) { failed: addrs[0] = (unsigned) -1; // converts to all 1s return addrs; } //printf("ip_findmyaddr fd=%d\n",fd); const int bufsize = 2000; char buf[bufsize]; int size = read(fd,buf,bufsize); if (size < 0) { goto failed; } buf[bufsize-1] = 0; if (size < bufsize) { buf[size] = 0; } char *cp = buf; //printf("ip_findmyaddr buf=%s\n",buf); while (n < maxaddrs) { char *ep = strchr(cp,'\n'); if (ep) *ep = 0; if (strlen(cp)) { uint32_t addr = inet_addr(cp); //printf("ip_findmyaddr cp=%s = 0x%x\n",cp,addr); if (addr != 0 && addr != (unsigned)-1) { addrs[n++] = inet_addr(cp); } } if (!ep) { addrs[n] = (unsigned) -1; // terminate the list. return addrs; } cp = ep+1; } addrs[maxaddrs] = (unsigned) -1; // converts to all 1s return addrs; } }; // namespace